Issues (1061)

Sources/News.php (10 issues)

1
<?php
2
3
/**
4
 * This file contains the files necessary to display news as an XML feed.
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
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Outputs xml data representing recent information or a profile.
21
 *
22
 * Can be passed subactions which decide what is output:
23
 *  'recent' for recent posts,
24
 *  'news' for news topics,
25
 *  'members' for recently registered members,
26
 *  'profile' for a member's profile.
27
 *  'posts' for a member's posts.
28
 *  'personal_messages' for a member's personal messages.
29
 *
30
 * When displaying a member's profile or posts, the u parameter identifies which member. Defaults
31
 * to the current user's id.
32
 * To display a member's personal messages, the u parameter must match the id of the current user.
33
 *
34
 * Outputs can be in RSS 0.92, RSS 2, Atom, RDF, or our own custom XML format. Default is RSS 2.
35
 *
36
 * Accessed via ?action=.xml.
37
 *
38
 * Does not use any templates, sub templates, or template layers.
39
 *
40
 * Uses Stats, Profile, Post, and PersonalMessage language files.
41
 */
42
function ShowXmlFeed()
43
{
44
	global $board, $board_info, $context, $scripturl, $boardurl, $txt, $modSettings, $user_info;
45
	global $query_this_board, $smcFunc, $forum_version, $settings, $cache_enable, $cachedir;
46
47
	// List all the different types of data they can pull.
48
	$subActions = array(
49
		'recent' => 'getXmlRecent',
50
		'news' => 'getXmlNews',
51
		'members' => 'getXmlMembers',
52
		'profile' => 'getXmlProfile',
53
		'posts' => 'getXmlPosts',
54
		'personal_messages' => 'getXmlPMs',
55
	);
56
57
	// Easy adding of sub actions
58
	call_integration_hook('integrate_xmlfeeds', array(&$subActions));
59
60
	$subaction = empty($_GET['sa']) || !isset($subActions[$_GET['sa']]) ? 'recent' : $_GET['sa'];
61
62
	// Make sure the id is a number and not "I like trying to hack the database".
63
	$context['xmlnews_uid'] = isset($_GET['u']) ? (int) $_GET['u'] : $user_info['id'];
64
65
	// Default to latest 5.  No more than 255, please.
66
	$context['xmlnews_limit'] = empty($_GET['limit']) || (int) $_GET['limit'] < 1 ? 5 : min((int) $_GET['limit'], 255);
67
68
	// Users can always export their own profile data
69
	if (in_array($subaction, array('profile', 'posts', 'personal_messages')) && !$user_info['is_guest'] && $context['xmlnews_uid'] == $user_info['id'])
70
		$modSettings['xmlnews_enable'] = true;
71
72
	// If it's not enabled, die.
73
	if (empty($modSettings['xmlnews_enable']))
74
		obExit(false);
75
76
	loadLanguage('Stats');
77
78
	// Show in rss or proprietary format?
79
	$xml_format = $_GET['type'] = isset($_GET['type']) && in_array($_GET['type'], array('smf', 'rss', 'rss2', 'atom', 'rdf')) ? $_GET['type'] : 'rss2';
80
81
	// Some general metadata for this feed. We'll change some of these values below.
82
	$feed_meta = array(
83
		'title' => '',
84
		'desc' => $txt['xml_rss_desc'],
85
		'author' => $context['forum_name'],
86
		'source' => $scripturl,
87
		'rights' => '© ' . date('Y') . ' ' . $context['forum_name'],
88
		'icon' => !empty($settings['og_image']) ? $settings['og_image'] : $boardurl . '/favicon.ico',
89
		'language' => !empty($txt['lang_locale']) ? str_replace("_", "-", substr($txt['lang_locale'], 0, strcspn($txt['lang_locale'], "."))) : 'en',
90
		'self' => $scripturl,
91
	);
92
	foreach (array('action', 'sa', 'type', 'board', 'boards', 'c', 'u', 'limit', 'offset') as $var)
93
		if (isset($_GET[$var]))
94
			$feed_meta['self'] .= ($feed_meta['self'] === $scripturl ? '?' : ';' ) . $var . '=' . $_GET[$var];
95
96
	// Handle the cases where a board, boards, or category is asked for.
97
	$query_this_board = 1;
98
	$context['optimize_msg'] = array(
99
		'highest' => 'm.id_msg <= b.id_last_msg',
100
	);
101
	if (!empty($_GET['c']) && empty($board))
102
	{
103
		$_GET['c'] = explode(',', $_GET['c']);
104
		foreach ($_GET['c'] as $i => $c)
105
			$_GET['c'][$i] = (int) $c;
106
107
		if (count($_GET['c']) == 1)
108
		{
109
			$request = $smcFunc['db_query']('', '
110
				SELECT name
111
				FROM {db_prefix}categories
112
				WHERE id_cat = {int:current_category}',
113
				array(
114
					'current_category' => (int) $_GET['c'][0],
115
				)
116
			);
117
			list ($feed_meta['title']) = $smcFunc['db_fetch_row']($request);
118
			$smcFunc['db_free_result']($request);
119
		}
120
121
		$request = $smcFunc['db_query']('', '
122
			SELECT b.id_board, b.num_posts
123
			FROM {db_prefix}boards AS b
124
			WHERE b.id_cat IN ({array_int:current_category_list})
125
				AND {query_see_board}',
126
			array(
127
				'current_category_list' => $_GET['c'],
128
			)
129
		);
130
		$total_cat_posts = 0;
131
		$boards = array();
132
		while ($row = $smcFunc['db_fetch_assoc']($request))
133
		{
134
			$boards[] = $row['id_board'];
135
			$total_cat_posts += $row['num_posts'];
136
		}
137
		$smcFunc['db_free_result']($request);
138
139
		if (!empty($boards))
140
			$query_this_board = 'b.id_board IN (' . implode(', ', $boards) . ')';
141
142
		// Try to limit the number of messages we look through.
143
		if ($total_cat_posts > 100 && $total_cat_posts > $modSettings['totalMessages'] / 15)
144
			$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 400 - $context['xmlnews_limit'] * 5);
145
	}
146
	elseif (!empty($_GET['boards']))
147
	{
148
		$_GET['boards'] = explode(',', $_GET['boards']);
149
		foreach ($_GET['boards'] as $i => $b)
150
			$_GET['boards'][$i] = (int) $b;
151
152
		$request = $smcFunc['db_query']('', '
153
			SELECT b.id_board, b.num_posts, b.name
154
			FROM {db_prefix}boards AS b
155
			WHERE b.id_board IN ({array_int:board_list})
156
				AND {query_see_board}
157
			LIMIT {int:limit}',
158
			array(
159
				'board_list' => $_GET['boards'],
160
				'limit' => count($_GET['boards']),
161
			)
162
		);
163
164
		// Either the board specified doesn't exist or you have no access.
165
		$num_boards = $smcFunc['db_num_rows']($request);
166
		if ($num_boards == 0)
167
			fatal_lang_error('no_board');
168
169
		$total_posts = 0;
170
		$boards = array();
171
		while ($row = $smcFunc['db_fetch_assoc']($request))
172
		{
173
			if ($num_boards == 1)
174
				$feed_meta['title'] = $row['name'];
175
176
			$boards[] = $row['id_board'];
177
			$total_posts += $row['num_posts'];
178
		}
179
		$smcFunc['db_free_result']($request);
180
181
		if (!empty($boards))
182
			$query_this_board = 'b.id_board IN (' . implode(', ', $boards) . ')';
183
184
		// The more boards, the more we're going to look through...
185
		if ($total_posts > 100 && $total_posts > $modSettings['totalMessages'] / 12)
186
			$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 500 - $context['xmlnews_limit'] * 5);
187
	}
188
	elseif (!empty($board))
189
	{
190
		$request = $smcFunc['db_query']('', '
191
			SELECT num_posts
192
			FROM {db_prefix}boards
193
			WHERE id_board = {int:current_board}
194
			LIMIT 1',
195
			array(
196
				'current_board' => $board,
197
			)
198
		);
199
		list ($total_posts) = $smcFunc['db_fetch_row']($request);
200
		$smcFunc['db_free_result']($request);
201
202
		$feed_meta['title'] = $board_info['name'];
203
		$feed_meta['source'] .= '?board=' . $board . '.0';
204
205
		$query_this_board = 'b.id_board = ' . $board;
206
207
		// Try to look through just a few messages, if at all possible.
208
		if ($total_posts > 80 && $total_posts > $modSettings['totalMessages'] / 10)
209
			$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 600 - $context['xmlnews_limit'] * 5);
210
	}
211
	else
212
	{
213
		$query_this_board = '{query_see_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
214
			AND b.id_board != ' . $modSettings['recycle_board'] : '');
215
		$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 100 - $context['xmlnews_limit'] * 5);
216
	}
217
218
	$feed_meta['title'] .= (!empty($feed_meta['title']) ? ' - ' : '') . $context['forum_name'];
219
220
	// Sanitize feed metadata values
221
	foreach ($feed_meta as $mkey => $mvalue)
222
		$feed_meta[$mkey] = strip_tags($mvalue);
223
224
	// We only want some information, not all of it.
225
	$cachekey = array($xml_format, $_GET['action'], $context['xmlnews_limit'], $subaction);
226
	foreach (array('board', 'boards', 'c') as $var)
227
		if (isset($_GET[$var]))
228
			$cachekey[] = $var . '=' . implode(',', (array) $_GET[$var]);
229
	$cachekey = md5($smcFunc['json_encode']($cachekey) . (!empty($query_this_board) ? $query_this_board : ''));
230
	$cache_t = microtime(true);
231
232
	// Get the associative array representing the xml.
233
	if (!empty($cache_enable) && (!$user_info['is_guest'] || $cache_enable >= 3))
234
	{
235
		$xml_data = cache_get_data('xmlfeed-' . $xml_format . ':' . ($user_info['is_guest'] ? '' : $user_info['id'] . '-') . $cachekey, 240);
236
	}
237
	if (empty($xml_data))
238
	{
239
		$call = call_helper($subActions[$subaction], true);
240
241
		if (!empty($call))
242
			$xml_data = call_user_func($call, $xml_format);
0 ignored issues
show
It seems like $call can also be of type boolean; however, parameter $function of call_user_func() does only seem to accept callable, 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

242
			$xml_data = call_user_func(/** @scrutinizer ignore-type */ $call, $xml_format);
Loading history...
243
244
		if (!empty($cache_enable) && (($user_info['is_guest'] && $cache_enable >= 3)
245
		|| (!$user_info['is_guest'] && (microtime(true) - $cache_t > 0.2))))
246
			cache_put_data('xmlfeed-' . $xml_format . ':' . ($user_info['is_guest'] ? '' : $user_info['id'] . '-') . $cachekey, $xml_data, 240);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $xml_data does not seem to be defined for all execution paths leading up to this point.
Loading history...
247
	}
248
249
	buildXmlFeed($xml_format, $xml_data, $feed_meta, $subaction);
250
251
	// Descriptive filenames = good
252
	$filename[] = $feed_meta['title'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$filename was never initialized. Although not strictly required by PHP, it is generally a good practice to add $filename = array(); before regardless.
Loading history...
253
	$filename[] = $subaction;
254
	if (in_array($subaction, array('profile', 'posts', 'personal_messages')))
255
		$filename[] = 'u=' . $context['xmlnews_uid'];
256
	if (!empty($boards))
257
		$filename[] = 'boards=' . implode(',', $boards);
258
	elseif (!empty($board))
259
		$filename[] = 'board=' . $board;
260
	$filename[] = $xml_format;
261
	$filename = preg_replace('/[^\p{L}\p{M}\p{N}\-]+/', '_', str_replace('"', '', un_htmlspecialchars(strip_tags(implode('-', $filename)))));
262
263
	// This is an xml file....
264
	ob_end_clean();
265
	if (!empty($modSettings['enableCompressedOutput']))
266
		@ob_start('ob_gzhandler');
267
	else
268
		ob_start();
269
270
	if ($xml_format == 'smf' || isset($_GET['debug']))
271
		header('content-type: text/xml; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set']));
272
	elseif ($xml_format == 'rss' || $xml_format == 'rss2')
273
		header('content-type: application/rss+xml; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set']));
274
	elseif ($xml_format == 'atom')
275
		header('content-type: application/atom+xml; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set']));
276
	elseif ($xml_format == 'rdf')
277
		header('content-type: ' . (isBrowser('ie') ? 'text/xml' : 'application/rdf+xml') . '; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set']));
278
279
	header('content-disposition: ' . (isset($_GET['download']) ? 'attachment' : 'inline') . '; filename="' . $filename . '.xml"');
280
281
	echo implode('', $context['feed']);
282
283
	obExit(false);
284
}
285
286
function buildXmlFeed($xml_format, $xml_data, $feed_meta, $subaction)
287
{
288
	global $context, $txt, $scripturl;
289
290
	/* Important: Do NOT change this to an HTTPS address!
291
	 *
292
	 * Why? Because XML namespace names must be both unique and invariant
293
	 * once defined. They look like URLs merely because that's a convenient
294
	 * way to ensure uniqueness, but they are not used as URLs. They are
295
	 * used as case-sensitive identifier strings. If the string changes in
296
	 * any way, XML processing software (including PHP's own XML functions)
297
	 * will interpret the two versions of the string as entirely different
298
	 * namespaces, which could cause it to mangle the XML horrifically
299
	 * during processing.
300
	 */
301
	$smf_ns = 'htt'.'p:/'.'/ww'.'w.simple'.'machines.o'.'rg/xml/' . $subaction;
302
303
	// Allow mods to add extra namespaces and tags to the feed/channel
304
	$namespaces = array(
305
		'rss' => array(),
306
		'rss2' => array('atom' => 'htt'.'p:/'.'/ww'.'w.w3.o'.'rg/2005/Atom'),
307
		'atom' => array('' => 'htt'.'p:/'.'/ww'.'w.w3.o'.'rg/2005/Atom'),
308
		'rdf' => array(
309
			'' => 'htt'.'p:/'.'/purl.o'.'rg/rss/1.0/',
310
			'rdf' => 'htt'.'p:/'.'/ww'.'w.w3.o'.'rg/1999/02/22-rdf-syntax-ns#',
311
			'dc' => 'htt'.'p:/'.'/purl.o'.'rg/dc/elements/1.1/',
312
		),
313
		'smf' => array(
314
			'smf' => $smf_ns,
315
		),
316
	);
317
	if (in_array($subaction, array('profile', 'posts', 'personal_messages')))
318
	{
319
		$namespaces['rss']['smf'] = $smf_ns;
320
		$namespaces['rss2']['smf'] = $smf_ns;
321
		$namespaces['atom']['smf'] = $smf_ns;
322
	}
323
324
	$extraFeedTags = array(
325
		'rss' => array(),
326
		'rss2' => array(),
327
		'atom' => array(),
328
		'rdf' => array(),
329
		'smf' => array(),
330
	);
331
332
	// Allow mods to specify any keys that need special handling
333
	$forceCdataKeys = array();
334
	$nsKeys = array();
335
336
	// Remember this, just in case...
337
	$orig_feed_meta = $feed_meta;
338
339
	// If mods want to do somthing with this feed, let them do that now.
340
	// Provide the feed's data, metadata, namespaces, extra feed-level tags, keys that need special handling, the feed format, and the requested subaction
341
	call_integration_hook('integrate_xml_data', array(&$xml_data, &$feed_meta, &$namespaces, &$extraFeedTags, &$forceCdataKeys, &$nsKeys, $xml_format, $subaction));
342
343
	// These can't be empty
344
	foreach (array('title', 'desc', 'source', 'self') as $mkey)
345
		$feed_meta[$mkey] = !empty($feed_meta[$mkey]) ? $feed_meta[$mkey] : $orig_feed_meta[$mkey];
346
347
	// Sanitize feed metadata values
348
	foreach ($feed_meta as $mkey => $mvalue)
349
		$feed_meta[$mkey] = cdata_parse(fix_possible_url($mvalue));
350
351
	$ns_string = '';
352
	if (!empty($namespaces[$xml_format]))
353
	{
354
		foreach ($namespaces[$xml_format] as $nsprefix => $nsurl)
355
			$ns_string .= ' xmlns' . ($nsprefix !== '' ? ':' : '') . $nsprefix . '="' . $nsurl . '"';
356
	}
357
358
	$i = in_array($xml_format, array('atom', 'smf')) ? 1 : 2;
359
360
	$extraFeedTags_string = '';
361
	if (!empty($extraFeedTags[$xml_format]))
362
	{
363
		$indent = str_repeat("\t", $i);
364
		foreach ($extraFeedTags[$xml_format] as $extraTag)
365
			$extraFeedTags_string .= "\n" . $indent . $extraTag;
366
	}
367
368
	$context['feed'] = array();
369
370
	// First, output the xml header.
371
	$context['feed']['header'] = '<?xml version="1.0" encoding="' . $context['character_set'] . '"?' . '>';
372
373
	// Are we outputting an rss feed or one with more information?
374
	if ($xml_format == 'rss' || $xml_format == 'rss2')
375
	{
376
		// Start with an RSS 2.0 header.
377
		$context['feed']['header'] .= '
378
<rss version=' . ($xml_format == 'rss2' ? '"2.0"' : '"0.92"') . ' xml:lang="' . strtr($txt['lang_locale'], '_', '-') . '"' . $ns_string . '>
379
	<channel>
380
		<title>' . $feed_meta['title'] . '</title>
381
		<link>' . $feed_meta['source'] . '</link>
382
		<description>' . $feed_meta['desc'] . '</description>';
383
384
		if (!empty($feed_meta['icon']))
385
			$context['feed']['header'] .= '
386
		<image>
387
			<url>' . $feed_meta['icon'] . '</url>
388
			<title>' . $feed_meta['title'] . '</title>
389
			<link>' . $feed_meta['source'] . '</link>
390
		</image>';
391
392
		if (!empty($feed_meta['rights']))
393
			$context['feed']['header'] .= '
394
		<copyright>' . $feed_meta['rights'] . '</copyright>';
395
396
		if (!empty($feed_meta['language']))
397
			$context['feed']['header'] .= '
398
		<language>' . $feed_meta['language'] . '</language>';
399
400
		// RSS2 calls for this.
401
		if ($xml_format == 'rss2')
402
			$context['feed']['header'] .= '
403
		<atom:link rel="self" type="application/rss+xml" href="' . $feed_meta['self'] . '" />';
404
405
		$context['feed']['header'] .= $extraFeedTags_string;
406
407
		// Write the data as an XML string to $context['feed']['items']
408
		dumpTags($xml_data, $i, $xml_format, $forceCdataKeys, $nsKeys);
409
410
		// Output the footer of the xml.
411
		$context['feed']['footer'] = '
412
	</channel>
413
</rss>';
414
	}
415
	elseif ($xml_format == 'atom')
416
	{
417
		$context['feed']['header'] .= '
418
<feed' . $ns_string . (!empty($feed_meta['language']) ? ' xml:lang="' . $feed_meta['language'] . '"' : '') . '>
419
	<title>' . $feed_meta['title'] . '</title>
420
	<link rel="alternate" type="text/html" href="' . $feed_meta['source'] . '" />
421
	<link rel="self" type="application/atom+xml" href="' . $feed_meta['self'] . '" />
422
	<updated>' . gmstrftime('%Y-%m-%dT%H:%M:%SZ') . '</updated>
423
	<id>' . $feed_meta['source'] . '</id>
424
	<subtitle>' . $feed_meta['desc'] . '</subtitle>
425
	<generator uri="https://www.simplemachines.org" version="' . SMF_VERSION . '">SMF</generator>';
426
427
		if (!empty($feed_meta['icon']))
428
			$context['feed']['header'] .= '
429
	<icon>' . $feed_meta['icon'] . '</icon>';
430
431
		if (!empty($feed_meta['author']))
432
			$context['feed']['header'] .= '
433
	<author>
434
		<name>' . $feed_meta['author'] . '</name>
435
	</author>';
436
437
		if (!empty($feed_meta['rights']))
438
			$context['feed']['header'] .= '
439
	<rights>' . $feed_meta['rights'] . '</rights>';
440
441
		$context['feed']['header'] .= $extraFeedTags_string;
442
443
		dumpTags($xml_data, $i, $xml_format, $forceCdataKeys, $nsKeys);
444
445
		$context['feed']['footer'] = '
446
</feed>';
447
	}
448
	elseif ($xml_format == 'rdf')
449
	{
450
		$context['feed']['header'] .= '
451
<rdf:RDF' . $ns_string . '>
452
	<channel rdf:about="' . $scripturl . '">
453
		<title>' . $feed_meta['title'] . '</title>
454
		<link>' . $feed_meta['source'] . '</link>
455
		<description>' . $feed_meta['desc'] . '</description>';
456
457
		$context['feed']['header'] .= $extraFeedTags_string;
458
459
		$context['feed']['header'] .= '
460
		<items>
461
			<rdf:Seq>';
462
463
		foreach ($xml_data as $item)
464
		{
465
			$link = array_filter($item['content'], function($e)
466
			{
467
				return ($e['tag'] == 'link');
468
			});
469
			$link = array_pop($link);
470
471
			$context['feed']['header'] .= '
472
				<rdf:li rdf:resource="' . $link['content'] . '" />';
473
		}
474
475
		$context['feed']['header'] .= '
476
			</rdf:Seq>
477
		</items>
478
	</channel>';
479
480
		dumpTags($xml_data, $i, $xml_format, $forceCdataKeys, $nsKeys);
481
482
		$context['feed']['footer'] = '
483
</rdf:RDF>';
484
	}
485
	// Otherwise, we're using our proprietary formats - they give more data, though.
486
	else
487
	{
488
		$context['feed']['header'] .= '
489
<smf:xml-feed xml:lang="' . strtr($txt['lang_locale'], '_', '-') . '"' . $ns_string . ' version="' . SMF_VERSION . '" forum-name="' . $context['forum_name'] . '" forum-url="' . $scripturl . '"' . (!empty($feed_meta['title']) && $feed_meta['title'] != $context['forum_name'] ? ' title="' . $feed_meta['title'] . '"' : '') . (!empty($feed_meta['desc']) ? ' description="' . $feed_meta['desc'] . '"' : '') . ' source="' . $feed_meta['source'] . '" generated-date-localized="' . strip_tags(timeformat(time(), false, 'forum')) . '" generated-date-UTC="' . gmstrftime('%F %T') . '">';
490
491
		// Hard to imagine anyone wanting to add these for the proprietary format, but just in case...
492
		$context['feed']['header'] .= $extraFeedTags_string;
493
494
		// Dump out that associative array.  Indent properly.... and use the right names for the base elements.
495
		dumpTags($xml_data, $i, $xml_format, $forceCdataKeys, $nsKeys);
496
497
		$context['feed']['footer'] = '
498
</smf:xml-feed>';
499
	}
500
}
501
502
/**
503
 * Called from dumpTags to convert data to xml
504
 * Finds urls for local site and sanitizes them
505
 *
506
 * @param string $val A string containing a possible URL
507
 * @return string $val The string with any possible URLs sanitized
508
 */
509
function fix_possible_url($val)
510
{
511
	global $modSettings, $context, $scripturl;
512
513
	if (substr($val, 0, strlen($scripturl)) != $scripturl)
514
		return $val;
515
516
	call_integration_hook('integrate_fix_url', array(&$val));
517
518
	if (empty($modSettings['queryless_urls']) || ($context['server']['is_cgi'] && ini_get('cgi.fix_pathinfo') == 0 && @get_cfg_var('cgi.fix_pathinfo') == 0) || (!$context['server']['is_apache'] && !$context['server']['is_lighttpd']))
519
		return $val;
520
521
	$val = preg_replace_callback('~\b' . preg_quote($scripturl, '~') . '\?((?:board|topic)=[^#"]+)(#[^"]*)?$~', function($m) use ($scripturl)
522
	{
523
		return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html' . (isset($m[2]) ? $m[2] : "");
524
	}, $val);
525
	return $val;
526
}
527
528
/**
529
 * Ensures supplied data is properly encapsulated in cdata xml tags
530
 * Called from getXmlProfile in News.php
531
 *
532
 * @param string $data XML data
533
 * @param string $ns A namespace prefix for the XML data elements (used by mods, maybe)
534
 * @param boolean $force If true, enclose the XML data in cdata tags no matter what (used by mods, maybe)
535
 * @return string The XML data enclosed in cdata tags when necessary
536
 */
537
function cdata_parse($data, $ns = '', $force = false)
538
{
539
	global $smcFunc;
540
541
	// Do we even need to do this?
542
	if (strpbrk($data, '<>&') == false && $force !== true)
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpbrk($data, '<>&') of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
543
		return $data;
544
545
	$cdata = '<![CDATA[';
546
547
	// @todo If we drop the obsolete $ns parameter, this whole loop could be replaced with a simple `str_replace(']]>', ']]]]><[CDATA[>', $data)`
548
549
	for ($pos = 0, $n = strlen($data); $pos < $n; null)
550
	{
551
		$positions = array(
552
			strpos($data, '&', $pos),
553
			strpos($data, ']]>', $pos),
554
		);
555
		if ($ns != '')
556
			$positions[] = strpos($data, '<', $pos);
557
		foreach ($positions as $k => $dummy)
558
		{
559
			if ($dummy === false)
560
				unset($positions[$k]);
561
		}
562
563
		$old = $pos;
564
		$pos = empty($positions) ? $n : min($positions);
565
566
		if ($pos - $old > 0)
567
			$cdata .= substr($data, $old, $pos - $old);
568
		if ($pos >= $n)
569
			break;
570
571
		if (substr($data, $pos, 1) == '<')
572
		{
573
			$pos2 = strpos($data, '>', $pos);
574
			if ($pos2 === false)
575
				$pos2 = $n;
576
			if (substr($data, $pos + 1, 1) == '/')
577
				$cdata .= ']]></' . $ns . ':' . substr($data, $pos + 2, $pos2 - $pos - 1) . '<![CDATA[';
578
			else
579
				$cdata .= ']]><' . $ns . ':' . substr($data, $pos + 1, $pos2 - $pos) . '<![CDATA[';
580
			$pos = $pos2 + 1;
581
		}
582
		elseif (substr($data, $pos, 3) == ']]>')
583
		{
584
			$cdata .= ']]]]><![CDATA[>';
585
			$pos = $pos + 3;
586
		}
587
		elseif (substr($data, $pos, 1) == '&')
588
		{
589
			// We only want to match XML entities here, not HTML ones.
590
			preg_match('~\G(&(?:amp|lt|gt|quot|apos|#(?:\d{1,7}|x[0-9a-fA-F]{1,6}));)~', $data, $matches, null, $pos);
591
592
			if (!empty($matches[1]))
593
			{
594
				$cdata .= ']]>' . $matches[1] . '<![CDATA[';
595
				$pos = $pos + strlen($matches[1]);
596
			}
597
			else
598
			{
599
				$cdata .= '&';
600
				$pos++;
601
			}
602
		}
603
	}
604
605
	$cdata .= ']]>';
606
607
	return strtr($cdata, array('<![CDATA[]]>' => ''));
608
}
609
610
/**
611
 * Formats data retrieved in other functions into xml format.
612
 * Additionally formats data based on the specific format passed.
613
 * This function is recursively called to handle sub arrays of data.
614
 *
615
 * @param array $data The array to output as xml data
616
 * @param int $i The amount of indentation to use.
617
 * @param string $xml_format The format to use ('atom', 'rss', 'rss2' or empty for plain XML)
618
 * @param array $forceCdataKeys A list of keys on which to force cdata wrapping (used by mods, maybe)
619
 * @param array $nsKeys Key-value pairs of namespace prefixes to pass to cdata_parse() (used by mods, maybe)
620
 */
621
function dumpTags($data, $i, $xml_format = '', $forceCdataKeys = array(), $nsKeys = array())
622
{
623
	global $context;
624
625
	if (empty($context['feed']['items']))
626
		$context['feed']['items'] = '';
627
628
	// For every array in the data...
629
	foreach ($data as $element)
630
	{
631
		// If a tag was passed, use it instead of the key.
632
		$key = $element['tag'];
633
		$val = isset($element['content']) ? $element['content'] : null;
634
		$attrs = isset($element['attributes']) ? $element['attributes'] : null;
635
636
		// Skip it, it's been set to null.
637
		if ($key === null || ($val === null && $attrs === null))
638
			continue;
639
640
		$forceCdata = in_array($key, $forceCdataKeys);
641
		$ns = !empty($nsKeys[$key]) ? $nsKeys[$key] : '';
642
643
		// First let's indent!
644
		$context['feed']['items'] .= "\n" . str_repeat("\t", $i);
645
646
		// Beginning tag.
647
		$context['feed']['items'] .= '<' . $key;
648
649
		if (!empty($attrs))
650
		{
651
			foreach ($attrs as $attr_key => $attr_value)
652
				$context['feed']['items'] .= ' ' . $attr_key . '="' . fix_possible_url($attr_value) . '"';
653
		}
654
655
		// If it's empty, simply output an empty element.
656
		if (empty($val) && $val !== '0' && $val !== 0)
657
		{
658
			$context['feed']['items'] .= ' />';
659
		}
660
		else
661
		{
662
			$context['feed']['items'] .= '>';
663
664
			// The element's value.
665
			if (is_array($val))
666
			{
667
				// An array.  Dump it, and then indent the tag.
668
				dumpTags($val, $i + 1, $xml_format, $forceCdataKeys, $nsKeys);
669
				$context['feed']['items'] .= "\n" . str_repeat("\t", $i);
670
			}
671
			// A string with returns in it.... show this as a multiline element.
672
			elseif (strpos($val, "\n") !== false)
673
				$context['feed']['items'] .= "\n" . (!empty($element['cdata']) || $forceCdata ? cdata_parse(fix_possible_url($val), $ns, $forceCdata) : fix_possible_url($val)) . "\n" . str_repeat("\t", $i);
674
			// A simple string.
675
			else
676
				$context['feed']['items'] .= !empty($element['cdata']) || $forceCdata ? cdata_parse(fix_possible_url($val), $ns, $forceCdata) : fix_possible_url($val);
677
678
			// Ending tag.
679
			$context['feed']['items'] .= '</' . $key . '>';
680
		}
681
	}
682
}
683
684
/**
685
 * Retrieve the list of members from database.
686
 * The array will be generated to match the format.
687
 *
688
 * @todo get the list of members from Subs-Members.
689
 *
690
 * @param string $xml_format The format to use. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'
691
 * @param bool $ascending If true, get the earliest members first. Default false.
692
 * @return array An array of arrays of feed items. Each array has keys corresponding to the appropriate tags for the specified format.
693
 */
694
function getXmlMembers($xml_format, $ascending = false)
695
{
696
	global $scripturl, $smcFunc, $txt, $context;
697
698
	if (!allowedTo('view_mlist'))
699
		return array();
700
701
	loadLanguage('Profile');
702
703
	// Find the most (or least) recent members.
704
	$request = $smcFunc['db_query']('', '
705
		SELECT id_member, member_name, real_name, date_registered, last_login
706
		FROM {db_prefix}members
707
		ORDER BY id_member {raw:ascdesc}
708
		LIMIT {int:limit}',
709
		array(
710
			'limit' => $context['xmlnews_limit'],
711
			'ascdesc' => !empty($ascending) ? 'ASC' : 'DESC',
712
		)
713
	);
714
	$data = array();
715
	while ($row = $smcFunc['db_fetch_assoc']($request))
716
	{
717
		// If any control characters slipped in somehow, kill the evil things
718
		array_walk($row, 'cleanXml');
719
720
		// Create a GUID for each member using the tag URI scheme
721
		$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['date_registered']) . ':member=' . $row['id_member'];
722
723
		// Make the data look rss-ish.
724
		if ($xml_format == 'rss' || $xml_format == 'rss2')
725
			$data[] = array(
726
				'tag' => 'item',
727
				'content' => array(
728
					array(
729
						'tag' => 'title',
730
						'content' => $row['real_name'],
731
						'cdata' => true,
732
					),
733
					array(
734
						'tag' => 'link',
735
						'content' => $scripturl . '?action=profile;u=' . $row['id_member'],
736
					),
737
					array(
738
						'tag' => 'comments',
739
						'content' => $scripturl . '?action=pm;sa=send;u=' . $row['id_member'],
740
					),
741
					array(
742
						'tag' => 'pubDate',
743
						'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['date_registered']),
744
					),
745
					array(
746
						'tag' => 'guid',
747
						'content' => $guid,
748
						'attributes' => array(
749
							'isPermaLink' => 'false',
750
						),
751
					),
752
				),
753
			);
754
		elseif ($xml_format == 'rdf')
755
			$data[] = array(
756
				'tag' => 'item',
757
				'attributes' => array('rdf:about' => $scripturl . '?action=profile;u=' . $row['id_member']),
758
				'content' => array(
759
					array(
760
						'tag' => 'dc:format',
761
						'content' => 'text/html',
762
					),
763
					array(
764
						'tag' => 'title',
765
						'content' => $row['real_name'],
766
						'cdata' => true,
767
					),
768
					array(
769
						'tag' => 'link',
770
						'content' => $scripturl . '?action=profile;u=' . $row['id_member'],
771
					),
772
				),
773
			);
774
		elseif ($xml_format == 'atom')
775
			$data[] = array(
776
				'tag' => 'entry',
777
				'content' => array(
778
					array(
779
						'tag' => 'title',
780
						'content' => $row['real_name'],
781
						'cdata' => true,
782
					),
783
					array(
784
						'tag' => 'link',
785
						'attributes' => array(
786
							'rel' => 'alternate',
787
							'type' => 'text/html',
788
							'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
789
						),
790
					),
791
					array(
792
						'tag' => 'published',
793
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['date_registered']),
794
					),
795
					array(
796
						'tag' => 'updated',
797
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['last_login']),
798
					),
799
					array(
800
						'tag' => 'id',
801
						'content' => $guid,
802
					),
803
				),
804
			);
805
		// More logical format for the data, but harder to apply.
806
		else
807
			$data[] = array(
808
				'tag' => 'member',
809
				'attributes' => array('label' => $txt['who_member']),
810
				'content' => array(
811
					array(
812
						'tag' => 'name',
813
						'attributes' => array('label' => $txt['name']),
814
						'content' => $row['real_name'],
815
						'cdata' => true,
816
					),
817
					array(
818
						'tag' => 'time',
819
						'attributes' => array('label' => $txt['date_registered'], 'UTC' => gmstrftime('%F %T', $row['date_registered'])),
820
						'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['date_registered'], false, 'forum'))),
821
					),
822
					array(
823
						'tag' => 'id',
824
						'content' => $row['id_member'],
825
					),
826
					array(
827
						'tag' => 'link',
828
						'attributes' => array('label' => $txt['url']),
829
						'content' => $scripturl . '?action=profile;u=' . $row['id_member'],
830
					),
831
				),
832
			);
833
	}
834
	$smcFunc['db_free_result']($request);
835
836
	return $data;
837
}
838
839
/**
840
 * Get the latest topics information from a specific board,
841
 * to display later.
842
 * The returned array will be generated to match the xml_format.
843
 *
844
 * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'.
845
 * @param bool $ascending If true, get the oldest topics first. Default false.
846
 * @return array An array of arrays of topic data for the feed. Each array has keys corresponding to the tags for the specified format.
847
 */
848
function getXmlNews($xml_format, $ascending = false)
849
{
850
	global $scripturl, $modSettings, $board, $user_info;
851
	global $query_this_board, $smcFunc, $context, $txt;
852
853
	/* Find the latest (or earliest) posts that:
854
		- are the first post in their topic.
855
		- are on an any board OR in a specified board.
856
		- can be seen by this user. */
857
858
	$done = false;
859
	$loops = 0;
860
	while (!$done)
861
	{
862
		$optimize_msg = implode(' AND ', $context['optimize_msg']);
863
		$request = $smcFunc['db_query']('', '
864
			SELECT
865
				m.smileys_enabled, m.poster_time, m.id_msg, m.subject, m.body, m.modified_time,
866
				m.icon, t.id_topic, t.id_board, t.num_replies,
867
				b.name AS bname,
868
				COALESCE(mem.id_member, 0) AS id_member,
869
				COALESCE(mem.email_address, m.poster_email) AS poster_email,
870
				COALESCE(mem.real_name, m.poster_name) AS poster_name
871
			FROM {db_prefix}topics AS t
872
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
873
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
874
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
875
			WHERE ' . $query_this_board . (empty($optimize_msg) ? '' : '
876
				AND {raw:optimize_msg}') . (empty($board) ? '' : '
877
				AND t.id_board = {int:current_board}') . ($modSettings['postmod_active'] ? '
878
				AND t.approved = {int:is_approved}' : '') . '
879
			ORDER BY t.id_first_msg {raw:ascdesc}
880
			LIMIT {int:limit}',
881
			array(
882
				'current_board' => $board,
883
				'is_approved' => 1,
884
				'limit' => $context['xmlnews_limit'],
885
				'optimize_msg' => $optimize_msg,
886
				'ascdesc' => !empty($ascending) ? 'ASC' : 'DESC',
887
			)
888
		);
889
		// If we don't have $context['xmlnews_limit'] results, try again with an unoptimized version covering all rows.
890
		if ($loops < 2 && $smcFunc['db_num_rows']($request) < $context['xmlnews_limit'])
891
		{
892
			$smcFunc['db_free_result']($request);
893
			if (empty($_GET['boards']) && empty($board))
894
				unset($context['optimize_msg']['lowest']);
895
			else
896
				$context['optimize_msg']['lowest'] = 'm.id_msg >= t.id_first_msg';
897
			$context['optimize_msg']['highest'] = 'm.id_msg <= t.id_last_msg';
898
			$loops++;
899
		}
900
		else
901
			$done = true;
902
	}
903
	$data = array();
904
	while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $request does not seem to be defined for all execution paths leading up to this point.
Loading history...
905
	{
906
		// If any control characters slipped in somehow, kill the evil things
907
		array_walk($row, 'cleanXml');
908
909
		// Limit the length of the message, if the option is set.
910
		if (!empty($modSettings['xmlnews_maxlen']) && $smcFunc['strlen'](str_replace('<br>', "\n", $row['body'])) > $modSettings['xmlnews_maxlen'])
911
			$row['body'] = strtr($smcFunc['substr'](str_replace('<br>', "\n", $row['body']), 0, $modSettings['xmlnews_maxlen'] - 3), array("\n" => '<br>')) . '...';
912
913
		$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
914
915
		censorText($row['body']);
916
		censorText($row['subject']);
917
918
		// Do we want to include any attachments?
919
		if (!empty($modSettings['attachmentEnable']) && !empty($modSettings['xmlnews_attachments']) && allowedTo('view_attachments', $row['id_board']))
920
		{
921
			$attach_request = $smcFunc['db_query']('', '
922
				SELECT
923
					a.id_attach, a.filename, COALESCE(a.size, 0) AS filesize, a.mime_type, a.downloads, a.approved, m.id_topic AS topic
924
				FROM {db_prefix}attachments AS a
925
					LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
926
				WHERE a.attachment_type = {int:attachment_type}
927
					AND a.id_msg = {int:message_id}',
928
				array(
929
					'message_id' => $row['id_msg'],
930
					'attachment_type' => 0,
931
					'is_approved' => 1,
932
				)
933
			);
934
			$loaded_attachments = array();
935
			while ($attach = $smcFunc['db_fetch_assoc']($attach_request))
936
			{
937
				// Include approved attachments only
938
				if ($attach['approved'])
939
					$loaded_attachments['attachment_' . $attach['id_attach']] = $attach;
940
			}
941
			$smcFunc['db_free_result']($attach_request);
942
943
			// Sort the attachments by size to make things easier below
944
			if (!empty($loaded_attachments))
945
			{
946
				uasort($loaded_attachments, function($a, $b)
947
				{
948
					if ($a['filesize'] == $b['filesize'])
949
						return 0;
950
					return ($a['filesize'] < $b['filesize']) ? -1 : 1;
951
				});
952
			}
953
			else
954
				$loaded_attachments = null;
955
		}
956
		else
957
			$loaded_attachments = null;
958
959
		// Create a GUID for this topic using the tag URI scheme
960
		$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['poster_time']) . ':topic=' . $row['id_topic'];
961
962
		// Being news, this actually makes sense in rss format.
963
		if ($xml_format == 'rss' || $xml_format == 'rss2')
964
		{
965
			// Only one attachment allowed in RSS.
966
			if ($loaded_attachments !== null)
967
			{
968
				$attachment = array_pop($loaded_attachments);
0 ignored issues
show
$loaded_attachments of type null is incompatible with the type array expected by parameter $array of array_pop(). ( Ignorable by Annotation )

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

968
				$attachment = array_pop(/** @scrutinizer ignore-type */ $loaded_attachments);
Loading history...
969
				$enclosure = array(
970
					'url' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
971
					'length' => $attachment['filesize'],
972
					'type' => $attachment['mime_type'],
973
				);
974
			}
975
			else
976
				$enclosure = null;
977
978
			$data[] = array(
979
				'tag' => 'item',
980
				'content' => array(
981
					array(
982
						'tag' => 'title',
983
						'content' => $row['subject'],
984
						'cdata' => true,
985
					),
986
					array(
987
						'tag' => 'link',
988
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
989
					),
990
					array(
991
						'tag' => 'description',
992
						'content' => $row['body'],
993
						'cdata' => true,
994
					),
995
					array(
996
						'tag' => 'author',
997
						'content' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? $row['poster_email'] . ' (' . $row['poster_name'] . ')' : null,
998
					),
999
					array(
1000
						'tag' => 'comments',
1001
						'content' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0',
1002
					),
1003
					array(
1004
						'tag' => 'category',
1005
						'content' => $row['bname'],
1006
					),
1007
					array(
1008
						'tag' => 'pubDate',
1009
						'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']),
1010
					),
1011
					array(
1012
						'tag' => 'guid',
1013
						'content' => $guid,
1014
						'attributes' => array(
1015
							'isPermaLink' => 'false',
1016
						),
1017
					),
1018
					array(
1019
						'tag' => 'enclosure',
1020
						'attributes' => $enclosure,
1021
					),
1022
				),
1023
			);
1024
		}
1025
		elseif ($xml_format == 'rdf')
1026
		{
1027
			$data[] = array(
1028
				'tag' => 'item',
1029
				'attributes' => array('rdf:about' => $scripturl . '?topic=' . $row['id_topic'] . '.0'),
1030
				'content' => array(
1031
					array(
1032
						'tag' => 'dc:format',
1033
						'content' => 'text/html',
1034
					),
1035
					array(
1036
						'tag' => 'title',
1037
						'content' => $row['subject'],
1038
						'cdata' => true,
1039
					),
1040
					array(
1041
						'tag' => 'link',
1042
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
1043
					),
1044
					array(
1045
						'tag' => 'description',
1046
						'content' => $row['body'],
1047
						'cdata' => true,
1048
					),
1049
				),
1050
			);
1051
		}
1052
		elseif ($xml_format == 'atom')
1053
		{
1054
			// Only one attachment allowed
1055
			if (!empty($loaded_attachments))
1056
			{
1057
				$attachment = array_pop($loaded_attachments);
1058
				$enclosure = array(
1059
					'rel' => 'enclosure',
1060
					'href' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
1061
					'length' => $attachment['filesize'],
1062
					'type' => $attachment['mime_type'],
1063
				);
1064
			}
1065
			else
1066
				$enclosure = null;
1067
1068
			$data[] = array(
1069
				'tag' => 'entry',
1070
				'content' => array(
1071
					array(
1072
						'tag' => 'title',
1073
						'content' => $row['subject'],
1074
						'cdata' => true,
1075
					),
1076
					array(
1077
						'tag' => 'link',
1078
						'attributes' => array(
1079
							'rel' => 'alternate',
1080
							'type' => 'text/html',
1081
							'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
1082
						),
1083
					),
1084
					array(
1085
						'tag' => 'summary',
1086
						'attributes' => array('type' => 'html'),
1087
						'content' => $row['body'],
1088
						'cdata' => true,
1089
					),
1090
					array(
1091
						'tag' => 'category',
1092
						'attributes' => array('term' => $row['bname']),
1093
					),
1094
					array(
1095
						'tag' => 'author',
1096
						'content' => array(
1097
							array(
1098
								'tag' => 'name',
1099
								'content' => $row['poster_name'],
1100
								'cdata' => true,
1101
							),
1102
							array(
1103
								'tag' => 'email',
1104
								'content' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? $row['poster_email'] : null,
1105
							),
1106
							array(
1107
								'tag' => 'uri',
1108
								'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : null,
1109
							),
1110
						)
1111
					),
1112
					array(
1113
						'tag' => 'published',
1114
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']),
1115
					),
1116
					array(
1117
						'tag' => 'updated',
1118
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']),
1119
					),
1120
					array(
1121
						'tag' => 'id',
1122
						'content' => $guid,
1123
					),
1124
					array(
1125
						'tag' => 'link',
1126
						'attributes' => $enclosure,
1127
					),
1128
				),
1129
			);
1130
		}
1131
		// The biggest difference here is more information.
1132
		else
1133
		{
1134
			loadLanguage('Post');
1135
1136
			$attachments = array();
1137
			if (!empty($loaded_attachments))
1138
			{
1139
				foreach ($loaded_attachments as $attachment)
1140
				{
1141
					$attachments[] = array(
1142
						'tag' => 'attachment',
1143
						'attributes' => array('label' => $txt['attachment']),
1144
						'content' => array(
1145
							array(
1146
								'tag' => 'id',
1147
								'content' => $attachment['id_attach'],
1148
							),
1149
							array(
1150
								'tag' => 'name',
1151
								'attributes' => array('label' => $txt['name']),
1152
								'content' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])),
1153
							),
1154
							array(
1155
								'tag' => 'downloads',
1156
								'attributes' => array('label' => $txt['downloads']),
1157
								'content' => $attachment['downloads'],
1158
							),
1159
							array(
1160
								'tag' => 'size',
1161
								'attributes' => array('label' => $txt['filesize']),
1162
								'content' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'],
1163
							),
1164
							array(
1165
								'tag' => 'byte_size',
1166
								'attributes' => array('label' => $txt['filesize']),
1167
								'content' => $attachment['filesize'],
1168
							),
1169
							array(
1170
								'tag' => 'link',
1171
								'attributes' => array('label' => $txt['url']),
1172
								'content' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'],
1173
							),
1174
						)
1175
					);
1176
				}
1177
			}
1178
			else
1179
				$attachments = null;
1180
1181
			$data[] = array(
1182
				'tag' => 'article',
1183
				'attributes' => array('label' => $txt['news']),
1184
				'content' => array(
1185
					array(
1186
						'tag' => 'time',
1187
						'attributes' => array('label' => $txt['date'], 'UTC' => gmstrftime('%F %T', $row['poster_time'])),
1188
						'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['poster_time'], false, 'forum'))),
1189
					),
1190
					array(
1191
						'tag' => 'id',
1192
						'content' => $row['id_topic'],
1193
					),
1194
					array(
1195
						'tag' => 'subject',
1196
						'attributes' => array('label' => $txt['subject']),
1197
						'content' => $row['subject'],
1198
						'cdata' => true,
1199
					),
1200
					array(
1201
						'tag' => 'body',
1202
						'attributes' => array('label' => $txt['message']),
1203
						'content' => $row['body'],
1204
						'cdata' => true,
1205
					),
1206
					array(
1207
						'tag' => 'poster',
1208
						'attributes' => array('label' => $txt['author']),
1209
						'content' => array(
1210
							array(
1211
								'tag' => 'name',
1212
								'attributes' => array('label' => $txt['name']),
1213
								'content' => $row['poster_name'],
1214
								'cdata' => true,
1215
							),
1216
							array(
1217
								'tag' => 'id',
1218
								'content' => $row['id_member'],
1219
							),
1220
							array(
1221
								'tag' => 'link',
1222
								'attributes' => !empty($row['id_member']) ? array('label' => $txt['url']) : null,
1223
								'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '',
1224
							),
1225
						)
1226
					),
1227
					array(
1228
						'tag' => 'topic',
1229
						'attributes' => array('label' => $txt['topic']),
1230
						'content' => $row['id_topic'],
1231
					),
1232
					array(
1233
						'tag' => 'board',
1234
						'attributes' => array('label' => $txt['board']),
1235
						'content' => array(
1236
							array(
1237
								'tag' => 'name',
1238
								'attributes' => array('label' => $txt['name']),
1239
								'content' => $row['bname'],
1240
							),
1241
							array(
1242
								'tag' => 'id',
1243
								'content' => $row['id_board'],
1244
							),
1245
							array(
1246
								'tag' => 'link',
1247
								'attributes' => array('label' => $txt['url']),
1248
								'content' => $scripturl . '?board=' . $row['id_board'] . '.0',
1249
							),
1250
						),
1251
					),
1252
					array(
1253
						'tag' => 'link',
1254
						'attributes' => array('label' => $txt['url']),
1255
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
1256
					),
1257
					array(
1258
						'tag' => 'attachments',
1259
						'attributes' => array('label' => $txt['attachments']),
1260
						'content' => $attachments,
1261
					),
1262
				),
1263
			);
1264
		}
1265
	}
1266
	$smcFunc['db_free_result']($request);
1267
1268
	return $data;
1269
}
1270
1271
/**
1272
 * Get the recent topics to display.
1273
 * The returned array will be generated to match the xml_format.
1274
 *
1275
 * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'
1276
 * @return array An array of arrays containing data for the feed. Each array has keys corresponding to the appropriate tags for the specified format.
1277
 */
1278
function getXmlRecent($xml_format)
1279
{
1280
	global $scripturl, $modSettings, $board, $txt;
1281
	global $query_this_board, $smcFunc, $context, $user_info, $sourcedir;
1282
1283
	require_once($sourcedir . '/Subs-Attachments.php');
1284
1285
	$done = false;
1286
	$loops = 0;
1287
	while (!$done)
1288
	{
1289
		$optimize_msg = implode(' AND ', $context['optimize_msg']);
1290
		$request = $smcFunc['db_query']('', '
1291
			SELECT m.id_msg
1292
			FROM {db_prefix}messages AS m
1293
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1294
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1295
			WHERE ' . $query_this_board . (empty($optimize_msg) ? '' : '
1296
				AND {raw:optimize_msg}') . (empty($board) ? '' : '
1297
				AND m.id_board = {int:current_board}') . ($modSettings['postmod_active'] ? '
1298
				AND m.approved = {int:is_approved}' : '') . '
1299
			ORDER BY m.id_msg DESC
1300
			LIMIT {int:limit}',
1301
			array(
1302
				'limit' => $context['xmlnews_limit'],
1303
				'current_board' => $board,
1304
				'is_approved' => 1,
1305
				'optimize_msg' => $optimize_msg,
1306
			)
1307
		);
1308
		// If we don't have $context['xmlnews_limit'] results, try again with an unoptimized version covering all rows.
1309
		if ($loops < 2 && $smcFunc['db_num_rows']($request) < $context['xmlnews_limit'])
1310
		{
1311
			$smcFunc['db_free_result']($request);
1312
			if (empty($_GET['boards']) && empty($board))
1313
				unset($context['optimize_msg']['lowest']);
1314
			else
1315
				$context['optimize_msg']['lowest'] = $loops ? 'm.id_msg >= t.id_first_msg' : 'm.id_msg >= (t.id_last_msg - t.id_first_msg) / 2';
1316
			$loops++;
1317
		}
1318
		else
1319
			$done = true;
1320
	}
1321
	$messages = array();
1322
	while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $request does not seem to be defined for all execution paths leading up to this point.
Loading history...
1323
		$messages[] = $row['id_msg'];
1324
	$smcFunc['db_free_result']($request);
1325
1326
	if (empty($messages))
1327
		return array();
1328
1329
	// Find the most recent posts this user can see.
1330
	$request = $smcFunc['db_query']('', '
1331
		SELECT
1332
			m.smileys_enabled, m.poster_time, m.id_msg, m.subject, m.body, m.id_topic, t.id_board,
1333
			b.name AS bname, t.num_replies, m.id_member, m.icon, mf.id_member AS id_first_member,
1334
			COALESCE(mem.real_name, m.poster_name) AS poster_name, mf.subject AS first_subject,
1335
			COALESCE(memf.real_name, mf.poster_name) AS first_poster_name,
1336
			COALESCE(mem.email_address, m.poster_email) AS poster_email, m.modified_time
1337
		FROM {db_prefix}messages AS m
1338
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1339
			INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
1340
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1341
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
1342
			LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = mf.id_member)
1343
		WHERE m.id_msg IN ({array_int:message_list})
1344
			' . (empty($board) ? '' : 'AND t.id_board = {int:current_board}') . '
1345
		ORDER BY m.id_msg DESC
1346
		LIMIT {int:limit}',
1347
		array(
1348
			'limit' => $context['xmlnews_limit'],
1349
			'current_board' => $board,
1350
			'message_list' => $messages,
1351
		)
1352
	);
1353
	$data = array();
1354
	while ($row = $smcFunc['db_fetch_assoc']($request))
1355
	{
1356
		// If any control characters slipped in somehow, kill the evil things
1357
		array_walk($row, 'cleanXml');
1358
1359
		// Limit the length of the message, if the option is set.
1360
		if (!empty($modSettings['xmlnews_maxlen']) && $smcFunc['strlen'](str_replace('<br>', "\n", $row['body'])) > $modSettings['xmlnews_maxlen'])
1361
			$row['body'] = strtr($smcFunc['substr'](str_replace('<br>', "\n", $row['body']), 0, $modSettings['xmlnews_maxlen'] - 3), array("\n" => '<br>')) . '...';
1362
1363
		$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
1364
1365
		censorText($row['body']);
1366
		censorText($row['subject']);
1367
1368
		// Do we want to include any attachments?
1369
		if (!empty($modSettings['attachmentEnable']) && !empty($modSettings['xmlnews_attachments']) && allowedTo('view_attachments', $row['id_board']))
1370
		{
1371
			$attach_request = $smcFunc['db_query']('', '
1372
				SELECT
1373
					a.id_attach, a.filename, COALESCE(a.size, 0) AS filesize, a.mime_type, a.downloads, a.approved, m.id_topic AS topic
1374
				FROM {db_prefix}attachments AS a
1375
					LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1376
				WHERE a.attachment_type = {int:attachment_type}
1377
					AND a.id_msg = {int:message_id}',
1378
				array(
1379
					'message_id' => $row['id_msg'],
1380
					'attachment_type' => 0,
1381
					'is_approved' => 1,
1382
				)
1383
			);
1384
			$loaded_attachments = array();
1385
			while ($attach = $smcFunc['db_fetch_assoc']($attach_request))
1386
			{
1387
				// Include approved attachments only
1388
				if ($attach['approved'])
1389
					$loaded_attachments['attachment_' . $attach['id_attach']] = $attach;
1390
			}
1391
			$smcFunc['db_free_result']($attach_request);
1392
1393
			// Sort the attachments by size to make things easier below
1394
			if (!empty($loaded_attachments))
1395
			{
1396
				uasort($loaded_attachments, function($a, $b)
1397
				{
1398
					if ($a['filesize'] == $b['filesize'])
1399
						return 0;
1400
1401
					return ($a['filesize'] < $b['filesize']) ? -1 : 1;
1402
				});
1403
			}
1404
			else
1405
				$loaded_attachments = null;
1406
		}
1407
		else
1408
			$loaded_attachments = null;
1409
1410
		// Create a GUID for this post using the tag URI scheme
1411
		$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['poster_time']) . ':msg=' . $row['id_msg'];
1412
1413
		// Doesn't work as well as news, but it kinda does..
1414
		if ($xml_format == 'rss' || $xml_format == 'rss2')
1415
		{
1416
			// Only one attachment allowed in RSS.
1417
			if ($loaded_attachments !== null)
1418
			{
1419
				$attachment = array_pop($loaded_attachments);
0 ignored issues
show
$loaded_attachments of type null is incompatible with the type array expected by parameter $array of array_pop(). ( Ignorable by Annotation )

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

1419
				$attachment = array_pop(/** @scrutinizer ignore-type */ $loaded_attachments);
Loading history...
1420
				$enclosure = array(
1421
					'url' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
1422
					'length' => $attachment['filesize'],
1423
					'type' => $attachment['mime_type'],
1424
				);
1425
			}
1426
			else
1427
				$enclosure = null;
1428
1429
			$data[] = array(
1430
				'tag' => 'item',
1431
				'content' => array(
1432
					array(
1433
						'tag' => 'title',
1434
						'content' => $row['subject'],
1435
						'cdata' => true,
1436
					),
1437
					array(
1438
						'tag' => 'link',
1439
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
1440
					),
1441
					array(
1442
						'tag' => 'description',
1443
						'content' => $row['body'],
1444
						'cdata' => true,
1445
					),
1446
					array(
1447
						'tag' => 'author',
1448
						'content' => (allowedTo('moderate_forum') || (!empty($row['id_member']) && $row['id_member'] == $user_info['id'])) ? $row['poster_email'] : null,
1449
					),
1450
					array(
1451
						'tag' => 'category',
1452
						'content' => $row['bname'],
1453
					),
1454
					array(
1455
						'tag' => 'comments',
1456
						'content' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0',
1457
					),
1458
					array(
1459
						'tag' => 'pubDate',
1460
						'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']),
1461
					),
1462
					array(
1463
						'tag' => 'guid',
1464
						'content' => $guid,
1465
						'attributes' => array(
1466
							'isPermaLink' => 'false',
1467
						),
1468
					),
1469
					array(
1470
						'tag' => 'enclosure',
1471
						'attributes' => $enclosure,
1472
					),
1473
				),
1474
			);
1475
		}
1476
		elseif ($xml_format == 'rdf')
1477
		{
1478
			$data[] = array(
1479
				'tag' => 'item',
1480
				'attributes' => array('rdf:about' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg']),
1481
				'content' => array(
1482
					array(
1483
						'tag' => 'dc:format',
1484
						'content' => 'text/html',
1485
					),
1486
					array(
1487
						'tag' => 'title',
1488
						'content' => $row['subject'],
1489
						'cdata' => true,
1490
					),
1491
					array(
1492
						'tag' => 'link',
1493
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
1494
					),
1495
					array(
1496
						'tag' => 'description',
1497
						'content' => $row['body'],
1498
						'cdata' => true,
1499
					),
1500
				),
1501
			);
1502
		}
1503
		elseif ($xml_format == 'atom')
1504
		{
1505
			// Only one attachment allowed
1506
			if (!empty($loaded_attachments))
1507
			{
1508
				$attachment = array_pop($loaded_attachments);
1509
				$enclosure = array(
1510
					'rel' => 'enclosure',
1511
					'href' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
1512
					'length' => $attachment['filesize'],
1513
					'type' => $attachment['mime_type'],
1514
				);
1515
			}
1516
			else
1517
				$enclosure = null;
1518
1519
			$data[] = array(
1520
				'tag' => 'entry',
1521
				'content' => array(
1522
					array(
1523
						'tag' => 'title',
1524
						'content' => $row['subject'],
1525
						'cdata' => true,
1526
					),
1527
					array(
1528
						'tag' => 'link',
1529
						'attributes' => array(
1530
							'rel' => 'alternate',
1531
							'type' => 'text/html',
1532
							'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
1533
						),
1534
					),
1535
					array(
1536
						'tag' => 'summary',
1537
						'attributes' => array('type' => 'html'),
1538
						'content' => $row['body'],
1539
						'cdata' => true,
1540
					),
1541
					array(
1542
						'tag' => 'category',
1543
						'attributes' => array('term' => $row['bname']),
1544
					),
1545
					array(
1546
						'tag' => 'author',
1547
						'content' => array(
1548
							array(
1549
								'tag' => 'name',
1550
								'content' => $row['poster_name'],
1551
								'cdata' => true,
1552
							),
1553
							array(
1554
								'tag' => 'email',
1555
								'content' => (allowedTo('moderate_forum') || (!empty($row['id_member']) && $row['id_member'] == $user_info['id'])) ? $row['poster_email'] : null,
1556
							),
1557
							array(
1558
								'tag' => 'uri',
1559
								'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : null,
1560
							),
1561
						),
1562
					),
1563
					array(
1564
						'tag' => 'published',
1565
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']),
1566
					),
1567
					array(
1568
						'tag' => 'updated',
1569
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']),
1570
					),
1571
					array(
1572
						'tag' => 'id',
1573
						'content' => $guid,
1574
					),
1575
					array(
1576
						'tag' => 'link',
1577
						'attributes' => $enclosure,
1578
					),
1579
				),
1580
			);
1581
		}
1582
		// A lot of information here.  Should be enough to please the rss-ers.
1583
		else
1584
		{
1585
			loadLanguage('Post');
1586
1587
			$attachments = array();
1588
			if (!empty($loaded_attachments))
1589
			{
1590
				foreach ($loaded_attachments as $attachment)
1591
				{
1592
					$attachments[] = array(
1593
						'tag' => 'attachment',
1594
						'attributes' => array('label' => $txt['attachment']),
1595
						'content' => array(
1596
							array(
1597
								'tag' => 'id',
1598
								'content' => $attachment['id_attach'],
1599
							),
1600
							array(
1601
								'tag' => 'name',
1602
								'attributes' => array('label' => $txt['name']),
1603
								'content' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])),
1604
							),
1605
							array(
1606
								'tag' => 'downloads',
1607
								'attributes' => array('label' => $txt['downloads']),
1608
								'content' => $attachment['downloads'],
1609
							),
1610
							array(
1611
								'tag' => 'size',
1612
								'attributes' => array('label' => $txt['filesize']),
1613
								'content' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'],
1614
							),
1615
							array(
1616
								'tag' => 'byte_size',
1617
								'attributes' => array('label' => $txt['filesize']),
1618
								'content' => $attachment['filesize'],
1619
							),
1620
							array(
1621
								'tag' => 'link',
1622
								'attributes' => array('label' => $txt['url']),
1623
								'content' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'],
1624
							),
1625
						)
1626
					);
1627
				}
1628
			}
1629
			else
1630
				$attachments = null;
1631
1632
			$data[] = array(
1633
				'tag' => 'recent-post',
1634
				'attributes' => array('label' => $txt['post']),
1635
				'content' => array(
1636
					array(
1637
						'tag' => 'time',
1638
						'attributes' => array('label' => $txt['date'], 'UTC' => gmstrftime('%F %T', $row['poster_time'])),
1639
						'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['poster_time'], false, 'forum'))),
1640
					),
1641
					array(
1642
						'tag' => 'id',
1643
						'content' => $row['id_msg'],
1644
					),
1645
					array(
1646
						'tag' => 'subject',
1647
						'attributes' => array('label' => $txt['subject']),
1648
						'content' => $row['subject'],
1649
						'cdata' => true,
1650
					),
1651
					array(
1652
						'tag' => 'body',
1653
						'attributes' => array('label' => $txt['message']),
1654
						'content' => $row['body'],
1655
						'cdata' => true,
1656
					),
1657
					array(
1658
						'tag' => 'starter',
1659
						'attributes' => array('label' => $txt['topic_started']),
1660
						'content' => array(
1661
							array(
1662
								'tag' => 'name',
1663
								'attributes' => array('label' => $txt['name']),
1664
								'content' => $row['first_poster_name'],
1665
								'cdata' => true,
1666
							),
1667
							array(
1668
								'tag' => 'id',
1669
								'content' => $row['id_first_member'],
1670
							),
1671
							array(
1672
								'tag' => 'link',
1673
								'attributes' => !empty($row['id_first_member']) ? array('label' => $txt['url']) : null,
1674
								'content' => !empty($row['id_first_member']) ? $scripturl . '?action=profile;u=' . $row['id_first_member'] : '',
1675
							),
1676
						),
1677
					),
1678
					array(
1679
						'tag' => 'poster',
1680
						'attributes' => array('label' => $txt['author']),
1681
						'content' => array(
1682
							array(
1683
								'tag' => 'name',
1684
								'attributes' => array('label' => $txt['name']),
1685
								'content' => $row['poster_name'],
1686
								'cdata' => true,
1687
							),
1688
							array(
1689
								'tag' => 'id',
1690
								'content' => $row['id_member'],
1691
							),
1692
							array(
1693
								'tag' => 'link',
1694
								'attributes' => !empty($row['id_member']) ? array('label' => $txt['url']) : null,
1695
								'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '',
1696
							),
1697
						),
1698
					),
1699
					array(
1700
						'tag' => 'topic',
1701
						'attributes' => array('label' => $txt['topic']),
1702
						'content' => array(
1703
							array(
1704
								'tag' => 'subject',
1705
								'attributes' => array('label' => $txt['subject']),
1706
								'content' => $row['first_subject'],
1707
								'cdata' => true,
1708
							),
1709
							array(
1710
								'tag' => 'id',
1711
								'content' => $row['id_topic'],
1712
							),
1713
							array(
1714
								'tag' => 'link',
1715
								'attributes' => array('label' => $txt['url']),
1716
								'content' => $scripturl . '?topic=' . $row['id_topic'] . '.new#new',
1717
							),
1718
						),
1719
					),
1720
					array(
1721
						'tag' => 'board',
1722
						'attributes' => array('label' => $txt['board']),
1723
						'content' => array(
1724
							array(
1725
								'tag' => 'name',
1726
								'attributes' => array('label' => $txt['name']),
1727
								'content' => $row['bname'],
1728
							),
1729
							array(
1730
								'tag' => 'id',
1731
								'content' => $row['id_board'],
1732
							),
1733
							array(
1734
								'tag' => 'link',
1735
								'attributes' => array('label' => $txt['url']),
1736
								'content' => $scripturl . '?board=' . $row['id_board'] . '.0',
1737
							),
1738
						),
1739
					),
1740
					array(
1741
						'tag' => 'link',
1742
						'attributes' => array('label' => $txt['url']),
1743
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
1744
					),
1745
					array(
1746
						'tag' => 'attachments',
1747
						'attributes' => array('label' => $txt['attachments']),
1748
						'content' => $attachments,
1749
					),
1750
				),
1751
			);
1752
		}
1753
	}
1754
	$smcFunc['db_free_result']($request);
1755
1756
	return $data;
1757
}
1758
1759
/**
1760
 * Get the profile information for member into an array,
1761
 * which will be generated to match the xml_format.
1762
 *
1763
 * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'
1764
 * @return array An array profile data
1765
 */
1766
function getXmlProfile($xml_format)
1767
{
1768
	global $scripturl, $memberContext, $user_info, $txt, $context;
1769
1770
	// You must input a valid user, and you must be allowed to view that user's profile.
1771
	if (empty($context['xmlnews_uid']) || ($context['xmlnews_uid'] != $user_info['id'] && !allowedTo('profile_view')) || !loadMemberData($context['xmlnews_uid']))
1772
		return array();
1773
1774
	// Load the member's contextual information! (Including custom fields for our proprietary XML type)
1775
	if (!loadMemberContext($context['xmlnews_uid'], ($xml_format == 'smf')))
1776
		return array();
1777
1778
	$profile = &$memberContext[$context['xmlnews_uid']];
1779
1780
	array_walk_recursive($profile, 'cleanXml');
1781
1782
	// Create a GUID for this member using the tag URI scheme
1783
	$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $profile['registered_timestamp']) . ':member=' . $profile['id'];
1784
1785
	if ($xml_format == 'rss' || $xml_format == 'rss2')
1786
	{
1787
		$data[] = array(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = array(); before regardless.
Loading history...
1788
			'tag' => 'item',
1789
			'content' => array(
1790
				array(
1791
					'tag' => 'title',
1792
					'content' => $profile['name'],
1793
					'cdata' => true,
1794
				),
1795
				array(
1796
					'tag' => 'link',
1797
					'content' => $scripturl . '?action=profile;u=' . $profile['id'],
1798
				),
1799
				array(
1800
					'tag' => 'description',
1801
					'content' => isset($profile['group']) ? $profile['group'] : $profile['post_group'],
1802
					'cdata' => true,
1803
				),
1804
				array(
1805
					'tag' => 'comments',
1806
					'content' => $scripturl . '?action=pm;sa=send;u=' . $profile['id'],
1807
				),
1808
				array(
1809
					'tag' => 'pubDate',
1810
					'content' => gmdate('D, d M Y H:i:s \G\M\T', $profile['registered_timestamp']),
1811
				),
1812
				array(
1813
					'tag' => 'guid',
1814
					'content' => $guid,
1815
					'attributes' => array(
1816
						'isPermaLink' => 'false',
1817
					),
1818
				),
1819
			)
1820
		);
1821
	}
1822
	elseif ($xml_format == 'rdf')
1823
	{
1824
		$data[] = array(
1825
			'tag' => 'item',
1826
			'attributes' => array('rdf:about' => $scripturl . '?action=profile;u=' . $profile['id']),
1827
			'content' => array(
1828
				array(
1829
					'tag' => 'dc:format',
1830
					'content' => 'text/html',
1831
				),
1832
				array(
1833
					'tag' => 'title',
1834
					'content' => $profile['name'],
1835
					'cdata' => true,
1836
				),
1837
				array(
1838
					'tag' => 'link',
1839
					'content' => $scripturl . '?action=profile;u=' . $profile['id'],
1840
				),
1841
				array(
1842
					'tag' => 'description',
1843
					'content' => isset($profile['group']) ? $profile['group'] : $profile['post_group'],
1844
					'cdata' => true,
1845
				),
1846
			)
1847
		);
1848
	}
1849
	elseif ($xml_format == 'atom')
1850
	{
1851
		$data[] = array(
1852
			'tag' => 'entry',
1853
			'content' => array(
1854
				array(
1855
					'tag' => 'title',
1856
					'content' => $profile['name'],
1857
					'cdata' => true,
1858
				),
1859
				array(
1860
					'tag' => 'link',
1861
					'attributes' => array(
1862
						'rel' => 'alternate',
1863
						'type' => 'text/html',
1864
						'href' => $scripturl . '?action=profile;u=' . $profile['id'],
1865
					),
1866
				),
1867
				array(
1868
					'tag' => 'summary',
1869
					'attributes' => array('type' => 'html'),
1870
					'content' => isset($profile['group']) ? $profile['group'] : $profile['post_group'],
1871
					'cdata' => true,
1872
				),
1873
				array(
1874
					'tag' => 'author',
1875
					'content' => array(
1876
						array(
1877
							'tag' => 'name',
1878
							'content' => $profile['name'],
1879
							'cdata' => true,
1880
						),
1881
						array(
1882
							'tag' => 'email',
1883
							'content' => $profile['show_email'] ? $profile['email'] : null,
1884
						),
1885
						array(
1886
							'tag' => 'uri',
1887
							'content' => !empty($profile['website']['url']) ? $profile['website']['url'] : null,
1888
						),
1889
					),
1890
				),
1891
				array(
1892
					'tag' => 'published',
1893
					'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $profile['registered_timestamp']),
1894
				),
1895
				array(
1896
					'tag' => 'updated',
1897
					'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $profile['last_login_timestamp']),
1898
				),
1899
				array(
1900
					'tag' => 'id',
1901
					'content' => $guid,
1902
				),
1903
			)
1904
		);
1905
	}
1906
	else
1907
	{
1908
		loadLanguage('Profile');
1909
1910
		$data = array(
1911
			array(
1912
				'tag' => 'username',
1913
				'attributes' => $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? array('label' => $txt['username']) : null,
1914
				'content' => $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? $profile['username'] : null,
1915
				'cdata' => true,
1916
			),
1917
			array(
1918
				'tag' => 'name',
1919
				'attributes' => array('label' => $txt['name']),
1920
				'content' => $profile['name'],
1921
				'cdata' => true,
1922
			),
1923
			array(
1924
				'tag' => 'link',
1925
				'attributes' => array('label' => $txt['url']),
1926
				'content' => $scripturl . '?action=profile;u=' . $profile['id'],
1927
			),
1928
			array(
1929
				'tag' => 'posts',
1930
				'attributes' => array('label' => $txt['member_postcount']),
1931
				'content' => $profile['posts'],
1932
			),
1933
			array(
1934
				'tag' => 'post-group',
1935
				'attributes' => array('label' => $txt['post_based_membergroup']),
1936
				'content' => $profile['post_group'],
1937
				'cdata' => true,
1938
			),
1939
			array(
1940
				'tag' => 'language',
1941
				'attributes' => array('label' => $txt['preferred_language']),
1942
				'content' => $profile['language'],
1943
				'cdata' => true,
1944
			),
1945
			array(
1946
				'tag' => 'last-login',
1947
				'attributes' => array('label' => $txt['lastLoggedIn'], 'UTC' => gmstrftime('%F %T', $profile['last_login_timestamp'])),
1948
				'content' => timeformat($profile['last_login_timestamp'], false, 'forum'),
1949
			),
1950
			array(
1951
				'tag' => 'registered',
1952
				'attributes' => array('label' => $txt['date_registered'], 'UTC' => gmstrftime('%F %T', $profile['registered_timestamp'])),
1953
				'content' => timeformat($profile['registered_timestamp'], false, 'forum'),
1954
			),
1955
			array(
1956
				'tag' => 'avatar',
1957
				'attributes' => !empty($profile['avatar']['url']) ? array('label' => $txt['personal_picture']) : null,
1958
				'content' => !empty($profile['avatar']['url']) ? $profile['avatar']['url'] : null,
1959
			),
1960
			array(
1961
				'tag' => 'signature',
1962
				'attributes' => !empty($profile['signature']) ? array('label' => $txt['signature']) : null,
1963
				'content' => !empty($profile['signature']) ? $profile['signature'] : null,
1964
				'cdata' => true,
1965
			),
1966
			array(
1967
				'tag' => 'blurb',
1968
				'attributes' => !empty($profile['blurb']) ? array('label' => $txt['personal_text']) : null,
1969
				'content' => !empty($profile['blurb']) ? $profile['blurb'] : null,
1970
				'cdata' => true,
1971
			),
1972
			array(
1973
				'tag' => 'title',
1974
				'attributes' => !empty($profile['title']) ? array('label' => $txt['title']) : null,
1975
				'content' => !empty($profile['title']) ? $profile['title'] : null,
1976
				'cdata' => true,
1977
			),
1978
			array(
1979
				'tag' => 'position',
1980
				'attributes' => !empty($profile['group']) ? array('label' => $txt['position']) : null,
1981
				'content' => !empty($profile['group']) ? $profile['group'] : null,
1982
				'cdata' => true,
1983
			),
1984
			array(
1985
				'tag' => 'email',
1986
				'attributes' => !empty($profile['show_email']) || $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? array('label' => $txt['user_email_address']) : null,
1987
				'content' => !empty($profile['show_email']) || $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? $profile['email'] : null,
1988
			),
1989
			array(
1990
				'tag' => 'website',
1991
				'attributes' => empty($profile['website']['url']) ? null : array('label' => $txt['website']),
1992
				'content' => empty($profile['website']['url']) ? null : array(
1993
					array(
1994
						'tag' => 'title',
1995
						'attributes' => !empty($profile['website']['title']) ? array('label' => $txt['website_title']) : null,
1996
						'content' => !empty($profile['website']['title']) ? $profile['website']['title'] : null,
1997
					),
1998
					array(
1999
						'tag' => 'link',
2000
						'attributes' => array('label' => $txt['website_url']),
2001
						'content' => $profile['website']['url'],
2002
					),
2003
				),
2004
			),
2005
			array(
2006
				'tag' => 'online',
2007
				'attributes' => !empty($profile['online']['is_online']) ? array('label' => $txt['online']) : null,
2008
				'content' => !empty($profile['online']['is_online']) ? 'true' : null,
2009
			),
2010
			array(
2011
				'tag' => 'ip_addresses',
2012
				'attributes' => array('label' => $txt['ip_address']),
2013
				'content' => allowedTo('moderate_forum') || $user_info['id'] == $profile['id'] ? array(
2014
					array(
2015
						'tag' => 'ip',
2016
						'attributes' => array('label' => $txt['most_recent_ip']),
2017
						'content' => $profile['ip'],
2018
					),
2019
					array(
2020
						'tag' => 'ip2',
2021
						'content' => $profile['ip'] != $profile['ip2'] ? $profile['ip2'] : null,
2022
					),
2023
				) : null,
2024
			),
2025
		);
2026
2027
		if (!empty($profile['birth_date']) && substr($profile['birth_date'], 0, 4) != '0000' && substr($profile['birth_date'], 0, 4) != '1004')
2028
		{
2029
			list ($birth_year, $birth_month, $birth_day) = sscanf($profile['birth_date'], '%d-%d-%d');
2030
			$datearray = getdate(forum_time());
2031
			$age = $datearray['year'] - $birth_year - (($datearray['mon'] > $birth_month || ($datearray['mon'] == $birth_month && $datearray['mday'] >= $birth_day)) ? 0 : 1);
2032
2033
			$data[] = array(
2034
				'tag' => 'age',
2035
				'attributes' => array('label' => $txt['age']),
2036
				'content' => $age,
2037
			);
2038
			$data[] = array(
2039
				'tag' => 'birthdate',
2040
				'attributes' => array('label' => $txt['dob']),
2041
				'content' => $profile['birth_date'],
2042
			);
2043
		}
2044
2045
		if (!empty($profile['custom_fields']))
2046
		{
2047
			foreach ($profile['custom_fields'] as $custom_field)
2048
			{
2049
				$data[] = array(
2050
					'tag' => $custom_field['col_name'],
2051
					'attributes' => array('label' => $custom_field['title']),
2052
					'content' => $custom_field['raw'],
2053
					'cdata' => true,
2054
				);
2055
			}
2056
		}
2057
	}
2058
2059
	// Save some memory.
2060
	unset($profile, $memberContext[$context['xmlnews_uid']]);
2061
2062
	return $data;
2063
}
2064
2065
/**
2066
 * Get a user's posts.
2067
 * The returned array will be generated to match the xml_format.
2068
 *
2069
 * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'
2070
 * @param bool $ascending If true, get the oldest posts first. Default false.
2071
 * @return array An array of arrays containing data for the feed. Each array has keys corresponding to the appropriate tags for the specified format.
2072
 */
2073
function getXmlPosts($xml_format, $ascending = false)
2074
{
2075
	global $scripturl, $modSettings, $board, $txt, $context, $user_info;
2076
	global $query_this_board, $smcFunc, $sourcedir, $cachedir;
2077
2078
	if (empty($context['xmlnews_uid']) || ($context['xmlnews_uid'] != $user_info['id'] && !allowedTo('profile_view')))
2079
		return array();
2080
2081
	$show_all = !empty($user_info['is_admin']) || defined('EXPORTING');
2082
2083
	require_once($sourcedir . '/Subs-Attachments.php');
2084
2085
	$request = $smcFunc['db_query']('', '
2086
		SELECT m.id_msg, m.id_topic, m.id_board, m.id_member, m.poster_name, m.poster_email, m.poster_ip, m.poster_time, m.subject,
2087
			modified_time, m.modified_name, m.modified_reason, m.body, m.likes, m.approved, m.smileys_enabled, b.name AS bname
2088
		FROM {db_prefix}messages as m
2089
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
2090
		WHERE id_member = {int:uid}
2091
			AND id_msg > {int:start_after}
2092
			AND ' . $query_this_board . ($modSettings['postmod_active'] && !$show_all ? '
2093
			AND approved = {int:is_approved}' : '') . '
2094
		ORDER BY id_msg {raw:ascdesc}
2095
		LIMIT {int:limit}',
2096
		array(
2097
			'limit' => $context['xmlnews_limit'],
2098
			'start_after' => !empty($context['posts_start']) ? $context['posts_start'] : 0,
2099
			'uid' => $context['xmlnews_uid'],
2100
			'is_approved' => 1,
2101
			'ascdesc' => !empty($ascending) ? 'ASC' : 'DESC',
2102
		)
2103
	);
2104
	$data = array();
2105
	while ($row = $smcFunc['db_fetch_assoc']($request))
2106
	{
2107
		$context['last'] = $row['id_msg'];
2108
2109
		// We want a readable version of the IP address
2110
		$row['poster_ip'] = inet_dtop($row['poster_ip']);
2111
2112
		// If any control characters slipped in somehow, kill the evil things
2113
		array_walk($row, 'cleanXml');
2114
2115
		// If using our own format, we want both the raw and the parsed content.
2116
		$row[$xml_format === 'smf' ? 'body_html' : 'body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
2117
2118
		// Do we want to include any attachments?
2119
		if (!empty($modSettings['attachmentEnable']) && !empty($modSettings['xmlnews_attachments']))
2120
		{
2121
			$attach_request = $smcFunc['db_query']('', '
2122
				SELECT
2123
					a.id_attach, a.filename, COALESCE(a.size, 0) AS filesize, a.mime_type, a.downloads, a.approved, m.id_topic AS topic
2124
				FROM {db_prefix}attachments AS a
2125
					LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
2126
				WHERE a.attachment_type = {int:attachment_type}
2127
					AND a.id_msg = {int:message_id}',
2128
				array(
2129
					'message_id' => $row['id_msg'],
2130
					'attachment_type' => 0,
2131
					'is_approved' => 1,
2132
				)
2133
			);
2134
			$loaded_attachments = array();
2135
			while ($attach = $smcFunc['db_fetch_assoc']($attach_request))
2136
			{
2137
				// Include approved attachments only, unless showing all.
2138
				if ($attach['approved'] || $show_all)
2139
					$loaded_attachments['attachment_' . $attach['id_attach']] = $attach;
2140
			}
2141
			$smcFunc['db_free_result']($attach_request);
2142
2143
			// Sort the attachments by size to make things easier below
2144
			if (!empty($loaded_attachments))
2145
			{
2146
				uasort($loaded_attachments, function($a, $b) {
2147
					if ($a['filesize'] == $b['filesize'])
2148
					        return 0;
2149
					return ($a['filesize'] < $b['filesize']) ? -1 : 1;
2150
				});
2151
			}
2152
			else
2153
				$loaded_attachments = null;
2154
		}
2155
		else
2156
			$loaded_attachments = null;
2157
2158
		// Create a GUID for this post using the tag URI scheme
2159
		$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['poster_time']) . ':msg=' . $row['id_msg'];
2160
2161
		if ($xml_format == 'rss' || $xml_format == 'rss2')
2162
		{
2163
			// Only one attachment allowed in RSS.
2164
			if ($loaded_attachments !== null)
2165
			{
2166
				$attachment = array_pop($loaded_attachments);
0 ignored issues
show
$loaded_attachments of type null is incompatible with the type array expected by parameter $array of array_pop(). ( Ignorable by Annotation )

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

2166
				$attachment = array_pop(/** @scrutinizer ignore-type */ $loaded_attachments);
Loading history...
2167
				$enclosure = array(
2168
					'url' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
2169
					'length' => $attachment['filesize'],
2170
					'type' => $attachment['mime_type'],
2171
				);
2172
			}
2173
			else
2174
				$enclosure = null;
2175
2176
			$data[] = array(
2177
				'tag' => 'item',
2178
				'content' => array(
2179
					array(
2180
						'tag' => 'title',
2181
						'content' => $row['subject'],
2182
						'cdata' => true,
2183
					),
2184
					array(
2185
						'tag' => 'link',
2186
						'content' => $scripturl . '?msg=' . $row['id_msg'],
2187
					),
2188
					array(
2189
						'tag' => 'description',
2190
						'content' => $row['body'],
2191
						'cdata' => true,
2192
					),
2193
					array(
2194
						'tag' => 'author',
2195
						'content' => (allowedTo('moderate_forum') || ($row['id_member'] == $user_info['id'])) ? $row['poster_email'] : null,
2196
					),
2197
					array(
2198
						'tag' => 'category',
2199
						'content' => $row['bname'],
2200
					),
2201
					array(
2202
						'tag' => 'comments',
2203
						'content' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0',
2204
					),
2205
					array(
2206
						'tag' => 'pubDate',
2207
						'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']),
2208
					),
2209
					array(
2210
						'tag' => 'guid',
2211
						'content' => $guid,
2212
						'attributes' => array(
2213
							'isPermaLink' => 'false',
2214
						),
2215
					),
2216
					array(
2217
						'tag' => 'enclosure',
2218
						'attributes' => $enclosure,
2219
					),
2220
				),
2221
			);
2222
		}
2223
		elseif ($xml_format == 'rdf')
2224
		{
2225
			$data[] = array(
2226
				'tag' => 'item',
2227
				'attributes' => array('rdf:about' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg']),
2228
				'content' => array(
2229
					array(
2230
						'tag' => 'dc:format',
2231
						'content' => 'text/html',
2232
					),
2233
					array(
2234
						'tag' => 'title',
2235
						'content' => $row['subject'],
2236
						'cdata' => true,
2237
					),
2238
					array(
2239
						'tag' => 'link',
2240
						'content' => $scripturl . '?msg=' . $row['id_msg'],
2241
					),
2242
					array(
2243
						'tag' => 'description',
2244
						'content' => $row['body'],
2245
						'cdata' => true,
2246
					),
2247
				),
2248
			);
2249
		}
2250
		elseif ($xml_format == 'atom')
2251
		{
2252
			// Only one attachment allowed
2253
			if (!empty($loaded_attachments))
2254
			{
2255
				$attachment = array_pop($loaded_attachments);
2256
				$enclosure = array(
2257
					'rel' => 'enclosure',
2258
					'href' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
2259
					'length' => $attachment['filesize'],
2260
					'type' => $attachment['mime_type'],
2261
				);
2262
			}
2263
			else
2264
				$enclosure = null;
2265
2266
			$data[] = array(
2267
				'tag' => 'entry',
2268
				'content' => array(
2269
					array(
2270
						'tag' => 'title',
2271
						'content' => $row['subject'],
2272
						'cdata' => true,
2273
					),
2274
					array(
2275
						'tag' => 'link',
2276
						'attributes' => array(
2277
							'rel' => 'alternate',
2278
							'type' => 'text/html',
2279
							'href' => $scripturl . '?msg=' . $row['id_msg'],
2280
						),
2281
					),
2282
					array(
2283
						'tag' => 'summary',
2284
						'attributes' => array('type' => 'html'),
2285
						'content' => $row['body'],
2286
						'cdata' => true,
2287
					),
2288
					array(
2289
						'tag' => 'author',
2290
						'content' => array(
2291
							array(
2292
								'tag' => 'name',
2293
								'content' => $row['poster_name'],
2294
								'cdata' => true,
2295
							),
2296
							array(
2297
								'tag' => 'email',
2298
								'content' => (allowedTo('moderate_forum') || ($row['id_member'] == $user_info['id'])) ? $row['poster_email'] : null,
2299
							),
2300
							array(
2301
								'tag' => 'uri',
2302
								'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : null,
2303
							),
2304
						),
2305
					),
2306
					array(
2307
						'tag' => 'published',
2308
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']),
2309
					),
2310
					array(
2311
						'tag' => 'updated',
2312
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']),
2313
					),
2314
					array(
2315
						'tag' => 'id',
2316
						'content' => $guid,
2317
					),
2318
					array(
2319
						'tag' => 'link',
2320
						'attributes' => $enclosure,
2321
					),
2322
				),
2323
			);
2324
		}
2325
		// A lot of information here.  Should be enough to please the rss-ers.
2326
		else
2327
		{
2328
			loadLanguage('Post');
2329
2330
			$attachments = array();
2331
			if (!empty($loaded_attachments))
2332
			{
2333
				foreach ($loaded_attachments as $attachment)
2334
				{
2335
					$attachments[] = array(
2336
						'tag' => 'attachment',
2337
						'attributes' => array('label' => $txt['attachment']),
2338
						'content' => array(
2339
							array(
2340
								'tag' => 'id',
2341
								'content' => $attachment['id_attach'],
2342
							),
2343
							array(
2344
								'tag' => 'name',
2345
								'attributes' => array('label' => $txt['name']),
2346
								'content' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])),
2347
							),
2348
							array(
2349
								'tag' => 'downloads',
2350
								'attributes' => array('label' => $txt['downloads']),
2351
								'content' => $attachment['downloads'],
2352
							),
2353
							array(
2354
								'tag' => 'size',
2355
								'attributes' => array('label' => $txt['filesize']),
2356
								'content' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'],
2357
							),
2358
							array(
2359
								'tag' => 'byte_size',
2360
								'attributes' => array('label' => $txt['filesize']),
2361
								'content' => $attachment['filesize'],
2362
							),
2363
							array(
2364
								'tag' => 'link',
2365
								'attributes' => array('label' => $txt['url']),
2366
								'content' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'],
2367
							),
2368
							array(
2369
								'tag' => 'approval-status',
2370
								'attributes' => $show_all ? array('label' => $txt['approval_status']) : null,
2371
								'content' => $show_all ? $attachment['approved'] : null,
2372
							),
2373
						)
2374
					);
2375
				}
2376
			}
2377
			else
2378
				$attachments = null;
2379
2380
			$data[] = array(
2381
				'tag' => 'member-post',
2382
				'attributes' => array('label' => $txt['post']),
2383
				'content' => array(
2384
					array(
2385
						'tag' => 'id',
2386
						'content' => $row['id_msg'],
2387
					),
2388
					array(
2389
						'tag' => 'subject',
2390
						'attributes' => array('label' => $txt['subject']),
2391
						'content' => $row['subject'],
2392
						'cdata' => true,
2393
					),
2394
					array(
2395
						'tag' => 'body',
2396
						'attributes' => array('label' => $txt['message']),
2397
						'content' => $row['body'],
2398
						'cdata' => true,
2399
					),
2400
					array(
2401
						'tag' => 'body_html',
2402
						'attributes' => array('label' => $txt['html']),
2403
						'content' => $row['body_html'],
2404
						'cdata' => true,
2405
					),
2406
					array(
2407
						'tag' => 'poster',
2408
						'attributes' => array('label' => $txt['author']),
2409
						'content' => array(
2410
							array(
2411
								'tag' => 'name',
2412
								'attributes' => array('label' => $txt['name']),
2413
								'content' => $row['poster_name'],
2414
								'cdata' => true,
2415
							),
2416
							array(
2417
								'tag' => 'id',
2418
								'content' => $row['id_member'],
2419
							),
2420
							array(
2421
								'tag' => 'link',
2422
								'attributes' => array('label' => $txt['url']),
2423
								'content' => $scripturl . '?action=profile;u=' . $row['id_member'],
2424
							),
2425
							array(
2426
								'tag' => 'email',
2427
								'attributes' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? array('label' => $txt['user_email_address']) : null,
2428
								'content' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? $row['poster_email'] : null,
2429
							),
2430
							array(
2431
								'tag' => 'ip',
2432
								'attributes' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? array('label' => $txt['ip']) : null,
2433
								'content' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? $row['poster_ip'] : null,
2434
							),
2435
						),
2436
					),
2437
					array(
2438
						'tag' => 'topic',
2439
						'attributes' => array('label' => $txt['topic']),
2440
						'content' => array(
2441
							array(
2442
								'tag' => 'id',
2443
								'content' => $row['id_topic'],
2444
							),
2445
							array(
2446
								'tag' => 'link',
2447
								'attributes' => array('label' => $txt['url']),
2448
								'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
2449
							),
2450
						),
2451
					),
2452
					array(
2453
						'tag' => 'board',
2454
						'attributes' => array('label' => $txt['board']),
2455
						'content' => array(
2456
							array(
2457
								'tag' => 'id',
2458
								'content' => $row['id_board'],
2459
							),
2460
							array(
2461
								'tag' => 'name',
2462
								'content' => $row['bname'],
2463
							),
2464
							array(
2465
								'tag' => 'link',
2466
								'attributes' => array('label' => $txt['url']),
2467
								'content' => $scripturl . '?board=' . $row['id_board'] . '.0',
2468
							),
2469
						),
2470
					),
2471
					array(
2472
						'tag' => 'link',
2473
						'attributes' => array('label' => $txt['url']),
2474
						'content' => $scripturl . '?msg=' . $row['id_msg'],
2475
					),
2476
					array(
2477
						'tag' => 'time',
2478
						'attributes' => array('label' => $txt['date'], 'UTC' => gmstrftime('%F %T', $row['poster_time'])),
2479
						'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['poster_time'], false, 'forum'))),
2480
					),
2481
					array(
2482
						'tag' => 'modified_time',
2483
						'attributes' => !empty($row['modified_time']) ? array('label' => $txt['modified_time'], 'UTC' => gmstrftime('%F %T', $row['modified_time'])) : null,
2484
						'content' => !empty($row['modified_time']) ? $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['modified_time'], false, 'forum'))) : null,
2485
					),
2486
					array(
2487
						'tag' => 'modified_by',
2488
						'attributes' => !empty($row['modified_name']) ? array('label' => $txt['modified_by']) : null,
2489
						'content' => !empty($row['modified_name']) ? $row['modified_name'] : null,
2490
						'cdata' => true,
2491
					),
2492
					array(
2493
						'tag' => 'modified_reason',
2494
						'attributes' => !empty($row['modified_reason']) ? array('label' => $txt['reason_for_edit']) : null,
2495
						'content' => !empty($row['modified_reason']) ? $row['modified_reason'] : null,
2496
						'cdata' => true,
2497
					),
2498
					array(
2499
						'tag' => 'likes',
2500
						'attributes' => array('label' => $txt['likes']),
2501
						'content' => $row['likes'],
2502
					),
2503
					array(
2504
						'tag' => 'approval-status',
2505
						'attributes' => $show_all ? array('label' => $txt['approval_status']) : null,
2506
						'content' => $show_all ? $row['approved'] : null,
2507
					),
2508
					array(
2509
						'tag' => 'attachments',
2510
						'attributes' => array('label' => $txt['attachments']),
2511
						'content' => $attachments,
2512
					),
2513
				),
2514
			);
2515
		}
2516
	}
2517
	$smcFunc['db_free_result']($request);
2518
2519
	return $data;
2520
}
2521
2522
/**
2523
 * Get a user's personal messages.
2524
 * Only the user can do this, and no one else -- not even the admin!
2525
 *
2526
 * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'
2527
 * @param bool $ascending If true, get the oldest PMs first. Default false.
2528
 * @return array An array of arrays containing data for the feed. Each array has keys corresponding to the appropriate tags for the specified format.
2529
 */
2530
function getXmlPMs($xml_format, $ascending = false)
2531
{
2532
	global $scripturl, $modSettings, $board, $txt, $context, $user_info;
2533
	global $query_this_board, $smcFunc, $sourcedir, $cachedir;
2534
2535
	// Personal messages are supposed to be private
2536
	if (empty($context['xmlnews_uid']) || ($context['xmlnews_uid'] != $user_info['id']))
2537
		return array();
2538
2539
	$select_id_members_to = $smcFunc['db_title'] === POSTGRE_TITLE ? "string_agg(pmr.id_member::text, ',')" : 'GROUP_CONCAT(pmr.id_member)';
2540
2541
	$select_to_names = $smcFunc['db_title'] === POSTGRE_TITLE ? "string_agg(COALESCE(mem.real_name, mem.member_name), ',')" : 'GROUP_CONCAT(COALESCE(mem.real_name, mem.member_name))';
2542
2543
	$request = $smcFunc['db_query']('', '
2544
		SELECT pm.id_pm, pm.msgtime, pm.subject, pm.body, pm.id_member_from, pm.from_name, nis.id_members_to, nis.to_names
2545
		FROM {db_prefix}personal_messages AS pm
2546
		INNER JOIN
2547
		(
2548
			SELECT pm2.id_pm, ' . $select_id_members_to . ' AS id_members_to, ' . $select_to_names . ' AS to_names
2549
			FROM {db_prefix}personal_messages AS pm2
2550
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pm2.id_pm = pmr.id_pm)
2551
				INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
2552
			WHERE pm2.id_pm > {int:start_after}
2553
				AND (
2554
					(pm2.id_member_from = {int:uid} AND pm2.deleted_by_sender = {int:not_deleted})
2555
					OR (pmr.id_member = {int:uid} AND pmr.deleted = {int:not_deleted})
2556
				)
2557
			GROUP BY pm2.id_pm
2558
			ORDER BY pm2.id_pm {raw:ascdesc}
2559
			LIMIT {int:limit}
2560
		) AS nis ON nis.id_pm = pm.id_pm
2561
		ORDER BY pm.id_pm {raw:ascdesc}',
2562
		array(
2563
			'limit' => $context['xmlnews_limit'],
2564
			'start_after' => !empty($context['personal_messages_start']) ? $context['personal_messages_start'] : 0,
2565
			'uid' => $context['xmlnews_uid'],
2566
			'not_deleted' => 0,
2567
			'ascdesc' => !empty($ascending) ? 'ASC' : 'DESC',
2568
		)
2569
	);
2570
	$data = array();
2571
	while ($row = $smcFunc['db_fetch_assoc']($request))
2572
	{
2573
		$context['personal_messages_start'] = $row['id_pm'];
2574
2575
		// If any control characters slipped in somehow, kill the evil things
2576
		array_walk($row, 'cleanXml');
2577
2578
		// If using our own format, we want both the raw and the parsed content.
2579
		$row[$xml_format === 'smf' ? 'body_html' : 'body'] = parse_bbc($row['body']);
2580
2581
		$recipients = array_combine(explode(',', $row['id_members_to']), explode(',', $row['to_names']));
2582
2583
		// Create a GUID for this post using the tag URI scheme
2584
		$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['msgtime']) . ':pm=' . $row['id_pm'];
2585
2586
		if ($xml_format == 'rss' || $xml_format == 'rss2')
2587
		{
2588
			$item = array(
2589
				'tag' => 'item',
2590
				'content' => array(
2591
					array(
2592
						'tag' => 'guid',
2593
						'content' => $guid,
2594
						'attributes' => array(
2595
							'isPermaLink' => 'false',
2596
						),
2597
					),
2598
					array(
2599
						'tag' => 'pubDate',
2600
						'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['msgtime']),
2601
					),
2602
					array(
2603
						'tag' => 'title',
2604
						'content' => $row['subject'],
2605
						'cdata' => true,
2606
					),
2607
					array(
2608
						'tag' => 'description',
2609
						'content' => $row['body'],
2610
						'cdata' => true,
2611
					),
2612
					array(
2613
						'tag' => 'smf:sender',
2614
						// This technically violates the RSS spec, but meh...
2615
						'content' => $row['from_name'],
2616
						'cdata' => true,
2617
					),
2618
				),
2619
			);
2620
2621
			foreach ($recipients as $recipient_id => $recipient_name)
2622
				$item['content'][] = array(
2623
					'tag' => 'smf:recipient',
2624
					'content' => $recipient_name,
2625
					'cdata' => true,
2626
				);
2627
2628
			$data[] = $item;
2629
		}
2630
		elseif ($xml_format == 'rdf')
2631
		{
2632
			$data[] = array(
2633
				'tag' => 'item',
2634
				'attributes' => array('rdf:about' => $scripturl . '?action=pm#msg' . $row['id_pm']),
2635
				'content' => array(
2636
					array(
2637
						'tag' => 'dc:format',
2638
						'content' => 'text/html',
2639
					),
2640
					array(
2641
						'tag' => 'title',
2642
						'content' => $row['subject'],
2643
						'cdata' => true,
2644
					),
2645
					array(
2646
						'tag' => 'link',
2647
						'content' => $scripturl . '?action=pm#msg' . $row['id_pm'],
2648
					),
2649
					array(
2650
						'tag' => 'description',
2651
						'content' => $row['body'],
2652
						'cdata' => true,
2653
					),
2654
				),
2655
			);
2656
		}
2657
		elseif ($xml_format == 'atom')
2658
		{
2659
			$item = array(
2660
				'tag' => 'entry',
2661
				'content' => array(
2662
					array(
2663
						'tag' => 'id',
2664
						'content' => $guid,
2665
					),
2666
					array(
2667
						'tag' => 'updated',
2668
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['msgtime']),
2669
					),
2670
					array(
2671
						'tag' => 'title',
2672
						'content' => $row['subject'],
2673
						'cdata' => true,
2674
					),
2675
					array(
2676
						'tag' => 'content',
2677
						'attributes' => array('type' => 'html'),
2678
						'content' => $row['body'],
2679
						'cdata' => true,
2680
					),
2681
					array(
2682
						'tag' => 'author',
2683
						'content' => array(
2684
							array(
2685
								'tag' => 'name',
2686
								'content' => $row['from_name'],
2687
								'cdata' => true,
2688
							),
2689
						),
2690
					),
2691
				),
2692
			);
2693
2694
			foreach ($recipients as $recipient_id => $recipient_name)
2695
				$item['content'][] = array(
2696
					'tag' => 'contributor',
2697
					'content' => array(
2698
						array(
2699
							'tag' => 'smf:role',
2700
							'content' => 'recipient',
2701
						),
2702
						array(
2703
							'tag' => 'name',
2704
							'content' => $recipient_name,
2705
							'cdata' => true,
2706
						),
2707
					),
2708
				);
2709
2710
			$data[] = $item;
2711
		}
2712
		else
2713
		{
2714
			loadLanguage('PersonalMessage');
2715
2716
			$item = array(
2717
				'tag' => 'personal-message',
2718
				'attributes' => array('label' => $txt['pm']),
2719
				'content' => array(
2720
					array(
2721
						'tag' => 'id',
2722
						'content' => $row['id_pm'],
2723
					),
2724
					array(
2725
						'tag' => 'sent-date',
2726
						'attributes' => array('label' => $txt['date'], 'UTC' => gmstrftime('%F %T', $row['msgtime'])),
2727
						'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['msgtime'], false, 'forum'))),
2728
					),
2729
					array(
2730
						'tag' => 'subject',
2731
						'attributes' => array('label' => $txt['subject']),
2732
						'content' => $row['subject'],
2733
						'cdata' => true,
2734
					),
2735
					array(
2736
						'tag' => 'body',
2737
						'attributes' => array('label' => $txt['message']),
2738
						'content' => $row['body'],
2739
						'cdata' => true,
2740
					),
2741
					array(
2742
						'tag' => 'body_html',
2743
						'attributes' => array('label' => $txt['html']),
2744
						'content' => $row['body_html'],
2745
						'cdata' => true,
2746
					),
2747
					array(
2748
						'tag' => 'sender',
2749
						'attributes' => array('label' => $txt['author']),
2750
						'content' => array(
2751
							array(
2752
								'tag' => 'name',
2753
								'attributes' => array('label' => $txt['name']),
2754
								'content' => $row['from_name'],
2755
								'cdata' => true,
2756
							),
2757
							array(
2758
								'tag' => 'id',
2759
								'content' => $row['id_member_from'],
2760
							),
2761
							array(
2762
								'tag' => 'link',
2763
								'attributes' => array('label' => $txt['url']),
2764
								'content' => $scripturl . '?action=profile;u=' . $row['id_member_from'],
2765
							),
2766
						),
2767
					),
2768
				),
2769
			);
2770
2771
			foreach ($recipients as $recipient_id => $recipient_name)
2772
				$item['content'][] = array(
2773
					'tag' => 'recipient',
2774
					'attributes' => array('label' => $txt['recipient']),
2775
					'content' => array(
2776
						array(
2777
							'tag' => 'name',
2778
							'attributes' => array('label' => $txt['name']),
2779
							'content' => $recipient_name,
2780
							'cdata' => true,
2781
						),
2782
						array(
2783
							'tag' => 'id',
2784
							'content' => $recipient_id,
2785
						),
2786
						array(
2787
							'tag' => 'link',
2788
							'attributes' => array('label' => $txt['url']),
2789
							'content' => $scripturl . '?action=profile;u=' . $recipient_id,
2790
						),
2791
					),
2792
				);
2793
2794
			$data[] = $item;
2795
		}
2796
	}
2797
	$smcFunc['db_free_result']($request);
2798
2799
	return $data;
2800
}
2801
2802
?>