Passed
Pull Request — release-2.1 (#5081)
by Jeremy
03:50
created

getXmlPMs()   F

Complexity

Conditions 19

Size

Total Lines 275
Code Lines 173

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 173
c 0
b 0
f 0
nop 1
dl 0
loc 275
rs 3.6133

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file contains the files necessary to display news as an XML feed.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2020 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 RC2
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Outputs xml data representing recent information or a profile.
21
 *
22
 * Can be passed subactions which decide what is output:
23
 *  'recent' for recent posts,
24
 *  'news' for news topics,
25
 *  'members' for recently registered members,
26
 *  'profile' for a member's profile.
27
 *  'posts' for a member's posts.
28
 *  'pms' 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
 * ...except when requesting all the user's own posts or PMs. Then we show a template indicating
40
 * our progress compiling the info. This template will auto-refresh until the all the info is
41
 * compiled, at which point we emit the full XML feed as a downloadable file.
42
 *
43
 * @uses Stats language file, and in special cases the Admin template and language file.
44
 */
45
function ShowXmlFeed()
46
{
47
	global $board, $board_info, $context, $scripturl, $boardurl, $txt, $modSettings, $user_info;
48
	global $query_this_board, $smcFunc, $forum_version, $settings, $cachedir;
49
50
	// List all the different types of data they can pull.
51
	$subActions = array(
52
		'recent' => array('getXmlRecent', 'recent-post'),
53
		'news' => array('getXmlNews', 'article'),
54
		'members' => array('getXmlMembers', 'member'),
55
		'profile' => array('getXmlProfile', null),
56
		'posts' => array('getXmlPosts', 'member-post'),
57
		'pms' => array('getXmlPMs', 'personal-message'),
58
	);
59
60
	// Easy adding of sub actions
61
	call_integration_hook('integrate_xmlfeeds', array(&$subActions));
62
63
	if (empty($_GET['sa']) || !isset($subActions[$_GET['sa']]))
64
		$_GET['sa'] = 'recent';
65
66
	// Users can always export their own profile data
67
	if (in_array($_GET['sa'], array('profile', 'posts', 'pms')) && !$user_info['is_guest'] && (empty($_GET['u']) || (int) $_GET['u'] == $user_info['id']))
68
	{
69
		$modSettings['xmlnews_enable'] = true;
70
71
		// Batch mode builds a whole file and then sends it all when done.
72
		if ($_GET['limit'] == 'all' && empty($_REQUEST['c']) && empty($_REQUEST['boards']) && empty($board))
73
		{
74
			$context['batch_mode'] = true;
75
			$_GET['limit'] = 50;
76
			unset($_GET['offset']);
77
78
			// We track our progress for greater efficiency
79
			$progress_file = $cachedir . '/xml-batch-' . $_GET['sa'] . '-' . $user_info['id'];
80
			if (file_exists($progress_file))
81
			{
82
				list($context[$_GET['sa'] . '_start'], $context['batch_prev'], $context['batch_total']) = explode(';', file_get_contents($progress_file));
83
84
				if ($context['batch_prev'] == $context['batch_total'])
85
					$context['batch_done'] = true;
86
			}
87
			else
88
				$context[$_GET['sa'] . '_start'] = 0;
89
		}
90
	}
91
92
	// If it's not enabled, die.
93
	if (empty($modSettings['xmlnews_enable']))
94
		obExit(false);
95
96
	loadLanguage('Stats');
97
98
	// Default to latest 5.  No more than 255, please.
99
	$_GET['limit'] = empty($_GET['limit']) || (int) $_GET['limit'] < 1 ? 5 : min((int) $_GET['limit'], 255);
100
	$_GET['offset'] = empty($_GET['offset']) || (int) $_GET['offset'] < 1 ? 0 : (int) $_GET['offset'];
101
102
	// Some general metadata for this feed. We'll change some of these values below.
103
	$feed_meta = array(
104
		'title' => '',
105
		'desc' => $txt['xml_rss_desc'],
106
		'author' => $context['forum_name'],
107
		'source' => $scripturl,
108
		'rights' => '© ' . date('Y') . ' ' . $context['forum_name'],
109
		'icon' => !empty($settings['og_image']) ? $settings['og_image'] : $boardurl . '/favicon.ico',
110
		'language' => !empty($txt['lang_locale']) ? str_replace("_", "-", substr($txt['lang_locale'], 0, strcspn($txt['lang_locale'], "."))) : 'en',
111
	);
112
113
	// Handle the cases where a board, boards, or category is asked for.
114
	$query_this_board = 1;
115
	$context['optimize_msg'] = array(
116
		'highest' => 'm.id_msg <= b.id_last_msg',
117
	);
118
	if (!empty($_REQUEST['c']) && empty($board))
119
	{
120
		$_REQUEST['c'] = explode(',', $_REQUEST['c']);
121
		foreach ($_REQUEST['c'] as $i => $c)
122
			$_REQUEST['c'][$i] = (int) $c;
123
124
		if (count($_REQUEST['c']) == 1)
125
		{
126
			$request = $smcFunc['db_query']('', '
127
				SELECT name
128
				FROM {db_prefix}categories
129
				WHERE id_cat = {int:current_category}',
130
				array(
131
					'current_category' => (int) $_REQUEST['c'][0],
132
				)
133
			);
134
			list ($feed_meta['title']) = $smcFunc['db_fetch_row']($request);
135
			$smcFunc['db_free_result']($request);
136
137
			$feed_meta['title'] = ' - ' . strip_tags($feed_meta['title']);
138
		}
139
140
		$request = $smcFunc['db_query']('', '
141
			SELECT b.id_board, b.num_posts
142
			FROM {db_prefix}boards AS b
143
			WHERE b.id_cat IN ({array_int:current_category_list})
144
				AND {query_see_board}',
145
			array(
146
				'current_category_list' => $_REQUEST['c'],
147
			)
148
		);
149
		$total_cat_posts = 0;
150
		$boards = array();
151
		while ($row = $smcFunc['db_fetch_assoc']($request))
152
		{
153
			$boards[] = $row['id_board'];
154
			$total_cat_posts += $row['num_posts'];
155
		}
156
		$smcFunc['db_free_result']($request);
157
158
		if (!empty($boards))
159
			$query_this_board = 'b.id_board IN (' . implode(', ', $boards) . ')';
160
161
		// Try to limit the number of messages we look through.
162
		if ($total_cat_posts > 100 && $total_cat_posts > $modSettings['totalMessages'] / 15)
163
			$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 400 - $_GET['limit'] * 5);
164
	}
165
	elseif (!empty($_REQUEST['boards']))
166
	{
167
		$_REQUEST['boards'] = explode(',', $_REQUEST['boards']);
168
		foreach ($_REQUEST['boards'] as $i => $b)
169
			$_REQUEST['boards'][$i] = (int) $b;
170
171
		$request = $smcFunc['db_query']('', '
172
			SELECT b.id_board, b.num_posts, b.name
173
			FROM {db_prefix}boards AS b
174
			WHERE b.id_board IN ({array_int:board_list})
175
				AND {query_see_board}
176
			LIMIT {int:limit}',
177
			array(
178
				'board_list' => $_REQUEST['boards'],
179
				'limit' => count($_REQUEST['boards']),
180
			)
181
		);
182
183
		// Either the board specified doesn't exist or you have no access.
184
		$num_boards = $smcFunc['db_num_rows']($request);
185
		if ($num_boards == 0)
186
			fatal_lang_error('no_board');
187
188
		$total_posts = 0;
189
		$boards = array();
190
		while ($row = $smcFunc['db_fetch_assoc']($request))
191
		{
192
			if ($num_boards == 1)
193
				$feed_meta['title'] = ' - ' . strip_tags($row['name']);
194
195
			$boards[] = $row['id_board'];
196
			$total_posts += $row['num_posts'];
197
		}
198
		$smcFunc['db_free_result']($request);
199
200
		if (!empty($boards))
201
			$query_this_board = 'b.id_board IN (' . implode(', ', $boards) . ')';
202
203
		// The more boards, the more we're going to look through...
204
		if ($total_posts > 100 && $total_posts > $modSettings['totalMessages'] / 12)
205
			$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 500 - $_GET['limit'] * 5);
206
	}
207
	elseif (!empty($board))
208
	{
209
		$request = $smcFunc['db_query']('', '
210
			SELECT num_posts
211
			FROM {db_prefix}boards
212
			WHERE id_board = {int:current_board}
213
			LIMIT 1',
214
			array(
215
				'current_board' => $board,
216
			)
217
		);
218
		list ($total_posts) = $smcFunc['db_fetch_row']($request);
219
		$smcFunc['db_free_result']($request);
220
221
		$feed_meta['title'] = ' - ' . strip_tags($board_info['name']);
222
		$feed_meta['source'] .= '?board=' . $board . '.0';
223
224
		$query_this_board = 'b.id_board = ' . $board;
225
226
		// Try to look through just a few messages, if at all possible.
227
		if ($total_posts > 80 && $total_posts > $modSettings['totalMessages'] / 10)
228
			$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 600 - $_GET['limit'] * 5);
229
	}
230
	else
231
	{
232
		$query_this_board = '{query_see_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
233
			AND b.id_board != ' . $modSettings['recycle_board'] : '');
234
		$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 100 - $_GET['limit'] * 5);
235
	}
236
237
	// Show in rss or proprietary format?
238
	$xml_format = isset($_GET['type']) && in_array($_GET['type'], array('smf', 'rss', 'rss2', 'atom', 'rdf')) ? $_GET['type'] : 'rss2';
239
240
	// @todo Birthdays?
241
242
	// List all the different types of data they can pull.
243
	$subActions = array(
244
		'recent' => array('getXmlRecent', 'recent-post'),
245
		'news' => array('getXmlNews', 'article'),
246
		'members' => array('getXmlMembers', 'member'),
247
		'profile' => array('getXmlProfile', null),
248
	);
249
250
	// Easy adding of sub actions
251
	call_integration_hook('integrate_xmlfeeds', array(&$subActions));
252
253
	if (empty($_GET['sa']) || !isset($subActions[$_GET['sa']]))
254
		$_GET['sa'] = 'recent';
255
256
	// We only want some information, not all of it.
257
	$cachekey = array($xml_format, $_GET['action'], $_GET['limit'], $_GET['sa'], $_GET['offset']);
258
	foreach (array('board', 'boards', 'c') as $var)
259
		if (isset($_REQUEST[$var]))
260
			$cachekey[] = $var . '=' . $_REQUEST[$var];
261
	$cachekey = md5($smcFunc['json_encode']($cachekey) . (!empty($query_this_board) ? $query_this_board : ''));
262
	$cache_t = microtime(true);
263
264
	// Get the associative array representing the xml.
265
	if (!empty($cache_enable) && (!$user_info['is_guest'] || $cache_enable >= 3))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cache_enable seems to never exist and therefore empty should always be true.
Loading history...
266
		$xml_data = cache_get_data('xmlfeed-' . $xml_format . ':' . ($user_info['is_guest'] ? '' : $user_info['id'] . '-') . $cachekey, 240);
267
	if (empty($xml_data))
268
	{
269
		$call = call_helper($subActions[$_GET['sa']][0], true);
270
271
		if (!empty($call))
272
			$xml_data = call_user_func($call, $xml_format);
0 ignored issues
show
Bug introduced by
It seems like $call can also be of type boolean; however, parameter $function of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

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

272
			$xml_data = call_user_func(/** @scrutinizer ignore-type */ $call, $xml_format);
Loading history...
273
274
		if (!empty($cache_enable) && (($user_info['is_guest'] && $cache_enable >= 3)
275
		|| (!$user_info['is_guest'] && (microtime(true) - $cache_t > 0.2))))
276
			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...
277
	}
278
279
	$feed_meta['title'] = $smcFunc['htmlspecialchars'](strip_tags($context['forum_name'])) . (isset($feed_meta['title']) ? $feed_meta['title'] : '');
280
281
	// Allow mods to add extra namespaces and tags to the feed/channel
282
	$namespaces = array(
283
		'rss' => array(),
284
		'rss2' => array('atom' => 'http://www.w3.org/2005/Atom'),
285
		'atom' => array('' => 'http://www.w3.org/2005/Atom'),
286
		'rdf' => array(
287
			'' => 'http://purl.org/rss/1.0/',
288
			'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
289
			'dc' => 'http://purl.org/dc/elements/1.1/',
290
		),
291
		'smf' => array(
292
			'' => 'https://www.simplemachines.org/xml/' . $_GET['sa'],
293
			'smf' => 'https://www.simplemachines.org/',
294
		),
295
	);
296
	if ($_GET['sa'] == 'pms')
297
	{
298
		$namespaces['rss']['smf'] = 'https://www.simplemachines.org/';
299
		$namespaces['rss2']['smf'] = 'https://www.simplemachines.org/';
300
		$namespaces['atom']['smf'] = 'https://www.simplemachines.org/';
301
	}
302
303
	$extraFeedTags = array(
304
		'rss' => array(),
305
		'rss2' => array(),
306
		'atom' => array(),
307
		'rdf' => array(),
308
		'smf' => array(),
309
	);
310
311
	// Allow mods to specify any keys that need special handling
312
	$forceCdataKeys = array();
313
	$nsKeys = array();
314
315
	// Remember this, just in case...
316
	$orig_feed_meta = $feed_meta;
317
318
	// If mods want to do somthing with this feed, let them do that now.
319
	// Provide the feed's data, metadata, namespaces, extra feed-level tags, keys that need special handling, the feed format, and the requested subaction
320
	call_integration_hook('integrate_xml_data', array(&$xml_data, &$feed_meta, &$namespaces, &$extraFeedTags, &$forceCdataKeys, &$nsKeys, $xml_format, $_GET['sa']));
321
322
	// These can't be empty
323
	foreach (array('title', 'desc', 'source') as $mkey)
324
		$feed_meta[$mkey] = !empty($feed_meta[$mkey]) ? $feed_meta[$mkey] : $orig_feed_meta[$mkey];
325
326
	// Sanitize basic feed metadata values
327
	foreach ($feed_meta as $mkey => $mvalue)
328
		$feed_meta[$mkey] = cdata_parse(strip_tags(fix_possible_url($feed_meta[$mkey])));
329
330
	$ns_string = '';
331
	if (!empty($namespaces[$xml_format]))
332
	{
333
		foreach ($namespaces[$xml_format] as $nsprefix => $nsurl)
334
			$ns_string .= ' xmlns' . ($nsprefix !== '' ? ':' : '') . $nsprefix . '="' . $nsurl . '"';
335
	}
336
337
	$extraFeedTags_string = '';
338
	if (!empty($extraFeedTags[$xml_format]))
339
	{
340
		$indent = "\t" . ($xml_format !== 'atom' ? "\t" : '');
341
		foreach ($extraFeedTags[$xml_format] as $extraTag)
342
			$extraFeedTags_string .= "\n" . $indent . $extraTag;
343
	}
344
345
	// Descriptive filenames = good
346
	$xml_filename[] = preg_replace('/\s+/', '_', $feed_meta['title']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$xml_filename was never initialized. Although not strictly required by PHP, it is generally a good practice to add $xml_filename = array(); before regardless.
Loading history...
347
	$xml_filename[] = $_GET['sa'];
348
	if (in_array($_GET['sa'], array('profile', 'posts', 'pms')))
349
		$xml_filename[] = 'u=' . (isset($_GET['u']) ? (int) $_GET['u'] : $user_info['id']);
350
	if (!empty($boards))
351
		$xml_filename[] = 'boards=' . implode(',', $boards);
352
	elseif (!empty($board))
353
		$xml_filename[] = 'board=' . $board;
354
	$xml_filename[] = $xml_format;
355
	$xml_filename = strtr(un_htmlspecialchars(implode('-', $xml_filename)), '"', '') ;
356
357
	// First, output the xml header.
358
	$context['feed']['header'] = '<?xml version="1.0" encoding="' . $context['character_set'] . '"?' . '>';
359
360
	// Are we outputting an rss feed or one with more information?
361
	if ($xml_format == 'rss' || $xml_format == 'rss2')
362
	{
363
		if ($xml_format == 'rss2')
364
			foreach ($_REQUEST as $var => $val)
365
				if (in_array($var, array('action', 'sa', 'type', 'board', 'boards', 'c', 'u', 'limit')))
366
					$url_parts[] = $var . '=' . (is_array($val) ? implode(',', $val) : $val);
367
368
		// Start with an RSS 2.0 header.
369
		$context['feed']['header'] .= '
370
<rss version=' . ($xml_format == 'rss2' ? '"2.0"' : '"0.92"') . ' xml:lang="' . strtr($txt['lang_locale'], '_', '-') . '"' . $ns_string . '>
371
	<channel>
372
		<title>' . $feed_meta['title'] . '</title>
373
		<link>' . $feed_meta['source'] . '</link>
374
		<description>' . $feed_meta['desc'] . '</description>';
375
376
		if (!empty($feed_meta['icon']))
377
			$context['feed']['header'] .= '
378
		<image>
379
			<url>' . $feed_meta['icon'] . '</url>
380
			<title>' . $feed_meta['title'] . '</title>
381
			<link>' . $feed_meta['source'] . '</link>
382
		</image>';
383
384
		if (!empty($feed_meta['rights']))
385
			$context['feed']['header'] .= '
386
		<copyright>' . $feed_meta['rights'] . '</copyright>';
387
388
		if (!empty($feed_meta['language']))
389
			$context['feed']['header'] .= '
390
		<language>' . $feed_meta['language'] . '</language>';
391
392
		// RSS2 calls for this.
393
		if ($xml_format == 'rss2')
394
			$context['feed']['header'] .= '
395
		<atom:link rel="self" type="application/rss+xml" href="' . $scripturl . (!empty($url_parts) ? '?' . implode(';', $url_parts) : '') . '" />';
396
397
		$context['feed']['header'] .= $extraFeedTags_string;
398
399
		// Output all of the associative array, start indenting with 2 tabs, and name everything "item".
400
		dumpTags($xml_data, 2, null, $xml_format, $forceCdataKeys, $nsKeys);
401
402
		// Output the footer of the xml.
403
		$context['feed']['footer'] = '
404
	</channel>
405
</rss>';
406
	}
407
	elseif ($xml_format == 'atom')
408
	{
409
		foreach ($_REQUEST as $var => $val)
410
			if (in_array($var, array('action', 'sa', 'type', 'board', 'boards', 'c', 'u', 'limit', 'offset')))
411
				$url_parts[] = $var . '=' . (is_array($val) ? implode(',', $val) : $val);
412
413
		$context['feed']['header'] .= '
414
<feed' . $ns_string . (!empty($feed_meta['language']) ? ' xml:lang="' . $feed_meta['language'] . '"' : '') . '>
415
	<title>' . $feed_meta['title'] . '</title>
416
	<link rel="alternate" type="text/html" href="' . $feed_meta['source'] . '" />
417
	<link rel="self" type="application/atom+xml" href="' . $scripturl . (!empty($url_parts) ? '?' . implode(';', $url_parts) : '') . '" />
418
	<updated>' . gmstrftime('%Y-%m-%dT%H:%M:%SZ') . '</updated>
419
	<id>' . $feed_meta['source'] . '</id>
420
	<subtitle>' . $feed_meta['desc'] . '</subtitle>
421
	<generator uri="https://www.simplemachines.org" version="' . trim(strtr($forum_version, array('SMF' => ''))) . '">SMF</generator>';
422
423
		if (!empty($feed_meta['icon']))
424
			$context['feed']['header'] .= '
425
	<icon>' . $feed_meta['icon'] . '</icon>';
426
427
		if (!empty($feed_meta['author']))
428
			$context['feed']['header'] .= '
429
	<author>
430
		<name>' . $feed_meta['author'] . '</name>
431
	</author>';
432
433
		if (!empty($feed_meta['rights']))
434
			$context['feed']['header'] .= '
435
	<rights>' . $feed_meta['rights'] . '</rights>';
436
437
		$context['feed']['header'] .= $extraFeedTags_string;
438
439
		dumpTags($xml_data, 1, null, $xml_format, $forceCdataKeys, $nsKeys);
440
441
		$context['feed']['footer'] = '
442
</feed>';
443
	}
444
	elseif ($xml_format == 'rdf')
445
	{
446
		$context['feed']['header'] .= '
447
<rdf:RDF' . $ns_string . '>
448
	<channel rdf:about="' . $scripturl . '">
449
		<title>' . $feed_meta['title'] . '</title>
450
		<link>' . $feed_meta['source'] . '</link>
451
		<description>' . $feed_meta['desc'] . '</description>';
452
453
		$context['feed']['header'] .= $extraFeedTags_string;
454
455
		$context['feed']['header'] .= '
456
		<items>
457
			<rdf:Seq>';
458
459
		foreach ($xml_data as $item)
460
		{
461
			$link = array_filter($item['content'], function($e)
462
			{
463
				return ($e['tag'] == 'link');
464
			});
465
			$link = array_pop($link);
466
467
			$context['feed']['header'] .= '
468
				<rdf:li rdf:resource="' . $link['content'] . '" />';
469
		}
470
471
		$context['feed']['header'] .= '
472
			</rdf:Seq>
473
		</items>
474
	</channel>';
475
476
		dumpTags($xml_data, 1, null, $xml_format, $forceCdataKeys, $nsKeys);
477
478
		$context['feed']['footer'] = '
479
</rdf:RDF>';
480
	}
481
	// Otherwise, we're using our proprietary formats - they give more data, though.
482
	else
483
	{
484
		$context['feed']['header'] .= '
485
<smf:xml-feed xml:lang="' . strtr($txt['lang_locale'], '_', '-') . '"' . $ns_string . '>';
486
487
		// Hard to imagine anyone wanting to add these for the proprietary format, but just in case...
488
		$context['feed']['header'] .= $extraFeedTags_string;
489
490
		// Dump out that associative array.  Indent properly.... and use the right names for the base elements.
491
		dumpTags($xml_data, 1, $subActions[$_GET['sa']][1], $xml_format, $forceCdataKeys, $nsKeys);
492
493
		$context['feed']['footer'] = '
494
</smf:xml-feed>';
495
	}
496
497
	// Batch mode involves a lot of reading and writing to a temporary file
498
	if (!empty($context['batch_mode']))
499
	{
500
		$xml_filepath = $cachedir . '/' . $xml_filename . '.xml';
501
502
		// Append our current items to the output file
503
		if (file_exists($xml_filepath))
504
		{
505
			$handle = fopen($xml_filepath, 'r+');
506
507
			// Trim off the existing feed footer
508
			ftruncate($handle, filesize($xml_filepath) - strlen($context['feed']['footer']));
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of ftruncate() does only seem to accept resource, 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

508
			ftruncate(/** @scrutinizer ignore-type */ $handle, filesize($xml_filepath) - strlen($context['feed']['footer']));
Loading history...
509
510
			// Add the new data
511
			fseek($handle, 0, SEEK_END);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of fseek() does only seem to accept resource, 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

511
			fseek(/** @scrutinizer ignore-type */ $handle, 0, SEEK_END);
Loading history...
512
			fwrite($handle, $context['feed']['items']);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, 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

512
			fwrite(/** @scrutinizer ignore-type */ $handle, $context['feed']['items']);
Loading history...
513
			fwrite($handle, $context['feed']['footer']);
514
515
			fclose($handle);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

515
			fclose(/** @scrutinizer ignore-type */ $handle);
Loading history...
516
		}
517
		else
518
			file_put_contents($xml_filepath, implode('', $context['feed']));
519
520
		if (!empty($context['batch_done']))
521
		{
522
			if (file_exists($xml_filepath))
523
				$feed = file_get_contents($xml_filepath);
524
			else
525
				$feed = implode('', $context['feed']);
526
527
			$_REQUEST['download'] = true;
528
			unlink($progress_file);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $progress_file does not seem to be defined for all execution paths leading up to this point.
Loading history...
529
			unlink($xml_filepath);
530
		}
531
		else
532
		{
533
			// This shouldn't interfere with normal feed reader operation, because the only way this
534
			// can happen is when the user is logged into their account, which isn't possible when
535
			// connecting via any normal feed reader.
536
			loadTemplate('Admin');
537
			loadLanguage('Admin');
538
			$context['sub_template'] = 'not_done';
539
			$context['continue_post_data'] = '';
540
			$context['continue_countdown'] = 3;
541
			$context['continue_percent'] = number_format(($context['batch_prev'] / $context['batch_total']) * 100, 1);
542
			$context['continue_get_data'] = '?action=' . $_REQUEST['action'] . ';sa=' . $_GET['sa'] . ';type=' . $xml_format . (!empty($_GET['u']) ? ';u=' . $_GET['u'] : '') . ';limit=all';
543
544
			if ($context['batch_prev'] == $context['batch_total'])
545
			{
546
				$context['continue_countdown'] = 1;
547
				$context['continue_post_data'] = '
548
					<script>
549
						var x = document.getElementsByName("cont");
550
						var i;
551
						for (i = 0; i < x.length; i++) {
552
							x[i].disabled = true;
553
						}
554
					</script>';
555
			}
556
		}
557
	}
558
	// Keepin' it simple...
559
	else
560
		$feed = implode('', $context['feed']);
561
562
	if (!empty($feed))
563
	{
564
		// This is an xml file....
565
		ob_end_clean();
566
		if (!empty($modSettings['enableCompressedOutput']))
567
			@ob_start('ob_gzhandler');
568
		else
569
			ob_start();
570
571
		if ($xml_format == 'smf' || isset($_REQUEST['debug']))
572
			header('content-type: text/xml; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set']));
573
		elseif ($xml_format == 'rss' || $xml_format == 'rss2')
574
			header('content-type: application/rss+xml; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set']));
575
		elseif ($xml_format == 'atom')
576
			header('content-type: application/atom+xml; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set']));
577
		elseif ($xml_format == 'rdf')
578
			header('content-type: ' . (isBrowser('ie') ? 'text/xml' : 'application/rdf+xml') . '; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set']));
579
580
		header('content-disposition: ' . (isset($_REQUEST['download']) ? 'attachment' : 'inline') . '; filename="' . $xml_filename . '.xml"');
581
582
		echo $feed;
583
584
		obExit(false);
585
	}
586
}
587
588
/**
589
 * Called from dumpTags to convert data to xml
590
 * Finds urls for local site and sanitizes them
591
 *
592
 * @param string $val A string containing a possible URL
593
 * @return string $val The string with any possible URLs sanitized
594
 */
595
function fix_possible_url($val)
596
{
597
	global $modSettings, $context, $scripturl;
598
599
	if (substr($val, 0, strlen($scripturl)) != $scripturl)
600
		return $val;
601
602
	call_integration_hook('integrate_fix_url', array(&$val));
603
604
	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']))
605
		return $val;
606
607
	$val = preg_replace_callback('~\b' . preg_quote($scripturl, '~') . '\?((?:board|topic)=[^#"]+)(#[^"]*)?$~', function($m) use ($scripturl)
608
	{
609
		return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html' . (isset($m[2]) ? $m[2] : "");
610
	}, $val);
611
	return $val;
612
}
613
614
/**
615
 * Ensures supplied data is properly encapsulated in cdata xml tags
616
 * Called from getXmlProfile in News.php
617
 *
618
 * @param string $data XML data
619
 * @param string $ns A namespace prefix for the XML data elements (used by mods, maybe)
620
 * @param boolean $force If true, enclose the XML data in cdata tags no matter what (used by mods, maybe)
621
 * @return string The XML data enclosed in cdata tags when necessary
622
 */
623
function cdata_parse($data, $ns = '', $force = false)
624
{
625
	global $smcFunc;
626
627
	// Do we even need to do this?
628
	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...
629
		return $data;
630
631
	$cdata = '<![CDATA[';
632
633
	// @todo If we drop the obsolete $ns parameter, this whole loop could be replaced with a simple `str_replace(']]>', ']]]]><[CDATA[>', $data)`
634
635
	for ($pos = 0, $n = $smcFunc['strlen']($data); $pos < $n; null)
636
	{
637
		$positions = array(
638
			$smcFunc['strpos']($data, '&', $pos),
639
			$smcFunc['strpos']($data, ']]>', $pos),
640
		);
641
		if ($ns != '')
642
			$positions[] = $smcFunc['strpos']($data, '<', $pos);
643
		foreach ($positions as $k => $dummy)
644
		{
645
			if ($dummy === false)
646
				unset($positions[$k]);
647
		}
648
649
		$old = $pos;
650
		$pos = empty($positions) ? $n : min($positions);
651
652
		if ($pos - $old > 0)
653
			$cdata .= $smcFunc['substr']($data, $old, $pos - $old);
654
		if ($pos >= $n)
655
			break;
656
657
		if ($smcFunc['substr']($data, $pos, 1) == '<')
658
		{
659
			$pos2 = $smcFunc['strpos']($data, '>', $pos);
660
			if ($pos2 === false)
661
				$pos2 = $n;
662
			if ($smcFunc['substr']($data, $pos + 1, 1) == '/')
663
				$cdata .= ']]></' . $ns . ':' . $smcFunc['substr']($data, $pos + 2, $pos2 - $pos - 1) . '<![CDATA[';
664
			else
665
				$cdata .= ']]><' . $ns . ':' . $smcFunc['substr']($data, $pos + 1, $pos2 - $pos) . '<![CDATA[';
666
			$pos = $pos2 + 1;
667
		}
668
		elseif ($smcFunc['substr']($data, $pos, 3) == ']]>')
669
		{
670
			$cdata .= ']]]]><![CDATA[>';
671
			$pos = $pos + 3;
672
		}
673
		elseif ($smcFunc['substr']($data, $pos, 1) == '&')
674
		{
675
			$pos2 = $smcFunc['strpos']($data, ';', $pos);
676
			if ($pos2 === false)
677
				$pos2 = $n;
678
			$ent = $smcFunc['substr']($data, $pos + 1, $pos2 - $pos - 1);
679
680
			if ($smcFunc['substr']($data, $pos + 1, 1) == '#')
681
				$cdata .= ']]>' . $smcFunc['substr']($data, $pos, $pos2 - $pos + 1) . '<![CDATA[';
682
			elseif (in_array($ent, array('amp', 'lt', 'gt', 'quot')))
683
				$cdata .= ']]>' . $smcFunc['substr']($data, $pos, $pos2 - $pos + 1) . '<![CDATA[';
684
685
			$pos = $pos2 + 1;
686
		}
687
	}
688
689
	$cdata .= ']]>';
690
691
	return strtr($cdata, array('<![CDATA[]]>' => ''));
692
}
693
694
/**
695
 * Formats data retrieved in other functions into xml format.
696
 * Additionally formats data based on the specific format passed.
697
 * This function is recursively called to handle sub arrays of data.
698
 *
699
 * @param array $data The array to output as xml data
700
 * @param int $i The amount of indentation to use.
701
 * @param null|string $tag
702
 * @param string $xml_format The format to use ('atom', 'rss', 'rss2' or empty for plain XML)
703
 * @param array $forceCdataKeys A list of keys on which to force cdata wrapping (used by mods, maybe)
704
 * @param array $nsKeys Key-value pairs of namespace prefixes to pass to cdata_parse() (used by mods, maybe)
705
 */
706
function dumpTags($data, $i, $tag = null, $xml_format = '', $forceCdataKeys = array(), $nsKeys = array())
707
{
708
	global $context;
709
710
	if (empty($context['feed']['items']))
711
		$context['feed']['items'] = '';
712
713
	// For every array in the data...
714
	foreach ($data as $element)
715
	{
716
		// If a tag was passed, use it instead of the key.
717
		$key = isset($tag) ? $tag : (isset($element['tag']) ? $element['tag'] : null);
718
		$val = isset($element['content']) ? $element['content'] : null;
719
		$attrs = isset($element['attributes']) ? $element['attributes'] : null;
720
721
		// Skip it, it's been set to null.
722
		if ($key === null || ($val === null && $attrs === null))
723
			continue;
724
725
		$forceCdata = in_array($key, $forceCdataKeys);
726
		$ns = !empty($nsKeys[$key]) ? $nsKeys[$key] : '';
727
728
		// First let's indent!
729
		$context['feed']['items'] .= "\n" . str_repeat("\t", $i);
730
731
		// Beginning tag.
732
		$context['feed']['items'] .= '<' . $key;
733
734
		if (!empty($attrs))
735
		{
736
			foreach ($attrs as $attr_key => $attr_value)
737
				$context['feed']['items'] .= ' ' . $attr_key . '="' . fix_possible_url($attr_value) . '"';
738
		}
739
740
		// If it's empty, simply output an empty element.
741
		if (empty($val) && $val !== '0' && $val !== 0)
742
		{
743
			$context['feed']['items'] .= ' />';
744
		}
745
		else
746
		{
747
			$context['feed']['items'] .= '>';
748
749
			// The element's value.
750
			if (is_array($val))
751
			{
752
				// An array.  Dump it, and then indent the tag.
753
				dumpTags($val, $i + 1, null, $xml_format, $forceCdataKeys, $nsKeys);
754
				$context['feed']['items'] .= "\n" . str_repeat("\t", $i);
755
			}
756
			// A string with returns in it.... show this as a multiline element.
757
			elseif (strpos($val, "\n") !== false)
758
				$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);
759
			// A simple string.
760
			else
761
				$context['feed']['items'] .= !empty($element['cdata']) || $forceCdata ? cdata_parse(fix_possible_url($val), $ns, $forceCdata) : fix_possible_url($val);
762
763
			// Ending tag.
764
			$context['feed']['items'] .= '</' . $key . '>';
765
		}
766
	}
767
}
768
769
/**
770
 * Retrieve the list of members from database.
771
 * The array will be generated to match the format.
772
 *
773
 * @todo get the list of members from Subs-Members.
774
 *
775
 * @param string $xml_format The format to use. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'
776
 * @return array An array of arrays of feed items. Each array has keys corresponding to the appropriate tags for the specified format.
777
 */
778
function getXmlMembers($xml_format)
779
{
780
	global $scripturl, $smcFunc, $txt, $context;
781
782
	if (!allowedTo('view_mlist'))
783
		return array();
784
785
	loadLanguage('Profile');
786
787
	// Find the most recent members.
788
	$request = $smcFunc['db_query']('', '
789
		SELECT id_member, member_name, real_name, date_registered, last_login
790
		FROM {db_prefix}members
791
		ORDER BY id_member DESC
792
		LIMIT {int:limit} OFFSET {int:offset}',
793
		array(
794
			'limit' => $_GET['limit'],
795
			'offset' => $_GET['offset'],
796
		)
797
	);
798
	$data = array();
799
	while ($row = $smcFunc['db_fetch_assoc']($request))
800
	{
801
		// If any control characters slipped in somehow, kill the evil things
802
		$row = preg_replace($context['utf8'] ? '/\pCc*/u' : '/[\x00-\x1F\x7F]*/', '', $row);
803
804
		// Create a GUID for each member using the tag URI scheme
805
		$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['date_registered']) . ':member=' . $row['id_member'];
806
807
		// Make the data look rss-ish.
808
		if ($xml_format == 'rss' || $xml_format == 'rss2')
809
			$data[] = array(
810
				'tag' => 'item',
811
				'content' => array(
812
					array(
813
						'tag' => 'title',
814
						'content' => $row['real_name'],
815
						'cdata' => true,
816
					),
817
					array(
818
						'tag' => 'link',
819
						'content' => $scripturl . '?action=profile;u=' . $row['id_member'],
820
					),
821
					array(
822
						'tag' => 'comments',
823
						'content' => $scripturl . '?action=pm;sa=send;u=' . $row['id_member'],
824
					),
825
					array(
826
						'tag' => 'pubDate',
827
						'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['date_registered']),
828
					),
829
					array(
830
						'tag' => 'guid',
831
						'content' => $guid,
832
						'attributes' => array(
833
							'isPermaLink' => 'false',
834
						),
835
					),
836
				),
837
			);
838
		elseif ($xml_format == 'rdf')
839
			$data[] = array(
840
				'tag' => 'item',
841
				'attributes' => array('rdf:about' => $scripturl . '?action=profile;u=' . $row['id_member']),
842
				'content' => array(
843
					array(
844
						'tag' => 'dc:format',
845
						'content' => 'text/html',
846
					),
847
					array(
848
						'tag' => 'title',
849
						'content' => $row['real_name'],
850
						'cdata' => true,
851
					),
852
					array(
853
						'tag' => 'link',
854
						'content' => $scripturl . '?action=profile;u=' . $row['id_member'],
855
					),
856
				),
857
			);
858
		elseif ($xml_format == 'atom')
859
			$data[] = array(
860
				'tag' => 'entry',
861
				'content' => array(
862
					array(
863
						'tag' => 'title',
864
						'content' => $row['real_name'],
865
						'cdata' => true,
866
					),
867
					array(
868
						'tag' => 'link',
869
						'attributes' => array(
870
							'rel' => 'alternate',
871
							'type' => 'text/html',
872
							'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
873
						),
874
					),
875
					array(
876
						'tag' => 'published',
877
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['date_registered']),
878
					),
879
					array(
880
						'tag' => 'updated',
881
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['last_login']),
882
					),
883
					array(
884
						'tag' => 'id',
885
						'content' => $guid,
886
					),
887
				),
888
			);
889
		// More logical format for the data, but harder to apply.
890
		else
891
			$data[] = array(
892
				'tag' => 'member',
893
				'attributes' => array('title' => $txt['who_member']),
894
				'content' => array(
895
					array(
896
						'tag' => 'name',
897
						'attributes' => array('title' => $txt['name']),
898
						'content' => $row['real_name'],
899
						'cdata' => true,
900
					),
901
					array(
902
						'tag' => 'time',
903
						'attributes' => array('title' => $txt['date_registered'], 'UTC' => gmstrftime('%F %T', $row['date_registered'])),
904
						'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['date_registered']))),
905
					),
906
					array(
907
						'tag' => 'id',
908
						'content' => $row['id_member'],
909
					),
910
					array(
911
						'tag' => 'link',
912
						'attributes' => array('title' => $txt['url']),
913
						'content' => $scripturl . '?action=profile;u=' . $row['id_member'],
914
					),
915
				),
916
			);
917
	}
918
	$smcFunc['db_free_result']($request);
919
920
	return $data;
921
}
922
923
/**
924
 * Get the latest topics information from a specific board,
925
 * to display later.
926
 * The returned array will be generated to match the xml_format.
927
 *
928
 * @todo does not belong here
929
 *
930
 * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'.
931
 * @return array An array of arrays of topic data for the feed. Each array has keys corresponding to the tags for the specified format.
932
 */
933
function getXmlNews($xml_format)
934
{
935
	global $scripturl, $modSettings, $board, $user_info;
936
	global $query_this_board, $smcFunc, $context, $txt;
937
938
	/* Find the latest posts that:
939
		- are the first post in their topic.
940
		- are on an any board OR in a specified board.
941
		- can be seen by this user.
942
		- are actually the latest posts. */
943
944
	$done = false;
945
	$loops = 0;
946
	while (!$done)
947
	{
948
		$optimize_msg = implode(' AND ', $context['optimize_msg']);
949
		$request = $smcFunc['db_query']('', '
950
			SELECT
951
				m.smileys_enabled, m.poster_time, m.id_msg, m.subject, m.body, m.modified_time,
952
				m.icon, t.id_topic, t.id_board, t.num_replies,
953
				b.name AS bname,
954
				COALESCE(mem.id_member, 0) AS id_member,
955
				COALESCE(mem.email_address, m.poster_email) AS poster_email,
956
				COALESCE(mem.real_name, m.poster_name) AS poster_name
957
			FROM {db_prefix}topics AS t
958
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
959
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
960
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
961
			WHERE ' . $query_this_board . (empty($optimize_msg) ? '' : '
962
				AND {raw:optimize_msg}') . (empty($board) ? '' : '
963
				AND t.id_board = {int:current_board}') . ($modSettings['postmod_active'] ? '
964
				AND t.approved = {int:is_approved}' : '') . '
965
			ORDER BY t.id_first_msg DESC
966
			LIMIT {int:limit} OFFSET {int:offset}',
967
			array(
968
				'current_board' => $board,
969
				'is_approved' => 1,
970
				'limit' => $_GET['limit'],
971
				'offset' => $_GET['offset'],
972
				'optimize_msg' => $optimize_msg,
973
			)
974
		);
975
		// If we don't have $_GET['limit'] results, try again with an unoptimized version covering all rows.
976
		if ($loops < 2 && $smcFunc['db_num_rows']($request) < $_GET['limit'])
977
		{
978
			$smcFunc['db_free_result']($request);
979
			if (empty($_REQUEST['boards']) && empty($board))
980
				unset($context['optimize_msg']['lowest']);
981
			else
982
				$context['optimize_msg']['lowest'] = 'm.id_msg >= t.id_first_msg';
983
			$context['optimize_msg']['highest'] = 'm.id_msg <= t.id_last_msg';
984
			$loops++;
985
		}
986
		else
987
			$done = true;
988
	}
989
	$data = array();
990
	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...
991
	{
992
		// If any control characters slipped in somehow, kill the evil things
993
		$row = preg_replace($context['utf8'] ? '/\pCc*/u' : '/[\x00-\x1F\x7F]*/', '', $row);
994
995
		// Limit the length of the message, if the option is set.
996
		if (!empty($modSettings['xmlnews_maxlen']) && $smcFunc['strlen'](str_replace('<br>', "\n", $row['body'])) > $modSettings['xmlnews_maxlen'])
997
			$row['body'] = strtr($smcFunc['substr'](str_replace('<br>', "\n", $row['body']), 0, $modSettings['xmlnews_maxlen'] - 3), array("\n" => '<br>')) . '...';
998
999
		$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
1000
1001
		censorText($row['body']);
1002
		censorText($row['subject']);
1003
1004
		// Do we want to include any attachments?
1005
		if (!empty($modSettings['attachmentEnable']) && !empty($modSettings['xmlnews_attachments']) && allowedTo('view_attachments', $row['id_board']))
1006
		{
1007
			$attach_request = $smcFunc['db_query']('', '
1008
				SELECT
1009
					a.id_attach, a.filename, COALESCE(a.size, 0) AS filesize, a.mime_type, a.downloads, a.approved, m.id_topic AS topic
1010
				FROM {db_prefix}attachments AS a
1011
					LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1012
				WHERE a.attachment_type = {int:attachment_type}
1013
					AND a.id_msg = {int:message_id}',
1014
				array(
1015
					'message_id' => $row['id_msg'],
1016
					'attachment_type' => 0,
1017
					'is_approved' => 1,
1018
				)
1019
			);
1020
			$loaded_attachments = array();
1021
			while ($attach = $smcFunc['db_fetch_assoc']($attach_request))
1022
			{
1023
				// Include approved attachments only
1024
				if ($attach['approved'])
1025
					$loaded_attachments['attachment_' . $attach['id_attach']] = $attach;
1026
			}
1027
			$smcFunc['db_free_result']($attach_request);
1028
1029
			// Sort the attachments by size to make things easier below
1030
			if (!empty($loaded_attachments))
1031
			{
1032
				uasort($loaded_attachments, function($a, $b)
1033
				{
1034
					if ($a['filesize'] == $b['filesize'])
1035
						return 0;
1036
					return ($a['filesize'] < $b['filesize']) ? -1 : 1;
1037
				});
1038
			}
1039
			else
1040
				$loaded_attachments = null;
1041
		}
1042
		else
1043
			$loaded_attachments = null;
1044
1045
		// Create a GUID for this topic using the tag URI scheme
1046
		$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['poster_time']) . ':topic=' . $row['id_topic'];
1047
1048
		// Being news, this actually makes sense in rss format.
1049
		if ($xml_format == 'rss' || $xml_format == 'rss2')
1050
		{
1051
			// Only one attachment allowed in RSS.
1052
			if ($loaded_attachments !== null)
1053
			{
1054
				$attachment = array_pop($loaded_attachments);
0 ignored issues
show
Bug introduced by
$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

1054
				$attachment = array_pop(/** @scrutinizer ignore-type */ $loaded_attachments);
Loading history...
1055
				$enclosure = array(
1056
					'url' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
1057
					'length' => $attachment['filesize'],
1058
					'type' => $attachment['mime_type'],
1059
				);
1060
			}
1061
			else
1062
				$enclosure = null;
1063
1064
			$data[] = array(
1065
				'tag' => 'item',
1066
				'content' => array(
1067
					array(
1068
						'tag' => 'title',
1069
						'content' => $row['subject'],
1070
						'cdata' => true,
1071
					),
1072
					array(
1073
						'tag' => 'link',
1074
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
1075
					),
1076
					array(
1077
						'tag' => 'description',
1078
						'content' => $row['body'],
1079
						'cdata' => true,
1080
					),
1081
					array(
1082
						'tag' => 'author',
1083
						'content' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? $row['poster_email'] . ' (' . $row['poster_name'] . ')' : null,
1084
					),
1085
					array(
1086
						'tag' => 'comments',
1087
						'content' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0',
1088
					),
1089
					array(
1090
						'tag' => 'category',
1091
						'content' => $row['bname'],
1092
					),
1093
					array(
1094
						'tag' => 'pubDate',
1095
						'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']),
1096
					),
1097
					array(
1098
						'tag' => 'guid',
1099
						'content' => $guid,
1100
						'attributes' => array(
1101
							'isPermaLink' => 'false',
1102
						),
1103
					),
1104
					array(
1105
						'tag' => 'enclosure',
1106
						'attributes' => $enclosure,
1107
					),
1108
				),
1109
			);
1110
		}
1111
		elseif ($xml_format == 'rdf')
1112
		{
1113
			$data[] = array(
1114
				'tag' => 'item',
1115
				'attributes' => array('rdf:about' => $scripturl . '?topic=' . $row['id_topic'] . '.0'),
1116
				'content' => array(
1117
					array(
1118
						'tag' => 'dc:format',
1119
						'content' => 'text/html',
1120
					),
1121
					array(
1122
						'tag' => 'title',
1123
						'content' => $row['subject'],
1124
						'cdata' => true,
1125
					),
1126
					array(
1127
						'tag' => 'link',
1128
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
1129
					),
1130
					array(
1131
						'tag' => 'description',
1132
						'content' => $row['body'],
1133
						'cdata' => true,
1134
					),
1135
				),
1136
			);
1137
		}
1138
		elseif ($xml_format == 'atom')
1139
		{
1140
			// Only one attachment allowed
1141
			if (!empty($loaded_attachments))
1142
			{
1143
				$attachment = array_pop($loaded_attachments);
1144
				$enclosure = array(
1145
					'rel' => 'enclosure',
1146
					'href' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
1147
					'length' => $attachment['filesize'],
1148
					'type' => $attachment['mime_type'],
1149
				);
1150
			}
1151
			else
1152
				$enclosure = null;
1153
1154
			$data[] = array(
1155
				'tag' => 'entry',
1156
				'content' => array(
1157
					array(
1158
						'tag' => 'title',
1159
						'content' => $row['subject'],
1160
						'cdata' => true,
1161
					),
1162
					array(
1163
						'tag' => 'link',
1164
						'attributes' => array(
1165
							'rel' => 'alternate',
1166
							'type' => 'text/html',
1167
							'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
1168
						),
1169
					),
1170
					array(
1171
						'tag' => 'summary',
1172
						'attributes' => array('type' => 'html'),
1173
						'content' => $row['body'],
1174
						'cdata' => true,
1175
					),
1176
					array(
1177
						'tag' => 'category',
1178
						'attributes' => array('term' => $row['bname']),
1179
					),
1180
					array(
1181
						'tag' => 'author',
1182
						'content' => array(
1183
							array(
1184
								'tag' => 'name',
1185
								'content' => $row['poster_name'],
1186
								'cdata' => true,
1187
							),
1188
							array(
1189
								'tag' => 'email',
1190
								'content' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? $row['poster_email'] : null,
1191
							),
1192
							array(
1193
								'tag' => 'uri',
1194
								'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : null,
1195
							),
1196
						)
1197
					),
1198
					array(
1199
						'tag' => 'published',
1200
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']),
1201
					),
1202
					array(
1203
						'tag' => 'updated',
1204
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']),
1205
					),
1206
					array(
1207
						'tag' => 'id',
1208
						'content' => $guid,
1209
					),
1210
					array(
1211
						'tag' => 'link',
1212
						'attributes' => $enclosure,
1213
					),
1214
				),
1215
			);
1216
		}
1217
		// The biggest difference here is more information.
1218
		else
1219
		{
1220
			loadLanguage('Post');
1221
1222
			$attachments = array();
1223
			if (!empty($loaded_attachments))
1224
			{
1225
				foreach ($loaded_attachments as $attachment)
1226
				{
1227
					$attachments[] = array(
1228
						'tag' => 'attachment',
1229
						'attributes' => array('title' => $txt['attachment']),
1230
						'content' => array(
1231
							array(
1232
								'tag' => 'id',
1233
								'content' => $attachment['id_attach'],
1234
							),
1235
							array(
1236
								'tag' => 'name',
1237
								'attributes' => array('title' => $txt['name']),
1238
								'content' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])),
1239
							),
1240
							array(
1241
								'tag' => 'downloads',
1242
								'attributes' => array('title' => $txt['downloads']),
1243
								'content' => $attachment['downloads'],
1244
							),
1245
							array(
1246
								'tag' => 'size',
1247
								'attributes' => array('title' => $txt['filesize']),
1248
								'content' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'],
1249
							),
1250
							array(
1251
								'tag' => 'byte_size',
1252
								'attributes' => array('title' => $txt['filesize']),
1253
								'content' => $attachment['filesize'],
1254
							),
1255
							array(
1256
								'tag' => 'link',
1257
								'attributes' => array('title' => $txt['url']),
1258
								'content' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'],
1259
							),
1260
						)
1261
					);
1262
				}
1263
			}
1264
			else
1265
				$attachments = null;
1266
1267
			$data[] = array(
1268
				'tag' => 'article',
1269
				'attributes' => array('title' => $txt['news']),
1270
				'content' => array(
1271
					array(
1272
						'tag' => 'time',
1273
						'attributes' => array('title' => $txt['date'], 'UTC' => gmstrftime('%F %T', $row['poster_time'])),
1274
						'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['poster_time']))),
1275
					),
1276
					array(
1277
						'tag' => 'id',
1278
						'content' => $row['id_topic'],
1279
					),
1280
					array(
1281
						'tag' => 'subject',
1282
						'attributes' => array('title' => $txt['subject']),
1283
						'content' => $row['subject'],
1284
						'cdata' => true,
1285
					),
1286
					array(
1287
						'tag' => 'body',
1288
						'attributes' => array('title' => $txt['message']),
1289
						'content' => $row['body'],
1290
						'cdata' => true,
1291
					),
1292
					array(
1293
						'tag' => 'poster',
1294
						'attributes' => array('title' => $txt['author']),
1295
						'content' => array(
1296
							array(
1297
								'tag' => 'name',
1298
								'attributes' => array('title' => $txt['name']),
1299
								'content' => $row['poster_name'],
1300
								'cdata' => true,
1301
							),
1302
							array(
1303
								'tag' => 'id',
1304
								'content' => $row['id_member'],
1305
							),
1306
							array(
1307
								'tag' => 'link',
1308
								'attributes' => !empty($row['id_member']) ? array('title' => $txt['url']) : null,
1309
								'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '',
1310
							),
1311
						)
1312
					),
1313
					array(
1314
						'tag' => 'topic',
1315
						'attributes' => array('title' => $txt['topic']),
1316
						'content' => $row['id_topic'],
1317
					),
1318
					array(
1319
						'tag' => 'board',
1320
						'attributes' => array('title' => $txt['board']),
1321
						'content' => array(
1322
							array(
1323
								'tag' => 'name',
1324
								'attributes' => array('title' => $txt['name']),
1325
								'content' => $row['bname'],
1326
							),
1327
							array(
1328
								'tag' => 'id',
1329
								'content' => $row['id_board'],
1330
							),
1331
							array(
1332
								'tag' => 'link',
1333
								'attributes' => array('title' => $txt['url']),
1334
								'content' => $scripturl . '?board=' . $row['id_board'] . '.0',
1335
							),
1336
						),
1337
					),
1338
					array(
1339
						'tag' => 'link',
1340
						'attributes' => array('title' => $txt['url']),
1341
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
1342
					),
1343
					array(
1344
						'tag' => 'attachments',
1345
						'attributes' => array('title' => $txt['attachments']),
1346
						'content' => $attachments,
1347
					),
1348
				),
1349
			);
1350
		}
1351
	}
1352
	$smcFunc['db_free_result']($request);
1353
1354
	return $data;
1355
}
1356
1357
/**
1358
 * Get the recent topics to display.
1359
 * The returned array will be generated to match the xml_format.
1360
 *
1361
 * @todo does not belong here.
1362
 *
1363
 * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'
1364
 * @return array An array of arrays containing data for the feed. Each array has keys corresponding to the appropriate tags for the specified format.
1365
 */
1366
function getXmlRecent($xml_format)
1367
{
1368
	global $scripturl, $modSettings, $board, $txt;
1369
	global $query_this_board, $smcFunc, $context, $user_info, $sourcedir;
1370
1371
	require_once($sourcedir . '/Subs-Attachments.php');
1372
1373
	$done = false;
1374
	$loops = 0;
1375
	while (!$done)
1376
	{
1377
		$optimize_msg = implode(' AND ', $context['optimize_msg']);
1378
		$request = $smcFunc['db_query']('', '
1379
			SELECT m.id_msg
1380
			FROM {db_prefix}messages AS m
1381
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1382
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1383
			WHERE ' . $query_this_board . (empty($optimize_msg) ? '' : '
1384
				AND {raw:optimize_msg}') . (empty($board) ? '' : '
1385
				AND m.id_board = {int:current_board}') . ($modSettings['postmod_active'] ? '
1386
				AND m.approved = {int:is_approved}' : '') . '
1387
			ORDER BY m.id_msg DESC
1388
			LIMIT {int:limit} OFFSET {int:offset}',
1389
			array(
1390
				'limit' => $_GET['limit'],
1391
				'offset' => $_GET['offset'],
1392
				'current_board' => $board,
1393
				'is_approved' => 1,
1394
				'optimize_msg' => $optimize_msg,
1395
			)
1396
		);
1397
		// If we don't have $_GET['limit'] results, try again with an unoptimized version covering all rows.
1398
		if ($loops < 2 && $smcFunc['db_num_rows']($request) < $_GET['limit'])
1399
		{
1400
			$smcFunc['db_free_result']($request);
1401
			if (empty($_REQUEST['boards']) && empty($board))
1402
				unset($context['optimize_msg']['lowest']);
1403
			else
1404
				$context['optimize_msg']['lowest'] = $loops ? 'm.id_msg >= t.id_first_msg' : 'm.id_msg >= (t.id_last_msg - t.id_first_msg) / 2';
1405
			$loops++;
1406
		}
1407
		else
1408
			$done = true;
1409
	}
1410
	$messages = array();
1411
	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...
1412
		$messages[] = $row['id_msg'];
1413
	$smcFunc['db_free_result']($request);
1414
1415
	if (empty($messages))
1416
		return array();
1417
1418
	// Find the most recent posts this user can see.
1419
	$request = $smcFunc['db_query']('', '
1420
		SELECT
1421
			m.smileys_enabled, m.poster_time, m.id_msg, m.subject, m.body, m.id_topic, t.id_board,
1422
			b.name AS bname, t.num_replies, m.id_member, m.icon, mf.id_member AS id_first_member,
1423
			COALESCE(mem.real_name, m.poster_name) AS poster_name, mf.subject AS first_subject,
1424
			COALESCE(memf.real_name, mf.poster_name) AS first_poster_name,
1425
			COALESCE(mem.email_address, m.poster_email) AS poster_email, m.modified_time
1426
		FROM {db_prefix}messages AS m
1427
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1428
			INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
1429
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1430
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
1431
			LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = mf.id_member)
1432
		WHERE m.id_msg IN ({array_int:message_list})
1433
			' . (empty($board) ? '' : 'AND t.id_board = {int:current_board}') . '
1434
		ORDER BY m.id_msg DESC
1435
		LIMIT {int:limit}',
1436
		array(
1437
			'limit' => $_GET['limit'],
1438
			'current_board' => $board,
1439
			'message_list' => $messages,
1440
		)
1441
	);
1442
	$data = array();
1443
	while ($row = $smcFunc['db_fetch_assoc']($request))
1444
	{
1445
		// If any control characters slipped in somehow, kill the evil things
1446
		$row = preg_replace($context['utf8'] ? '/\pCc*/u' : '/[\x00-\x1F\x7F]*/', '', $row);
1447
1448
		// Limit the length of the message, if the option is set.
1449
		if (!empty($modSettings['xmlnews_maxlen']) && $smcFunc['strlen'](str_replace('<br>', "\n", $row['body'])) > $modSettings['xmlnews_maxlen'])
1450
			$row['body'] = strtr($smcFunc['substr'](str_replace('<br>', "\n", $row['body']), 0, $modSettings['xmlnews_maxlen'] - 3), array("\n" => '<br>')) . '...';
1451
1452
		$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
1453
1454
		censorText($row['body']);
1455
		censorText($row['subject']);
1456
1457
		// Do we want to include any attachments?
1458
		if (!empty($modSettings['attachmentEnable']) && !empty($modSettings['xmlnews_attachments']) && allowedTo('view_attachments', $row['id_board']))
1459
		{
1460
			$attach_request = $smcFunc['db_query']('', '
1461
				SELECT
1462
					a.id_attach, a.filename, COALESCE(a.size, 0) AS filesize, a.mime_type, a.downloads, a.approved, m.id_topic AS topic
1463
				FROM {db_prefix}attachments AS a
1464
					LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1465
				WHERE a.attachment_type = {int:attachment_type}
1466
					AND a.id_msg = {int:message_id}',
1467
				array(
1468
					'message_id' => $row['id_msg'],
1469
					'attachment_type' => 0,
1470
					'is_approved' => 1,
1471
				)
1472
			);
1473
			$loaded_attachments = array();
1474
			while ($attach = $smcFunc['db_fetch_assoc']($attach_request))
1475
			{
1476
				// Include approved attachments only
1477
				if ($attach['approved'])
1478
					$loaded_attachments['attachment_' . $attach['id_attach']] = $attach;
1479
			}
1480
			$smcFunc['db_free_result']($attach_request);
1481
1482
			// Sort the attachments by size to make things easier below
1483
			if (!empty($loaded_attachments))
1484
			{
1485
				uasort($loaded_attachments, function($a, $b)
1486
				{
1487
					if ($a['filesize'] == $b['filesize'])
1488
						return 0;
1489
1490
					return ($a['filesize'] < $b['filesize']) ? -1 : 1;
1491
				});
1492
			}
1493
			else
1494
				$loaded_attachments = null;
1495
		}
1496
		else
1497
			$loaded_attachments = null;
1498
1499
		// Create a GUID for this post using the tag URI scheme
1500
		$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['poster_time']) . ':msg=' . $row['id_msg'];
1501
1502
		// Doesn't work as well as news, but it kinda does..
1503
		if ($xml_format == 'rss' || $xml_format == 'rss2')
1504
		{
1505
			// Only one attachment allowed in RSS.
1506
			if ($loaded_attachments !== null)
1507
			{
1508
				$attachment = array_pop($loaded_attachments);
0 ignored issues
show
Bug introduced by
$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

1508
				$attachment = array_pop(/** @scrutinizer ignore-type */ $loaded_attachments);
Loading history...
1509
				$enclosure = array(
1510
					'url' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
1511
					'length' => $attachment['filesize'],
1512
					'type' => $attachment['mime_type'],
1513
				);
1514
			}
1515
			else
1516
				$enclosure = null;
1517
1518
			$data[] = array(
1519
				'tag' => 'item',
1520
				'content' => array(
1521
					array(
1522
						'tag' => 'title',
1523
						'content' => $row['subject'],
1524
						'cdata' => true,
1525
					),
1526
					array(
1527
						'tag' => 'link',
1528
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
1529
					),
1530
					array(
1531
						'tag' => 'description',
1532
						'content' => $row['body'],
1533
						'cdata' => true,
1534
					),
1535
					array(
1536
						'tag' => 'author',
1537
						'content' => (allowedTo('moderate_forum') || (!empty($row['id_member']) && $row['id_member'] == $user_info['id'])) ? $row['poster_email'] : null,
1538
					),
1539
					array(
1540
						'tag' => 'category',
1541
						'content' => $row['bname'],
1542
					),
1543
					array(
1544
						'tag' => 'comments',
1545
						'content' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0',
1546
					),
1547
					array(
1548
						'tag' => 'pubDate',
1549
						'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']),
1550
					),
1551
					array(
1552
						'tag' => 'guid',
1553
						'content' => $guid,
1554
						'attributes' => array(
1555
							'isPermaLink' => 'false',
1556
						),
1557
					),
1558
					array(
1559
						'tag' => 'enclosure',
1560
						'attributes' => $enclosure,
1561
					),
1562
				),
1563
			);
1564
		}
1565
		elseif ($xml_format == 'rdf')
1566
		{
1567
			$data[] = array(
1568
				'tag' => 'item',
1569
				'attributes' => array('rdf:about' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg']),
1570
				'content' => array(
1571
					array(
1572
						'tag' => 'dc:format',
1573
						'content' => 'text/html',
1574
					),
1575
					array(
1576
						'tag' => 'title',
1577
						'content' => $row['subject'],
1578
						'cdata' => true,
1579
					),
1580
					array(
1581
						'tag' => 'link',
1582
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
1583
					),
1584
					array(
1585
						'tag' => 'description',
1586
						'content' => $row['body'],
1587
						'cdata' => true,
1588
					),
1589
				),
1590
			);
1591
		}
1592
		elseif ($xml_format == 'atom')
1593
		{
1594
			// Only one attachment allowed
1595
			if (!empty($loaded_attachments))
1596
			{
1597
				$attachment = array_pop($loaded_attachments);
1598
				$enclosure = array(
1599
					'rel' => 'enclosure',
1600
					'href' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
1601
					'length' => $attachment['filesize'],
1602
					'type' => $attachment['mime_type'],
1603
				);
1604
			}
1605
			else
1606
				$enclosure = null;
1607
1608
			$data[] = array(
1609
				'tag' => 'entry',
1610
				'content' => array(
1611
					array(
1612
						'tag' => 'title',
1613
						'content' => $row['subject'],
1614
						'cdata' => true,
1615
					),
1616
					array(
1617
						'tag' => 'link',
1618
						'attributes' => array(
1619
							'rel' => 'alternate',
1620
							'type' => 'text/html',
1621
							'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
1622
						),
1623
					),
1624
					array(
1625
						'tag' => 'summary',
1626
						'attributes' => array('type' => 'html'),
1627
						'content' => $row['body'],
1628
						'cdata' => true,
1629
					),
1630
					array(
1631
						'tag' => 'category',
1632
						'attributes' => array('term' => $row['bname']),
1633
					),
1634
					array(
1635
						'tag' => 'author',
1636
						'content' => array(
1637
							array(
1638
								'tag' => 'name',
1639
								'content' => $row['poster_name'],
1640
								'cdata' => true,
1641
							),
1642
							array(
1643
								'tag' => 'email',
1644
								'content' => (allowedTo('moderate_forum') || (!empty($row['id_member']) && $row['id_member'] == $user_info['id'])) ? $row['poster_email'] : null,
1645
							),
1646
							array(
1647
								'tag' => 'uri',
1648
								'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : null,
1649
							),
1650
						),
1651
					),
1652
					array(
1653
						'tag' => 'published',
1654
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']),
1655
					),
1656
					array(
1657
						'tag' => 'updated',
1658
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']),
1659
					),
1660
					array(
1661
						'tag' => 'id',
1662
						'content' => $guid,
1663
					),
1664
					array(
1665
						'tag' => 'link',
1666
						'attributes' => $enclosure,
1667
					),
1668
				),
1669
			);
1670
		}
1671
		// A lot of information here.  Should be enough to please the rss-ers.
1672
		else
1673
		{
1674
			loadLanguage('Post');
1675
1676
			$attachments = array();
1677
			if (!empty($loaded_attachments))
1678
			{
1679
				foreach ($loaded_attachments as $attachment)
1680
				{
1681
					$attachments[] = array(
1682
						'tag' => 'attachment',
1683
						'attributes' => array('title' => $txt['attachment']),
1684
						'content' => array(
1685
							array(
1686
								'tag' => 'id',
1687
								'content' => $attachment['id_attach'],
1688
							),
1689
							array(
1690
								'tag' => 'name',
1691
								'attributes' => array('title' => $txt['name']),
1692
								'content' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])),
1693
							),
1694
							array(
1695
								'tag' => 'downloads',
1696
								'attributes' => array('title' => $txt['downloads']),
1697
								'content' => $attachment['downloads'],
1698
							),
1699
							array(
1700
								'tag' => 'size',
1701
								'attributes' => array('title' => $txt['filesize']),
1702
								'content' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'],
1703
							),
1704
							array(
1705
								'tag' => 'byte_size',
1706
								'attributes' => array('title' => $txt['filesize']),
1707
								'content' => $attachment['filesize'],
1708
							),
1709
							array(
1710
								'tag' => 'link',
1711
								'attributes' => array('title' => $txt['url']),
1712
								'content' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'],
1713
							),
1714
						)
1715
					);
1716
				}
1717
			}
1718
			else
1719
				$attachments = null;
1720
1721
			$data[] = array(
1722
				'tag' => 'recent-post',
1723
				'attributes' => array('title' => $txt['post']),
1724
				'content' => array(
1725
					array(
1726
						'tag' => 'time',
1727
						'attributes' => array('title' => $txt['date'], 'UTC' => gmstrftime('%F %T', $row['poster_time'])),
1728
						'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['poster_time']))),
1729
					),
1730
					array(
1731
						'tag' => 'id',
1732
						'content' => $row['id_msg'],
1733
					),
1734
					array(
1735
						'tag' => 'subject',
1736
						'attributes' => array('title' => $txt['subject']),
1737
						'content' => $row['subject'],
1738
						'cdata' => true,
1739
					),
1740
					array(
1741
						'tag' => 'body',
1742
						'attributes' => array('title' => $txt['message']),
1743
						'content' => $row['body'],
1744
						'cdata' => true,
1745
					),
1746
					array(
1747
						'tag' => 'starter',
1748
						'attributes' => array('title' => $txt['topic_started']),
1749
						'content' => array(
1750
							array(
1751
								'tag' => 'name',
1752
								'attributes' => array('title' => $txt['name']),
1753
								'content' => $row['first_poster_name'],
1754
								'cdata' => true,
1755
							),
1756
							array(
1757
								'tag' => 'id',
1758
								'content' => $row['id_first_member'],
1759
							),
1760
							array(
1761
								'tag' => 'link',
1762
								'attributes' => !empty($row['id_first_member']) ? array('title' => $txt['url']) : null,
1763
								'content' => !empty($row['id_first_member']) ? $scripturl . '?action=profile;u=' . $row['id_first_member'] : '',
1764
							),
1765
						),
1766
					),
1767
					array(
1768
						'tag' => 'poster',
1769
						'attributes' => array('title' => $txt['author']),
1770
						'content' => array(
1771
							array(
1772
								'tag' => 'name',
1773
								'attributes' => array('title' => $txt['name']),
1774
								'content' => $row['poster_name'],
1775
								'cdata' => true,
1776
							),
1777
							array(
1778
								'tag' => 'id',
1779
								'content' => $row['id_member'],
1780
							),
1781
							array(
1782
								'tag' => 'link',
1783
								'attributes' => !empty($row['id_member']) ? array('title' => $txt['url']) : null,
1784
								'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '',
1785
							),
1786
						),
1787
					),
1788
					array(
1789
						'tag' => 'topic',
1790
						'attributes' => array('title' => $txt['topic']),
1791
						'content' => array(
1792
							array(
1793
								'tag' => 'subject',
1794
								'attributes' => array('title' => $txt['subject']),
1795
								'content' => $row['first_subject'],
1796
								'cdata' => true,
1797
							),
1798
							array(
1799
								'tag' => 'id',
1800
								'content' => $row['id_topic'],
1801
							),
1802
							array(
1803
								'tag' => 'link',
1804
								'attributes' => array('title' => $txt['url']),
1805
								'content' => $scripturl . '?topic=' . $row['id_topic'] . '.new#new',
1806
							),
1807
						),
1808
					),
1809
					array(
1810
						'tag' => 'board',
1811
						'attributes' => array('title' => $txt['board']),
1812
						'content' => array(
1813
							array(
1814
								'tag' => 'name',
1815
								'attributes' => array('title' => $txt['name']),
1816
								'content' => $row['bname'],
1817
							),
1818
							array(
1819
								'tag' => 'id',
1820
								'content' => $row['id_board'],
1821
							),
1822
							array(
1823
								'tag' => 'link',
1824
								'attributes' => array('title' => $txt['url']),
1825
								'content' => $scripturl . '?board=' . $row['id_board'] . '.0',
1826
							),
1827
						),
1828
					),
1829
					array(
1830
						'tag' => 'link',
1831
						'attributes' => array('title' => $txt['url']),
1832
						'content' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
1833
					),
1834
					array(
1835
						'tag' => 'attachments',
1836
						'attributes' => array('title' => $txt['attachments']),
1837
						'content' => $attachments,
1838
					),
1839
				),
1840
			);
1841
		}
1842
	}
1843
	$smcFunc['db_free_result']($request);
1844
1845
	return $data;
1846
}
1847
1848
/**
1849
 * Get the profile information for member into an array,
1850
 * which will be generated to match the xml_format.
1851
 *
1852
 * @todo refactor.
1853
 *
1854
 * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'
1855
 * @return array An array profile data
1856
 */
1857
function getXmlProfile($xml_format)
1858
{
1859
	global $scripturl, $memberContext, $user_profile, $user_info, $txt, $context;
1860
1861
	// Make sure the id is a number and not "I like trying to hack the database".
1862
	$_GET['u'] = isset($_GET['u']) ? (int) $_GET['u'] : $user_info['id'];
1863
1864
	// You must input a valid user....
1865
	if (empty($_GET['u']) || !loadMemberData((int) $_GET['u']))
1866
		return array();
1867
1868
	// Load the member's contextual information! (Including custom fields for our proprietary XML type)
1869
	if (!loadMemberContext($_GET['u'], ($xml_format == 'smf')) || !allowedTo('profile_view'))
1870
		return array();
1871
1872
	// Okay, I admit it, I'm lazy.  Stupid $_GET['u'] is long and hard to type.
1873
	$profile = &$memberContext[$_GET['u']];
1874
1875
	// Create a GUID for this member using the tag URI scheme
1876
	$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $user_profile[$profile['id']]['date_registered']) . ':member=' . $profile['id'];
1877
1878
	if ($xml_format == 'rss' || $xml_format == 'rss2')
1879
	{
1880
		$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...
1881
			'tag' => 'item',
1882
			'content' => array(
1883
				array(
1884
					'tag' => 'title',
1885
					'content' => $profile['name'],
1886
					'cdata' => true,
1887
				),
1888
				array(
1889
					'tag' => 'link',
1890
					'content' => $scripturl . '?action=profile;u=' . $profile['id'],
1891
				),
1892
				array(
1893
					'tag' => 'description',
1894
					'content' => isset($profile['group']) ? $profile['group'] : $profile['post_group'],
1895
					'cdata' => true,
1896
				),
1897
				array(
1898
					'tag' => 'comments',
1899
					'content' => $scripturl . '?action=pm;sa=send;u=' . $profile['id'],
1900
				),
1901
				array(
1902
					'tag' => 'pubDate',
1903
					'content' => gmdate('D, d M Y H:i:s \G\M\T', $user_profile[$profile['id']]['date_registered']),
1904
				),
1905
				array(
1906
					'tag' => 'guid',
1907
					'content' => $guid,
1908
					'attributes' => array(
1909
						'isPermaLink' => 'false',
1910
					),
1911
				),
1912
			)
1913
		);
1914
	}
1915
	elseif ($xml_format == 'rdf')
1916
	{
1917
		$data[] = array(
1918
			'tag' => 'item',
1919
			'attributes' => array('rdf:about' => $scripturl . '?action=profile;u=' . $profile['id']),
1920
			'content' => array(
1921
				array(
1922
					'tag' => 'dc:format',
1923
					'content' => 'text/html',
1924
				),
1925
				array(
1926
					'tag' => 'title',
1927
					'content' => $profile['name'],
1928
					'cdata' => true,
1929
				),
1930
				array(
1931
					'tag' => 'link',
1932
					'content' => $scripturl . '?action=profile;u=' . $profile['id'],
1933
				),
1934
				array(
1935
					'tag' => 'description',
1936
					'content' => isset($profile['group']) ? $profile['group'] : $profile['post_group'],
1937
					'cdata' => true,
1938
				),
1939
			)
1940
		);
1941
	}
1942
	elseif ($xml_format == 'atom')
1943
	{
1944
		$data[] = array(
1945
			'tag' => 'entry',
1946
			'content' => array(
1947
				array(
1948
					'tag' => 'title',
1949
					'content' => $profile['name'],
1950
					'cdata' => true,
1951
				),
1952
				array(
1953
					'tag' => 'link',
1954
					'attributes' => array(
1955
						'rel' => 'alternate',
1956
						'type' => 'text/html',
1957
						'href' => $scripturl . '?action=profile;u=' . $profile['id'],
1958
					),
1959
				),
1960
				array(
1961
					'tag' => 'summary',
1962
					'attributes' => array('type' => 'html'),
1963
					'content' => isset($profile['group']) ? $profile['group'] : $profile['post_group'],
1964
					'cdata' => true,
1965
				),
1966
				array(
1967
					'tag' => 'author',
1968
					'content' => array(
1969
						array(
1970
							'tag' => 'name',
1971
							'content' => $profile['name'],
1972
							'cdata' => true,
1973
						),
1974
						array(
1975
							'tag' => 'email',
1976
							'content' => $profile['show_email'] ? $profile['email'] : null,
1977
						),
1978
						array(
1979
							'tag' => 'uri',
1980
							'content' => !empty($profile['website']['url']) ? $profile['website']['url'] : null,
1981
						),
1982
					),
1983
				),
1984
				array(
1985
					'tag' => 'published',
1986
					'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $user_profile[$profile['id']]['date_registered']),
1987
				),
1988
				array(
1989
					'tag' => 'updated',
1990
					'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $user_profile[$profile['id']]['last_login']),
1991
				),
1992
				array(
1993
					'tag' => 'id',
1994
					'content' => $guid,
1995
				),
1996
			)
1997
		);
1998
	}
1999
	else
2000
	{
2001
		loadLanguage('Profile');
2002
2003
		$data = array(
2004
			array(
2005
				'tag' => 'username',
2006
				'attributes' => $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? array('title' => $txt['username']) : null,
2007
				'content' => $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? $profile['username'] : null,
2008
				'cdata' => true,
2009
			),
2010
			array(
2011
				'tag' => 'name',
2012
				'attributes' => array('title' => $txt['name']),
2013
				'content' => $profile['name'],
2014
				'cdata' => true,
2015
			),
2016
			array(
2017
				'tag' => 'link',
2018
				'attributes' => array('title' => $txt['url']),
2019
				'content' => $scripturl . '?action=profile;u=' . $profile['id'],
2020
			),
2021
			array(
2022
				'tag' => 'posts',
2023
				'attributes' => array('title' => $txt['member_postcount']),
2024
				'content' => $profile['posts'],
2025
			),
2026
			array(
2027
				'tag' => 'post-group',
2028
				'attributes' => array('title' => $txt['membergroups_group_type_post']),
2029
				'content' => $profile['post_group'],
2030
				'cdata' => true,
2031
			),
2032
			array(
2033
				'tag' => 'language',
2034
				'attributes' => array('title' => $txt['preferred_language']),
2035
				'content' => $profile['language'],
2036
				'cdata' => true,
2037
			),
2038
			array(
2039
				'tag' => 'last-login',
2040
				'attributes' => array('title' => $txt['lastLoggedIn']),
2041
				'content' => gmdate('D, d M Y H:i:s \G\M\T', $user_profile[$profile['id']]['last_login']),
2042
			),
2043
			array(
2044
				'tag' => 'registered',
2045
				'attributes' => array('title' => $txt['date_registered'], 'UTC' => gmstrftime('%F %T', $row['date_registered'])),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $row seems to be never defined.
Loading history...
2046
				'content' => gmdate('D, d M Y H:i:s \G\M\T', $user_profile[$profile['id']]['date_registered']),
2047
			),
2048
			array(
2049
				'tag' => 'avatar',
2050
				'attributes' => !empty($profile['avatar']['url']) ? array('title' => $txt['personal_picture']) : null,
2051
				'content' => !empty($profile['avatar']['url']) ? $profile['avatar']['url'] : null,
2052
			),
2053
			array(
2054
				'tag' => 'signature',
2055
				'attributes' => !empty($profile['signature']) ? array('title' => $txt['signature']) : null,
2056
				'content' => !empty($profile['signature']) ? $profile['signature'] : null,
2057
				'cdata' => true,
2058
			),
2059
			array(
2060
				'tag' => 'blurb',
2061
				'attributes' => !empty($profile['blurb']) ? array('title' => $txt['personal_text']) : null,
2062
				'content' => !empty($profile['blurb']) ? $profile['blurb'] : null,
2063
				'cdata' => true,
2064
			),
2065
			array(
2066
				'tag' => 'title',
2067
				'attributes' => !empty($profile['title']) ? array('title' => $txt['title']) : null,
2068
				'content' => !empty($profile['title']) ? $profile['title'] : null,
2069
				'cdata' => true,
2070
			),
2071
			array(
2072
				'tag' => 'position',
2073
				'attributes' => !empty($profile['group']) ? array('title' => $txt['position']) : null,
2074
				'content' => !empty($profile['group']) ? $profile['group'] : null,
2075
				'cdata' => true,
2076
			),
2077
			array(
2078
				'tag' => 'email',
2079
				'attributes' => !empty($profile['show_email']) || $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? array('title' => $txt['user_email_address']) : null,
2080
				'content' => !empty($profile['show_email']) || $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? $profile['email'] : null,
2081
			),
2082
			array(
2083
				'tag' => 'website',
2084
				'attributes' => array('title' => $txt['website']),
2085
				'content' => empty($profile['website']['url']) ? null : array(
2086
					array(
2087
						'tag' => 'title',
2088
						'attributes' => !empty($profile['website']['title']) ? array('title' => $txt['website_title']) : null,
2089
						'content' => !empty($profile['website']['title']) ? $profile['website']['title'] : null,
2090
					),
2091
					array(
2092
						'tag' => 'link',
2093
						'attributes' => array('title' => $txt['website_url']),
2094
						'content' => $profile['website']['url'],
2095
					),
2096
				),
2097
			),
2098
			array(
2099
				'tag' => 'online',
2100
				'attributes' => !empty($profile['online']['is_online']) ? array('title' => $txt['online']) : null,
2101
				'content' => !empty($profile['online']['is_online']) ? '' : null,
2102
			),
2103
			array(
2104
				'tag' => 'ip_addresses',
2105
				'attributes' => array('title' => $txt['ip_address']),
2106
				'content' => allowedTo('moderate_forum') || $user_info['id'] == $profile['id'] ? array(
2107
					array(
2108
						'tag' => 'ip',
2109
						'attributes' => array('title' => $txt['most_recent_ip']),
2110
						'content' => $profile['ip'],
2111
					),
2112
					array(
2113
						'tag' => 'ip2',
2114
						'content' => $profile['ip'] != $profile['ip2'] ? $profile['ip2'] : null,
2115
					),
2116
				) : null,
2117
			),
2118
		);
2119
2120
		if (!empty($profile['birth_date']) && substr($profile['birth_date'], 0, 4) != '0000' && substr($profile['birth_date'], 0, 4) != '1004')
2121
		{
2122
			list ($birth_year, $birth_month, $birth_day) = sscanf($profile['birth_date'], '%d-%d-%d');
2123
			$datearray = getdate(forum_time());
2124
			$age = $datearray['year'] - $birth_year - (($datearray['mon'] > $birth_month || ($datearray['mon'] == $birth_month && $datearray['mday'] >= $birth_day)) ? 0 : 1);
2125
2126
			$data[] = array(
2127
				'tag' => 'age',
2128
				'attributes' => array('title' => $txt['age']),
2129
				'content' => $age,
2130
			);
2131
			$data[] = array(
2132
				'tag' => 'birthdate',
2133
				'attributes' => array('title' => $txt['dob']),
2134
				'content' => $profile['birth_date'],
2135
			);
2136
		}
2137
2138
		if (!empty($profile['custom_fields']))
2139
		{
2140
			foreach ($profile['custom_fields'] as $custom_field)
2141
			{
2142
				$data[] = array(
2143
					'tag' => $custom_field['col_name'],
2144
					'attributes' => array('title' => $custom_field['title']),
2145
					'content' => $custom_field['raw'],
2146
					'cdata' => true,
2147
				);
2148
			}
2149
		}
2150
	}
2151
2152
	// Save some memory.
2153
	unset($profile, $memberContext[$_GET['u']]);
2154
2155
	return $data;
2156
}
2157
2158
/**
2159
 * Get a user's posts.
2160
 * The returned array will be generated to match the xml_format.
2161
 *
2162
 * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'
2163
 * @return array An array of arrays containing data for the feed. Each array has keys corresponding to the appropriate tags for the specified format.
2164
 */
2165
function getXmlPosts($xml_format)
2166
{
2167
	global $scripturl, $modSettings, $board, $txt, $context, $user_info;
2168
	global $query_this_board, $smcFunc, $sourcedir, $cachedir;
2169
2170
	$uid = isset($_GET['u']) ? (int) $_GET['u'] : $user_info['id'];
2171
2172
	if (empty($uid) || (!allowedTo('profile_view') && $uid != $user_info['id']))
2173
		return array();
2174
2175
	$show_all = $user_info['is_admin'] || $uid == $user_info['id'];
2176
2177
	// You are allowed in this special case to see your own posts from anywhere
2178
	if ($show_all)
2179
		$query_this_board = preg_replace('/\{query_see_board\}\s*(AND )?/', '', $query_this_board);
2180
2181
	require_once($sourcedir . '/Subs-Attachments.php');
2182
2183
	// Need to know the total so we can track our progress
2184
	if (!empty($context['batch_mode']) && empty($context['batch_total']))
2185
	{
2186
		$request = $smcFunc['db_query']('', '
2187
			SELECT COUNT(m.id_msg)
2188
			FROM {db_prefix}messages as m
2189
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
2190
			WHERE id_member = {int:uid}
2191
				AND ' . $query_this_board . ($modSettings['postmod_active'] && !$show_all ? '
2192
				AND approved = {int:is_approved}' : ''),
2193
			array(
2194
				'uid' => $uid,
2195
				'is_approved' => 1,
2196
			)
2197
		);
2198
		list($context['batch_total']) = $smcFunc['db_fetch_row']($request);
2199
		$smcFunc['db_free_result']($request);
2200
	}
2201
2202
	$request = $smcFunc['db_query']('', '
2203
		SELECT m.id_msg, m.id_topic, m.id_board, m.poster_name, m.poster_email, m.poster_ip, m.poster_time, m.subject,
2204
			modified_time, m.modified_name, m.modified_reason, m.body, m.likes, m.approved, m.smileys_enabled
2205
		FROM {db_prefix}messages as m
2206
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
2207
		WHERE id_member = {int:uid}
2208
			AND id_msg > {int:start_after}
2209
			AND ' . $query_this_board . ($modSettings['postmod_active'] && !$show_all ? '
2210
			AND approved = {int:is_approved}' : '') . '
2211
		ORDER BY id_msg
2212
		LIMIT {int:limit} OFFSET {int:offset}',
2213
		array(
2214
			'limit' => $_GET['limit'],
2215
			'offset' => !empty($context['posts_start']) ? 0 : $_GET['offset'],
2216
			'start_after' => !empty($context['posts_start']) ? $context['posts_start'] : 0,
2217
			'uid' => $uid,
2218
			'is_approved' => 1,
2219
		)
2220
	);
2221
	$data = array();
2222
	while ($row = $smcFunc['db_fetch_assoc']($request))
2223
	{
2224
		$last = $row['id_msg'];
2225
2226
		// We want a readable version of the IP address
2227
		$row['poster_ip'] = inet_dtop($row['poster_ip']);
2228
2229
		// If any control characters slipped in somehow, kill the evil things
2230
		$row = preg_replace($context['utf8'] ? '/\pCc*/u' : '/[\x00-\x1F\x7F]*/', '', $row);
2231
2232
		// If using our own format, we want the raw BBC
2233
		if ($xml_format != 'smf')
2234
			$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
2235
2236
		// Do we want to include any attachments?
2237
		if (!empty($modSettings['attachmentEnable']) && !empty($modSettings['xmlnews_attachments']))
2238
		{
2239
			$attach_request = $smcFunc['db_query']('', '
2240
				SELECT
2241
					a.id_attach, a.filename, COALESCE(a.size, 0) AS filesize, a.mime_type, a.downloads, a.approved, m.id_topic AS topic
2242
				FROM {db_prefix}attachments AS a
2243
					LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
2244
				WHERE a.attachment_type = {int:attachment_type}
2245
					AND a.id_msg = {int:message_id}',
2246
				array(
2247
					'message_id' => $row['id_msg'],
2248
					'attachment_type' => 0,
2249
					'is_approved' => 1,
2250
				)
2251
			);
2252
			$loaded_attachments = array();
2253
			while ($attach = $smcFunc['db_fetch_assoc']($attach_request))
2254
			{
2255
				// Include approved attachments only
2256
				if ($attach['approved'])
2257
					$loaded_attachments['attachment_' . $attach['id_attach']] = $attach;
2258
			}
2259
			$smcFunc['db_free_result']($attach_request);
2260
2261
			// Sort the attachments by size to make things easier below
2262
			if (!empty($loaded_attachments))
2263
			{
2264
				uasort($loaded_attachments, function($a, $b) {
2265
					if ($a['filesize'] == $b['filesize'])
2266
					        return 0;
2267
					return ($a['filesize'] < $b['filesize']) ? -1 : 1;
2268
				});
2269
			}
2270
			else
2271
				$loaded_attachments = null;
2272
		}
2273
		else
2274
			$loaded_attachments = null;
2275
2276
		// Create a GUID for this post using the tag URI scheme
2277
		$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['poster_time']) . ':msg=' . $row['id_msg'];
2278
2279
		if ($xml_format == 'rss' || $xml_format == 'rss2')
2280
		{
2281
			// Only one attachment allowed in RSS.
2282
			if ($loaded_attachments !== null)
2283
			{
2284
				$attachment = array_pop($loaded_attachments);
2285
				$enclosure = array(
2286
					'url' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
2287
					'length' => $attachment['filesize'],
2288
					'type' => $attachment['mime_type'],
2289
				);
2290
			}
2291
			else
2292
				$enclosure = null;
2293
2294
			$data[] = array(
2295
				'tag' => 'item',
2296
				'content' => array(
2297
					array(
2298
						'tag' => 'title',
2299
						'content' => $row['subject'],
2300
						'cdata' => true,
2301
					),
2302
					array(
2303
						'tag' => 'link',
2304
						'content' => $scripturl . '?msg=' . $row['id_msg'],
2305
					),
2306
					array(
2307
						'tag' => 'description',
2308
						'content' => $row['body'],
2309
						'cdata' => true,
2310
					),
2311
					array(
2312
						'tag' => 'author',
2313
						'content' => (allowedTo('moderate_forum') || ($uid == $user_info['id'])) ? $row['poster_email'] : null,
2314
					),
2315
					array(
2316
						'tag' => 'category',
2317
						'content' => $row['bname'],
2318
					),
2319
					array(
2320
						'tag' => 'comments',
2321
						'content' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0',
2322
					),
2323
					array(
2324
						'tag' => 'pubDate',
2325
						'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']),
2326
					),
2327
					array(
2328
						'tag' => 'guid',
2329
						'content' => $guid,
2330
						'attributes' => array(
2331
							'isPermaLink' => 'false',
2332
						),
2333
					),
2334
					array(
2335
						'tag' => 'enclosure',
2336
						'attributes' => $enclosure,
2337
					),
2338
				),
2339
			);
2340
		}
2341
		elseif ($xml_format == 'rdf')
2342
		{
2343
			$data[] = array(
2344
				'tag' => 'item',
2345
				'attributes' => array('rdf:about' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg']),
2346
				'content' => array(
2347
					array(
2348
						'tag' => 'dc:format',
2349
						'content' => 'text/html',
2350
					),
2351
					array(
2352
						'tag' => 'title',
2353
						'content' => $row['subject'],
2354
						'cdata' => true,
2355
					),
2356
					array(
2357
						'tag' => 'link',
2358
						'content' => $scripturl . '?msg=' . $row['id_msg'],
2359
					),
2360
					array(
2361
						'tag' => 'description',
2362
						'content' => $row['body'],
2363
						'cdata' => true,
2364
					),
2365
				),
2366
			);
2367
		}
2368
		elseif ($xml_format == 'atom')
2369
		{
2370
			// Only one attachment allowed
2371
			if (!empty($loaded_attachments))
2372
			{
2373
				$attachment = array_pop($loaded_attachments);
2374
				$enclosure = array(
2375
					'rel' => 'enclosure',
2376
					'href' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']),
2377
					'length' => $attachment['filesize'],
2378
					'type' => $attachment['mime_type'],
2379
				);
2380
			}
2381
			else
2382
				$enclosure = null;
2383
2384
			$data[] = array(
2385
				'tag' => 'entry',
2386
				'content' => array(
2387
					array(
2388
						'tag' => 'title',
2389
						'content' => $row['subject'],
2390
						'cdata' => true,
2391
					),
2392
					array(
2393
						'tag' => 'link',
2394
						'attributes' => array(
2395
							'rel' => 'alternate',
2396
							'type' => 'text/html',
2397
							'href' => $scripturl . '?msg=' . $row['id_msg'],
2398
						),
2399
					),
2400
					array(
2401
						'tag' => 'summary',
2402
						'attributes' => array('type' => 'html'),
2403
						'content' => $row['body'],
2404
						'cdata' => true,
2405
					),
2406
					array(
2407
						'tag' => 'author',
2408
						'content' => array(
2409
							array(
2410
								'tag' => 'name',
2411
								'content' => $row['poster_name'],
2412
								'cdata' => true,
2413
							),
2414
							array(
2415
								'tag' => 'email',
2416
								'content' => (allowedTo('moderate_forum') || ($uid == $user_info['id'])) ? $row['poster_email'] : null,
2417
							),
2418
							array(
2419
								'tag' => 'uri',
2420
								'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $uid : null,
2421
							),
2422
						),
2423
					),
2424
					array(
2425
						'tag' => 'published',
2426
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']),
2427
					),
2428
					array(
2429
						'tag' => 'updated',
2430
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']),
2431
					),
2432
					array(
2433
						'tag' => 'id',
2434
						'content' => $guid,
2435
					),
2436
					array(
2437
						'tag' => 'link',
2438
						'attributes' => $enclosure,
2439
					),
2440
				),
2441
			);
2442
		}
2443
		// A lot of information here.  Should be enough to please the rss-ers.
2444
		else
2445
		{
2446
			loadLanguage('Post');
2447
2448
			$attachments = array();
2449
			if (!empty($loaded_attachments))
2450
			{
2451
				foreach ($loaded_attachments as $attachment)
2452
				{
2453
					$attachments[] = array(
2454
						'tag' => 'attachment',
2455
						'attributes' => array('title' => $txt['attachment']),
2456
						'content' => array(
2457
							array(
2458
								'tag' => 'id',
2459
								'content' => $attachment['id_attach'],
2460
							),
2461
							array(
2462
								'tag' => 'name',
2463
								'attributes' => array('title' => $txt['name']),
2464
								'content' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])),
2465
							),
2466
							array(
2467
								'tag' => 'downloads',
2468
								'attributes' => array('title' => $txt['downloads']),
2469
								'content' => $attachment['downloads'],
2470
							),
2471
							array(
2472
								'tag' => 'size',
2473
								'attributes' => array('title' => $txt['filesize']),
2474
								'content' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'],
2475
							),
2476
							array(
2477
								'tag' => 'byte_size',
2478
								'attributes' => array('title' => $txt['filesize']),
2479
								'content' => $attachment['filesize'],
2480
							),
2481
							array(
2482
								'tag' => 'link',
2483
								'attributes' => array('title' => $txt['url']),
2484
								'content' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'],
2485
							),
2486
						)
2487
					);
2488
				}
2489
			}
2490
			else
2491
				$attachments = null;
2492
2493
			$data[] = array(
2494
				'tag' => 'member-post',
2495
				'attributes' => array('title' => $txt['post']),
2496
				'content' => array(
2497
					array(
2498
						'tag' => 'id',
2499
						'content' => $row['id_msg'],
2500
					),
2501
					array(
2502
						'tag' => 'subject',
2503
						'attributes' => array('title' => $txt['subject']),
2504
						'content' => $row['subject'],
2505
						'cdata' => true,
2506
					),
2507
					array(
2508
						'tag' => 'body',
2509
						'attributes' => array('title' => $txt['message']),
2510
						'content' => $row['body'],
2511
						'cdata' => true,
2512
					),
2513
					array(
2514
						'tag' => 'poster',
2515
						'attributes' => array('title' => $txt['author']),
2516
						'content' => array(
2517
							array(
2518
								'tag' => 'name',
2519
								'attributes' => array('title' => $txt['name']),
2520
								'content' => $row['poster_name'],
2521
								'cdata' => true,
2522
							),
2523
							array(
2524
								'tag' => 'id',
2525
								'content' => $uid,
2526
							),
2527
							array(
2528
								'tag' => 'link',
2529
								'attributes' => array('title' => $txt['url']),
2530
								'content' => $scripturl . '?action=profile;u=' . $uid,
2531
							),
2532
							array(
2533
								'tag' => 'email',
2534
								'attributes' => (allowedTo('moderate_forum') || $uid == $user_info['id']) ? array('title' => $txt['user_email_address']) : null,
2535
								'content' => (allowedTo('moderate_forum') || $uid == $user_info['id']) ? $row['poster_email'] : null,
2536
							),
2537
							array(
2538
								'tag' => 'ip',
2539
								'attributes' => (allowedTo('moderate_forum') || $uid == $user_info['id']) ? array('title' => $txt['ip']) : null,
2540
								'content' => (allowedTo('moderate_forum') || $uid == $user_info['id']) ? $row['poster_ip'] : null,
2541
							),
2542
						),
2543
					),
2544
					array(
2545
						'tag' => 'topic',
2546
						'attributes' => array('title' => $txt['topic']),
2547
						'content' => array(
2548
							array(
2549
								'tag' => 'id',
2550
								'content' => $row['id_topic'],
2551
							),
2552
							array(
2553
								'tag' => 'link',
2554
								'attributes' => array('title' => $txt['url']),
2555
								'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
2556
							),
2557
						),
2558
					),
2559
					array(
2560
						'tag' => 'board',
2561
						'attributes' => array('title' => $txt['board']),
2562
						'content' => array(
2563
							array(
2564
								'tag' => 'id',
2565
								'content' => $row['id_board'],
2566
							),
2567
							array(
2568
								'tag' => 'link',
2569
								'attributes' => array('title' => $txt['url']),
2570
								'content' => $scripturl . '?board=' . $row['id_board'] . '.0',
2571
							),
2572
						),
2573
					),
2574
					array(
2575
						'tag' => 'link',
2576
						'attributes' => array('title' => $txt['url']),
2577
						'content' => $scripturl . '?msg=' . $row['id_msg'],
2578
					),
2579
					array(
2580
						'tag' => 'time',
2581
						'attributes' => array('title' => $txt['date'], 'UTC' => gmstrftime('%F %T', $row['poster_time'])),
2582
						'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['poster_time']))),
2583
					),
2584
					array(
2585
						'tag' => 'modified_time',
2586
						'attributes' => !empty($row['modified_time']) ? array('title' => $txt['modified_time'], 'UTC' => gmstrftime('%F %T', $row['modified_time'])) : null,
2587
						'content' => !empty($row['modified_time']) ? $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['modified_time']))) : null,
2588
					),
2589
					array(
2590
						'tag' => 'modified_by',
2591
						'attributes' => !empty($row['modified_name']) ? array('title' => $txt['modified_by']) : null,
2592
						'content' => !empty($row['modified_name']) ? $row['modified_name'] : null,
2593
						'cdata' => true,
2594
					),
2595
					array(
2596
						'tag' => 'modified_reason',
2597
						'attributes' => !empty($row['modified_reason']) ? array('title' => $txt['reason_for_edit']) : null,
2598
						'content' => !empty($row['modified_reason']) ? $row['modified_reason'] : null,
2599
						'cdata' => true,
2600
					),
2601
					array(
2602
						'tag' => 'likes',
2603
						'attributes' => array('title' => $txt['likes']),
2604
						'content' => $row['likes'],
2605
					),
2606
					array(
2607
						'tag' => 'attachments',
2608
						'attributes' => array('title' => $txt['attachments']),
2609
						'content' => $attachments,
2610
					),
2611
				),
2612
			);
2613
		}
2614
	}
2615
	$smcFunc['db_free_result']($request);
2616
2617
	// If we're in batch mode, make a note of our progress.
2618
	if (!empty($context['batch_mode']))
2619
	{
2620
		$context['batch_prev'] = (empty($context['batch_prev']) ? 0 : $context['batch_prev']) + count($data);
2621
2622
		file_put_contents($cachedir . '/xml-batch-posts-' . $uid, implode(';', array($last, $context['batch_prev'], $context['batch_total'])));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $last does not seem to be defined for all execution paths leading up to this point.
Loading history...
2623
	}
2624
2625
	return $data;
2626
}
2627
2628
/**
2629
 * Get a user's personal messages.
2630
 * Only the user can do this, and no one else -- not even the admin!
2631
 *
2632
 * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'
2633
 * @return array An array of arrays containing data for the feed. Each array has keys corresponding to the appropriate tags for the specified format.
2634
 */
2635
function getXmlPMs($xml_format)
2636
{
2637
	global $scripturl, $modSettings, $board, $txt, $context, $user_info;
2638
	global $query_this_board, $smcFunc, $sourcedir, $cachedir;
2639
2640
	// Personal messages are supposed to be private
2641
	if (isset($_GET['u']) && (int) $_GET['u'] != $user_info['id'])
2642
		return array();
2643
2644
	// For batch mode, we need to know how many there are
2645
	if (!empty($context['batch_mode']) && empty($context['batch_total']))
2646
	{
2647
		$request = $smcFunc['db_query']('', '
2648
			SELECT COUNT(pm.id_pm)
2649
			FROM {db_prefix}personal_messages AS pm
2650
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pm.id_pm = pmr.id_pm)
2651
			WHERE (pm.id_member_from = {int:uid} OR pmr.id_member = {int:uid})',
2652
			array(
2653
				'uid' => $user_info['id'],
2654
			)
2655
		);
2656
		list($context['batch_total']) = $smcFunc['db_fetch_row']($request);
2657
		$smcFunc['db_free_result']($request);
2658
	}
2659
2660
	$request = $smcFunc['db_query']('', '
2661
		SELECT pm.id_pm, pm.msgtime, pm.subject, pm.body, pm.id_member_from, pm.from_name, GROUP_CONCAT(pmr.id_member) AS id_members_to, GROUP_CONCAT(COALESCE(mem.real_name, mem.member_name)) AS to_names
2662
		FROM {db_prefix}personal_messages AS pm
2663
			INNER JOIN {db_prefix}pm_recipients AS pmr ON (pm.id_pm = pmr.id_pm)
2664
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
2665
		WHERE (pm.id_member_from = {int:uid} OR pmr.id_member = {int:uid})
2666
			AND pm.id_pm > {int:start_after}
2667
		GROUP BY pm.id_pm
2668
		ORDER BY pm.id_pm
2669
		LIMIT {int:limit} OFFSET {int:offset}',
2670
		array(
2671
			'limit' => $_GET['limit'],
2672
			'offset' => !empty($context['pms_start']) ? 0 : $_GET['offset'],
2673
			'start_after' => !empty($context['pms_start']) ? $context['pms_start'] : 0,
2674
			'uid' => $user_info['id'],
2675
		)
2676
	);
2677
	$data = array();
2678
	while ($row = $smcFunc['db_fetch_assoc']($request))
2679
	{
2680
		$last = $row['id_pm'];
2681
2682
		// If any control characters slipped in somehow, kill the evil things
2683
		$row = preg_replace($context['utf8'] ? '/\pCc*/u' : '/[\x00-\x1F\x7F]*/', '', $row);
2684
2685
		// If using our own format, we want the raw BBC
2686
		if ($xml_format != 'smf')
2687
			$row['body'] = parse_bbc($row['body']);
2688
2689
		$recipients = array_combine(explode(',', $row['id_members_to']), explode(',', $row['to_names']));
2690
2691
		// Create a GUID for this post using the tag URI scheme
2692
		$guid = 'tag:' . parse_url($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['msgtime']) . ':pm=' . $row['id_pm'];
2693
2694
		if ($xml_format == 'rss' || $xml_format == 'rss2')
2695
		{
2696
			$item = array(
2697
				'tag' => 'item',
2698
				'content' => array(
2699
					array(
2700
						'tag' => 'guid',
2701
						'content' => $guid,
2702
						'attributes' => array(
2703
							'isPermaLink' => 'false',
2704
						),
2705
					),
2706
					array(
2707
						'tag' => 'pubDate',
2708
						'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['msgtime']),
2709
					),
2710
					array(
2711
						'tag' => 'title',
2712
						'content' => $row['subject'],
2713
						'cdata' => true,
2714
					),
2715
					array(
2716
						'tag' => 'description',
2717
						'content' => $row['body'],
2718
						'cdata' => true,
2719
					),
2720
					array(
2721
						'tag' => 'smf:sender',
2722
						// This technically violates the RSS spec, but meh...
2723
						'content' => $row['from_name'],
2724
						'cdata' => true,
2725
					),
2726
				),
2727
			);
2728
2729
			foreach ($recipients as $recipient_id => $recipient_name)
2730
				$item['content'][] = array(
2731
					'tag' => 'smf:recipient',
2732
					'content' => $recipient_name,
2733
					'cdata' => true,
2734
				);
2735
2736
			$data[] = $item;
2737
		}
2738
		elseif ($xml_format == 'rdf')
2739
		{
2740
			$data[] = array(
2741
				'tag' => 'item',
2742
				'attributes' => array('rdf:about' => $scripturl . '?action=pm#msg' . $row['id_pm']),
2743
				'content' => array(
2744
					array(
2745
						'tag' => 'dc:format',
2746
						'content' => 'text/html',
2747
					),
2748
					array(
2749
						'tag' => 'title',
2750
						'content' => $row['subject'],
2751
						'cdata' => true,
2752
					),
2753
					array(
2754
						'tag' => 'link',
2755
						'content' => $scripturl . '?action=pm#msg' . $row['id_pm'],
2756
					),
2757
					array(
2758
						'tag' => 'description',
2759
						'content' => $row['body'],
2760
						'cdata' => true,
2761
					),
2762
				),
2763
			);
2764
		}
2765
		elseif ($xml_format == 'atom')
2766
		{
2767
			$item = array(
2768
				'tag' => 'entry',
2769
				'content' => array(
2770
					array(
2771
						'tag' => 'id',
2772
						'content' => $guid,
2773
					),
2774
					array(
2775
						'tag' => 'updated',
2776
						'content' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['msgtime']),
2777
					),
2778
					array(
2779
						'tag' => 'title',
2780
						'content' => $row['subject'],
2781
						'cdata' => true,
2782
					),
2783
					array(
2784
						'tag' => 'content',
2785
						'attributes' => array('type' => 'html'),
2786
						'content' => $row['body'],
2787
						'cdata' => true,
2788
					),
2789
					array(
2790
						'tag' => 'author',
2791
						'content' => array(
2792
							array(
2793
								'tag' => 'name',
2794
								'content' => $row['from_name'],
2795
								'cdata' => true,
2796
							),
2797
						),
2798
					),
2799
				),
2800
			);
2801
2802
			foreach ($recipients as $recipient_id => $recipient_name)
2803
				$item['content'][] = array(
2804
					'tag' => 'contributor',
2805
					'content' => array(
2806
						array(
2807
							'tag' => 'smf:role',
2808
							'content' => 'recipient',
2809
						),
2810
						array(
2811
							'tag' => 'name',
2812
							'content' => $recipient_name,
2813
							'cdata' => true,
2814
						),
2815
					),
2816
				);
2817
2818
			$data[] = $item;
2819
		}
2820
		else
2821
		{
2822
			loadLanguage('PersonalMessage');
2823
2824
			$item = array(
2825
				'tag' => 'personal-message',
2826
				'attributes' => array('title' => $txt['pm']),
2827
				'content' => array(
2828
					array(
2829
						'tag' => 'id',
2830
						'content' => $row['id_pm'],
2831
					),
2832
					array(
2833
						'tag' => 'sent-date',
2834
						'attributes' => array('title' => $txt['date'], 'UTC' => gmstrftime('%F %T', $row['msg_time'])),
2835
						'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['msgtime']))),
2836
					),
2837
					array(
2838
						'tag' => 'subject',
2839
						'attributes' => array('title' => $txt['subject']),
2840
						'content' => $row['subject'],
2841
						'cdata' => true,
2842
					),
2843
					array(
2844
						'tag' => 'body',
2845
						'attributes' => array('title' => $txt['message']),
2846
						'content' => $row['body'],
2847
						'cdata' => true,
2848
					),
2849
					array(
2850
						'tag' => 'sender',
2851
						'attributes' => array('title' => $txt['author']),
2852
						'content' => array(
2853
							array(
2854
								'tag' => 'name',
2855
								'attributes' => array('title' => $txt['name']),
2856
								'content' => $row['from_name'],
2857
								'cdata' => true,
2858
							),
2859
							array(
2860
								'tag' => 'id',
2861
								'content' => $row['id_member_from'],
2862
							),
2863
							array(
2864
								'tag' => 'link',
2865
								'attributes' => array('title' => $txt['url']),
2866
								'content' => $scripturl . '?action=profile;u=' . $row['id_member_from'],
2867
							),
2868
						),
2869
					),
2870
				),
2871
			);
2872
2873
			foreach ($recipients as $recipient_id => $recipient_name)
2874
				$item['content'][] = array(
2875
					'tag' => 'recipient',
2876
					'attributes' => array('title' => $txt['recipient']),
2877
					'content' => array(
2878
						array(
2879
							'tag' => 'name',
2880
							'attributes' => array('title' => $txt['name']),
2881
							'content' => $recipient_name,
2882
							'cdata' => true,
2883
						),
2884
						array(
2885
							'tag' => 'id',
2886
							'content' => $recipient_id,
2887
						),
2888
						array(
2889
							'tag' => 'link',
2890
							'attributes' => array('title' => $txt['url']),
2891
							'content' => $scripturl . '?action=profile;u=' . $recipient_id,
2892
						),
2893
					),
2894
				);
2895
2896
			$data[] = $item;
2897
		}
2898
	}
2899
	$smcFunc['db_free_result']($request);
2900
2901
	// If we're in batch mode, make a note of our progress.
2902
	if (!empty($context['batch_mode']))
2903
	{
2904
		$context['batch_prev'] = (empty($context['batch_prev']) ? 0 : $context['batch_prev']) + count($data);
2905
2906
		file_put_contents($cachedir . '/xml-batch-pms-' . $user_info['id'], implode(';', array($last, $context['batch_prev'], $context['batch_total'])));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $last does not seem to be defined for all execution paths leading up to this point.
Loading history...
2907
	}
2908
2909
	return $data;
2910
}
2911
2912
?>