News::action_xmlprofile()   F
last analyzed

Complexity

Conditions 27
Paths 7181

Size

Total Lines 141
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 756

Importance

Changes 0
Metric Value
cc 27
eloc 74
nc 7181
nop 1
dl 0
loc 141
rs 0
c 0
b 0
f 0
ccs 0
cts 109
cp 0
crap 756

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
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 Beta 1
14
 *
15
 */
16
17
namespace ElkArte\Controller;
18
19
use BBC\ParserWrapper;
20
use ElkArte\AbstractController;
21
use ElkArte\Cache\Cache;
22
use ElkArte\Exceptions\Exception;
23
use ElkArte\Helper\Util;
24
use ElkArte\Http\Headers;
25
use ElkArte\Languages\Txt;
26
use ElkArte\MembersList;
27
28
/**
29
 * News Controller class
30
 */
31
class News extends AbstractController
32
{
33
	/** @var string Holds news specific version board query for news feeds */
34
	private $_query_this_board;
35
36
	/** @var int Holds the limit for the number of items to get */
37
	private $_limit;
38
39
	/**
40
	 * {@inheritDoc}
41
	 */
42
	public function trackStats($action = '')
43
	{
44
		if ($action === 'action_showfeed')
45
		{
46
			return false;
47
		}
48
49
		return parent::trackStats($action);
50
	}
51
52
	/**
53
	 * Dispatcher. Forwards to the action to execute.
54
	 *
55
	 * @see AbstractController::action_index
56
	 */
57
	public function action_index()
58
	{
59
		// do... something, of your favorite.
60
		// $this->action_xmlnews();
61
	}
62
63
	/**
64
	 * Outputs xml data representing recent information or a profile.
65
	 *
66
	 * What it does:
67
	 *
68
	 * - Can be passed 4 subactions which decide what is output:
69
	 *     * 'recent' for recent posts,
70
	 *     * 'news' for news topics,
71
	 *     * 'members' for recently registered members,
72
	 *     * 'profile' for a member's profile.
73
	 * - To display a member's profile, a user id has to be given. (;u=1) e.g. ?action=.xml;sa=profile;u=1;type=atom
74
	 * - Outputs a feed based on the 'type'
75
	 *       * parameter is 'rss', 'rss2', 'rdf', 'atom'.
76
	 * - Several sub action options are respected
77
	 *     * limit=x - display the "x" most recent posts
78
	 *     * board=y - display only the recent posts from board "y"
79
	 *     * boards=x,y,z - display only the recent posts from the specified boards
80
	 *     * c=x or c=x,y,z - display only the recent posts from boards in the specified category/categories
81
	 *     * action=.xml;sa=recent;board=2;limit=10
82
	 * - Accessed via ?action=.xml
83
	 * - Does not use any templates, sub templates, or template layers.
84
	 * - Use ;debug to view output for debugging feeds
85
	 *
86
	 * @uses Stats language file.
87
	 */
88
	public function action_showfeed(): void
89
	{
90
		global $board, $board_info, $context, $txt, $modSettings, $db_show_debug;
91
92
		// If it's not enabled, die.
93
		if (empty($modSettings['xmlnews_enable']))
94
		{
95
			obExit(false);
96
		}
97
98
		// This is just here to make it easier for the developers :P
99
		$db_show_debug = false;
100
101
		require_once(SUBSDIR . '/News.subs.php');
102
103
		Txt::load('Stats');
104
		$txt['xml_rss_desc'] = replaceBasicActionUrl($txt['xml_rss_desc']);
105
106
		// Default to latest 5.  No more than what is defined in the ACP or 255
107
		$limit = empty($modSettings['xmlnews_limit']) ? 5 : min($modSettings['xmlnews_limit'], 255);
108
		$this->_limit = min($this->_req->getQuery('limit', 'intval', $limit), $limit);
109
110
		// Handle the cases where a board, boards, or category is asked for.
111
		$this->_query_this_board = '1=1';
112
		$context['optimize_msg'] = [
113
			'highest' => 'm.id_msg <= b.id_last_msg',
114
		];
115
116
		// Specifying specific categories only?
117
		if ($this->_req->hasQuery('c') && empty($board))
118
		{
119
			$c_param = $this->_req->getQuery('c', 'trim|strval', '');
120
			$categories = $c_param === '' ? [] : array_map('intval', explode(',', $c_param));
121
122
			if (count($categories) === 1)
123
			{
124
				require_once(SUBSDIR . '/Categories.subs.php');
125
				$feed_title = categoryName($categories[0]);
126
				$feed_title = ' - ' . strip_tags($feed_title);
127
			}
128
129
			require_once(SUBSDIR . '/Boards.subs.php');
130
			$boards_posts = boardsPosts([], $categories);
131
			$total_cat_posts = array_sum($boards_posts);
132
			$boards = array_keys($boards_posts);
133
134
			if (!empty($boards))
135
			{
136
				$this->_query_this_board = 'b.id_board IN (' . implode(', ', $boards) . ')';
137
			}
138
139
			// Try to limit the number of messages we look through.
140
			if ($total_cat_posts > 100 && $total_cat_posts > $modSettings['totalMessages'] / 15)
141
			{
142
				$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 400 - $this->_limit * 5);
143
			}
144
		}
145
		// Maybe they only want to see feeds form some certain boards?
146
		elseif ($this->_req->hasQuery('boards'))
147
		{
148
			require_once(SUBSDIR . '/Boards.subs.php');
149
			$boards_param = $this->_req->getQuery('boards', 'trim|strval', '');
150
			$query_boards = $boards_param === '' ? [] : array_map('intval', explode(',', $boards_param));
151
152
			$boards_data = fetchBoardsInfo(['boards' => $query_boards], ['selects' => 'detailed']);
153
154
			// Either the board specified doesn't exist or you have no access.
155
			$num_boards = count($boards_data);
156
			if ($num_boards === 0)
157
			{
158
				throw new Exception('no_board');
159
			}
160
161
			$total_posts = 0;
162
			$boards = array_keys($boards_data);
163
			foreach ($boards_data as $row)
164
			{
165
				if ($num_boards === 1)
166
				{
167
					$feed_title = ' - ' . strip_tags($row['name']);
168
				}
169
170
				$total_posts += $row['num_posts'];
171
			}
172
173
			$this->_query_this_board = 'b.id_board IN (' . implode(', ', $boards) . ')';
174
175
			// The more boards, the more we're going to look through...
176
			if ($total_posts > 100 && $total_posts > $modSettings['totalMessages'] / 12)
177
			{
178
				$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 500 - $this->_limit * 5);
179
			}
180
		}
181
		// Just a single board
182
		elseif (!empty($board))
183
		{
184
			require_once(SUBSDIR . '/Boards.subs.php');
185
			$boards_data = fetchBoardsInfo(['boards' => $board], ['selects' => 'posts']);
186
187
			$feed_title = ' - ' . strip_tags($board_info['name']);
188
189
			$this->_query_this_board = 'b.id_board = ' . $board;
190
191
			// Try to look through just a few messages, if at all possible.
192
			if ($boards_data[(int) $board]['num_posts'] > 80 && $boards_data[(int) $board]['num_posts'] > $modSettings['totalMessages'] / 10)
193
			{
194
				$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 600 - $this->_limit * 5);
195
			}
196
		}
197
		else
198
		{
199
			$this->_query_this_board = '{query_see_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
200
				AND b.id_board != ' . $modSettings['recycle_board'] : '');
201
			$context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 100 - $this->_limit * 5);
202
		}
203
204
		// If format isn't set, or is wrong, rss2 is default
205
		$xml_format = $this->_req->getQuery('type', 'trim', 'rss2');
206
		if (!in_array($xml_format, ['rss', 'rss2', 'atom', 'rdf']))
207
		{
208
			$xml_format = 'rss2';
209
		}
210
211
		// List all the different types of data they can pull.
212
		$subActions = [
213
			'recent' => ['action_xmlrecent'],
214
			'news' => ['action_xmlnews'],
215
			'members' => ['action_xmlmembers'],
216
			'profile' => ['action_xmlprofile'],
217
		];
218
219
		// Easy adding of sub actions
220
		call_integration_hook('integrate_xmlfeeds', [&$subActions]);
221
222
		$subAction = $this->_req->getQuery('sa', 'strtolower', 'recent');
223
		$subAction = isset($subActions[$subAction]) ? $subAction : 'recent';
224
225
		// We only want some information, not all of it.
226
		$cache_action = $this->_req->getQuery('action', 'trim|strval', '');
227
		$cachekey = [$xml_format, $cache_action, $this->_limit, $subAction];
228
		foreach (['board', 'boards', 'c'] as $var)
229
		{
230
			$val = $this->_req->getQuery($var, 'trim|strval');
231
			if ($val !== null)
232
			{
233
				$cachekey[] = $val;
234
			}
235
		}
236
237
		$cachekey = md5(serialize($cachekey) . (empty($this->_query_this_board) ? '' : $this->_query_this_board));
238
		$cache_t = microtime(true);
239
		$cache = Cache::instance();
240
241
		// Get the associative array representing the xml.
242
		if ($this->user->is_guest === false || $cache->levelHigherThan(2))
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
243
		{
244
			$xml = $cache->get('xmlfeed-' . $xml_format . ':' . ($this->user->is_guest ? '' : $this->user->id . '-') . $cachekey, 240);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
245
		}
246
247
		if (empty($xml))
248
		{
249
			$xml = $this->{$subActions[$subAction][0]}($xml_format);
250
251
			if ($cache->isEnabled() && (($this->user->is_guest && $cache->levelHigherThan(2)) || ($this->user->is_guest === false && (microtime(true) - $cache_t > 0.2))))
252
			{
253
				$cache->put('xmlfeed-' . $xml_format . ':' . ($this->user->is_guest ? '' : $this->user->id . '-') . $cachekey, $xml, 240);
254
			}
255
		}
256
257
		$context['feed_title'] = encode_special(strip_tags(un_htmlspecialchars($context['forum_name']) . ($feed_title ?? '')));
258
259
		// We send a feed with recent posts, and alerts for PMs for logged-in users
260
		$context['recent_posts_data'] = $xml;
261
		$context['xml_format'] = $xml_format;
262
263
		obStart(!empty($modSettings['enableCompressedOutput']));
264
265
		// This is an xml file....
266
		$headers = Headers::instance();
267
		if ($this->_req->hasQuery('debug'))
268
		{
269
			$headers->contentType('text/xml', 'UTF-8');
270
		}
271
		elseif ($xml_format === 'rss' || $xml_format === 'rss2')
272
		{
273
			$headers->contentType('application/rss+xml', 'UTF-8');
274
		}
275
		elseif ($xml_format === 'atom')
276
		{
277
			$headers->contentType('application/atom+xml', 'UTF-8');
278
		}
279
		elseif ($xml_format === 'rdf')
280
		{
281
			$headers->contentType('application/rdf+xml', 'UTF-8');
282
		}
283
284
		// Set our own 30min cache control so auto-readers know how often to check in
285
		$context['no_last_modified'] = true;
286
		$headers
287
			->header('Cache-Control', 'max-age=' . (3600 * .5) . ', private')
288
			->header('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT');
289
290
		theme()->getTemplates()->load('Xml');
291
		theme()->getLayers()->removeAll();
292
293
		// Are we outputting a rss feed or one with more information?
294
		if ($xml_format === 'rss' || $xml_format === 'rss2')
295
		{
296
			$context['sub_template'] = 'feedrss';
297
		}
298
		elseif ($xml_format === 'atom')
299
		{
300
			$url_parts = [];
301
			foreach (['board', 'boards', 'c'] as $var)
302
			{
303
				$val = $this->_req->getQuery($var, 'trim|strval');
304
				if ($val !== null)
305
				{
306
					$url_parts[] = $var . '=' . $val;
307
				}
308
			}
309
310
			$context['url_parts'] = empty($url_parts) ? '' : implode(';', $url_parts);
311
			$context['sub_template'] = 'feedatom';
312
		}
313
		// rdf by default
314
		else
315
		{
316
			$context['sub_template'] = 'rdf';
317
		}
318
	}
319
320
	/**
321
	 * Retrieve the list of members from database.
322
	 * The array will be generated to match the format.
323
	 *
324
	 * @param string $xml_format
325
	 * @return array
326
	 */
327
	public function action_xmlmembers($xml_format): array
328
	{
329
		global $scripturl;
330
331
		// Not allowed, then you get nothing
332
		if (!allowedTo('view_mlist'))
333
		{
334
			return [];
335
		}
336
337
		// Find the most recent members.
338
		require_once(SUBSDIR . '/Members.subs.php');
339
		$members = recentMembers((int) $this->_limit);
340
341
		// No data yet
342
		$data = [];
343
344
		require_once(SUBSDIR . '/News.subs.php');
345
		foreach ($members as $member)
346
		{
347
			// Make the data look rss-ish.
348
			if ($xml_format === 'rss' || $xml_format === 'rss2')
349
			{
350
				$data[] = [
351
					'title' => cdata_parse($member['real_name']),
352
					'link' => $scripturl . '?action=profile;u=' . $member['id_member'],
353
					'comments' => $scripturl . '?action=pm;sa=send;u=' . $member['id_member'],
354
					'pubDate' => gmdate('D, d M Y H:i:s \G\M\T', $member['date_registered']),
355
					'guid' => $scripturl . '?action=profile;u=' . $member['id_member'],
356
				];
357
			}
358
			elseif ($xml_format === 'rdf')
359
			{
360
				$data[] = [
361
					'title' => cdata_parse($member['real_name']),
362
					'link' => $scripturl . '?action=profile;u=' . $member['id_member'],
363
				];
364
			}
365
			elseif ($xml_format === 'atom')
366
			{
367
				$data[] = [
368
					'title' => cdata_parse($member['real_name']),
369
					'link' => $scripturl . '?action=profile;u=' . $member['id_member'],
370
					'published' => Util::gmstrftime('%Y-%m-%dT%H:%M:%SZ', $member['date_registered']),
371
					'updated' => Util::gmstrftime('%Y-%m-%dT%H:%M:%SZ', $member['last_login']),
372
					'id' => $scripturl . '?action=profile;u=' . $member['id_member'],
373
				];
374
			}
375
			// More logical format for the data, but harder to apply.
376
			else
377
			{
378
				$data[] = [
379
					'name' => cdata_parse($member['real_name']),
380
					'time' => htmlspecialchars(strip_tags(standardTime($member['date_registered'])), ENT_COMPAT, 'UTF-8'),
381
					'id' => $member['id_member'],
382
					'link' => $scripturl . '?action=profile;u=' . $member['id_member']
383
				];
384
			}
385
		}
386
387
		return $data;
388
	}
389
390
	/**
391
	 * Get the latest topics information from a specific board, to display later.
392
	 * The returned array will be generated to match the xmf_format.
393
	 *
394
	 * @param string $xml_format one of rss, rss2, rdf, atom
395
	 * @return array array of topics
396
	 */
397
	public function action_xmlnews($xml_format): array
398
	{
399
		global $scripturl, $modSettings, $board;
400
401
		// Get the latest topics from a board
402
		require_once(SUBSDIR . '/News.subs.php');
403
		$results = getXMLNews($this->_query_this_board, $board, $this->_limit);
404
405
		// Prepare it for the feed in the format chosen (rss, atom, etc)
406
		$data = [];
407
		$bbc_parser = ParserWrapper::instance();
408
409
		foreach ($results as $row)
410
		{
411
			// Limit the length of the message, if the option is set.
412
			if (!empty($modSettings['xmlnews_maxlen']) && Util::strlen(str_replace('<br />', "\n", $row['body'])) > $modSettings['xmlnews_maxlen'])
413
			{
414
				$row['body'] = strtr(Util::shorten_text(str_replace('<br />', "\n", $row['body']), $modSettings['xmlnews_maxlen'], true), ["\n" => '<br />']);
415
			}
416
417
			$row['body'] = $bbc_parser->parseMessage($row['body'], $row['smileys_enabled']);
418
419
			// Dirty mouth?
420
			$row['body'] = censor($row['body']);
421
			$row['subject'] = censor($row['subject']);
422
423
			// Being news, this actually makes sense in rss format.
424
			if ($xml_format === 'rss' || $xml_format === 'rss2')
425
			{
426
				$data[] = [
427
					'title' => cdata_parse($row['subject']),
428
					'link' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
429
					'description' => cdata_parse(str_replace('&', '&#x26;', un_htmlspecialchars($row['body']))),
430
					'author' => showEmailAddress($row['id_member']) ? $row['poster_email'] . ' (' . un_htmlspecialchars($row['poster_name']) . ')' : '<![CDATA[[email protected] (' . un_htmlspecialchars($row['poster_name']) . ')]]>',
431
					'comments' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0',
432
					'category' => '<![CDATA[' . $row['bname'] . ']]>',
433
					'pubDate' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']),
434
					'guid' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
435
				];
436
437
				// Add the poster name on if we are rss2
438
				if ($xml_format === 'rss2')
439
				{
440
					$data[count($data) - 1]['dc:creator'] = $row['poster_name'];
441
					unset($data[count($data) - 1]['author']);
442
				}
443
			}
444
			// RDF Format anyone
445
			elseif ($xml_format === 'rdf')
446
			{
447
				$data[] = [
448
					'title' => cdata_parse($row['subject']),
449
					'link' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
450
					'description' => cdata_parse($row['body']),
451
				];
452
			}
453
			// Atom feed
454
			elseif ($xml_format === 'atom')
455
			{
456
				$data[] = [
457
					'title' => cdata_parse($row['subject']),
458
					'link' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
459
					'summary' => cdata_parse($row['body']),
460
					'category' => $row['bname'],
461
					'author' => [
462
						'name' => $row['poster_name'],
463
						'email' => showEmailAddress($row['id_member']) ? $row['poster_email'] : null,
464
						'uri' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member'],
465
					],
466
					'published' => Util::gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']),
467
					'modified' => Util::gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']),
468
					'id' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
469
				];
470
			}
471
			// The biggest difference here is more information.
472
			else
473
			{
474
				$data[] = [
475
					'time' => htmlspecialchars(strip_tags(standardTime($row['poster_time'])), ENT_COMPAT, 'UTF-8'),
476
					'id' => $row['id_topic'],
477
					'subject' => cdata_parse($row['subject']),
478
					'body' => cdata_parse($row['body']),
479
					'poster' => [
480
						'name' => cdata_parse($row['poster_name']),
481
						'id' => $row['id_member'],
482
						'link' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member'],
483
					],
484
					'topic' => $row['id_topic'],
485
					'board' => [
486
						'name' => cdata_parse($row['bname']),
487
						'id' => $row['id_board'],
488
						'link' => $scripturl . '?board=' . $row['id_board'] . '.0',
489
					],
490
					'link' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
491
				];
492
			}
493
		}
494
495
		return $data;
496
	}
497
498
	/**
499
	 * Get the recent topics to display.
500
	 * The returned array will be generated to match the xml_format.
501
	 *
502
	 * @param string $xml_format one of rss, rss2, rdf, atom
503
	 * @return array of recent posts
504
	 */
505
	public function action_xmlrecent($xml_format): array
506
	{
507
		global $scripturl, $modSettings, $board;
508
509
		// Get the latest news
510
		require_once(SUBSDIR . '/News.subs.php');
511
		$results = getXMLRecent($this->_query_this_board, $board, $this->_limit);
512
513
		// Loop on the results and prepare them in the format requested
514
		$data = [];
515
		$bbc_parser = ParserWrapper::instance();
516
517
		foreach ($results as $row)
518
		{
519
			// Limit the length of the message, if the option is set.
520
			if (!empty($modSettings['xmlnews_maxlen']) && Util::strlen(str_replace('<br />', "\n", $row['body'])) > $modSettings['xmlnews_maxlen'])
521
			{
522
				$row['body'] = strtr(Util::shorten_text(str_replace('<br />', "\n", $row['body']), $modSettings['xmlnews_maxlen'], true), ["\n" => '<br />']);
523
			}
524
525
			$row['body'] = $bbc_parser->parseMessage($row['body'], $row['smileys_enabled']);
526
527
			// You can't say that
528
			$row['body'] = censor($row['body']);
529
			$row['subject'] = censor($row['subject']);
530
531
			// Doesn't work as well as news, but it kinda does..
532
			if ($xml_format === 'rss' || $xml_format === 'rss2')
533
			{
534
				$data[] = [
535
					'title' => $row['subject'],
536
					'link' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
537
					'description' => cdata_parse(str_replace('&', '&#x26;', un_htmlspecialchars($row['body']))),
538
					'author' => showEmailAddress($row['id_member']) ? $row['poster_email'] . ' (' . un_htmlspecialchars($row['poster_name']) . ')' : '<![CDATA[[email protected] (' . un_htmlspecialchars($row['poster_name']) . ')]]>',
539
					'category' => cdata_parse($row['bname']),
540
					'comments' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0',
541
					'pubDate' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']),
542
					'guid' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg']
543
				];
544
545
				// Add the poster name on if we are rss2
546
				if ($xml_format === 'rss2')
547
				{
548
					$data[count($data) - 1]['dc:creator'] = $row['poster_name'];
549
					unset($data[count($data) - 1]['author']);
550
				}
551
			}
552
			elseif ($xml_format === 'rdf')
553
			{
554
				$data[] = [
555
					'title' => $row['subject'],
556
					'link' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
557
					'description' => cdata_parse($row['body']),
558
				];
559
			}
560
			elseif ($xml_format === 'atom')
561
			{
562
				$data[] = [
563
					'title' => $row['subject'],
564
					'link' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
565
					'summary' => cdata_parse($row['body']),
566
					'category' => $row['bname'],
567
					'author' => [
568
						'name' => $row['poster_name'],
569
						'email' => showEmailAddress($row['id_member']) ? $row['poster_email'] : null,
570
						'uri' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member']
571
					],
572
					'published' => Util::gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']),
573
					'updated' => Util::gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']),
574
					'id' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
575
				];
576
			}
577
			// A lot of information here.  Should be enough to please the rss-ers.
578
			else
579
			{
580
				$data[] = [
581
					'time' => htmlspecialchars(strip_tags(standardTime($row['poster_time'])), ENT_COMPAT, 'UTF-8'),
582
					'id' => $row['id_msg'],
583
					'subject' => cdata_parse($row['subject']),
584
					'body' => cdata_parse($row['body']),
585
					'starter' => [
586
						'name' => cdata_parse($row['first_poster_name']),
587
						'id' => $row['id_first_member'],
588
						'link' => empty($row['id_first_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_first_member']
589
					],
590
					'poster' => [
591
						'name' => cdata_parse($row['poster_name']),
592
						'id' => $row['id_member'],
593
						'link' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member']
594
					],
595
					'topic' => [
596
						'subject' => cdata_parse($row['first_subject']),
597
						'id' => $row['id_topic'],
598
						'link' => $scripturl . '?topic=' . $row['id_topic'] . '.new#new'
599
					],
600
					'board' => [
601
						'name' => cdata_parse($row['bname']),
602
						'id' => $row['id_board'],
603
						'link' => $scripturl . '?board=' . $row['id_board'] . '.0'
604
					],
605
					'link' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg']
606
				];
607
			}
608
		}
609
610
		return $data;
611
	}
612
613
	/**
614
	 * Get the profile information for member into an array,
615
	 * which will be generated to match the xml_format.
616
	 *
617
	 * @param string $xml_format one of rss, rss2, rdf, atom
618
	 * @return array array of profile data.
619
	 */
620
	public function action_xmlprofile($xml_format): array
621
	{
622
		global $scripturl, $modSettings, $language;
623
624
		// You must input a valid user....
625
		if (empty($this->_req->query->u))
626
		{
627
			return [];
628
		}
629
630
		// Make sure the id is a number and not "I like trying to hack the database".
631
		$uid = (int) $this->_req->query->u;
632
633
		// You must input a valid user....
634
		if (MembersList::load($uid) === false)
635
		{
636
			return [];
637
		}
638
639
		// Load the member's contextual information!
640
		if (!allowedTo('profile_view_any'))
641
		{
642
			return [];
643
		}
644
645
		$member = MembersList::get($uid);
646
		$member->loadContext();
647
648
		// No feed data yet
649
		$data = [];
650
651
		require_once(SUBSDIR . '/News.subs.php');
652
		if ($xml_format === 'rss' || $xml_format === 'rss2')
653
		{
654
			$data = [[
655
				'title' => cdata_parse($member['name']),
656
				'link' => $scripturl . '?action=profile;u=' . $member['id'],
657
				'description' => cdata_parse($member['group'] ?? $member['post_group']),
658
				'comments' => $scripturl . '?action=pm;sa=send;u=' . $member['id'],
659
				'pubDate' => gmdate('D, d M Y H:i:s \G\M\T', $member->date_registered),
0 ignored issues
show
Bug Best Practice introduced by
The property date_registered does not exist on anonymous//sources/ElkArte/MembersList.php$0. Since you implemented __get, consider adding a @property annotation.
Loading history...
660
				'guid' => $scripturl . '?action=profile;u=' . $member['id'],
661
			]];
662
		}
663
		elseif ($xml_format === 'rdf')
664
		{
665
			$data = [[
666
				'title' => cdata_parse($member['name']),
667
				'link' => $scripturl . '?action=profile;u=' . $member['id'],
668
				'description' => cdata_parse($member['group'] ?? $member['post_group']),
669
			]];
670
		}
671
		elseif ($xml_format === 'atom')
672
		{
673
			$data[] = [
674
				'title' => cdata_parse($member['name']),
675
				'link' => $scripturl . '?action=profile;u=' . $member['id'],
676
				'summary' => cdata_parse($member['group'] ?? $member['post_group']),
677
				'author' => [
678
					'name' => $member['real_name'],
679
					'email' => showEmailAddress($member['id']) ? $member['email'] : null,
680
					'uri' => empty($member['website']) ? '' : $member['website']['url']
681
				],
682
				'published' => Util::gmstrftime('%Y-%m-%dT%H:%M:%SZ', $member->date_registered),
683
				'updated' => Util::gmstrftime('%Y-%m-%dT%H:%M:%SZ', $member->last_login),
0 ignored issues
show
Bug Best Practice introduced by
The property last_login does not exist on anonymous//sources/ElkArte/MembersList.php$0. Since you implemented __get, consider adding a @property annotation.
Loading history...
684
				'id' => $scripturl . '?action=profile;u=' . $member['id'],
685
				'logo' => empty($member['avatar']) ? '' : $member['avatar']['url'],
686
			];
687
		}
688
		else
689
		{
690
			$data = [
691
				'username' => $this->user->is_admin || $this->user->id == $member['id'] ? cdata_parse($member['username']) : '',
0 ignored issues
show
Bug Best Practice introduced by
The property is_admin does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
692
				'name' => cdata_parse($member['name']),
693
				'link' => $scripturl . '?action=profile;u=' . $member['id'],
694
				'posts' => $member['posts'],
695
				'post-group' => cdata_parse($member['post_group']),
696
				'language' => cdata_parse(empty($member['language']) ? Util::ucwords(strtr($language, ['_' => ' ', '-utf8' => ''])) : $member['language']),
697
				'last-login' => gmdate('D, d M Y H:i:s \G\M\T', $member->last_login),
698
				'registered' => gmdate('D, d M Y H:i:s \G\M\T', $member->date_registered)
699
			];
700
701
			// Everything below here might not be set, and thus maybe shouldn't be displayed.
702
			if ($member['avatar']['name'] !== '')
703
			{
704
				$data['avatar'] = $member['avatar']['url'];
705
			}
706
707
			// If they are online, show an empty tag... no reason to put anything inside it.
708
			if ($member['online']['is_online'])
709
			{
710
				$data['online'] = '';
711
			}
712
713
			if ($member['signature'] !== '')
714
			{
715
				$data['signature'] = cdata_parse($member['signature']);
716
			}
717
718
			if ($member['title'] !== '')
719
			{
720
				$data['title'] = cdata_parse($member['title']);
721
			}
722
723
			if ($member['website']['title'] !== '')
724
			{
725
				$data['website'] = [
726
					'title' => cdata_parse($member['website']['title']),
727
					'link' => $member['website']['url']
728
				];
729
			}
730
731
			if ($member['group'] !== '')
732
			{
733
				$data['position'] = cdata_parse($member['group']);
734
			}
735
736
			if (!empty($modSettings['karmaMode']))
737
			{
738
				$data['karma'] = [
739
					'good' => $member['karma']['good'],
740
					'bad' => $member['karma']['bad']
741
				];
742
			}
743
744
			if ($member['show_email'])
745
			{
746
				$data['email'] = $member['email'];
747
			}
748
749
			if (!empty($member['birth_date']) && !str_starts_with($member['birth_date'], '0000'))
750
			{
751
				[$birth_year, $birth_month, $birth_day] = sscanf($member['birth_date'], '%d-%d-%d');
752
				$datearray = getdate(forum_time());
753
				$data['age'] = $datearray['year'] - $birth_year - (($datearray['mon'] > $birth_month || ($datearray['mon'] === $birth_month && $datearray['mday'] >= $birth_day)) ? 0 : 1);
754
			}
755
		}
756
757
		// Save some memory.
758
		MembersList::unset($uid);
759
760
		return $data;
761
	}
762
}
763