Issues (1065)

Sources/News.php (11 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 2025 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1.5
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' => sprintf($txt['xml_rss_desc'], $context['forum_name']),
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', false);
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 AS b
193
			WHERE id_board = {int:current_board}
194
				AND {query_see_board}
195
			LIMIT 1',
196
			array(
197
				'current_board' => $board,
198
			)
199
		);
200
201
		if ($smcFunc['db_num_rows']($request) == 0)
202
			fatal_lang_error('no_board', false);
203
204
		list ($total_posts) = $smcFunc['db_fetch_row']($request);
205
		$smcFunc['db_free_result']($request);
206
207
		$feed_meta['title'] = $board_info['name'];
208
		$feed_meta['source'] .= '?board=' . $board . '.0';
209
210
		$query_this_board = 'b.id_board = ' . $board;
211
212
		// Try to look through just a few messages, if at all possible.
213
		if ($total_posts > 80 && $total_posts > $modSettings['totalMessages'] / 10)
214
			$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 600 - $context['xmlnews_limit'] * 5);
215
	}
216
	else
217
	{
218
		$query_this_board = '{query_see_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
219
			AND b.id_board != ' . $modSettings['recycle_board'] : '');
220
		$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 100 - $context['xmlnews_limit'] * 5);
221
	}
222
223
	$feed_meta['title'] .= (!empty($feed_meta['title']) ? ' - ' : '') . $context['forum_name'];
224
225
	// Sanitize feed metadata values
226
	foreach ($feed_meta as $mkey => $mvalue)
227
		$feed_meta[$mkey] = strip_tags($mvalue);
228
229
	// We only want some information, not all of it.
230
	$cachekey = array($xml_format, $_GET['action'], $context['xmlnews_limit'], $subaction);
231
	foreach (array('board', 'boards', 'c') as $var)
232
		if (isset($_GET[$var]))
233
			$cachekey[] = $var . '=' . implode(',', (array) $_GET[$var]);
234
	$cachekey = md5($smcFunc['json_encode']($cachekey) . (!empty($query_this_board) ? $query_this_board : ''));
235
	$cache_t = microtime(true);
236
237
	// Get the associative array representing the xml.
238
	if (!empty($cache_enable) && (!$user_info['is_guest'] || $cache_enable >= 3))
239
	{
240
		$xml_data = cache_get_data('xmlfeed-' . $xml_format . ':' . ($user_info['is_guest'] ? '' : $user_info['id'] . '-') . $cachekey, 240);
241
	}
242
	if (empty($xml_data))
243
	{
244
		$call = call_helper($subActions[$subaction], true);
245
246
		if (!empty($call))
247
			$xml_data = call_user_func($call, $xml_format);
0 ignored issues
show
It seems like $call can also be of type boolean; however, parameter $callback 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

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

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

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

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