Passed
Pull Request — release-2.1 (#6101)
by Jon
04:02
created

ExportProfileData_Background::add_dtd()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 5
b 0
f 0
nc 1
nop 9
dl 0
loc 8
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * This file incrementally exports a member's profile data to a downloadable file.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2020 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 RC2
14
 */
15
16
/**
17
 * Class ExportProfileData_Background
18
 */
19
class ExportProfileData_Background extends SMF_BackgroundTask
20
{
21
	/**
22
	 * Some private variables to help the static functions in this class.
23
	 */
24
	private static $export_details = array();
25
	private static $real_modSettings = array();
26
	private static $xslt_info = array('stylesheet' => '', 'doctype' => '');
27
28
	/**
29
	 * This is the main dispatcher for the class.
30
	 * It calls the correct private function based on the information stored in
31
	 * the task details.
32
	 *
33
	 * @return bool Always returns true
34
	 */
35
	public function execute()
36
	{
37
		global $sourcedir;
38
39
		if (!defined('EXPORTING'))
40
			define('EXPORTING', 1);
41
42
		// This could happen if the user manually changed the URL params of the export request.
43
		if ($this->_details['format'] == 'HTML' && (!class_exists('DOMDocument') || !class_exists('XSLTProcessor')))
44
		{
45
			require_once($sourcedir . DIRECTORY_SEPARATOR . 'Profile-Export.php');
46
			$export_formats = get_export_formats();
47
48
			$this->_details['format'] = 'XML_XSLT';
49
			$this->_details['format_settings'] = $export_formats['XML_XSLT'];
50
		}
51
52
		// Inform static functions of the export format, etc.
53
		self::$export_details = $this->_details;
54
55
		// For exports only, members can always see their own posts, even in boards that they can no longer access.
56
		$member_info = $this->getMinUserInfo(array($this->_details['uid']));
57
		$member_info = array_merge($member_info[$this->_details['uid']], array(
58
			'buddies' => array(),
59
			'query_see_board' => '1=1',
60
			'query_see_message_board' => '1=1',
61
			'query_see_topic_board' => '1=1',
62
			'query_wanna_see_board' => '1=1',
63
			'query_wanna_see_message_board' => '1=1',
64
			'query_wanna_see_topic_board' => '1=1',
65
		));
66
67
		// Use some temporary integration hooks to manipulate BBC parsing during export.
68
		add_integration_function('integrate_pre_parsebbc', 'ExportProfileData_Background::pre_parsebbc', false);
69
		add_integration_function('integrate_post_parsebbc', 'ExportProfileData_Background::post_parsebbc', false);
70
		add_integration_function('integrate_bbc_codes', 'ExportProfileData_Background::bbc_codes', false);
71
		add_integration_function('integrate_post_parseAttachBBC', 'ExportProfileData_Background::post_parseAttachBBC', false);
72
		add_integration_function('integrate_attach_bbc_validate', 'ExportProfileData_Background::attach_bbc_validate', false);
73
74
		// We currently support exporting to XML and HTML
75
		if ($this->_details['format'] == 'XML')
76
			$this->exportXml($member_info);
77
		elseif ($this->_details['format'] == 'HTML')
78
			$this->exportHtml($member_info);
79
		elseif ($this->_details['format'] == 'XML_XSLT')
80
			$this->exportXmlXslt($member_info);
81
82
		return true;
83
	}
84
85
	/**
86
	 * The workhorse of this class. Compiles profile data to XML files.
87
	 *
88
	 * @param array $member_info Minimal $user_info about the relevant member.
89
	 */
90
	protected function exportXml($member_info)
91
	{
92
		global $smcFunc, $sourcedir, $context, $modSettings, $settings, $user_info, $mbname;
93
		global $user_profile, $txt, $scripturl, $query_this_board;
94
95
		// For convenience...
96
		$uid = $this->_details['uid'];
97
		$lang = $this->_details['lang'];
98
		$included = $this->_details['included'];
99
		$start = $this->_details['start'];
100
		$latest = $this->_details['latest'];
101
		$datatype = $this->_details['datatype'];
102
103
		if (!isset($included[$datatype]['func']) || !isset($included[$datatype]['langfile']))
104
			return;
105
106
		require_once($sourcedir . DIRECTORY_SEPARATOR . 'News.php');
107
		require_once($sourcedir . DIRECTORY_SEPARATOR . 'ScheduledTasks.php');
108
109
		// Setup.
110
		$done = false;
111
		$delay = 0;
112
		$func = $included[$datatype]['func'];
0 ignored issues
show
Unused Code introduced by
The assignment to $func is dead and can be removed.
Loading history...
113
		$context['xmlnews_uid'] = $uid;
114
		$context['xmlnews_limit'] = !empty($modSettings['export_rate']) ? $modSettings['export_rate'] : 250;
115
		$context[$datatype . '_start'] = $start[$datatype];
116
		$datatypes = array_keys($included);
117
118
		// Fake a wee bit of $user_info so that loading the member data & language doesn't choke.
119
		$user_info = $member_info;
120
121
		loadEssentialThemeData();
122
		$settings['actual_theme_dir'] = $settings['theme_dir'];
123
		$context['user']['id'] = $uid;
124
		$context['user']['language'] = $lang;
125
		loadMemberData($uid);
126
		loadLanguage(implode('+', array_unique(array('index', 'Modifications', 'Stats', 'Profile', $included[$datatype]['langfile']))), $lang);
127
128
		// @todo Ask lawyers whether the GDPR requires us to include posts in the recycle bin.
129
		$query_this_board = '{query_see_message_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' AND m.id_board != ' . $modSettings['recycle_board'] : '');
130
131
		// We need a valid export directory.
132
		if (empty($modSettings['export_dir']) || !file_exists($modSettings['export_dir']))
133
		{
134
			require_once($sourcedir . DIRECTORY_SEPARATOR . 'Profile-Export.php');
135
			if (create_export_dir() === false)
136
				return;
137
		}
138
139
		$export_dir_slash = $modSettings['export_dir'] . DIRECTORY_SEPARATOR;
140
141
		$idhash = hash_hmac('sha1', $uid, get_auth_secret());
142
		$idhash_ext = $idhash . '.' . $this->_details['format_settings']['extension'];
143
144
		// Increment the file number until we reach one that doesn't exist.
145
		$filenum = 1;
146
		$realfile = $export_dir_slash . $filenum . '_' . $idhash_ext;
147
		while (file_exists($realfile))
148
			$realfile = $export_dir_slash . ++$filenum . '_' . $idhash_ext;
149
150
		$tempfile = $export_dir_slash . $idhash_ext . '.tmp';
151
		$progressfile = $export_dir_slash . $idhash_ext . '.progress.json';
152
153
		$feed_meta = array(
154
			'title' => sprintf($txt['profile_of_username'], $user_profile[$uid]['real_name']),
155
			'desc' => sentence_list(array_map(function ($datatype) use ($txt) { return $txt[$datatype]; }, array_keys($included))),
156
			'author' => $mbname,
157
			'source' => $scripturl . '?action=profile;u=' . $uid,
158
			'self' => '', // Unused, but can't be null.
159
			'page' => &$filenum,
160
		);
161
162
		// Some paranoid hosts disable or hamstring the disk space functions in an attempt at security via obscurity.
163
		$check_diskspace = !empty($modSettings['export_min_diskspace_pct']) && function_exists('disk_free_space') && function_exists('disk_total_space') && intval(@disk_total_space($modSettings['export_dir']) >= 1440);
164
		$minspace = $check_diskspace ? ceil(disk_total_space($modSettings['export_dir']) * $modSettings['export_min_diskspace_pct'] / 100) : 0;
165
166
		// If a necessary file is missing, we need to start over.
167
		if (!file_exists($tempfile) || !file_exists($progressfile) || filesize($progressfile) == 0)
168
		{
169
			foreach (array_merge(array($tempfile, $progressfile), glob($export_dir_slash . '*_' . $idhash_ext)) as $fpath)
0 ignored issues
show
Bug introduced by
It seems like glob($export_dir_slash . '*_' . $idhash_ext) can also be of type false; however, parameter $array2 of array_merge() does only seem to accept array|null, maybe add an additional type check? ( Ignorable by Annotation )

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

169
			foreach (array_merge(array($tempfile, $progressfile), /** @scrutinizer ignore-type */ glob($export_dir_slash . '*_' . $idhash_ext)) as $fpath)
Loading history...
170
				@unlink($fpath);
171
172
			$filenum = 1;
173
			$realfile = $export_dir_slash . $filenum . '_' . $idhash_ext;
174
175
			buildXmlFeed('smf', array(), $feed_meta, 'profile');
176
			file_put_contents($tempfile, implode('', $context['feed']), LOCK_EX);
177
178
			$progress = array_fill_keys($datatypes, 0);
179
			file_put_contents($progressfile, $smcFunc['json_encode']($progress), LOCK_EX);
180
		}
181
		else
182
			$progress = $smcFunc['json_decode'](file_get_contents($progressfile), true);
183
184
		// Get the data, always in ascending order.
185
		$xml_data = call_user_func($included[$datatype]['func'], 'smf', true);
186
187
		// No data retrived? Just move on then.
188
		if (empty($xml_data))
189
			$datatype_done = true;
190
191
		// Basic profile data is quick and easy.
192
		elseif ($datatype == 'profile')
193
		{
194
			buildXmlFeed('smf', $xml_data, $feed_meta, 'profile');
195
			file_put_contents($tempfile, implode('', $context['feed']), LOCK_EX);
196
197
			$progress[$datatype] = time();
198
			$datatype_done = true;
199
200
			// Cache for subsequent reuse.
201
			$profile_basic_items = $context['feed']['items'];
202
			cache_put_data('export_profile_basic-' . $uid, $profile_basic_items, MAX_CLAIM_THRESHOLD);
203
		}
204
205
		// Posts and PMs...
206
		else
207
		{
208
			// We need the basic profile data in every export file.
209
			$profile_basic_items = cache_get_data('export_profile_basic-' . $uid, MAX_CLAIM_THRESHOLD);
210
			if (empty($profile_basic_items))
211
			{
212
				$profile_data = call_user_func($included['profile']['func'], 'smf', true);
213
				buildXmlFeed('smf', $profile_data, $feed_meta, 'profile');
214
				$profile_basic_items = $context['feed']['items'];
215
				cache_put_data('export_profile_basic-' . $uid, $profile_basic_items, MAX_CLAIM_THRESHOLD);
216
				unset($context['feed']);
217
			}
218
219
			$per_page = $this->_details['format_settings']['per_page'];
220
			$prev_item_count = empty($this->_details['item_count']) ? 0 : $this->_details['item_count'];
221
222
			// If the temp file has grown enormous, save it so we can start a new one.
223
			clearstatcache();
224
			if (file_exists($tempfile) && filesize($tempfile) >= 1024 * 1024 * 250)
225
			{
226
				rename($tempfile, $realfile);
227
				$realfile = $export_dir_slash . ++$filenum . '_' . $idhash_ext;
228
229
				if (empty($context['feed']['header']))
230
					buildXmlFeed('smf', array(), $feed_meta, 'profile');
231
232
				file_put_contents($tempfile, implode('', array($context['feed']['header'], $profile_basic_items, $context['feed']['footer'])), LOCK_EX);
233
234
				$prev_item_count = 0;
235
			}
236
237
			// Split $xml_data into reasonably sized chunks.
238
			if (empty($prev_item_count))
239
			{
240
				$xml_data = array_chunk($xml_data, $per_page);
241
			}
242
			else
243
			{
244
				$first_chunk = array_splice($xml_data, 0, $per_page - $prev_item_count);
245
				$xml_data = array_merge(array($first_chunk), array_chunk($xml_data, $per_page));
246
				unset($first_chunk);
247
			}
248
249
			foreach ($xml_data as $chunk => $items)
250
			{
251
				unset($new_item_count, $last_id);
252
253
				// Remember the last item so we know where to start next time.
254
				$last_item = end($items);
255
				if (isset($last_item['content'][0]['content']) && $last_item['content'][0]['tag'] === 'id')
256
					$last_id = $last_item['content'][0]['content'];
257
258
				// Build the XML string from the data.
259
				buildXmlFeed('smf', $items, $feed_meta, 'profile');
260
261
				// If disk space is insufficient, pause for a day so the admin can fix it.
262
				if ($check_diskspace && disk_free_space($modSettings['export_dir']) - $minspace <= strlen(implode('', $context['feed']) . self::$xslt_info['stylesheet']))
263
				{
264
					loadLanguage('Errors');
265
					log_error(sprintf($txt['export_low_diskspace'], $modSettings['export_min_diskspace_pct']));
266
267
					$delay = 86400;
268
				}
269
				else
270
				{
271
					// We need a file to write to, of course.
272
					if (!file_exists($tempfile))
273
						file_put_contents($tempfile, implode('', array($context['feed']['header'], $profile_basic_items, $context['feed']['footer'])), LOCK_EX);
274
275
					// Insert the new data before the feed footer.
276
					$handle = fopen($tempfile, 'r+');
277
					if (is_resource($handle))
278
					{
279
						flock($handle, LOCK_EX);
280
281
						fseek($handle, strlen($context['feed']['footer']) * -1, SEEK_END);
282
283
						$bytes_written = fwrite($handle, $context['feed']['items'] . $context['feed']['footer']);
284
285
						// If we couldn't write everything, revert the changes and consider the write to have failed.
286
						if ($bytes_written > 0 && $bytes_written < strlen($context['feed']['items'] . $context['feed']['footer']))
287
						{
288
							fseek($handle, $bytes_written * -1, SEEK_END);
289
							$pointer_pos = ftell($handle);
290
							ftruncate($handle, $pointer_pos);
291
							rewind($handle);
292
							fseek($handle, 0, SEEK_END);
293
							fwrite($handle, $context['feed']['footer']);
294
295
							$bytes_written = false;
296
						}
297
298
						flock($handle, LOCK_UN);
299
						fclose($handle);
300
					}
301
302
					// Write failed. We'll try again next time.
303
					if (empty($bytes_written))
304
					{
305
						$delay = MAX_CLAIM_THRESHOLD;
306
						break;
307
					}
308
309
					// All went well.
310
					else
311
					{
312
						// Track progress by ID where appropriate, and by time otherwise.
313
						$progress[$datatype] = !isset($last_id) ? time() : $last_id;
314
						file_put_contents($progressfile, $smcFunc['json_encode']($progress), LOCK_EX);
315
316
						// Are we done with this datatype yet?
317
						if (!isset($last_id) || (count($items) < $per_page && $last_id >= $latest[$datatype]))
318
							$datatype_done = true;
319
320
						// Finished the file for this chunk, so move on to the next one.
321
						if (count($items) >= $per_page - $prev_item_count)
322
						{
323
							rename($tempfile, $realfile);
324
							$realfile = $export_dir_slash . ++$filenum . '_' . $idhash_ext;
325
326
							file_put_contents($tempfile, implode('', array($context['feed']['header'], $profile_basic_items, $context['feed']['footer'])), LOCK_EX);
327
328
							$prev_item_count = $new_item_count = 0;
329
						}
330
						// This was the last chunk.
331
						else
332
						{
333
							// Should we append more items to this file next time?
334
							$new_item_count = isset($last_id) ? $prev_item_count + count($items) : 0;
335
						}
336
					}
337
				}
338
			}
339
		}
340
341
		if (!empty($datatype_done))
342
		{
343
			$datatype_key = array_search($datatype, $datatypes);
344
			$done = !isset($datatypes[$datatype_key + 1]);
345
346
			if (!$done)
347
				$datatype = $datatypes[$datatype_key + 1];
348
		}
349
350
		// Remove the .tmp extension from the final tempfile so the system knows it's done.
351
		if (!empty($done))
352
		{
353
			rename($tempfile, $realfile);
354
		}
355
356
		// Oops. Apparently some sneaky monkey cancelled the export while we weren't looking.
357
		elseif (!file_exists($progressfile))
358
		{
359
			@unlink($tempfile);
360
			return;
361
		}
362
363
		// We have more work to do again later.
364
		else
365
		{
366
			$start[$datatype] = $progress[$datatype];
367
368
			$new_details = array(
369
				'format' => $this->_details['format'],
370
				'uid' => $uid,
371
				'lang' => $lang,
372
				'included' => $included,
373
				'start' => $start,
374
				'latest' => $latest,
375
				'datatype' => $datatype,
376
				'format_settings' => $this->_details['format_settings'],
377
				'last_page' => $this->_details['last_page'],
378
				'dlfilename' => $this->_details['dlfilename'],
379
			);
380
			if (!empty($new_item_count))
381
				$new_details['item_count'] = $new_item_count;
382
383
			$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
384
				array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
385
				array('$sourcedir/tasks/ExportProfileData.php', 'ExportProfileData_Background', $smcFunc['json_encode']($new_details), time() - MAX_CLAIM_THRESHOLD + $delay),
386
				array()
387
			);
388
389
			if (!file_exists($tempfile))
390
			{
391
				buildXmlFeed('smf', array(), $feed_meta, 'profile');
392
				file_put_contents($tempfile, implode('', array($context['feed']['header'], !empty($profile_basic_items) ? $profile_basic_items : '', $context['feed']['footer'])), LOCK_EX);
393
			}
394
		}
395
396
		file_put_contents($progressfile, $smcFunc['json_encode']($progress), LOCK_EX);
397
	}
398
399
	/**
400
	 * Compiles profile data to HTML.
401
	 *
402
	 * Internally calls exportXml() and then uses an XSLT stylesheet to
403
	 * transform the XML files into HTML.
404
	 *
405
	 * @param array $member_info Minimal $user_info about the relevant member.
406
	 */
407
	protected function exportHtml($member_info)
408
	{
409
		global $modSettings, $context, $smcFunc, $sourcedir;
410
411
		$context['export_last_page'] = $this->_details['last_page'];
412
		$context['export_dlfilename'] = $this->_details['dlfilename'];
413
414
		// Perform the export to XML.
415
		$this->exportXml($member_info);
416
417
		// Determine which files, if any, are ready to be transformed.
418
		$export_dir_slash = $modSettings['export_dir'] . DIRECTORY_SEPARATOR;
419
		$idhash = hash_hmac('sha1', $this->_details['uid'], get_auth_secret());
420
		$idhash_ext = $idhash . '.' . $this->_details['format_settings']['extension'];
421
422
		$new_exportfiles = array();
423
		foreach (glob($export_dir_slash . '*_' . $idhash_ext) as $completed_file)
424
		{
425
			if (file_get_contents($completed_file, false, null, 0, 6) == '<?xml ')
426
				$new_exportfiles[] = $completed_file;
427
		}
428
		if (empty($new_exportfiles))
429
			return;
430
431
		// Just in case...
432
		@set_time_limit(MAX_CLAIM_THRESHOLD * 2);
433
		if (function_exists('apache_reset_timeout'))
434
			@apache_reset_timeout();
435
436
		// Get the XSLT stylesheet.
437
		require_once($sourcedir . DIRECTORY_SEPARATOR . 'Profile-Export.php');
438
		self::$xslt_info = get_xslt_stylesheet($this->_details['format'], $this->_details['uid']);
439
440
		// Set up the XSLT processor.
441
		$xslt = new DOMDocument();
442
		$xslt->loadXML(self::$xslt_info['stylesheet']);
443
		$xsltproc = new XSLTProcessor();
444
		$xsltproc->importStylesheet($xslt);
445
446
		// Transform the files to HTML.
447
		$xmldoc = new DOMDocument();
448
		foreach ($new_exportfiles as $exportfile)
449
		{
450
			$xmldoc->load($exportfile);
451
			$xsltproc->transformToURI($xmldoc, $exportfile);
452
		}
453
	}
454
455
	/**
456
	 * Compiles profile data to XML with embedded XSLT.
457
	 *
458
	 * Internally calls exportXml() and then embeds an XSLT stylesheet into
459
	 * the XML so that it can be processed by the client.
460
	 *
461
	 * @param array $member_info Minimal $user_info about the relevant member.
462
	 */
463
	protected function exportXmlXslt($member_info)
464
	{
465
		global $modSettings, $context, $smcFunc, $sourcedir;
466
467
		$context['export_last_page'] = $this->_details['last_page'];
468
		$context['export_dlfilename'] = $this->_details['dlfilename'];
469
470
		// Embedded XSLT requires adding a special DTD and processing instruction in the main XML document.
471
		add_integration_function('integrate_xml_data', 'ExportProfileData_Background::add_dtd', false);
472
473
		// Perform the export to XML.
474
		$this->exportXml($member_info);
475
476
		// Make sure we have everything we need.
477
		if (empty(self::$xslt_info['stylesheet']))
478
		{
479
			require_once($sourcedir . DIRECTORY_SEPARATOR . 'Profile-Export.php');
480
			self::$xslt_info = get_xslt_stylesheet($this->_details['format'], $this->_details['uid']);
481
		}
482
		if (empty($context['feed']['footer']))
483
		{
484
			require_once($sourcedir . DIRECTORY_SEPARATOR . 'News.php');
485
			buildXmlFeed('smf', array(), array_fill_keys(array('title', 'desc', 'source', 'self'), ''), 'profile');
486
		}
487
488
		// Find any completed files that don't yet have the stylesheet embedded in them.
489
		$export_dir_slash = $modSettings['export_dir'] . DIRECTORY_SEPARATOR;
490
		$idhash = hash_hmac('sha1', $this->_details['uid'], get_auth_secret());
491
		$idhash_ext = $idhash . '.' . $this->_details['format_settings']['extension'];
492
493
		$test_length = strlen(self::$xslt_info['stylesheet']) + strlen($context['feed']['footer']);
494
495
		$new_exportfiles = array();
496
		foreach (glob($export_dir_slash . '*_' . $idhash_ext) as $completed_file)
497
		{
498
			if (filesize($completed_file) < $test_length || file_get_contents($completed_file, false, null, $test_length * -1) !== self::$xslt_info['stylesheet'] . $context['feed']['footer'])
499
				$new_exportfiles[] = $completed_file;
500
		}
501
		if (empty($new_exportfiles))
502
			return;
503
504
		// Embedding the XSLT means writing to the file yet again.
505
		foreach ($new_exportfiles as $exportfile)
506
		{
507
			$handle = fopen($exportfile, 'r+');
508
			if (is_resource($handle))
509
			{
510
				flock($handle, LOCK_EX);
511
512
				fseek($handle, strlen($context['feed']['footer']) * -1, SEEK_END);
513
514
				$bytes_written = fwrite($handle, self::$xslt_info['stylesheet'] . $context['feed']['footer']);
515
516
				// If we couldn't write everything, revert the changes.
517
				if ($bytes_written > 0 && $bytes_written < strlen(self::$xslt_info['stylesheet'] . $context['feed']['footer']))
518
				{
519
					fseek($handle, $bytes_written * -1, SEEK_END);
520
					$pointer_pos = ftell($handle);
521
					ftruncate($handle, $pointer_pos);
522
					rewind($handle);
523
					fseek($handle, 0, SEEK_END);
524
					fwrite($handle, $context['feed']['footer']);
525
				}
526
527
				flock($handle, LOCK_UN);
528
				fclose($handle);
529
			}
530
		}
531
	}
532
533
	/**
534
	 * Adds a custom DOCTYPE definition and an XSLT processing instruction to
535
	 * the main XML file's header.
536
	 */
537
	public static function add_dtd(&$xml_data, &$feed_meta, &$namespaces, &$extraFeedTags, &$forceCdataKeys, &$nsKeys, $xml_format, $subaction, &$doctype)
538
	{
539
		global $sourcedir;
540
541
		require_once($sourcedir . DIRECTORY_SEPARATOR . 'Profile-Export.php');
542
		self::$xslt_info = get_xslt_stylesheet(self::$export_details['format'], self::$export_details['uid']);
543
544
		$doctype = self::$xslt_info['doctype'];
545
	}
546
547
	/**
548
	 * Adjusts some parse_bbc() parameters for the special case of exports.
549
	 */
550
	public static function pre_parsebbc(&$message, &$smileys, &$cache_id, &$parse_tags)
551
	{
552
		global $modSettings, $context, $user_info;
553
554
		$cache_id = '';
555
556
		if (in_array(self::$export_details['format'], array('HTML', 'XML_XSLT')))
557
		{
558
			foreach (array('smileys_url', 'attachmentThumbnails') as $var)
559
				if (isset($modSettings[$var]))
560
					self::$real_modSettings[$var] = $modSettings[$var];
561
562
			$modSettings['smileys_url'] = '.';
563
			$modSettings['attachmentThumbnails'] = false;
564
		}
565
		else
566
		{
567
			$smileys = false;
568
569
			if (!isset($modSettings['disabledBBC']))
570
				$modSettings['disabledBBC'] = 'attach';
571
			else
572
			{
573
				self::$real_modSettings['disabledBBC'] = $modSettings['disabledBBC'];
574
575
				if (strpos($modSettings['disabledBBC'], 'attach') === false)
576
					$modSettings['disabledBBC'] = implode(',', array_merge(array_filter(explode(',', $modSettings['disabledBBC'])), array('attach')));
577
			}
578
		}
579
	}
580
581
	/**
582
	 * Reverses changes made by pre_parsebbc()
583
	 */
584
	public static function post_parsebbc(&$message, &$smileys, &$cache_id, &$parse_tags)
585
	{
586
		global $modSettings, $context;
587
588
		foreach (array('disabledBBC', 'smileys_url', 'attachmentThumbnails') as $var)
589
			if (isset(self::$real_modSettings[$var]))
590
				$modSettings[$var] = self::$real_modSettings[$var];
591
	}
592
593
	/**
594
	 * Adjusts certain BBCodes for the special case of exports.
595
	 */
596
	public static function bbc_codes(&$codes, &$no_autolink_tags)
597
	{
598
		foreach ($codes as &$code)
599
		{
600
			// To make the "Select" link work we'd need to embed a bunch more JS. Not worth it.
601
			if ($code['tag'] === 'code')
602
				$code['content'] = preg_replace('~<a class="codeoperation\b.*?</a>~', '', $code['content']);
603
		}
604
	}
605
606
	/**
607
	 * Adjusts the attachment download URL for the special case of exports.
608
	 */
609
	public static function post_parseAttachBBC(&$attachContext)
610
	{
611
		global $scripturl, $context;
612
		static $dltokens;
613
614
		if (empty($dltokens[$context['xmlnews_uid']]))
615
		{
616
			$idhash = hash_hmac('sha1', $context['xmlnews_uid'], get_auth_secret());
617
			$dltokens[$context['xmlnews_uid']] = hash_hmac('sha1', $idhash, get_auth_secret());
618
		}
619
620
		$attachContext['orig_href'] = $scripturl . '?action=profile;area=dlattach;u=' . $context['xmlnews_uid'] . ';attach=' . $attachContext['id'] . ';t=' . $dltokens[$context['xmlnews_uid']];
621
		$attachContext['href'] = rawurlencode($attachContext['id'] . ' - ' . html_entity_decode($attachContext['name']));
622
	}
623
624
	/**
625
	 * Adjusts the format of the HTML produced by the attach BBCode.
626
	 */
627
	public static function attach_bbc_validate(&$returnContext, $currentAttachment, $tag, $data, $disabled, $params)
628
	{
629
		global $smcFunc, $txt;
630
631
		$orig_link = '<a href="' . $currentAttachment['orig_href'] . '" class="bbc_link">' . $txt['export_download_original'] . '</a>';
632
		$hidden_orig_link = ' <a href="' . $currentAttachment['orig_href'] . '" class="bbc_link dlattach_' . $currentAttachment['id'] . '" style="display:none; flex: 1 0 auto; margin: auto;">' . $txt['export_download_original'] . '</a>';
633
634
		if ($params['{display}'] == 'link')
635
		{
636
			$returnContext .= ' (' . $orig_link . ')';
637
		}
638
		elseif (!empty($currentAttachment['is_image']))
639
		{
640
			$returnContext = '<span style="display: inline-flex; justify-content: center; align-items: center; position: relative;">' . preg_replace(
641
				array(
642
					'thumbnail_toggle' => '~</?a\b[^>]*>~',
643
					'src' => '~src="' . preg_quote($currentAttachment['href'], '~') . ';image"~',
644
				),
645
				array(
646
					'thumbnail_toggle' => '',
647
					'src' => 'src="' . $currentAttachment['href'] . '" onerror="$(\'.dlattach_' . $currentAttachment['id'] . '\').show(); $(\'.dlattach_' . $currentAttachment['id'] . '\').css({\'position\': \'absolute\'});"',
648
				),
649
				$returnContext
650
			) . $hidden_orig_link . '</span>' ;
651
		}
652
		elseif (strpos($currentAttachment['mime_type'], 'video/') === 0)
653
		{
654
			$returnContext = preg_replace(
655
				array(
656
					'src' => '~src="' . preg_quote($currentAttachment['href'], '~') . '"~',
657
					'opening_tag' => '~^<div class="videocontainer"~',
658
					'closing_tag' => '~</div>$~',
659
				),
660
				array(
661
					'src' => '$0 onerror="$(this).fadeTo(0, 0.2); $(\'.dlattach_' . $currentAttachment['id'] . '\').show(); $(\'.dlattach_' . $currentAttachment['id'] . '\').css({\'position\': \'absolute\'});"',
662
					'opening_tag' => '<div class="videocontainer" style="display: flex; justify-content: center; align-items: center; position: relative;"',
663
					'closing_tag' =>  $hidden_orig_link . '</div>',
664
				),
665
				$returnContext
666
			);
667
		}
668
		elseif (strpos($currentAttachment['mime_type'], 'audio/') === 0)
669
		{
670
			$returnContext = '<span style="display: inline-flex; justify-content: center; align-items: center; position: relative;">' . preg_replace(
671
				array(
672
					'opening_tag' => '~^<audio\b~',
673
				),
674
				array(
675
					'opening_tag' => '<audio onerror="$(this).fadeTo(0, 0); $(\'.dlattach_' . $currentAttachment['id'] . '\').show(); $(\'.dlattach_' . $currentAttachment['id'] . '\').css({\'position\': \'absolute\'});"',
676
				),
677
				$returnContext
678
			) . $hidden_orig_link . '</span>';
679
		}
680
		else
681
		{
682
			$returnContext = '<span style="display: inline-flex; justify-content: center; align-items: center; position: relative;">' . preg_replace(
683
				array(
684
					'obj_opening' => '~^<object\b~',
685
					'link' => '~<a href="' . preg_quote($currentAttachment['href'], '~') . '" class="bbc_link">([^<]*)</a>~',
686
				),
687
				array(
688
					'obj_opening' => '<object onerror="$(this).fadeTo(0, 0.2); $(\'.dlattach_' . $currentAttachment['id'] . '\').show(); $(\'.dlattach_' . $currentAttachment['id'] . '\').css({\'position\': \'absolute\'});"~',
689
					'link' => '$0 (' . $orig_link . ')',
690
				),
691
				$returnContext
692
			) . $hidden_orig_link . '</span>';
693
		}
694
	}
695
}
696
697
?>