qa-feed.php 12.1 KB
Newer Older
Scott committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
<?php
/*
	Question2Answer by Gideon Greenspan and contributors
	http://www.question2answer.org/

	Description: Handles all requests to RSS feeds, first checking if they should be available


	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	More about this license: http://www.question2answer.org/license.php
*/

Scott committed
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
	header('Location: ../');
	exit;
}

@ini_set('display_errors', 0); // we don't want to show PHP errors to RSS readers

qa_report_process_stage('init_feed');

require_once QA_INCLUDE_DIR . 'app/options.php';


// Functions used within this file

/**
 * Database failure handler function for RSS feeds - outputs HTTP and text errors
Scott committed
38 39 40 41
 * @param $type
 * @param int $errno
 * @param string $error
 * @param string $query
Scott committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
 */
function qa_feed_db_fail_handler($type, $errno = null, $error = null, $query = null)
{
	header('HTTP/1.1 500 Internal Server Error');
	echo qa_lang_html('main/general_error');
	qa_exit('error');
}


/**
 * Common function called when a non-existent feed is requested - outputs HTTP and text errors
 */
function qa_feed_not_found()
{
	header('HTTP/1.0 404 Not Found');
	echo qa_lang_html('misc/feed_not_found');
	qa_exit();
}


/**
 * Common function to load appropriate set of questions for requested feed, check category exists, and set up page title
Scott committed
64 65 66 67 68 69 70 71 72
 * @param array $categoryslugs
 * @param string $allkey
 * @param string $catkey
 * @param string $title
 * @param array $questionselectspec1
 * @param array $questionselectspec2
 * @param array $questionselectspec3
 * @param array $questionselectspec4
 * @return array
Scott committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
 */
function qa_feed_load_ifcategory($categoryslugs, $allkey, $catkey, &$title,
	$questionselectspec1 = null, $questionselectspec2 = null, $questionselectspec3 = null, $questionselectspec4 = null)
{
	$countslugs = @count($categoryslugs);

	list($questions1, $questions2, $questions3, $questions4, $categories, $categoryid) = qa_db_select_with_pending(
		$questionselectspec1,
		$questionselectspec2,
		$questionselectspec3,
		$questionselectspec4,
		$countslugs ? qa_db_category_nav_selectspec($categoryslugs, false) : null,
		$countslugs ? qa_db_slugs_to_category_id_selectspec($categoryslugs) : null
	);

	if ($countslugs && !isset($categoryid))
		qa_feed_not_found();
Scott committed
90

Scott committed
91 92
	if (isset($allkey))
		$title = (isset($categoryid) && isset($catkey)) ? qa_lang_sub($catkey, $categories[$categoryid]['title']) : qa_lang($allkey);
Scott committed
93

Scott committed
94 95 96 97 98 99 100
	return array_merge(
		is_array($questions1) ? $questions1 : array(),
		is_array($questions2) ? $questions2 : array(),
		is_array($questions3) ? $questions3 : array(),
		is_array($questions4) ? $questions4 : array()
	);
}
Scott committed
101 102


Scott committed
103
// Connect to database and get the type of feed and category requested (in some cases these are overridden later)
Scott committed
104

Scott committed
105
qa_db_connect('qa_feed_db_fail_handler');
106
qa_initialize_postdb_plugins();
Scott committed
107

Scott committed
108 109
$requestlower = strtolower(qa_request());
$foursuffix = substr($requestlower, -4);
Scott committed
110

Scott committed
111 112 113
if ($foursuffix == '.rss' || $foursuffix == '.xml') {
	$requestlower = substr($requestlower, 0, -4);
}
Scott committed
114

Scott committed
115
$requestlowerparts = explode('/', $requestlower);
Scott committed
116

Scott committed
117 118
$feedtype = @$requestlowerparts[1];
$feedparams = array_slice($requestlowerparts, 2);
Scott committed
119 120


Scott committed
121
// Choose which option needs to be checked to determine if this feed can be requested, and stop if no matches
Scott committed
122

Scott committed
123 124
$feedoption = null;
$categoryslugs = $feedparams;
Scott committed
125

Scott committed
126 127 128 129
switch ($feedtype) {
	case 'questions':
		$feedoption = 'feed_for_questions';
		break;
Scott committed
130

Scott committed
131 132 133 134 135
	case 'hot':
		$feedoption = 'feed_for_hot';
		if (!QA_ALLOW_UNINDEXED_QUERIES)
			$categoryslugs = null;
		break;
Scott committed
136

Scott committed
137 138 139 140 141
	case 'unanswered':
		$feedoption = 'feed_for_unanswered';
		if (!QA_ALLOW_UNINDEXED_QUERIES)
			$categoryslugs = null;
		break;
Scott committed
142

Scott committed
143 144 145 146 147
	case 'answers':
	case 'comments':
	case 'activity':
		$feedoption = 'feed_for_activity';
		break;
Scott committed
148

Scott committed
149 150 151
	case 'qa':
		$feedoption = 'feed_for_qa';
		break;
Scott committed
152

Scott committed
153 154 155 156 157 158
	case 'tag':
		if (strlen(@$feedparams[0])) {
			$feedoption = 'feed_for_tag_qs';
			$categoryslugs = null;
		}
		break;
Scott committed
159

Scott committed
160 161 162 163 164 165 166
	case 'search':
		if (strlen(@$feedparams[0])) {
			$feedoption = 'feed_for_search';
			$categoryslugs = null;
		}
		break;
}
Scott committed
167

Scott committed
168
$countslugs = @count($categoryslugs);
Scott committed
169

Scott committed
170 171
if (!isset($feedoption))
	qa_feed_not_found();
Scott committed
172 173


Scott committed
174
// Check that all the appropriate options are in place to allow this feed to be retrieved
Scott committed
175

Scott committed
176 177
if (!(qa_opt($feedoption) && ($countslugs ? (qa_using_categories() && qa_opt('feed_per_category')) : true)))
	qa_feed_not_found();
Scott committed
178 179


Scott committed
180
// Retrieve the appropriate questions and other information for this feed
Scott committed
181

Scott committed
182
require_once QA_INCLUDE_DIR . 'db/selects.php';
Scott committed
183

Scott committed
184 185 186 187 188
$sitetitle = qa_opt('site_title');
$siteurl = qa_opt('site_url');
$full = qa_opt('feed_full_text');
$count = qa_opt('feed_number_items');
$showurllinks = qa_opt('show_url_links');
Scott committed
189

Scott committed
190 191
$linkrequest = $feedtype . ($countslugs ? ('/' . implode('/', $categoryslugs)) : '');
$linkparams = null;
Scott committed
192

Scott committed
193 194 195 196 197 198
switch ($feedtype) {
	case 'questions':
		$questions = qa_feed_load_ifcategory($categoryslugs, 'main/recent_qs_title', 'main/recent_qs_in_x', $title,
			qa_db_qs_selectspec(null, 'created', 0, $categoryslugs, null, false, $full, $count)
		);
		break;
Scott committed
199

Scott committed
200 201 202 203 204
	case 'hot':
		$questions = qa_feed_load_ifcategory($categoryslugs, 'main/hot_qs_title', 'main/hot_qs_in_x', $title,
			qa_db_qs_selectspec(null, 'hotness', 0, $categoryslugs, null, false, $full, $count)
		);
		break;
Scott committed
205

Scott committed
206 207 208 209 210
	case 'unanswered':
		$questions = qa_feed_load_ifcategory($categoryslugs, 'main/unanswered_qs_title', 'main/unanswered_qs_in_x', $title,
			qa_db_unanswered_qs_selectspec(null, null, 0, $categoryslugs, false, $full, $count)
		);
		break;
Scott committed
211

Scott committed
212 213 214 215 216
	case 'answers':
		$questions = qa_feed_load_ifcategory($categoryslugs, 'main/recent_as_title', 'main/recent_as_in_x', $title,
			qa_db_recent_a_qs_selectspec(null, 0, $categoryslugs, null, false, $full, $count)
		);
		break;
Scott committed
217

Scott committed
218 219 220 221 222
	case 'comments':
		$questions = qa_feed_load_ifcategory($categoryslugs, 'main/recent_cs_title', 'main/recent_cs_in_x', $title,
			qa_db_recent_c_qs_selectspec(null, 0, $categoryslugs, null, false, $full, $count)
		);
		break;
Scott committed
223

Scott committed
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
	case 'qa':
		$questions = qa_feed_load_ifcategory($categoryslugs, 'main/recent_qs_as_title', 'main/recent_qs_as_in_x', $title,
			qa_db_qs_selectspec(null, 'created', 0, $categoryslugs, null, false, $full, $count),
			qa_db_recent_a_qs_selectspec(null, 0, $categoryslugs, null, false, $full, $count)
		);
		break;

	case 'activity':
		$questions = qa_feed_load_ifcategory($categoryslugs, 'main/recent_activity_title', 'main/recent_activity_in_x', $title,
			qa_db_qs_selectspec(null, 'created', 0, $categoryslugs, null, false, $full, $count),
			qa_db_recent_a_qs_selectspec(null, 0, $categoryslugs, null, false, $full, $count),
			qa_db_recent_c_qs_selectspec(null, 0, $categoryslugs, null, false, $full, $count),
			qa_db_recent_edit_qs_selectspec(null, 0, $categoryslugs, null, true, $full, $count)
		);
		break;
Scott committed
239

Scott committed
240 241
	case 'tag':
		$tag = $feedparams[0];
Scott committed
242

Scott committed
243 244 245
		$questions = qa_feed_load_ifcategory(null, null, null, $title,
			qa_db_tag_recent_qs_selectspec(null, $tag, 0, $full, $count)
		);
Scott committed
246

Scott committed
247 248 249
		$title = qa_lang_sub('main/questions_tagged_x', $tag);
		$linkrequest = 'tag/' . $tag;
		break;
Scott committed
250

Scott committed
251 252
	case 'search':
		require_once QA_INCLUDE_DIR . 'app/search.php';
Scott committed
253

Scott committed
254
		$query = $feedparams[0];
Scott committed
255

Scott committed
256
		$results = qa_get_search_results($query, 0, $count, null, true, $full);
Scott committed
257

Scott committed
258 259 260
		$title = qa_lang_sub('main/results_for_x', $query);
		$linkrequest = 'search';
		$linkparams = array('q' => $query);
Scott committed
261

Scott committed
262
		$questions = array();
Scott committed
263

Scott committed
264 265 266 267
		foreach ($results as $result) {
			$setarray = array(
				'title' => $result['title'],
				'url' => $result['url'],
Scott committed
268 269
			);

Scott committed
270 271 272 273 274 275 276
			if (isset($result['question']))
				$questions[] = array_merge($result['question'], $setarray);
			elseif (isset($result['url']))
				$questions[] = $setarray;
		}
		break;
}
Scott committed
277 278


Scott committed
279
// Remove duplicate questions (perhaps referenced in an answer and a comment) and cut down to size
Scott committed
280

Scott committed
281 282 283
require_once QA_INCLUDE_DIR . 'app/format.php';
require_once QA_INCLUDE_DIR . 'app/updates.php';
require_once QA_INCLUDE_DIR . 'util/string.php';
Scott committed
284

Scott committed
285 286
if ($feedtype != 'search' && $feedtype != 'hot') // leave search results and hot questions sorted by relevance
	$questions = qa_any_sort_and_dedupe($questions);
Scott committed
287

Scott committed
288 289
$questions = array_slice($questions, 0, $count);
$blockwordspreg = qa_get_block_words_preg();
Scott committed
290 291


Scott committed
292
// Prepare the XML output
Scott committed
293

Scott committed
294
$lines = array();
Scott committed
295

Scott committed
296 297 298
$lines[] = '<?xml version="1.0" encoding="utf-8"?>';
$lines[] = '<rss version="2.0">';
$lines[] = '<channel>';
Scott committed
299

Scott committed
300 301 302
$lines[] = '<title>' . qa_xml($sitetitle . ' - ' . $title) . '</title>';
$lines[] = '<link>' . qa_xml(qa_path($linkrequest, $linkparams, $siteurl)) . '</link>';
$lines[] = '<description>Powered by Question2Answer</description>';
Scott committed
303

Scott committed
304 305 306
foreach ($questions as $question) {
	// Determine whether this is a question, answer or comment, and act accordingly
	$options = array('blockwordspreg' => @$blockwordspreg, 'showurllinks' => $showurllinks);
Scott committed
307

Scott committed
308 309
	$time = null;
	$htmlcontent = null;
Scott committed
310

Scott committed
311 312
	if (isset($question['opostid'])) {
		$time = $question['otime'];
Scott committed
313

Scott committed
314 315
		if ($full)
			$htmlcontent = qa_viewer_html($question['ocontent'], $question['oformat'], $options);
Scott committed
316

Scott committed
317 318
	} elseif (isset($question['postid'])) {
		$time = $question['created'];
Scott committed
319

Scott committed
320 321 322
		if ($full)
			$htmlcontent = qa_viewer_html($question['content'], $question['format'], $options);
	}
Scott committed
323

Scott committed
324 325 326
	if ($feedtype == 'search') {
		$titleprefix = '';
		$urlxml = qa_xml($question['url']);
Scott committed
327

Scott committed
328 329 330 331 332 333
	} else {
		switch (@$question['obasetype'] . '-' . @$question['oupdatetype']) {
			case 'Q-':
			case '-':
				$langstring = null;
				break;
Scott committed
334

Scott committed
335 336 337
			case 'Q-' . QA_UPDATE_VISIBLE:
				$langstring = $question['hidden'] ? 'misc/feed_hidden_prefix' : 'misc/feed_reshown_prefix';
				break;
Scott committed
338

Scott committed
339 340 341
			case 'Q-' . QA_UPDATE_CLOSED:
				$langstring = isset($question['closedbyid']) ? 'misc/feed_closed_prefix' : 'misc/feed_reopened_prefix';
				break;
Scott committed
342

Scott committed
343 344 345
			case 'Q-' . QA_UPDATE_TAGS:
				$langstring = 'misc/feed_retagged_prefix';
				break;
Scott committed
346

Scott committed
347 348 349
			case 'Q-' . QA_UPDATE_CATEGORY:
				$langstring = 'misc/feed_recategorized_prefix';
				break;
Scott committed
350

Scott committed
351 352 353
			case 'A-':
				$langstring = 'misc/feed_a_prefix';
				break;
Scott committed
354

Scott committed
355 356 357
			case 'A-' . QA_UPDATE_SELECTED:
				$langstring = 'misc/feed_a_selected_prefix';
				break;
Scott committed
358

Scott committed
359 360 361
			case 'A-' . QA_UPDATE_VISIBLE:
				$langstring = $question['ohidden'] ? 'misc/feed_hidden_prefix' : 'misc/feed_a_reshown_prefix';
				break;
Scott committed
362

Scott committed
363 364 365
			case 'A-' . QA_UPDATE_CONTENT:
				$langstring = 'misc/feed_a_edited_prefix';
				break;
Scott committed
366

Scott committed
367 368 369
			case 'C-':
				$langstring = 'misc/feed_c_prefix';
				break;
Scott committed
370

Scott committed
371 372 373
			case 'C-' . QA_UPDATE_TYPE:
				$langstring = 'misc/feed_c_moved_prefix';
				break;
Scott committed
374

Scott committed
375 376 377
			case 'C-' . QA_UPDATE_VISIBLE:
				$langstring = $question['ohidden'] ? 'misc/feed_hidden_prefix' : 'misc/feed_c_reshown_prefix';
				break;
Scott committed
378

Scott committed
379 380 381
			case 'C-' . QA_UPDATE_CONTENT:
				$langstring = 'misc/feed_c_edited_prefix';
				break;
Scott committed
382

Scott committed
383 384 385 386
			case 'Q-' . QA_UPDATE_CONTENT:
			default:
				$langstring = 'misc/feed_edited_prefix';
				break;
Scott committed
387 388 389

		}

Scott committed
390
		$titleprefix = isset($langstring) ? qa_lang($langstring) : '';
Scott committed
391

Scott committed
392 393
		$urlxml = qa_xml(qa_q_path($question['postid'], $question['title'], true, @$question['obasetype'], @$question['opostid']));
	}
Scott committed
394

Scott committed
395 396
	if (isset($blockwordspreg))
		$question['title'] = qa_block_words_replace($question['title'], $blockwordspreg);
Scott committed
397

Scott committed
398
	// Build the inner XML structure for each item
Scott committed
399

Scott committed
400 401 402
	$lines[] = '<item>';
	$lines[] = '<title>' . qa_xml($titleprefix . $question['title']) . '</title>';
	$lines[] = '<link>' . $urlxml . '</link>';
Scott committed
403

Scott committed
404 405
	if (isset($htmlcontent))
		$lines[] = '<description>' . qa_xml($htmlcontent) . '</description>';
Scott committed
406

Scott committed
407 408
	if (isset($question['categoryname']))
		$lines[] = '<category>' . qa_xml($question['categoryname']) . '</category>';
Scott committed
409

Scott committed
410
	$lines[] = '<guid isPermaLink="true">' . $urlxml . '</guid>';
Scott committed
411

Scott committed
412 413
	if (isset($time))
		$lines[] = '<pubDate>' . qa_xml(gmdate('r', $time)) . '</pubDate>';
Scott committed
414

Scott committed
415 416
	$lines[] = '</item>';
}
Scott committed
417

Scott committed
418 419
$lines[] = '</channel>';
$lines[] = '</rss>';
Scott committed
420 421


Scott committed
422
// Disconnect here, once all output is ready to go
Scott committed
423

Scott committed
424
qa_db_disconnect();
Scott committed
425 426


Scott committed
427
// Output the XML - and we're done!
Scott committed
428

Scott committed
429 430
header('Content-type: text/xml; charset=utf-8');
echo implode("\n", $lines);