Issues (1686)

sources/ElkArte/IlaIntegrate.php (1 issue)

1
<?php
2
3
/**
4
 * @package   ElkArte Forum
5
 * @copyright ElkArte Forum contributors
6
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
7
 *
8
 * @version 2.0 dev
9
 *
10
 */
11
12
namespace ElkArte;
13
14
use BBC\Codes;
15
use ElkArte\Helper\Util;
16
17
/**
18
 * Class IlaIntegrate
19
 */
20
class IlaIntegrate
21
{
22
	/** @var string holds the rendered html from the bbc [attach] tag */
23
	public static $typeTag = '';
24
25
	/**
26 1
	 * Register ILA hooks to the system.  This is called by the Hooks class, loadIntegrations()
27
	 *
28 1
	 * Note for addon authors.
29
	 * All you need to do is extend $modSettings['autoload_integrate'] with "path/filename.php" or the
30 1
	 * "namespace/class" of your integration.  Using filenameIntegrate is a nice touch but not required.
31
	 * You then use Hooks::instance()->enableIntegration(path/file); or as
32 1
	 * Hooks::instance()->enableIntegration(\\ElkArte\\IlaIntegrate); to add your integration.
33
	 * register() and settingsRegister() will both be called.
34
	 * You can add and remove the name based on if your addon is enabled/disabled and not worry
35
	 * about removing hooks (they do no use permanent)
36
	 *
37
	 * @return array
38
	 */
39
	public static function register()
40
	{
41
		global $modSettings;
42
43
		if (empty($modSettings['attachment_inline_enabled']))
44
		{
45
			return [];
46
		}
47
48
		// $hook, $function, $file
49
		return [
50
			['integrate_additional_bbc', '\\ElkArte\\IlaIntegrate::integrate_additional_bbc'],
51
			['integrate_post_bbc_parser', '\\ElkArte\\IlaIntegrate::integrate_post_parser']
52
		];
53
	}
54
55
	/**
56
	 * Register ACP config hooks for setting values
57
	 *
58
	 * @return array
59
	 */
60
	public static function settingsRegister()
61
	{
62
		// $hook, $function, $file
63
		return [
64
			['integrate_modify_attachment_settings', '\\ElkArte\\IlaIntegrate::integrate_modify_attachment_settings'],
65
		];
66
	}
67
68
	/**
69
	 * After parse is done, we need to sub in the message id for proper lightbox navigation
70
	 *
71
	 * @param string $message
72
	 */
73
	public static function integrate_post_parser(&$message)
74
	{
75
		global $context;
76
77
		$lighbox_message = 'data-lightboxmessage="' . (empty($context['id_msg']) ? '0' : $context['id_msg']) . '"';
78
		$message = str_replace('data-lightboxmessage="0"', $lighbox_message, $message);
79
	}
80
81
	/**
82
	 * - Adds in new BBC code tags for use with inline images
83
	 *
84
	 * @param array $additional_bbc
85
	 */
86
	public static function integrate_additional_bbc(&$additional_bbc)
87
	{
88
		global $modSettings, $txt;
89
90
		// Generally we don't want to render in these tags ...
91
		$disallow = [
92
			'quote' => 1,
93
			'code' => 1,
94
			'nobbc' => 1,
95
			'html' => 1,
96
			'php' => 1,
97
		];
98
99
		// Why enable it to disable the tags, oh well
100
		$disabledBBC = empty($modSettings['disabledBBC']) ? array() : explode(',', $modSettings['disabledBBC']);
101
		$disabled = in_array('attach', $disabledBBC, true);
102
		$disabledUrl = in_array('attachurl', $disabledBBC, true);
103
104
		// Want to see them in quotes eh?
105
		if (!empty($modSettings['attachment_inline_quotes']))
106
		{
107
			unset($disallow['quote']);
108
		}
109
110
		// Add ILA codes
111
		$additional_bbc = array_merge($additional_bbc, array(
112
			// Just a simple attach [attach][/attach]
113
			array(
114
				Codes::ATTR_TAG => 'attach',
115
				Codes::ATTR_TYPE => Codes::TYPE_UNPARSED_CONTENT,
116
				Codes::ATTR_DISABLED => $disabled,
117
				Codes::ATTR_RESET => '',
118
				Codes::ATTR_CONTENT => &self::$typeTag,
119
				Codes::ATTR_VALIDATE => $disabled ? null : self::buildTag(),
120
				Codes::ATTR_DISALLOW_PARENTS => $disallow,
121
				Codes::ATTR_DISABLED_CONTENT => '<a href="' . getUrl('action', ['action' => 'dlattach', 'attach' => '$1']) . '">(' . $txt['link'] . '-$1)</a> ',
122
				Codes::ATTR_BLOCK_LEVEL => false,
123
				Codes::ATTR_AUTOLINK => false,
124
				Codes::ATTR_LENGTH => 6,
125
			),
126
			// Attach, with perhaps a type [attach type=xyz][/attach]
127
			array(
128
				Codes::ATTR_TAG => 'attach',
129
				Codes::ATTR_TYPE => Codes::TYPE_UNPARSED_CONTENT,
130
				Codes::ATTR_PARAM => array(
131
					'type' => array(
132
						Codes::PARAM_ATTR_OPTIONAL => true,
133
						Codes::PARAM_ATTR_VALUE => ';$1',
134
						Codes::PARAM_ATTR_MATCH => '(thumb|image)',
135
					),
136
				),
137
				Codes::ATTR_DISABLED => $disabled,
138
				Codes::ATTR_RESET => '~~{type}',
139
				Codes::ATTR_CONTENT => &self::$typeTag,
140
				Codes::ATTR_VALIDATE => $disabled ? null : self::buildTag(),
141
				Codes::ATTR_DISALLOW_PARENTS => $disallow,
142
				Codes::ATTR_DISABLED_CONTENT => '<a href="' . getUrl('action', ['action' => 'dlattach', 'attach' => '$1']) . '">(' . $txt['link'] . '-$1)</a> ',
143
				Codes::ATTR_BLOCK_LEVEL => false,
144
				Codes::ATTR_AUTOLINK => false,
145
				Codes::ATTR_LENGTH => 6,
146
			),
147
			// Require a width with optional height/align, allows either use of full image and/or ;thumb
148
			// [attach width=300 align=??][/attach]
149
			array(
150
				Codes::ATTR_TAG => 'attach',
151
				Codes::ATTR_TYPE => Codes::TYPE_UNPARSED_CONTENT,
152
				Codes::ATTR_PARAM => array(
153
					'width' => array(
154
						Codes::PARAM_ATTR_VALUE => 'width:100%;max-width:$1px;',
155
						Codes::PARAM_ATTR_MATCH => '(\d+)',
156
					),
157
					'height' => array(
158
						Codes::PARAM_ATTR_OPTIONAL => true,
159
						Codes::PARAM_ATTR_VALUE => 'max-height:$1px;',
160
						Codes::PARAM_ATTR_MATCH => '(\d+)',
161
					),
162
					'align' => array(
163
						Codes::PARAM_ATTR_OPTIONAL => true,
164
						Codes::PARAM_ATTR_VALUE => 'float$1;',
165
						Codes::PARAM_ATTR_MATCH => '(right|left|center)',
166
					),
167
				),
168
				Codes::ATTR_DISABLED => $disabled,
169
				Codes::ATTR_RESET => '{width}{height}~{align}',
170
				Codes::ATTR_CONTENT => &self::$typeTag,
171
				Codes::ATTR_VALIDATE => $disabled ? null : self::buildTag(),
172
				Codes::ATTR_DISALLOW_PARENTS => $disallow,
173
				Codes::ATTR_DISABLED_CONTENT => '<a href="' . getUrl('action', ['action' => 'dlattach', 'attach' => '$1']) . '">(' . $txt['link'] . '-$1)</a> ',
174
				Codes::ATTR_BLOCK_LEVEL => false,
175
				Codes::ATTR_AUTOLINK => false,
176
				Codes::ATTR_LENGTH => 6,
177
			),
178
			// Require a height with option width/align [attach height=300 align=??][/attach]
179
			array(
180
				Codes::ATTR_TAG => 'attach',
181
				Codes::ATTR_TYPE => Codes::TYPE_UNPARSED_CONTENT,
182
				Codes::ATTR_PARAM => array(
183
					'height' => array(
184
						Codes::PARAM_ATTR_VALUE => 'max-height:$1px;',
185
						Codes::PARAM_ATTR_MATCH => '(\d+)',
186
					),
187
					'width' => array(
188
						Codes::PARAM_ATTR_OPTIONAL => true,
189
						Codes::PARAM_ATTR_VALUE => 'width:100%;max-width:$1px;',
190
						Codes::PARAM_ATTR_MATCH => '(\d+)',
191
					),
192
					'align' => array(
193
						Codes::PARAM_ATTR_OPTIONAL => true,
194
						Codes::PARAM_ATTR_VALUE => 'float$1',
195
						Codes::PARAM_ATTR_MATCH => '(right|left|center)',
196
					),
197
				),
198
				Codes::ATTR_DISABLED => $disabled,
199
				Codes::ATTR_RESET => '{width}{height}~{align}',
200
				Codes::ATTR_CONTENT => &self::$typeTag,
201
				Codes::ATTR_VALIDATE => $disabled ? null : self::buildTag(),
202
				Codes::ATTR_DISALLOW_PARENTS => $disallow,
203
				Codes::ATTR_DISABLED_CONTENT => '<a href="' . getUrl('action', ['action' => 'dlattach', 'attach' => '$1']) . '">(' . $txt['link'] . '-$1)</a> ',
204
				Codes::ATTR_BLOCK_LEVEL => false,
205
				Codes::ATTR_AUTOLINK => false,
206
				Codes::ATTR_LENGTH => 6,
207
			),
208
			// Align with an optional a type? [attach align=right type=thumb][/attach]
209
			array(
210
				Codes::ATTR_TAG => 'attach',
211
				Codes::ATTR_TYPE => Codes::TYPE_UNPARSED_CONTENT,
212
				Codes::ATTR_PARAM => array(
213
					'align' => array(
214
						Codes::PARAM_ATTR_VALUE => 'float$1',
215
						Codes::PARAM_ATTR_MATCH => '(right|left|center)',
216
					),
217
					'type' => array(
218
						Codes::PARAM_ATTR_OPTIONAL => true,
219
						Codes::PARAM_ATTR_VALUE => ';$1',
220
						Codes::PARAM_ATTR_MATCH => '(thumb|image)',
221
					),
222
				),
223
				Codes::ATTR_DISABLED => $disabled,
224
				Codes::ATTR_RESET => '~{align}~{type}',
225
				Codes::ATTR_CONTENT => &self::$typeTag,
226
				Codes::ATTR_VALIDATE => $disabled ? null : self::buildTag(),
227
				Codes::ATTR_DISALLOW_PARENTS => $disallow,
228
				Codes::ATTR_DISABLED_CONTENT => '<a href="' . getUrl('action', ['action' => 'dlattach', 'attach' => '$1']) . '">(' . $txt['link'] . '-$1)</a> ',
229
				Codes::ATTR_BLOCK_LEVEL => false,
230
				Codes::ATTR_AUTOLINK => false,
231
				Codes::ATTR_LENGTH => 6,
232
			),
233
			// [attachurl=xx] -- no image but a link with some details
234
			array(
235
				Codes::ATTR_TAG => 'attachurl',
236
				Codes::ATTR_TYPE => Codes::TYPE_UNPARSED_CONTENT,
237
				Codes::ATTR_DISABLED => $disabledUrl,
238
				Codes::ATTR_CONTENT => '$1',
239
				Codes::ATTR_VALIDATE => $disabledUrl ? null : self::validate_url(),
240
				Codes::ATTR_DISALLOW_PARENTS => $disallow,
241
				Codes::ATTR_DISABLED_CONTENT => '<a href="' . getUrl('action', ['action' => 'dlattach', 'attach' => '$1']) . '">(' . $txt['link'] . '-$1)</a> ',
242
				Codes::ATTR_BLOCK_LEVEL => false,
243
				Codes::ATTR_AUTOLINK => false,
244
				Codes::ATTR_LENGTH => 9,
245
			),
246
		));
247
	}
248
249
	/**
250
	 * This provides for the control of returned tags.  The tag will be different base on
251
	 * - Preview, Approval Y/N, Image Y/N, File mime type and width, height, align, type attributes
252
	 *
253
	 * - Determines if the ILA is an image or not
254
	 * - Sets the lightbox attributes if an image is identified
255
	 * - Sets a pending approval image if the attachment is not approved and not a preview
256
	 * - Keeps track of attachment usage to prevent displaying below the post
257
	 * - Sets self::$typeTag which is a reference to the tag content attribute
258
	 *
259
	 * @return callable
260
	 */
261
	public static function buildTag()
262
	{
263
		global $modSettings;
264
265
		return static function (&$data, $disabled, $tag) use ($modSettings) {
266
			$num = $data;
267
			$attachment = [];
268
			$preview = self::isPreview($num);
269
270
			// Was this tag dynamically disabled from the parser, aka print page or other addon?
271
			if (in_array('attach', $disabled, true))
272
			{
273
				self::$typeTag = $tag[Codes::ATTR_DISABLED_CONTENT];
274
				return;
275
			}
276
277
			// Not a preview, then determine the actual type of attachment we are dealing with
278
			if (!$preview)
279
			{
280
				require_once(SUBSDIR . '/Attachments.subs.php');
281
				$attachment = isAttachmentImage($num);
282
			}
283
284
			// Grab the tags content value, at this point it will have completed parameter exchange
285
			$parameters = $tag[Codes::ATTR_CONTENT] ?? '~~~';
286
			$parameters = explode('~', $parameters);
287
288
			$style = $parameters[0] ?? '';
289
			$class = $parameters[1] ?? '';
290
			$type = $parameters[2] ?? (empty($style) ? ';thumb' : '');
291
292
			// Not approved gets a bland pending image
293
			if (empty($attachment['is_approved']) && !$preview)
294
			{
295
				self::$typeTag = '
296
					<img src="' . getUrl('action', ['action' => 'dlattach', 'id' => 'ila']) . '" alt="X" class="bbc_img' . $class . '" loading="lazy" />';
297
			}
298
			// An image will get the light box treatment
299
			elseif (!empty($attachment['is_image']) || $preview)
300
			{
301
				$type = empty($modSettings['attachmentThumbnails']) ? '' : $type;
302
				$alt = Util::htmlspecialchars($attachment['filename'] ?? 'X');
303
				self::$typeTag = '
304
					<a id="link_$1" data-lightboximage="$1" data-lightboxmessage="0" href="' . getUrl('action', ['action' => 'dlattach', 'attach' => '$1', 'image']) . '">
305
						<img src="' . getUrl('action', ['action' => 'dlattach', 'attach' => '$1', 'ila' => '1']) . $type . '" style="' . $style . '" alt="' . $alt . '" class="bbc_img ' . $class . '" loading="lazy" />
306
					</a>';
307
			}
308
			// Not an image, determine a mime thumbnail or use a default thumbnail
309
			else
310
			{
311
				$thumbUrl = returnMimeThumb(($attachment['fileext'] ?? ''), true);
312
				if ($attachment === false)
313
				{
314
					self::$typeTag = '
315
					<img src="' . $thumbUrl . '" alt="X" class="bbc_img' . $class . '" loading="lazy"/>';
316
				}
317
				else
318
				{
319
					self::$typeTag = '
320
					<a href="' . getUrl('action', ['action' => 'dlattach', 'attach' => $num]) . '">
321
						<img src="' . $thumbUrl . '" alt="' . $attachment['filename'] . '" class="bbc_img ' . $class . '" loading="lazy" />
322
					</a>';
323
				}
324
			}
325
326
			self::trackIlaUsage($num);
327
		};
328
	}
329
330
	/**
331
	 * This prevents a little repetition and provides a some control for url tags
332
	 *
333
	 * - Determines if the ILA is an image or not
334
	 * - Keeps track of attachment usage to prevent displaying below the post
335
	 *
336
	 * @return callable
337
	 */
338
	public static function validate_url()
339
	{
340
		global $txt;
341
342
		return static function (&$data) use ($txt) {
343
			$num = $data;
344
			$attachment = false;
345
346
			// Not a preview, then sanitize the attach id and determine the details
347
			$preview = self::isPreview($num);
348
			if (!$preview)
349
			{
350
				require_once(SUBSDIR . '/Attachments.subs.php');
351
				$attachment = isAttachmentImage($num);
352
			}
353
354
			// Not approved gets a bland message
355
			if (empty($attachment['is_approved']) && !$preview)
356
			{
357
				$data = '
358
				<a href="#">
359
					<i class="icon icon-small i-paperclip"></i>&nbsp;' . $txt['awaiting_approval'] . '
360
				</a>&nbsp;';
361
			}
362
			// If we got the details ...
363
			elseif ($attachment)
364
			{
365
				$data = '
366
				<a href="' . getUrl('action', ['action' => 'dlattach', 'attach' => $num]) . '">
367
					<i class="icon icon-small i-paperclip"></i>&nbsp;' . $attachment['filename'] . '
368
				</a>&nbsp;(' . $attachment['size'] . ($attachment['is_image'] ? ' ' . $attachment['width'] . 'x' . $attachment['height'] : '') . ')';
369
			}
370
			else
371
			{
372
				$data = '
373
				<a href="' . getUrl('action', ['action' => 'dlattach', 'attach' => $num]) . '">
374
					<i class="icon icon-small i-paperclip"></i>&nbsp;' . $num . '
375
				</a>';
376
			}
377
378
			self::trackIlaUsage($num);
379
		};
380
	}
381
382
	/**
383
	 * Checks if this is a request for a yet posted attachment preview
384
	 * Will intval the attachment number if not a preview
385
	 *
386
	 * @param string $data if ila will be (int)'ed otherwise left alone
387
	 * @return bool
388
	 */
389
	public static function isPreview(&$data)
390
	{
391
		if (strpos($data, 'post_tmp_' . User::$info->id . '_') === false)
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
392
		{
393
			$data = (int) $data;
394
			return false;
395
		}
396
397
		return true;
398
	}
399
400
	/**
401
	 * Keeps track of attachment usage to prevent displaying below the post
402
	 *
403
	 * @param int $data
404
	 */
405
	public static function trackIlaUsage($data)
406
	{
407
		global $context;
408
409
		$context['ila_dont_show_attach_below'][] = $data;
410
		$context['ila_dont_show_attach_below'] = array_unique($context['ila_dont_show_attach_below']);
411
	}
412
413
	/**
414
	 * Settings hook for the admin panel
415
	 *
416
	 * What it does:
417
	 *
418
	 * - Defines our settings array and uses our settings class to manage the data
419
	 *
420
	 * @param array $config_vars
421
	 */
422
	public static function integrate_modify_attachment_settings(&$config_vars)
423
	{
424
		$config_vars[] = ['title', 'attachment_inline_title'];
425
		$config_vars[] = ['check', 'attachment_inline_enabled'];
426
		$config_vars[] = ['check', 'attachment_inline_quotes'];
427
	}
428
}
429