'getXmlRecent', 'news' => 'getXmlNews', 'members' => 'getXmlMembers', 'profile' => 'getXmlProfile', 'posts' => 'getXmlPosts', 'personal_messages' => 'getXmlPMs', ); // Easy adding of sub actions call_integration_hook('integrate_xmlfeeds', array(&$subActions)); $subaction = empty($_GET['sa']) || !isset($subActions[$_GET['sa']]) ? 'recent' : $_GET['sa']; // Make sure the id is a number and not "I like trying to hack the database". $context['xmlnews_uid'] = isset($_GET['u']) ? (int) $_GET['u'] : $user_info['id']; // Default to latest 5. No more than 255, please. $context['xmlnews_limit'] = empty($_GET['limit']) || (int) $_GET['limit'] < 1 ? 5 : min((int) $_GET['limit'], 255); // Users can always export their own profile data if (in_array($subaction, array('profile', 'posts', 'personal_messages')) && !$user_info['is_guest'] && $context['xmlnews_uid'] == $user_info['id']) $modSettings['xmlnews_enable'] = true; // If it's not enabled, die. if (empty($modSettings['xmlnews_enable'])) obExit(false); loadLanguage('Stats'); // Show in rss or proprietary format? $xml_format = $_GET['type'] = isset($_GET['type']) && in_array($_GET['type'], array('smf', 'rss', 'rss2', 'atom', 'rdf')) ? $_GET['type'] : 'rss2'; // Some general metadata for this feed. We'll change some of these values below. $feed_meta = array( 'title' => '', 'desc' => sprintf($txt['xml_rss_desc'], $context['forum_name']), 'author' => $context['forum_name'], 'source' => $scripturl, 'rights' => '© ' . date('Y') . ' ' . $context['forum_name'], 'icon' => !empty($settings['og_image']) ? $settings['og_image'] : $boardurl . '/favicon.ico', 'language' => !empty($txt['lang_locale']) ? str_replace("_", "-", substr($txt['lang_locale'], 0, strcspn($txt['lang_locale'], "."))) : 'en', 'self' => $scripturl, ); foreach (array('action', 'sa', 'type', 'board', 'boards', 'c', 'u', 'limit', 'offset') as $var) if (isset($_GET[$var])) $feed_meta['self'] .= ($feed_meta['self'] === $scripturl ? '?' : ';' ) . $var . '=' . $_GET[$var]; // Handle the cases where a board, boards, or category is asked for. $query_this_board = 1; $context['optimize_msg'] = array( 'highest' => 'm.id_msg <= b.id_last_msg', ); if (!empty($_GET['c']) && empty($board)) { $_GET['c'] = explode(',', $_GET['c']); foreach ($_GET['c'] as $i => $c) $_GET['c'][$i] = (int) $c; if (count($_GET['c']) == 1) { $request = $smcFunc['db_query']('', ' SELECT name FROM {db_prefix}categories WHERE id_cat = {int:current_category}', array( 'current_category' => (int) $_GET['c'][0], ) ); list ($feed_meta['title']) = $smcFunc['db_fetch_row']($request); $smcFunc['db_free_result']($request); } $request = $smcFunc['db_query']('', ' SELECT b.id_board, b.num_posts FROM {db_prefix}boards AS b WHERE b.id_cat IN ({array_int:current_category_list}) AND {query_see_board}', array( 'current_category_list' => $_GET['c'], ) ); $total_cat_posts = 0; $boards = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { $boards[] = $row['id_board']; $total_cat_posts += $row['num_posts']; } $smcFunc['db_free_result']($request); if (!empty($boards)) $query_this_board = 'b.id_board IN (' . implode(', ', $boards) . ')'; // Try to limit the number of messages we look through. if ($total_cat_posts > 100 && $total_cat_posts > $modSettings['totalMessages'] / 15) $context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 400 - $context['xmlnews_limit'] * 5); } elseif (!empty($_GET['boards'])) { $_GET['boards'] = explode(',', $_GET['boards']); foreach ($_GET['boards'] as $i => $b) $_GET['boards'][$i] = (int) $b; $request = $smcFunc['db_query']('', ' SELECT b.id_board, b.num_posts, b.name FROM {db_prefix}boards AS b WHERE b.id_board IN ({array_int:board_list}) AND {query_see_board} LIMIT {int:limit}', array( 'board_list' => $_GET['boards'], 'limit' => count($_GET['boards']), ) ); // Either the board specified doesn't exist or you have no access. $num_boards = $smcFunc['db_num_rows']($request); if ($num_boards == 0) fatal_lang_error('no_board', false); $total_posts = 0; $boards = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { if ($num_boards == 1) $feed_meta['title'] = $row['name']; $boards[] = $row['id_board']; $total_posts += $row['num_posts']; } $smcFunc['db_free_result']($request); if (!empty($boards)) $query_this_board = 'b.id_board IN (' . implode(', ', $boards) . ')'; // The more boards, the more we're going to look through... if ($total_posts > 100 && $total_posts > $modSettings['totalMessages'] / 12) $context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 500 - $context['xmlnews_limit'] * 5); } elseif (!empty($board)) { $request = $smcFunc['db_query']('', ' SELECT num_posts FROM {db_prefix}boards AS b WHERE id_board = {int:current_board} AND {query_see_board} LIMIT 1', array( 'current_board' => $board, ) ); if ($smcFunc['db_num_rows']($request) == 0) fatal_lang_error('no_board', false); list ($total_posts) = $smcFunc['db_fetch_row']($request); $smcFunc['db_free_result']($request); $feed_meta['title'] = $board_info['name']; $feed_meta['source'] .= '?board=' . $board . '.0'; $query_this_board = 'b.id_board = ' . $board; // Try to look through just a few messages, if at all possible. if ($total_posts > 80 && $total_posts > $modSettings['totalMessages'] / 10) $context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 600 - $context['xmlnews_limit'] * 5); } else { $query_this_board = '{query_see_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' AND b.id_board != ' . $modSettings['recycle_board'] : ''); $context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 100 - $context['xmlnews_limit'] * 5); } $feed_meta['title'] .= (!empty($feed_meta['title']) ? ' - ' : '') . $context['forum_name']; // Sanitize feed metadata values foreach ($feed_meta as $mkey => $mvalue) $feed_meta[$mkey] = strip_tags($mvalue); // We only want some information, not all of it. $cachekey = array($xml_format, $_GET['action'], $context['xmlnews_limit'], $subaction); foreach (array('board', 'boards', 'c') as $var) if (isset($_GET[$var])) $cachekey[] = $var . '=' . implode(',', (array) $_GET[$var]); $cachekey = md5($smcFunc['json_encode']($cachekey) . (!empty($query_this_board) ? $query_this_board : '')); $cache_t = microtime(true); // Get the associative array representing the xml. if (!empty($cache_enable) && (!$user_info['is_guest'] || $cache_enable >= 3)) { $xml_data = cache_get_data('xmlfeed-' . $xml_format . ':' . ($user_info['is_guest'] ? '' : $user_info['id'] . '-') . $cachekey, 240); } if (empty($xml_data)) { $call = call_helper($subActions[$subaction], true); if (!empty($call)) $xml_data = call_user_func($call, $xml_format); if (!empty($cache_enable) && (($user_info['is_guest'] && $cache_enable >= 3) || (!$user_info['is_guest'] && (microtime(true) - $cache_t > 0.2)))) cache_put_data('xmlfeed-' . $xml_format . ':' . ($user_info['is_guest'] ? '' : $user_info['id'] . '-') . $cachekey, $xml_data, 240); } buildXmlFeed($xml_format, $xml_data, $feed_meta, $subaction); // Descriptive filenames = good $filename[] = $feed_meta['title']; $filename[] = $subaction; if (in_array($subaction, array('profile', 'posts', 'personal_messages'))) $filename[] = 'u=' . $context['xmlnews_uid']; if (!empty($boards)) $filename[] = 'boards=' . implode(',', $boards); elseif (!empty($board)) $filename[] = 'board=' . $board; $filename[] = $xml_format; $filename = preg_replace($context['utf8'] ? '/[^\p{L}\p{M}\p{N}\-]+/u' : '/[\s_,.\/\\;:\'<>?|\[\]{}~!@#$%^&*()=+`]+/', '_', str_replace('"', '', un_htmlspecialchars(strip_tags(implode('-', $filename))))); // This is an xml file.... ob_end_clean(); if (!empty($modSettings['enableCompressedOutput'])) @ob_start('ob_gzhandler'); else ob_start(); if ($xml_format == 'smf' || isset($_GET['debug'])) header('content-type: text/xml; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set'])); elseif ($xml_format == 'rss' || $xml_format == 'rss2') header('content-type: application/rss+xml; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set'])); elseif ($xml_format == 'atom') header('content-type: application/atom+xml; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set'])); elseif ($xml_format == 'rdf') header('content-type: ' . (isBrowser('ie') ? 'text/xml' : 'application/rdf+xml') . '; charset=' . (empty($context['character_set']) ? 'UTF-8' : $context['character_set'])); header('content-disposition: ' . (isset($_GET['download']) ? 'attachment' : 'inline') . '; filename="' . $filename . '.xml"'); echo implode('', $context['feed']); obExit(false); } function buildXmlFeed($xml_format, $xml_data, $feed_meta, $subaction) { global $context, $txt, $scripturl; /* Important: Do NOT change this to an HTTPS address! * * Why? Because XML namespace names must be both unique and invariant * once defined. They look like URLs merely because that's a convenient * way to ensure uniqueness, but they are not used as URLs. They are * used as case-sensitive identifier strings. If the string changes in * any way, XML processing software (including PHP's own XML functions) * will interpret the two versions of the string as entirely different * namespaces, which could cause it to mangle the XML horrifically * during processing. */ $smf_ns = 'htt'.'p:/'.'/ww'.'w.simple'.'machines.o'.'rg/xml/' . $subaction; // Allow mods to add extra namespaces and tags to the feed/channel $namespaces = array( 'rss' => array(), 'rss2' => array('atom' => 'htt'.'p:/'.'/ww'.'w.w3.o'.'rg/2005/Atom'), 'atom' => array('' => 'htt'.'p:/'.'/ww'.'w.w3.o'.'rg/2005/Atom'), 'rdf' => array( '' => 'htt'.'p:/'.'/purl.o'.'rg/rss/1.0/', 'rdf' => 'htt'.'p:/'.'/ww'.'w.w3.o'.'rg/1999/02/22-rdf-syntax-ns#', 'dc' => 'htt'.'p:/'.'/purl.o'.'rg/dc/elements/1.1/', ), 'smf' => array( 'smf' => $smf_ns, ), ); if (in_array($subaction, array('profile', 'posts', 'personal_messages'))) { $namespaces['rss']['smf'] = $smf_ns; $namespaces['rss2']['smf'] = $smf_ns; $namespaces['atom']['smf'] = $smf_ns; } $extraFeedTags = array( 'rss' => array(), 'rss2' => array(), 'atom' => array(), 'rdf' => array(), 'smf' => array(), ); // Allow mods to specify any keys that need special handling $forceCdataKeys = array(); $nsKeys = array(); // Maybe someone needs to insert a DOCTYPE declaration? $doctype = ''; // Remember this, just in case... $orig_feed_meta = $feed_meta; // If mods want to do somthing with this feed, let them do that now. // Provide the feed's data, metadata, namespaces, extra feed-level tags, keys that need special handling, the feed format, and the requested subaction call_integration_hook('integrate_xml_data', array(&$xml_data, &$feed_meta, &$namespaces, &$extraFeedTags, &$forceCdataKeys, &$nsKeys, $xml_format, $subaction, &$doctype)); // These can't be empty foreach (array('title', 'desc', 'source', 'self') as $mkey) $feed_meta[$mkey] = !empty($feed_meta[$mkey]) ? $feed_meta[$mkey] : $orig_feed_meta[$mkey]; // Sanitize feed metadata values foreach ($feed_meta as $mkey => $mvalue) $feed_meta[$mkey] = cdata_parse(fix_possible_url($mvalue)); $ns_string = ''; if (!empty($namespaces[$xml_format])) { foreach ($namespaces[$xml_format] as $nsprefix => $nsurl) $ns_string .= ' xmlns' . ($nsprefix !== '' ? ':' : '') . $nsprefix . '="' . $nsurl . '"'; } $i = in_array($xml_format, array('atom', 'smf')) ? 1 : 2; $extraFeedTags_string = ''; if (!empty($extraFeedTags[$xml_format])) { $indent = str_repeat("\t", $i); foreach ($extraFeedTags[$xml_format] as $extraTag) $extraFeedTags_string .= "\n" . $indent . $extraTag; } $context['feed'] = array(); // First, output the xml header. $context['feed']['header'] = '' . ($doctype !== '' ? "\n" . trim($doctype) : ''); // Are we outputting an rss feed or one with more information? if ($xml_format == 'rss' || $xml_format == 'rss2') { // Start with an RSS 2.0 header. $context['feed']['header'] .= ' ' . $feed_meta['title'] . ' ' . $feed_meta['source'] . ' ' . $feed_meta['desc'] . ''; if (!empty($feed_meta['icon'])) $context['feed']['header'] .= ' ' . $feed_meta['icon'] . ' ' . $feed_meta['title'] . ' ' . $feed_meta['source'] . ' '; if (!empty($feed_meta['rights'])) $context['feed']['header'] .= ' ' . $feed_meta['rights'] . ''; if (!empty($feed_meta['language'])) $context['feed']['header'] .= ' ' . $feed_meta['language'] . ''; // RSS2 calls for this. if ($xml_format == 'rss2') $context['feed']['header'] .= ' '; $context['feed']['header'] .= $extraFeedTags_string; // Write the data as an XML string to $context['feed']['items'] dumpTags($xml_data, $i, $xml_format, $forceCdataKeys, $nsKeys); // Output the footer of the xml. $context['feed']['footer'] = ' '; } elseif ($xml_format == 'atom') { $context['feed']['header'] .= ' ' . $feed_meta['title'] . ' ' . smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ') . ' ' . $feed_meta['source'] . ' ' . $feed_meta['desc'] . ' SMF'; if (!empty($feed_meta['icon'])) $context['feed']['header'] .= ' ' . $feed_meta['icon'] . ''; if (!empty($feed_meta['author'])) $context['feed']['header'] .= ' ' . $feed_meta['author'] . ' '; if (!empty($feed_meta['rights'])) $context['feed']['header'] .= ' ' . $feed_meta['rights'] . ''; $context['feed']['header'] .= $extraFeedTags_string; dumpTags($xml_data, $i, $xml_format, $forceCdataKeys, $nsKeys); $context['feed']['footer'] = ' '; } elseif ($xml_format == 'rdf') { $context['feed']['header'] .= ' ' . $feed_meta['title'] . ' ' . $feed_meta['source'] . ' ' . $feed_meta['desc'] . ''; $context['feed']['header'] .= $extraFeedTags_string; $context['feed']['header'] .= ' '; foreach ($xml_data as $item) { $link = array_filter( $item['content'], function($e) { return ($e['tag'] == 'link'); } ); $link = array_pop($link); $context['feed']['header'] .= ' '; } $context['feed']['header'] .= ' '; dumpTags($xml_data, $i, $xml_format, $forceCdataKeys, $nsKeys); $context['feed']['footer'] = ' '; } // Otherwise, we're using our proprietary formats - they give more data, though. else { $context['feed']['header'] .= ' '; // Hard to imagine anyone wanting to add these for the proprietary format, but just in case... $context['feed']['header'] .= $extraFeedTags_string; // Dump out that associative array. Indent properly.... and use the right names for the base elements. dumpTags($xml_data, $i, $xml_format, $forceCdataKeys, $nsKeys); $context['feed']['footer'] = ' '; } } /** * Called from dumpTags to convert data to xml * Finds urls for local site and sanitizes them * * @param string $val A string containing a possible URL * @return string $val The string with any possible URLs sanitized */ function fix_possible_url($val) { global $modSettings, $context, $scripturl; if (substr($val, 0, strlen($scripturl)) != $scripturl) return $val; call_integration_hook('integrate_fix_url', array(&$val)); 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'])) return $val; $val = preg_replace_callback( '~\b' . preg_quote($scripturl, '~') . '\?((?:board|topic)=[^#"]+)(#[^"]*)?$~', function($m) use ($scripturl) { return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html' . (isset($m[2]) ? $m[2] : ""); }, $val ); return $val; } /** * Ensures supplied data is properly encapsulated in cdata xml tags * Called from getXmlProfile in News.php * * @param string $data XML data * @param string $ns A namespace prefix for the XML data elements (used by mods, maybe) * @param boolean $force If true, enclose the XML data in cdata tags no matter what (used by mods, maybe) * @return string The XML data enclosed in cdata tags when necessary */ function cdata_parse($data, $ns = '', $force = false) { global $smcFunc; // Do we even need to do this? if (strpbrk($data, '<>&') == false && $force !== true) return $data; $cdata = '', ']]]]><[CDATA[>', $data)` for ($pos = 0, $n = strlen($data); $pos < $n; null) { $positions = array( strpos($data, ']]>', $pos), ); if ($ns != '') $positions[] = strpos($data, '<', $pos); foreach ($positions as $k => $dummy) { if ($dummy === false) unset($positions[$k]); } $old = $pos; $pos = empty($positions) ? $n : min($positions); if ($pos - $old > 0) $cdata .= substr($data, $old, $pos - $old); if ($pos >= $n) break; if (substr($data, $pos, 1) == '<') { $pos2 = strpos($data, '>', $pos); if ($pos2 === false) $pos2 = $n; if (substr($data, $pos + 1, 1) == '/') $cdata .= ']]><' . $ns . ':' . substr($data, $pos + 1, $pos2 - $pos) . '') { $cdata .= ']]]]>'; $pos = $pos + 3; } } $cdata .= ']]>'; return strtr($cdata, array('' => '')); } /** * Formats data retrieved in other functions into xml format. * Additionally formats data based on the specific format passed. * This function is recursively called to handle sub arrays of data. * * @param array $data The array to output as xml data * @param int $i The amount of indentation to use. * @param string $xml_format The format to use ('atom', 'rss', 'rss2' or empty for plain XML) * @param array $forceCdataKeys A list of keys on which to force cdata wrapping (used by mods, maybe) * @param array $nsKeys Key-value pairs of namespace prefixes to pass to cdata_parse() (used by mods, maybe) */ function dumpTags($data, $i, $xml_format = '', $forceCdataKeys = array(), $nsKeys = array()) { global $context; if (empty($context['feed']['items'])) $context['feed']['items'] = ''; // For every array in the data... foreach ($data as $element) { $key = isset($element['tag']) ? $element['tag'] : null; $val = isset($element['content']) ? $element['content'] : null; $attrs = isset($element['attributes']) ? $element['attributes'] : null; // Skip it, it's been set to null. if ($key === null || ($val === null && $attrs === null)) continue; $forceCdata = in_array($key, $forceCdataKeys); $ns = !empty($nsKeys[$key]) ? $nsKeys[$key] : ''; // First let's indent! $context['feed']['items'] .= "\n" . str_repeat("\t", $i); // Beginning tag. $context['feed']['items'] .= '<' . $key; if (!empty($attrs)) { foreach ($attrs as $attr_key => $attr_value) $context['feed']['items'] .= ' ' . $attr_key . '="' . fix_possible_url($attr_value) . '"'; } // If it's empty, simply output an empty element. if (empty($val) && $val !== '0' && $val !== 0) { $context['feed']['items'] .= ' />'; } else { $context['feed']['items'] .= '>'; // The element's value. if (is_array($val)) { // An array. Dump it, and then indent the tag. dumpTags($val, $i + 1, $xml_format, $forceCdataKeys, $nsKeys); $context['feed']['items'] .= "\n" . str_repeat("\t", $i); } // A string with returns in it.... show this as a multiline element. elseif (strpos($val, "\n") !== false) $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); // A simple string. else $context['feed']['items'] .= !empty($element['cdata']) || $forceCdata ? cdata_parse(fix_possible_url($val), $ns, $forceCdata) : fix_possible_url($val); // Ending tag. $context['feed']['items'] .= ''; } } } /** * Retrieve the list of members from database. * The array will be generated to match the format. * * @todo get the list of members from Subs-Members. * * @param string $xml_format The format to use. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf' * @param bool $ascending If true, get the earliest members first. Default false. * @return array An array of arrays of feed items. Each array has keys corresponding to the appropriate tags for the specified format. */ function getXmlMembers($xml_format, $ascending = false) { global $scripturl, $smcFunc, $txt, $context; if (!allowedTo('view_mlist')) return array(); loadLanguage('Profile'); // Find the most (or least) recent members. $request = $smcFunc['db_query']('', ' SELECT id_member, member_name, real_name, date_registered, last_login FROM {db_prefix}members ORDER BY id_member {raw:ascdesc} LIMIT {int:limit}', array( 'limit' => $context['xmlnews_limit'], 'ascdesc' => !empty($ascending) ? 'ASC' : 'DESC', ) ); $data = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { // If any control characters slipped in somehow, kill the evil things $row = filter_var($row, FILTER_CALLBACK, array('options' => 'cleanXml')); // Create a GUID for each member using the tag URI scheme $guid = 'tag:' . parse_iri($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['date_registered']) . ':member=' . $row['id_member']; // Make the data look rss-ish. if ($xml_format == 'rss' || $xml_format == 'rss2') $data[] = array( 'tag' => 'item', 'content' => array( array( 'tag' => 'title', 'content' => $row['real_name'], 'cdata' => true, ), array( 'tag' => 'link', 'content' => $scripturl . '?action=profile;u=' . $row['id_member'], ), array( 'tag' => 'comments', 'content' => $scripturl . '?action=pm;sa=send;u=' . $row['id_member'], ), array( 'tag' => 'pubDate', 'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['date_registered']), ), array( 'tag' => 'guid', 'content' => $guid, 'attributes' => array( 'isPermaLink' => 'false', ), ), ), ); elseif ($xml_format == 'rdf') $data[] = array( 'tag' => 'item', 'attributes' => array('rdf:about' => $scripturl . '?action=profile;u=' . $row['id_member']), 'content' => array( array( 'tag' => 'dc:format', 'content' => 'text/html', ), array( 'tag' => 'title', 'content' => $row['real_name'], 'cdata' => true, ), array( 'tag' => 'link', 'content' => $scripturl . '?action=profile;u=' . $row['id_member'], ), ), ); elseif ($xml_format == 'atom') $data[] = array( 'tag' => 'entry', 'content' => array( array( 'tag' => 'title', 'content' => $row['real_name'], 'cdata' => true, ), array( 'tag' => 'link', 'attributes' => array( 'rel' => 'alternate', 'type' => 'text/html', 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], ), ), array( 'tag' => 'published', 'content' => smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['date_registered']), ), array( 'tag' => 'updated', 'content' => smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['last_login']), ), array( 'tag' => 'id', 'content' => $guid, ), ), ); // More logical format for the data, but harder to apply. else $data[] = array( 'tag' => 'member', 'attributes' => array('label' => $txt['who_member']), 'content' => array( array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => $row['real_name'], 'cdata' => true, ), array( 'tag' => 'time', 'attributes' => array('label' => $txt['date_registered'], 'UTC' => smf_gmstrftime('%F %T', $row['date_registered'])), 'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['date_registered'], false, 'forum'))), ), array( 'tag' => 'id', 'content' => $row['id_member'], ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?action=profile;u=' . $row['id_member'], ), ), ); } $smcFunc['db_free_result']($request); return $data; } /** * Get the latest topics information from a specific board, * to display later. * The returned array will be generated to match the xml_format. * * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf'. * @param bool $ascending If true, get the oldest topics first. Default false. * @return array An array of arrays of topic data for the feed. Each array has keys corresponding to the tags for the specified format. */ function getXmlNews($xml_format, $ascending = false) { global $scripturl, $modSettings, $board, $user_info; global $query_this_board, $smcFunc, $context, $txt; /* Find the latest (or earliest) posts that: - are the first post in their topic. - are on an any board OR in a specified board. - can be seen by this user. */ $done = false; $loops = 0; while (!$done) { $optimize_msg = implode(' AND ', $context['optimize_msg']); $request = $smcFunc['db_query']('', ' SELECT m.smileys_enabled, m.poster_time, m.id_msg, m.subject, m.body, m.modified_time, m.icon, t.id_topic, t.id_board, t.num_replies, b.name AS bname, COALESCE(mem.id_member, 0) AS id_member, COALESCE(mem.email_address, m.poster_email) AS poster_email, COALESCE(mem.real_name, m.poster_name) AS poster_name FROM {db_prefix}topics AS t INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) WHERE ' . $query_this_board . (empty($optimize_msg) ? '' : ' AND {raw:optimize_msg}') . (empty($board) ? '' : ' AND t.id_board = {int:current_board}') . ($modSettings['postmod_active'] ? ' AND t.approved = {int:is_approved}' : '') . ' ORDER BY t.id_first_msg {raw:ascdesc} LIMIT {int:limit}', array( 'current_board' => $board, 'is_approved' => 1, 'limit' => $context['xmlnews_limit'], 'optimize_msg' => $optimize_msg, 'ascdesc' => !empty($ascending) ? 'ASC' : 'DESC', ) ); // If we don't have $context['xmlnews_limit'] results, try again with an unoptimized version covering all rows. if ($loops < 2 && $smcFunc['db_num_rows']($request) < $context['xmlnews_limit']) { $smcFunc['db_free_result']($request); if (empty($_GET['boards']) && empty($board)) unset($context['optimize_msg']['lowest']); else $context['optimize_msg']['lowest'] = 'm.id_msg >= t.id_first_msg'; $context['optimize_msg']['highest'] = 'm.id_msg <= t.id_last_msg'; $loops++; } else $done = true; } $data = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { // If any control characters slipped in somehow, kill the evil things $row = filter_var($row, FILTER_CALLBACK, array('options' => 'cleanXml')); // Limit the length of the message, if the option is set. if (!empty($modSettings['xmlnews_maxlen']) && $smcFunc['strlen'](str_replace('
', "\n", $row['body'])) > $modSettings['xmlnews_maxlen']) $row['body'] = strtr($smcFunc['substr'](str_replace('
', "\n", $row['body']), 0, $modSettings['xmlnews_maxlen'] - 3), array("\n" => '
')) . '...'; $row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); censorText($row['body']); censorText($row['subject']); // Do we want to include any attachments? if (!empty($modSettings['attachmentEnable']) && !empty($modSettings['xmlnews_attachments']) && allowedTo('view_attachments', $row['id_board'])) { $attach_request = $smcFunc['db_query']('', ' SELECT a.id_attach, a.filename, COALESCE(a.size, 0) AS filesize, a.mime_type, a.downloads, a.approved, m.id_topic AS topic FROM {db_prefix}attachments AS a LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) WHERE a.attachment_type = {int:attachment_type} AND a.id_msg = {int:message_id}', array( 'message_id' => $row['id_msg'], 'attachment_type' => 0, 'is_approved' => 1, ) ); $loaded_attachments = array(); while ($attach = $smcFunc['db_fetch_assoc']($attach_request)) { // Include approved attachments only if ($attach['approved']) $loaded_attachments['attachment_' . $attach['id_attach']] = $attach; } $smcFunc['db_free_result']($attach_request); // Sort the attachments by size to make things easier below if (!empty($loaded_attachments)) { uasort( $loaded_attachments, function($a, $b) { if ($a['filesize'] == $b['filesize']) return 0; return ($a['filesize'] < $b['filesize']) ? -1 : 1; } ); } else $loaded_attachments = null; } else $loaded_attachments = null; // Create a GUID for this topic using the tag URI scheme $guid = 'tag:' . parse_iri($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['poster_time']) . ':topic=' . $row['id_topic']; // Being news, this actually makes sense in rss format. if ($xml_format == 'rss' || $xml_format == 'rss2') { // Only one attachment allowed in RSS. if ($loaded_attachments !== null) { $attachment = array_pop($loaded_attachments); $enclosure = array( 'url' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']), 'length' => $attachment['filesize'], 'type' => $attachment['mime_type'], ); } else $enclosure = null; $data[] = array( 'tag' => 'item', 'content' => array( array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'link', 'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0', ), array( 'tag' => 'description', 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'author', 'content' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? $row['poster_email'] . ' (' . $row['poster_name'] . ')' : null, 'cdata' => true, ), array( 'tag' => 'comments', 'content' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0', ), array( 'tag' => 'category', 'content' => $row['bname'], 'cdata' => true, ), array( 'tag' => 'pubDate', 'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']), ), array( 'tag' => 'guid', 'content' => $guid, 'attributes' => array( 'isPermaLink' => 'false', ), ), array( 'tag' => 'enclosure', 'attributes' => $enclosure, ), ), ); } elseif ($xml_format == 'rdf') { $data[] = array( 'tag' => 'item', 'attributes' => array('rdf:about' => $scripturl . '?topic=' . $row['id_topic'] . '.0'), 'content' => array( array( 'tag' => 'dc:format', 'content' => 'text/html', ), array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'link', 'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0', ), array( 'tag' => 'description', 'content' => $row['body'], 'cdata' => true, ), ), ); } elseif ($xml_format == 'atom') { // Only one attachment allowed if (!empty($loaded_attachments)) { $attachment = array_pop($loaded_attachments); $enclosure = array( 'rel' => 'enclosure', 'href' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']), 'length' => $attachment['filesize'], 'type' => $attachment['mime_type'], ); } else $enclosure = null; $data[] = array( 'tag' => 'entry', 'content' => array( array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'link', 'attributes' => array( 'rel' => 'alternate', 'type' => 'text/html', 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0', ), ), array( 'tag' => 'summary', 'attributes' => array('type' => 'html'), 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'category', 'attributes' => array('term' => $row['bname']), 'cdata' => true, ), array( 'tag' => 'author', 'content' => array( array( 'tag' => 'name', 'content' => $row['poster_name'], 'cdata' => true, ), array( 'tag' => 'email', 'content' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? $row['poster_email'] : null, 'cdata' => true, ), array( 'tag' => 'uri', 'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : null, ), ) ), array( 'tag' => 'published', 'content' => smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']), ), array( 'tag' => 'updated', 'content' => smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']), ), array( 'tag' => 'id', 'content' => $guid, ), array( 'tag' => 'link', 'attributes' => $enclosure, ), ), ); } // The biggest difference here is more information. else { loadLanguage('Post'); $attachments = array(); if (!empty($loaded_attachments)) { foreach ($loaded_attachments as $attachment) { $attachments[] = array( 'tag' => 'attachment', 'attributes' => array('label' => $txt['attachment']), 'content' => array( array( 'tag' => 'id', 'content' => $attachment['id_attach'], ), array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])), ), array( 'tag' => 'downloads', 'attributes' => array('label' => $txt['downloads']), 'content' => $attachment['downloads'], ), array( 'tag' => 'size', 'attributes' => array('label' => $txt['filesize']), 'content' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'], ), array( 'tag' => 'byte_size', 'attributes' => array('label' => $txt['filesize']), 'content' => $attachment['filesize'], ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'], ), ) ); } } else $attachments = null; $data[] = array( 'tag' => 'article', 'attributes' => array('label' => $txt['news']), 'content' => array( array( 'tag' => 'time', 'attributes' => array('label' => $txt['date'], 'UTC' => smf_gmstrftime('%F %T', $row['poster_time'])), 'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['poster_time'], false, 'forum'))), ), array( 'tag' => 'id', 'content' => $row['id_topic'], ), array( 'tag' => 'subject', 'attributes' => array('label' => $txt['subject']), 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'body', 'attributes' => array('label' => $txt['message']), 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'poster', 'attributes' => array('label' => $txt['author']), 'content' => array( array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => $row['poster_name'], 'cdata' => true, ), array( 'tag' => 'id', 'content' => $row['id_member'], ), array( 'tag' => 'link', 'attributes' => !empty($row['id_member']) ? array('label' => $txt['url']) : null, 'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '', ), ) ), array( 'tag' => 'topic', 'attributes' => array('label' => $txt['topic']), 'content' => $row['id_topic'], ), array( 'tag' => 'board', 'attributes' => array('label' => $txt['board']), 'content' => array( array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => $row['bname'], 'cdata' => true, ), array( 'tag' => 'id', 'content' => $row['id_board'], ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?board=' . $row['id_board'] . '.0', ), ), ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0', ), array( 'tag' => 'attachments', 'attributes' => array('label' => $txt['attachments']), 'content' => $attachments, ), ), ); } } $smcFunc['db_free_result']($request); return $data; } /** * Get the recent topics to display. * The returned array will be generated to match the xml_format. * * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf' * @return array An array of arrays containing data for the feed. Each array has keys corresponding to the appropriate tags for the specified format. */ function getXmlRecent($xml_format) { global $scripturl, $modSettings, $board, $txt; global $query_this_board, $smcFunc, $context, $user_info, $sourcedir; require_once($sourcedir . '/Subs-Attachments.php'); $done = false; $loops = 0; while (!$done) { $optimize_msg = implode(' AND ', $context['optimize_msg']); $request = $smcFunc['db_query']('', ' SELECT m.id_msg FROM {db_prefix}messages AS m INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) WHERE ' . $query_this_board . (empty($optimize_msg) ? '' : ' AND {raw:optimize_msg}') . (empty($board) ? '' : ' AND m.id_board = {int:current_board}') . ($modSettings['postmod_active'] ? ' AND m.approved = {int:is_approved} AND t.approved = {int:is_approved}' : '') . ' ORDER BY m.id_msg DESC LIMIT {int:limit}', array( 'limit' => $context['xmlnews_limit'], 'current_board' => $board, 'is_approved' => 1, 'optimize_msg' => $optimize_msg, ) ); // If we don't have $context['xmlnews_limit'] results, try again with an unoptimized version covering all rows. if ($loops < 2 && $smcFunc['db_num_rows']($request) < $context['xmlnews_limit']) { $smcFunc['db_free_result']($request); if (empty($_GET['boards']) && empty($board)) unset($context['optimize_msg']['lowest']); else $context['optimize_msg']['lowest'] = $loops ? 'm.id_msg >= t.id_first_msg' : 'm.id_msg >= (t.id_last_msg - t.id_first_msg) / 2'; $loops++; } else $done = true; } $messages = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) $messages[] = $row['id_msg']; $smcFunc['db_free_result']($request); if (empty($messages)) return array(); // Find the most recent posts this user can see. $request = $smcFunc['db_query']('', ' SELECT m.smileys_enabled, m.poster_time, m.id_msg, m.subject, m.body, m.id_topic, t.id_board, b.name AS bname, t.num_replies, m.id_member, m.icon, mf.id_member AS id_first_member, COALESCE(mem.real_name, m.poster_name) AS poster_name, mf.subject AS first_subject, COALESCE(memf.real_name, mf.poster_name) AS first_poster_name, COALESCE(mem.email_address, m.poster_email) AS poster_email, m.modified_time FROM {db_prefix}messages AS m INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg) INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = mf.id_member) WHERE m.id_msg IN ({array_int:message_list}) ' . (empty($board) ? '' : 'AND t.id_board = {int:current_board}') . ' ORDER BY m.id_msg DESC LIMIT {int:limit}', array( 'limit' => $context['xmlnews_limit'], 'current_board' => $board, 'message_list' => $messages, ) ); $data = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { // If any control characters slipped in somehow, kill the evil things $row = filter_var($row, FILTER_CALLBACK, array('options' => 'cleanXml')); // Limit the length of the message, if the option is set. if (!empty($modSettings['xmlnews_maxlen']) && $smcFunc['strlen'](str_replace('
', "\n", $row['body'])) > $modSettings['xmlnews_maxlen']) $row['body'] = strtr($smcFunc['substr'](str_replace('
', "\n", $row['body']), 0, $modSettings['xmlnews_maxlen'] - 3), array("\n" => '
')) . '...'; $row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); censorText($row['body']); censorText($row['subject']); // Do we want to include any attachments? if (!empty($modSettings['attachmentEnable']) && !empty($modSettings['xmlnews_attachments']) && allowedTo('view_attachments', $row['id_board'])) { $attach_request = $smcFunc['db_query']('', ' SELECT a.id_attach, a.filename, COALESCE(a.size, 0) AS filesize, a.mime_type, a.downloads, a.approved, m.id_topic AS topic FROM {db_prefix}attachments AS a LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) WHERE a.attachment_type = {int:attachment_type} AND a.id_msg = {int:message_id}', array( 'message_id' => $row['id_msg'], 'attachment_type' => 0, 'is_approved' => 1, ) ); $loaded_attachments = array(); while ($attach = $smcFunc['db_fetch_assoc']($attach_request)) { // Include approved attachments only if ($attach['approved']) $loaded_attachments['attachment_' . $attach['id_attach']] = $attach; } $smcFunc['db_free_result']($attach_request); // Sort the attachments by size to make things easier below if (!empty($loaded_attachments)) { uasort( $loaded_attachments, function($a, $b) { if ($a['filesize'] == $b['filesize']) return 0; return ($a['filesize'] < $b['filesize']) ? -1 : 1; } ); } else $loaded_attachments = null; } else $loaded_attachments = null; // Create a GUID for this post using the tag URI scheme $guid = 'tag:' . parse_iri($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['poster_time']) . ':msg=' . $row['id_msg']; // Doesn't work as well as news, but it kinda does.. if ($xml_format == 'rss' || $xml_format == 'rss2') { // Only one attachment allowed in RSS. if ($loaded_attachments !== null) { $attachment = array_pop($loaded_attachments); $enclosure = array( 'url' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']), 'length' => $attachment['filesize'], 'type' => $attachment['mime_type'], ); } else $enclosure = null; $data[] = array( 'tag' => 'item', 'content' => array( array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'link', 'content' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], ), array( 'tag' => 'description', 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'author', 'content' => (allowedTo('moderate_forum') || (!empty($row['id_member']) && $row['id_member'] == $user_info['id'])) ? $row['poster_email'] : null, 'cdata' => true, ), array( 'tag' => 'category', 'content' => $row['bname'], 'cdata' => true, ), array( 'tag' => 'comments', 'content' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0', ), array( 'tag' => 'pubDate', 'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']), ), array( 'tag' => 'guid', 'content' => $guid, 'attributes' => array( 'isPermaLink' => 'false', ), ), array( 'tag' => 'enclosure', 'attributes' => $enclosure, ), ), ); } elseif ($xml_format == 'rdf') { $data[] = array( 'tag' => 'item', 'attributes' => array('rdf:about' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg']), 'content' => array( array( 'tag' => 'dc:format', 'content' => 'text/html', ), array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'link', 'content' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], ), array( 'tag' => 'description', 'content' => $row['body'], 'cdata' => true, ), ), ); } elseif ($xml_format == 'atom') { // Only one attachment allowed if (!empty($loaded_attachments)) { $attachment = array_pop($loaded_attachments); $enclosure = array( 'rel' => 'enclosure', 'href' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']), 'length' => $attachment['filesize'], 'type' => $attachment['mime_type'], ); } else $enclosure = null; $data[] = array( 'tag' => 'entry', 'content' => array( array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'link', 'attributes' => array( 'rel' => 'alternate', 'type' => 'text/html', 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], ), ), array( 'tag' => 'summary', 'attributes' => array('type' => 'html'), 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'category', 'attributes' => array('term' => $row['bname']), 'cdata' => true, ), array( 'tag' => 'author', 'content' => array( array( 'tag' => 'name', 'content' => $row['poster_name'], 'cdata' => true, ), array( 'tag' => 'email', 'content' => (allowedTo('moderate_forum') || (!empty($row['id_member']) && $row['id_member'] == $user_info['id'])) ? $row['poster_email'] : null, 'cdata' => true, ), array( 'tag' => 'uri', 'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : null, ), ), ), array( 'tag' => 'published', 'content' => smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']), ), array( 'tag' => 'updated', 'content' => smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']), ), array( 'tag' => 'id', 'content' => $guid, ), array( 'tag' => 'link', 'attributes' => $enclosure, ), ), ); } // A lot of information here. Should be enough to please the rss-ers. else { loadLanguage('Post'); $attachments = array(); if (!empty($loaded_attachments)) { foreach ($loaded_attachments as $attachment) { $attachments[] = array( 'tag' => 'attachment', 'attributes' => array('label' => $txt['attachment']), 'content' => array( array( 'tag' => 'id', 'content' => $attachment['id_attach'], ), array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])), ), array( 'tag' => 'downloads', 'attributes' => array('label' => $txt['downloads']), 'content' => $attachment['downloads'], ), array( 'tag' => 'size', 'attributes' => array('label' => $txt['filesize']), 'content' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'], ), array( 'tag' => 'byte_size', 'attributes' => array('label' => $txt['filesize']), 'content' => $attachment['filesize'], ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'], ), ) ); } } else $attachments = null; $data[] = array( 'tag' => 'recent-post', // Hyphen rather than underscore for backward compatibility reasons 'attributes' => array('label' => $txt['post']), 'content' => array( array( 'tag' => 'time', 'attributes' => array('label' => $txt['date'], 'UTC' => smf_gmstrftime('%F %T', $row['poster_time'])), 'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['poster_time'], false, 'forum'))), ), array( 'tag' => 'id', 'content' => $row['id_msg'], ), array( 'tag' => 'subject', 'attributes' => array('label' => $txt['subject']), 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'body', 'attributes' => array('label' => $txt['message']), 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'starter', 'attributes' => array('label' => $txt['topic_started']), 'content' => array( array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => $row['first_poster_name'], 'cdata' => true, ), array( 'tag' => 'id', 'content' => $row['id_first_member'], ), array( 'tag' => 'link', 'attributes' => !empty($row['id_first_member']) ? array('label' => $txt['url']) : null, 'content' => !empty($row['id_first_member']) ? $scripturl . '?action=profile;u=' . $row['id_first_member'] : '', ), ), ), array( 'tag' => 'poster', 'attributes' => array('label' => $txt['author']), 'content' => array( array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => $row['poster_name'], 'cdata' => true, ), array( 'tag' => 'id', 'content' => $row['id_member'], ), array( 'tag' => 'link', 'attributes' => !empty($row['id_member']) ? array('label' => $txt['url']) : null, 'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '', ), ), ), array( 'tag' => 'topic', 'attributes' => array('label' => $txt['topic']), 'content' => array( array( 'tag' => 'subject', 'attributes' => array('label' => $txt['subject']), 'content' => $row['first_subject'], 'cdata' => true, ), array( 'tag' => 'id', 'content' => $row['id_topic'], ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?topic=' . $row['id_topic'] . '.new#new', ), ), ), array( 'tag' => 'board', 'attributes' => array('label' => $txt['board']), 'content' => array( array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => $row['bname'], 'cdata' => true, ), array( 'tag' => 'id', 'content' => $row['id_board'], ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?board=' . $row['id_board'] . '.0', ), ), ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], ), array( 'tag' => 'attachments', 'attributes' => array('label' => $txt['attachments']), 'content' => $attachments, ), ), ); } } $smcFunc['db_free_result']($request); return $data; } /** * Get the profile information for member into an array, * which will be generated to match the xml_format. * * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf' * @return array An array profile data */ function getXmlProfile($xml_format) { global $scripturl, $memberContext, $user_info, $txt, $context; // You must input a valid user, and you must be allowed to view that user's profile. if (empty($context['xmlnews_uid']) || ($context['xmlnews_uid'] != $user_info['id'] && !allowedTo('profile_view')) || !loadMemberData($context['xmlnews_uid'])) return array(); // Load the member's contextual information! (Including custom fields for our proprietary XML type) if (!loadMemberContext($context['xmlnews_uid'], ($xml_format == 'smf'))) return array(); $profile = &$memberContext[$context['xmlnews_uid']]; // If any control characters slipped in somehow, kill the evil things $profile = filter_var($profile, FILTER_CALLBACK, array('options' => 'cleanXml')); // Create a GUID for this member using the tag URI scheme $guid = 'tag:' . parse_iri($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $profile['registered_timestamp']) . ':member=' . $profile['id']; if ($xml_format == 'rss' || $xml_format == 'rss2') { $data[] = array( 'tag' => 'item', 'content' => array( array( 'tag' => 'title', 'content' => $profile['name'], 'cdata' => true, ), array( 'tag' => 'link', 'content' => $scripturl . '?action=profile;u=' . $profile['id'], ), array( 'tag' => 'description', 'content' => isset($profile['group']) ? $profile['group'] : $profile['post_group'], 'cdata' => true, ), array( 'tag' => 'comments', 'content' => $scripturl . '?action=pm;sa=send;u=' . $profile['id'], ), array( 'tag' => 'pubDate', 'content' => gmdate('D, d M Y H:i:s \G\M\T', $profile['registered_timestamp']), ), array( 'tag' => 'guid', 'content' => $guid, 'attributes' => array( 'isPermaLink' => 'false', ), ), ) ); } elseif ($xml_format == 'rdf') { $data[] = array( 'tag' => 'item', 'attributes' => array('rdf:about' => $scripturl . '?action=profile;u=' . $profile['id']), 'content' => array( array( 'tag' => 'dc:format', 'content' => 'text/html', ), array( 'tag' => 'title', 'content' => $profile['name'], 'cdata' => true, ), array( 'tag' => 'link', 'content' => $scripturl . '?action=profile;u=' . $profile['id'], ), array( 'tag' => 'description', 'content' => isset($profile['group']) ? $profile['group'] : $profile['post_group'], 'cdata' => true, ), ) ); } elseif ($xml_format == 'atom') { $data[] = array( 'tag' => 'entry', 'content' => array( array( 'tag' => 'title', 'content' => $profile['name'], 'cdata' => true, ), array( 'tag' => 'link', 'attributes' => array( 'rel' => 'alternate', 'type' => 'text/html', 'href' => $scripturl . '?action=profile;u=' . $profile['id'], ), ), array( 'tag' => 'summary', 'attributes' => array('type' => 'html'), 'content' => isset($profile['group']) ? $profile['group'] : $profile['post_group'], 'cdata' => true, ), array( 'tag' => 'author', 'content' => array( array( 'tag' => 'name', 'content' => $profile['name'], 'cdata' => true, ), array( 'tag' => 'email', 'content' => $profile['show_email'] ? $profile['email'] : null, 'cdata' => true, ), array( 'tag' => 'uri', 'content' => !empty($profile['website']['url']) ? $profile['website']['url'] : $scripturl . '?action=profile;u=' . $profile['id_member'], 'cdata' => !empty($profile['website']['url']), ), ), ), array( 'tag' => 'published', 'content' => smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ', $profile['registered_timestamp']), ), array( 'tag' => 'updated', 'content' => smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ', $profile['last_login_timestamp']), ), array( 'tag' => 'id', 'content' => $guid, ), ) ); } else { loadLanguage('Profile'); $data = array( array( 'tag' => 'username', 'attributes' => $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? array('label' => $txt['username']) : null, 'content' => $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? $profile['username'] : null, 'cdata' => true, ), array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => $profile['name'], 'cdata' => true, ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?action=profile;u=' . $profile['id'], ), array( 'tag' => 'posts', 'attributes' => array('label' => $txt['member_postcount']), 'content' => $profile['posts'], ), array( 'tag' => 'post-group', 'attributes' => array('label' => $txt['post_based_membergroup']), 'content' => $profile['post_group'], 'cdata' => true, ), array( 'tag' => 'language', 'attributes' => array('label' => $txt['preferred_language']), 'content' => $profile['language'], 'cdata' => true, ), array( 'tag' => 'last-login', 'attributes' => array('label' => $txt['lastLoggedIn'], 'UTC' => smf_gmstrftime('%F %T', $profile['last_login_timestamp'])), 'content' => timeformat($profile['last_login_timestamp'], false, 'forum'), ), array( 'tag' => 'registered', 'attributes' => array('label' => $txt['date_registered'], 'UTC' => smf_gmstrftime('%F %T', $profile['registered_timestamp'])), 'content' => timeformat($profile['registered_timestamp'], false, 'forum'), ), array( 'tag' => 'avatar', 'attributes' => !empty($profile['avatar']['url']) ? array('label' => $txt['personal_picture']) : null, 'content' => !empty($profile['avatar']['url']) ? $profile['avatar']['url'] : null, 'cdata' => true, ), array( 'tag' => 'signature', 'attributes' => !empty($profile['signature']) ? array('label' => $txt['signature']) : null, 'content' => !empty($profile['signature']) ? $profile['signature'] : null, 'cdata' => true, ), array( 'tag' => 'blurb', 'attributes' => !empty($profile['blurb']) ? array('label' => $txt['personal_text']) : null, 'content' => !empty($profile['blurb']) ? $profile['blurb'] : null, 'cdata' => true, ), array( 'tag' => 'title', 'attributes' => !empty($profile['title']) ? array('label' => $txt['title']) : null, 'content' => !empty($profile['title']) ? $profile['title'] : null, 'cdata' => true, ), array( 'tag' => 'position', 'attributes' => !empty($profile['group']) ? array('label' => $txt['position']) : null, 'content' => !empty($profile['group']) ? $profile['group'] : null, 'cdata' => true, ), array( 'tag' => 'email', 'attributes' => !empty($profile['show_email']) || $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? array('label' => $txt['user_email_address']) : null, 'content' => !empty($profile['show_email']) || $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? $profile['email'] : null, 'cdata' => true, ), array( 'tag' => 'website', 'attributes' => empty($profile['website']['url']) ? null : array('label' => $txt['website']), 'content' => empty($profile['website']['url']) ? null : array( array( 'tag' => 'title', 'attributes' => !empty($profile['website']['title']) ? array('label' => $txt['website_title']) : null, 'content' => !empty($profile['website']['title']) ? $profile['website']['title'] : null, 'cdata' => true, ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['website_url']), 'content' => $profile['website']['url'], 'cdata' => true, ), ), ), array( 'tag' => 'online', 'attributes' => !empty($profile['online']['is_online']) ? array('label' => $txt['online']) : null, 'content' => !empty($profile['online']['is_online']) ? 'true' : null, ), array( 'tag' => 'ip_addresses', 'attributes' => array('label' => $txt['ip_address']), 'content' => allowedTo('moderate_forum') || $user_info['id'] == $profile['id'] ? array( array( 'tag' => 'ip', 'attributes' => array('label' => $txt['most_recent_ip']), 'content' => $profile['ip'], ), array( 'tag' => 'ip2', 'content' => $profile['ip'] != $profile['ip2'] ? $profile['ip2'] : null, ), ) : null, ), ); if (!empty($profile['birth_date']) && substr($profile['birth_date'], 0, 4) != '0000' && substr($profile['birth_date'], 0, 4) != '1004') { list ($birth_year, $birth_month, $birth_day) = sscanf($profile['birth_date'], '%d-%d-%d'); $datearray = getdate(time()); $age = $datearray['year'] - $birth_year - (($datearray['mon'] > $birth_month || ($datearray['mon'] == $birth_month && $datearray['mday'] >= $birth_day)) ? 0 : 1); $data[] = array( 'tag' => 'age', 'attributes' => array('label' => $txt['age']), 'content' => $age, ); $data[] = array( 'tag' => 'birthdate', 'attributes' => array('label' => $txt['dob']), 'content' => $profile['birth_date'], ); } if (!empty($profile['custom_fields'])) { foreach ($profile['custom_fields'] as $custom_field) { $data[] = array( 'tag' => $custom_field['col_name'], 'attributes' => array('label' => $custom_field['title']), 'content' => $custom_field['simple'], 'cdata' => true, ); } } } // Save some memory. unset($profile, $memberContext[$context['xmlnews_uid']]); return $data; } /** * Get a user's posts. * The returned array will be generated to match the xml_format. * * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf' * @param bool $ascending If true, get the oldest posts first. Default false. * @return array An array of arrays containing data for the feed. Each array has keys corresponding to the appropriate tags for the specified format. */ function getXmlPosts($xml_format, $ascending = false) { global $scripturl, $modSettings, $board, $txt, $context, $user_info; global $query_this_board, $smcFunc, $sourcedir, $cachedir; if (empty($context['xmlnews_uid']) || ($context['xmlnews_uid'] != $user_info['id'] && !allowedTo('profile_view'))) return array(); $show_all = !empty($user_info['is_admin']) || defined('EXPORTING'); $query_this_message_board = str_replace(array('{query_see_board}', 'b.'), array('{query_see_message_board}', 'm.'), $query_this_board); require_once($sourcedir . '/Subs-Attachments.php'); /* MySQL can choke if we use joins in the main query when the user has * massively long posts. To avoid that, we get the names of the boards * and the user's displayed name in separate queries. */ $boardnames = array(); $request = $smcFunc['db_query']('', ' SELECT id_board, name FROM {db_prefix}boards', array() ); while ($row = $smcFunc['db_fetch_assoc']($request)) $boardnames[$row['id_board']] = $row['name']; $smcFunc['db_free_result']($request); if ($context['xmlnews_uid'] == $user_info['id']) $poster_name = $user_info['name']; else { $request = $smcFunc['db_query']('', ' SELECT COALESCE(real_name, member_name) AS poster_name FROM {db_prefix}members WHERE id_member = {int:uid}', array( 'uid' => $context['xmlnews_uid'], ) ); list($poster_name) = $smcFunc['db_fetch_row']($request); $smcFunc['db_free_result']($request); } $request = $smcFunc['db_query']('', ' SELECT m.id_msg, m.id_topic, m.id_board, m.id_member, m.poster_email, m.poster_ip, m.poster_time, m.subject, m.modified_time, m.modified_name, m.modified_reason, m.body, m.likes, m.approved, m.smileys_enabled FROM {db_prefix}messages AS m' . ($modSettings['postmod_active'] && !$show_all ?' INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)' : '') . ' WHERE m.id_member = {int:uid} AND m.id_msg > {int:start_after} AND ' . $query_this_message_board . ($modSettings['postmod_active'] && !$show_all ? ' AND m.approved = {int:is_approved} AND t.approved = {int:is_approved}' : '') . ' ORDER BY m.id_msg {raw:ascdesc} LIMIT {int:limit}', array( 'limit' => $context['xmlnews_limit'], 'start_after' => !empty($context['posts_start']) ? $context['posts_start'] : 0, 'uid' => $context['xmlnews_uid'], 'is_approved' => 1, 'ascdesc' => !empty($ascending) ? 'ASC' : 'DESC', ) ); $data = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { $context['last'] = $row['id_msg']; // We want a readable version of the IP address $row['poster_ip'] = inet_dtop($row['poster_ip']); // If any control characters slipped in somehow, kill the evil things $row = filter_var($row, FILTER_CALLBACK, array('options' => 'cleanXml')); // If using our own format, we want both the raw and the parsed content. $row[$xml_format === 'smf' ? 'body_html' : 'body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); // Do we want to include any attachments? if (!empty($modSettings['attachmentEnable']) && !empty($modSettings['xmlnews_attachments'])) { $attach_request = $smcFunc['db_query']('', ' SELECT a.id_attach, a.filename, COALESCE(a.size, 0) AS filesize, a.mime_type, a.downloads, a.approved, m.id_topic AS topic FROM {db_prefix}attachments AS a LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) WHERE a.attachment_type = {int:attachment_type} AND a.id_msg = {int:message_id}', array( 'message_id' => $row['id_msg'], 'attachment_type' => 0, 'is_approved' => 1, ) ); $loaded_attachments = array(); while ($attach = $smcFunc['db_fetch_assoc']($attach_request)) { // Include approved attachments only, unless showing all. if ($attach['approved'] || $show_all) $loaded_attachments['attachment_' . $attach['id_attach']] = $attach; } $smcFunc['db_free_result']($attach_request); // Sort the attachments by size to make things easier below if (!empty($loaded_attachments)) { uasort( $loaded_attachments, function($a, $b) { if ($a['filesize'] == $b['filesize']) return 0; return ($a['filesize'] < $b['filesize']) ? -1 : 1; } ); } else $loaded_attachments = null; } else $loaded_attachments = null; // Create a GUID for this post using the tag URI scheme $guid = 'tag:' . parse_iri($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['poster_time']) . ':msg=' . $row['id_msg']; if ($xml_format == 'rss' || $xml_format == 'rss2') { // Only one attachment allowed in RSS. if ($loaded_attachments !== null) { $attachment = array_pop($loaded_attachments); $enclosure = array( 'url' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']), 'length' => $attachment['filesize'], 'type' => $attachment['mime_type'], ); } else $enclosure = null; $data[] = array( 'tag' => 'item', 'content' => array( array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'link', 'content' => $scripturl . '?msg=' . $row['id_msg'], ), array( 'tag' => 'description', 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'author', 'content' => (allowedTo('moderate_forum') || ($row['id_member'] == $user_info['id'])) ? $row['poster_email'] : null, 'cdata' => true, ), array( 'tag' => 'category', 'content' => $boardnames[$row['id_board']], 'cdata' => true, ), array( 'tag' => 'comments', 'content' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0', ), array( 'tag' => 'pubDate', 'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']), ), array( 'tag' => 'guid', 'content' => $guid, 'attributes' => array( 'isPermaLink' => 'false', ), ), array( 'tag' => 'enclosure', 'attributes' => $enclosure, ), ), ); } elseif ($xml_format == 'rdf') { $data[] = array( 'tag' => 'item', 'attributes' => array('rdf:about' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg']), 'content' => array( array( 'tag' => 'dc:format', 'content' => 'text/html', ), array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'link', 'content' => $scripturl . '?msg=' . $row['id_msg'], ), array( 'tag' => 'description', 'content' => $row['body'], 'cdata' => true, ), ), ); } elseif ($xml_format == 'atom') { // Only one attachment allowed if (!empty($loaded_attachments)) { $attachment = array_pop($loaded_attachments); $enclosure = array( 'rel' => 'enclosure', 'href' => fix_possible_url($scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach']), 'length' => $attachment['filesize'], 'type' => $attachment['mime_type'], ); } else $enclosure = null; $data[] = array( 'tag' => 'entry', 'content' => array( array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'link', 'attributes' => array( 'rel' => 'alternate', 'type' => 'text/html', 'href' => $scripturl . '?msg=' . $row['id_msg'], ), ), array( 'tag' => 'summary', 'attributes' => array('type' => 'html'), 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'author', 'content' => array( array( 'tag' => 'name', 'content' => $poster_name, 'cdata' => true, ), array( 'tag' => 'email', 'content' => (allowedTo('moderate_forum') || ($row['id_member'] == $user_info['id'])) ? $row['poster_email'] : null, 'cdata' => true, ), array( 'tag' => 'uri', 'content' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : null, ), ), ), array( 'tag' => 'published', 'content' => smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']), ), array( 'tag' => 'updated', 'content' => smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']), ), array( 'tag' => 'id', 'content' => $guid, ), array( 'tag' => 'link', 'attributes' => $enclosure, ), ), ); } // A lot of information here. Should be enough to please the rss-ers. else { loadLanguage('Post'); $attachments = array(); if (!empty($loaded_attachments)) { foreach ($loaded_attachments as $attachment) { $attachments[] = array( 'tag' => 'attachment', 'attributes' => array('label' => $txt['attachment']), 'content' => array( array( 'tag' => 'id', 'content' => $attachment['id_attach'], ), array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])), ), array( 'tag' => 'downloads', 'attributes' => array('label' => $txt['downloads']), 'content' => $attachment['downloads'], ), array( 'tag' => 'size', 'attributes' => array('label' => $txt['filesize']), 'content' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'], ), array( 'tag' => 'byte_size', 'attributes' => array('label' => $txt['filesize']), 'content' => $attachment['filesize'], ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'], ), array( 'tag' => 'approval_status', 'attributes' => $show_all ? array('label' => $txt['approval_status']) : null, 'content' => $show_all ? $attachment['approved'] : null, ), ) ); } } else $attachments = null; $data[] = array( 'tag' => 'member_post', 'attributes' => array('label' => $txt['post']), 'content' => array( array( 'tag' => 'id', 'content' => $row['id_msg'], ), array( 'tag' => 'subject', 'attributes' => array('label' => $txt['subject']), 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'body', 'attributes' => array('label' => $txt['message']), 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'body_html', 'attributes' => array('label' => $txt['html']), 'content' => $row['body_html'], 'cdata' => true, ), array( 'tag' => 'poster', 'attributes' => array('label' => $txt['author']), 'content' => array( array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => $poster_name, 'cdata' => true, ), array( 'tag' => 'id', 'content' => $row['id_member'], ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?action=profile;u=' . $row['id_member'], ), array( 'tag' => 'email', 'attributes' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? array('label' => $txt['user_email_address']) : null, 'content' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? $row['poster_email'] : null, 'cdata' => true, ), array( 'tag' => 'ip', 'attributes' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? array('label' => $txt['ip']) : null, 'content' => (allowedTo('moderate_forum') || $row['id_member'] == $user_info['id']) ? $row['poster_ip'] : null, ), ), ), array( 'tag' => 'topic', 'attributes' => array('label' => $txt['topic']), 'content' => array( array( 'tag' => 'id', 'content' => $row['id_topic'], ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?topic=' . $row['id_topic'] . '.0', ), ), ), array( 'tag' => 'board', 'attributes' => array('label' => $txt['board']), 'content' => array( array( 'tag' => 'id', 'content' => $row['id_board'], ), array( 'tag' => 'name', 'content' => $boardnames[$row['id_board']], 'cdata' => true, ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?board=' . $row['id_board'] . '.0', ), ), ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?msg=' . $row['id_msg'], ), array( 'tag' => 'time', 'attributes' => array('label' => $txt['date'], 'UTC' => smf_gmstrftime('%F %T', $row['poster_time'])), 'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['poster_time'], false, 'forum'))), ), array( 'tag' => 'modified_time', 'attributes' => !empty($row['modified_time']) ? array('label' => $txt['modified_time'], 'UTC' => smf_gmstrftime('%F %T', $row['modified_time'])) : null, 'content' => !empty($row['modified_time']) ? $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['modified_time'], false, 'forum'))) : null, ), array( 'tag' => 'modified_by', 'attributes' => !empty($row['modified_name']) ? array('label' => $txt['modified_by']) : null, 'content' => !empty($row['modified_name']) ? $row['modified_name'] : null, 'cdata' => true, ), array( 'tag' => 'modified_reason', 'attributes' => !empty($row['modified_reason']) ? array('label' => $txt['reason_for_edit']) : null, 'content' => !empty($row['modified_reason']) ? $row['modified_reason'] : null, 'cdata' => true, ), array( 'tag' => 'likes', 'attributes' => array('label' => $txt['likes']), 'content' => $row['likes'], ), array( 'tag' => 'approval_status', 'attributes' => $show_all ? array('label' => $txt['approval_status']) : null, 'content' => $show_all ? $row['approved'] : null, ), array( 'tag' => 'attachments', 'attributes' => array('label' => $txt['attachments']), 'content' => $attachments, ), ), ); } } $smcFunc['db_free_result']($request); return $data; } /** * Get a user's personal messages. * Only the user can do this, and no one else -- not even the admin! * * @param string $xml_format The XML format. Can be 'atom', 'rdf', 'rss', 'rss2' or 'smf' * @param bool $ascending If true, get the oldest PMs first. Default false. * @return array An array of arrays containing data for the feed. Each array has keys corresponding to the appropriate tags for the specified format. */ function getXmlPMs($xml_format, $ascending = false) { global $scripturl, $modSettings, $board, $txt, $context, $user_info; global $smcFunc, $sourcedir, $cachedir; // Personal messages are supposed to be private if (empty($context['xmlnews_uid']) || ($context['xmlnews_uid'] != $user_info['id'])) return array(); $select_id_members_to = $smcFunc['db_title'] === POSTGRE_TITLE ? "string_agg(pmr.id_member::text, ',')" : 'GROUP_CONCAT(pmr.id_member)'; $select_to_names = $smcFunc['db_title'] === POSTGRE_TITLE ? "string_agg(COALESCE(mem.real_name, mem.member_name), ',')" : 'GROUP_CONCAT(COALESCE(mem.real_name, mem.member_name))'; $request = $smcFunc['db_query']('', ' SELECT pm.id_pm, pm.msgtime, pm.subject, pm.body, pm.id_member_from, nis.from_name, nis.id_members_to, nis.to_names FROM {db_prefix}personal_messages AS pm INNER JOIN ( SELECT pm2.id_pm, COALESCE(memf.real_name, pm2.from_name) AS from_name, ' . $select_id_members_to . ' AS id_members_to, ' . $select_to_names . ' AS to_names FROM {db_prefix}personal_messages AS pm2 INNER JOIN {db_prefix}pm_recipients AS pmr ON (pm2.id_pm = pmr.id_pm) INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member) LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = pm2.id_member_from) WHERE pm2.id_pm > {int:start_after} AND ( (pm2.id_member_from = {int:uid} AND pm2.deleted_by_sender = {int:not_deleted}) OR (pmr.id_member = {int:uid} AND pmr.deleted = {int:not_deleted}) ) GROUP BY pm2.id_pm, COALESCE(memf.real_name, pm2.from_name) ORDER BY pm2.id_pm {raw:ascdesc} LIMIT {int:limit} ) AS nis ON nis.id_pm = pm.id_pm ORDER BY pm.id_pm {raw:ascdesc}', array( 'limit' => $context['xmlnews_limit'], 'start_after' => !empty($context['personal_messages_start']) ? $context['personal_messages_start'] : 0, 'uid' => $context['xmlnews_uid'], 'not_deleted' => 0, 'ascdesc' => !empty($ascending) ? 'ASC' : 'DESC', ) ); $data = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { $context['personal_messages_start'] = $row['id_pm']; // If any control characters slipped in somehow, kill the evil things $row = filter_var($row, FILTER_CALLBACK, array('options' => 'cleanXml')); // If using our own format, we want both the raw and the parsed content. $row[$xml_format === 'smf' ? 'body_html' : 'body'] = parse_bbc($row['body']); $recipients = array_combine(explode(',', $row['id_members_to']), explode(',', $row['to_names'])); // Create a GUID for this post using the tag URI scheme $guid = 'tag:' . parse_iri($scripturl, PHP_URL_HOST) . ',' . gmdate('Y-m-d', $row['msgtime']) . ':pm=' . $row['id_pm']; if ($xml_format == 'rss' || $xml_format == 'rss2') { $item = array( 'tag' => 'item', 'content' => array( array( 'tag' => 'guid', 'content' => $guid, 'attributes' => array( 'isPermaLink' => 'false', ), ), array( 'tag' => 'pubDate', 'content' => gmdate('D, d M Y H:i:s \G\M\T', $row['msgtime']), ), array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'description', 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'smf:sender', // This technically violates the RSS spec, but meh... 'content' => $row['from_name'], 'cdata' => true, ), ), ); foreach ($recipients as $recipient_id => $recipient_name) $item['content'][] = array( 'tag' => 'smf:recipient', 'content' => $recipient_name, 'cdata' => true, ); $data[] = $item; } elseif ($xml_format == 'rdf') { $data[] = array( 'tag' => 'item', 'attributes' => array('rdf:about' => $scripturl . '?action=pm#msg' . $row['id_pm']), 'content' => array( array( 'tag' => 'dc:format', 'content' => 'text/html', ), array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'link', 'content' => $scripturl . '?action=pm#msg' . $row['id_pm'], ), array( 'tag' => 'description', 'content' => $row['body'], 'cdata' => true, ), ), ); } elseif ($xml_format == 'atom') { $item = array( 'tag' => 'entry', 'content' => array( array( 'tag' => 'id', 'content' => $guid, ), array( 'tag' => 'updated', 'content' => smf_gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['msgtime']), ), array( 'tag' => 'title', 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'content', 'attributes' => array('type' => 'html'), 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'author', 'content' => array( array( 'tag' => 'name', 'content' => $row['from_name'], 'cdata' => true, ), ), ), ), ); foreach ($recipients as $recipient_id => $recipient_name) $item['content'][] = array( 'tag' => 'contributor', 'content' => array( array( 'tag' => 'smf:role', 'content' => 'recipient', ), array( 'tag' => 'name', 'content' => $recipient_name, 'cdata' => true, ), ), ); $data[] = $item; } else { loadLanguage('PersonalMessage'); $item = array( 'tag' => 'personal_message', 'attributes' => array('label' => $txt['pm']), 'content' => array( array( 'tag' => 'id', 'content' => $row['id_pm'], ), array( 'tag' => 'sent_date', 'attributes' => array('label' => $txt['date'], 'UTC' => smf_gmstrftime('%F %T', $row['msgtime'])), 'content' => $smcFunc['htmlspecialchars'](strip_tags(timeformat($row['msgtime'], false, 'forum'))), ), array( 'tag' => 'subject', 'attributes' => array('label' => $txt['subject']), 'content' => $row['subject'], 'cdata' => true, ), array( 'tag' => 'body', 'attributes' => array('label' => $txt['message']), 'content' => $row['body'], 'cdata' => true, ), array( 'tag' => 'body_html', 'attributes' => array('label' => $txt['html']), 'content' => $row['body_html'], 'cdata' => true, ), array( 'tag' => 'sender', 'attributes' => array('label' => $txt['author']), 'content' => array( array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => $row['from_name'], 'cdata' => true, ), array( 'tag' => 'id', 'content' => $row['id_member_from'], ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?action=profile;u=' . $row['id_member_from'], ), ), ), ), ); foreach ($recipients as $recipient_id => $recipient_name) $item['content'][] = array( 'tag' => 'recipient', 'attributes' => array('label' => $txt['recipient']), 'content' => array( array( 'tag' => 'name', 'attributes' => array('label' => $txt['name']), 'content' => $recipient_name, 'cdata' => true, ), array( 'tag' => 'id', 'content' => $recipient_id, ), array( 'tag' => 'link', 'attributes' => array('label' => $txt['url']), 'content' => $scripturl . '?action=profile;u=' . $recipient_id, ), ), ); $data[] = $item; } } $smcFunc['db_free_result']($request); return $data; } ?>