question.php 16.4 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 22
<?php
/*
	Question2Answer by Gideon Greenspan and contributors
	http://www.question2answer.org/

	File: qa-include/qa-page-question.php
	Description: Controller for question page (only viewing functionality here)


	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
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
	header('Location: ../');
	exit;
}

require_once QA_INCLUDE_DIR . 'app/cookies.php';
require_once QA_INCLUDE_DIR . 'app/format.php';
require_once QA_INCLUDE_DIR . 'db/selects.php';
require_once QA_INCLUDE_DIR . 'util/sort.php';
require_once QA_INCLUDE_DIR . 'util/string.php';
require_once QA_INCLUDE_DIR . 'app/captcha.php';
require_once QA_INCLUDE_DIR . 'pages/question-view.php';
require_once QA_INCLUDE_DIR . 'app/updates.php';

$questionid = qa_request_part(0);
$userid = qa_get_logged_in_userid();
$cookieid = qa_cookie_get();
$pagestate = qa_get_state();
Scott committed
41 42 43 44


//	Get information about this question

Scott committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
$cacheHandler = Q2A_Storage_CacheManager::getInstance();
$cacheKey = "page:question:$questionid";
$useCache = $userid === null && $cacheHandler->isEnabled() && !qa_is_http_post() && empty($pagestate);
$saveCache = false;

if ($useCache) {
	$questionData = $cacheHandler->get($cacheKey);
}

if (!isset($questionData)) {
	$questionData = qa_db_select_with_pending(
		qa_db_full_post_selectspec($userid, $questionid),
		qa_db_full_child_posts_selectspec($userid, $questionid),
		qa_db_full_a_child_posts_selectspec($userid, $questionid),
		qa_db_post_parent_q_selectspec($questionid),
		qa_db_post_close_post_selectspec($questionid),
		qa_db_post_duplicates_selectspec($questionid),
		qa_db_post_meta_selectspec($questionid, 'qa_q_extra'),
		qa_db_category_nav_selectspec($questionid, true, true, true),
		isset($userid) ? qa_db_is_favorite_selectspec($userid, QA_ENTITY_QUESTION, $questionid) : null
	);
Scott committed
66

Scott committed
67 68 69
	// whether to save the cache (actioned below, after basic checks)
	$saveCache = $useCache;
}
Scott committed
70

Scott committed
71
list($question, $childposts, $achildposts, $parentquestion, $closepost, $duplicateposts, $extravalue, $categories, $favorite) = $questionData;
Scott committed
72

Scott committed
73

Scott committed
74 75
if ($question['basetype'] != 'Q') // don't allow direct viewing of other types of post
	$question = null;
Scott committed
76

Scott committed
77 78
if (isset($question)) {
	$q_request = qa_q_request($questionid, $question['title']);
Amiya committed
79

Scott committed
80 81 82 83
	if (trim($q_request, '/') !== trim(qa_request(), '/')) {
		// redirect if the current URL is incorrect
		qa_redirect($q_request);
	}
Amiya committed
84

Scott committed
85
	$question['extra'] = $extravalue;
Scott committed
86

Scott committed
87 88
	$answers = qa_page_q_load_as($question, $childposts);
	$commentsfollows = qa_page_q_load_c_follows($question, $childposts, $achildposts, $duplicateposts);
Scott committed
89

Scott committed
90
	$question = $question + qa_page_q_post_rules($question, null, null, $childposts + $duplicateposts); // array union
Scott committed
91

Scott committed
92 93
	if ($question['selchildid'] && (@$answers[$question['selchildid']]['type'] != 'A'))
		$question['selchildid'] = null; // if selected answer is hidden or somehow not there, consider it not selected
Scott committed
94

Scott committed
95 96 97 98
	foreach ($answers as $key => $answer) {
		$answers[$key] = $answer + qa_page_q_post_rules($answer, $question, $answers, $achildposts);
		$answers[$key]['isselected'] = ($answer['postid'] == $question['selchildid']);
	}
Scott committed
99

Scott committed
100 101 102
	foreach ($commentsfollows as $key => $commentfollow) {
		$parent = ($commentfollow['parentid'] == $questionid) ? $question : @$answers[$commentfollow['parentid']];
		$commentsfollows[$key] = $commentfollow + qa_page_q_post_rules($commentfollow, $parent, $commentsfollows, null);
Scott committed
103
	}
Scott committed
104
}
Scott committed
105 106 107

//	Deal with question not found or not viewable, otherwise report the view event

Scott committed
108 109
if (!isset($question))
	return include QA_INCLUDE_DIR . 'qa-page-not-found.php';
Scott committed
110

Scott committed
111 112
if (!$question['viewable']) {
	$qa_content = qa_content_prepare();
Scott committed
113

Scott committed
114 115 116 117 118 119 120 121
	if ($question['queued'])
		$qa_content['error'] = qa_lang_html('question/q_waiting_approval');
	elseif ($question['flagcount'] && !isset($question['lastuserid']))
		$qa_content['error'] = qa_lang_html('question/q_hidden_flagged');
	elseif ($question['authorlast'])
		$qa_content['error'] = qa_lang_html('question/q_hidden_author');
	else
		$qa_content['error'] = qa_lang_html('question/q_hidden_other');
Scott committed
122

Scott committed
123
	$qa_content['suggest_next'] = qa_html_suggest_qs_tags(qa_using_tags());
Scott committed
124

Scott committed
125 126
	return $qa_content;
}
Scott committed
127

Scott committed
128
$permiterror = qa_user_post_permit_error('permit_view_q_page', $question, null, false);
Scott committed
129

Scott committed
130 131 132
if ($permiterror && (qa_is_human_probably() || !qa_opt('allow_view_q_bots'))) {
	$qa_content = qa_content_prepare();
	$topage = qa_q_request($questionid, $question['title']);
Scott committed
133

Scott committed
134 135 136 137
	switch ($permiterror) {
		case 'login':
			$qa_content['error'] = qa_insert_login_links(qa_lang_html('main/view_q_must_login'), $topage);
			break;
Scott committed
138

Scott committed
139 140 141
		case 'confirm':
			$qa_content['error'] = qa_insert_login_links(qa_lang_html('main/view_q_must_confirm'), $topage);
			break;
Scott committed
142

Scott committed
143 144 145
		case 'approve':
			$qa_content['error'] = qa_lang_html('main/view_q_must_be_approved');
			break;
Scott committed
146

Scott committed
147 148 149
		default:
			$qa_content['error'] = qa_lang_html('users/no_permission');
			break;
Scott committed
150 151
	}

Scott committed
152 153 154
	return $qa_content;
}

Scott committed
155

156
//	Save question data to cache (if older than configured limit)
Scott committed
157

Scott committed
158 159 160 161
if ($saveCache) {
	$questionAge = qa_opt('db_time') - $question['created'];
	if ($questionAge > 86400 * qa_opt('caching_q_start')) {
		$cacheHandler->set($cacheKey, $questionData, qa_opt('caching_q_time'));
Scott committed
162
	}
Scott committed
163
}
Scott committed
164 165


Scott committed
166 167
//	Determine if captchas will be required

Scott committed
168 169
$captchareason = qa_user_captcha_reason(qa_user_level_for_post($question));
$usecaptcha = ($captchareason != false);
Scott committed
170 171 172 173 174


//	If we're responding to an HTTP POST, include file that handles all posting/editing/etc... logic
//	This is in a separate file because it's a *lot* of logic, and will slow down ordinary page views

Scott committed
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
$pagestart = qa_get_start();
$showid = qa_get('show');
$pageerror = null;
$formtype = null;
$formpostid = null;
$jumptoanchor = null;
$commentsall = null;

if (substr($pagestate, 0, 13) == 'showcomments-') {
	$commentsall = substr($pagestate, 13);
	$pagestate = null;

} elseif (isset($showid)) {
	foreach ($commentsfollows as $comment) {
		if ($comment['postid'] == $showid) {
			$commentsall = $comment['parentid'];
			break;
		}
Scott committed
193
	}
Scott committed
194
}
Scott committed
195

Scott committed
196 197
if (qa_is_http_post() || strlen($pagestate))
	require QA_INCLUDE_DIR . 'pages/question-post.php';
Scott committed
198

Scott committed
199
$formrequested = isset($formtype);
Scott committed
200

Scott committed
201 202
if ((!$formrequested) && $question['answerbutton']) {
	$immedoption = qa_opt('show_a_form_immediate');
Scott committed
203

Scott committed
204 205 206
	if (($immedoption == 'always') || (($immedoption == 'if_no_as') && (!$question['isbyuser']) && (!$question['acount'])))
		$formtype = 'a_add'; // show answer form by default
}
Scott committed
207 208 209 210


//	Get information on the users referenced

Scott committed
211
$usershtml = qa_userids_handles_html(array_merge(array($question), $answers, $commentsfollows), true);
Scott committed
212 213 214 215


//	Prepare content for theme

Scott committed
216
$qa_content = qa_content_prepare(true, array_keys(qa_category_path($categories, $question['categoryid'])));
Scott committed
217

Scott committed
218 219 220
if (isset($userid) && !$formrequested)
	$qa_content['favorite'] = qa_favorite_form(QA_ENTITY_QUESTION, $questionid, $favorite,
		qa_lang($favorite ? 'question/remove_q_favorites' : 'question/add_q_favorites'));
Scott committed
221

Scott committed
222
$qa_content['script_rel'][] = 'qa-content/qa-question.js?' . QA_VERSION;
Scott committed
223

Scott committed
224 225
if (isset($pageerror))
	$qa_content['error'] = $pageerror; // might also show voting error set in qa-index.php
Scott committed
226

Scott committed
227 228
elseif ($question['queued'])
	$qa_content['error'] = $question['isbyuser'] ? qa_lang_html('question/q_your_waiting_approval') : qa_lang_html('question/q_waiting_your_approval');
Scott committed
229

Scott committed
230 231
if ($question['hidden'])
	$qa_content['hidden'] = true;
Scott committed
232

Scott committed
233
qa_sort_by($commentsfollows, 'created');
Scott committed
234 235 236 237


//	Prepare content for the question...

Scott committed
238 239 240 241 242
if ($formtype == 'q_edit') { // ...in edit mode
	$qa_content['title'] = qa_lang_html($question['editable'] ? 'question/edit_q_title' :
		(qa_using_categories() ? 'question/recat_q_title' : 'question/retag_q_title'));
	$qa_content['form_q_edit'] = qa_page_q_edit_q_form($qa_content, $question, @$qin, @$qerrors, $completetags, $categories);
	$qa_content['q_view']['raw'] = $question;
Scott committed
243

Scott committed
244 245
} else { // ...in view mode
	$qa_content['q_view'] = qa_page_q_question_view($question, $parentquestion, $closepost, $usershtml, $formrequested);
Scott committed
246

Scott committed
247
	$qa_content['title'] = $qa_content['q_view']['title'];
Scott committed
248

Scott committed
249
	$qa_content['description'] = qa_html(qa_shorten_string_line(qa_viewer_text($question['content'], $question['format']), 150));
Scott committed
250

Scott committed
251
	$categorykeyword = @$categories[$question['categoryid']]['title'];
Scott committed
252

Scott committed
253 254 255 256 257
	$qa_content['keywords'] = qa_html(implode(',', array_merge(
		(qa_using_categories() && strlen($categorykeyword)) ? array($categorykeyword) : array(),
		qa_tagstring_to_tags($question['tags'])
	))); // as far as I know, META keywords have zero effect on search rankings or listings, but many people have asked for this
}
Scott committed
258

Scott committed
259 260 261
$microdata = qa_opt('use_microdata');
if ($microdata) {
	$qa_content['head_lines'][] = '<meta itemprop="name" content="' . qa_html($qa_content['q_view']['raw']['title']) . '">';
Scott committed
262
	$qa_content['html_tags'] .= ' itemscope itemtype="http://schema.org/QAPage"';
Scott committed
263 264
	$qa_content['main_tags'] = ' itemscope itemtype="http://schema.org/Question"';
}
Scott committed
265

Scott committed
266 267 268

//	Prepare content for an answer being edited (if any) or to be added

Scott committed
269 270 271
if ($formtype == 'a_edit') {
	$qa_content['a_form'] = qa_page_q_edit_a_form($qa_content, 'a' . $formpostid, $answers[$formpostid],
		$question, $answers, $commentsfollows, @$aeditin[$formpostid], @$aediterrors[$formpostid]);
Scott committed
272

Scott committed
273 274
	$qa_content['a_form']['c_list'] = qa_page_q_comment_follow_list($question, $answers[$formpostid],
		$commentsfollows, true, $usershtml, $formrequested, $formpostid);
Scott committed
275

Scott committed
276
	$jumptoanchor = 'a' . $formpostid;
Scott committed
277

Scott committed
278 279
} elseif (($formtype == 'a_add') || ($question['answerbutton'] && !$formrequested)) {
	$qa_content['a_form'] = qa_page_q_add_a_form($qa_content, 'anew', $captchareason, $question, @$anewin, @$anewerrors, $formtype == 'a_add', $formrequested);
Scott committed
280

Scott committed
281 282 283 284 285 286
	if ($formrequested) {
		$jumptoanchor = 'anew';
	} elseif ($formtype == 'a_add') {
		$qa_content['script_onloads'][] = array(
			"qa_element_revealed=document.getElementById('anew');"
		);
Scott committed
287
	}
Scott committed
288
}
Scott committed
289 290 291 292


//	Prepare content for comments on the question, plus add or edit comment forms

Scott committed
293 294 295
if ($formtype == 'q_close') {
	$qa_content['q_view']['c_form'] = qa_page_q_close_q_form($qa_content, $question, 'close', @$closein, @$closeerrors);
	$jumptoanchor = 'close';
Scott committed
296

Scott committed
297 298 299
} elseif ((($formtype == 'c_add') && ($formpostid == $questionid)) || ($question['commentbutton'] && !$formrequested)) { // ...to be added
	$qa_content['q_view']['c_form'] = qa_page_q_add_c_form($qa_content, $question, $question, 'c' . $questionid,
		$captchareason, @$cnewin[$questionid], @$cnewerrors[$questionid], $formtype == 'c_add');
Scott committed
300

Scott committed
301 302 303 304
	if (($formtype == 'c_add') && ($formpostid == $questionid)) {
		$jumptoanchor = 'c' . $questionid;
		$commentsall = $questionid;
	}
Scott committed
305

Scott committed
306 307 308
} elseif (($formtype == 'c_edit') && (@$commentsfollows[$formpostid]['parentid'] == $questionid)) { // ...being edited
	$qa_content['q_view']['c_form'] = qa_page_q_edit_c_form($qa_content, 'c' . $formpostid, $commentsfollows[$formpostid],
		@$ceditin[$formpostid], @$cediterrors[$formpostid]);
Scott committed
309

Scott committed
310 311 312
	$jumptoanchor = 'c' . $formpostid;
	$commentsall = $questionid;
}
Scott committed
313

Scott committed
314 315
$qa_content['q_view']['c_list'] = qa_page_q_comment_follow_list($question, $question, $commentsfollows,
	$commentsall == $questionid, $usershtml, $formrequested, $formpostid); // ...for viewing
Scott committed
316 317 318 319


//	Prepare content for existing answers (could be added to by Ajax)

Scott committed
320 321 322 323
$qa_content['a_list'] = array(
	'tags' => 'id="a_list"',
	'as' => array(),
);
Scott committed
324

Scott committed
325
// sort according to the site preferences
Scott committed
326

Scott committed
327 328 329
if (qa_opt('sort_answers_by') == 'votes') {
	foreach ($answers as $answerid => $answer)
		$answers[$answerid]['sortvotes'] = $answer['downvotes'] - $answer['upvotes'];
Scott committed
330

Scott committed
331
	qa_sort_by($answers, 'sortvotes', 'created');
Scott committed
332

Scott committed
333 334 335
} else {
	qa_sort_by($answers, 'created');
}
Scott committed
336

Scott committed
337
// further changes to ordering to deal with queued, hidden and selected answers
Scott committed
338

Scott committed
339 340 341
$countfortitle = $question['acount'];
$nextposition = 10000;
$answerposition = array();
Scott committed
342

Scott committed
343 344 345
foreach ($answers as $answerid => $answer) {
	if ($answer['viewable']) {
		$position = $nextposition++;
Scott committed
346

Scott committed
347 348
		if ($answer['hidden'])
			$position += 10000;
Scott committed
349

Scott committed
350 351 352
		elseif ($answer['queued']) {
			$position -= 10000;
			$countfortitle++; // include these in displayed count
Scott committed
353

Scott committed
354 355
		} elseif ($answer['isselected'] && qa_opt('show_selected_first'))
			$position -= 5000;
Scott committed
356

Scott committed
357 358 359
		$answerposition[$answerid] = $position;
	}
}
Scott committed
360

Scott committed
361
asort($answerposition, SORT_NUMERIC);
Scott committed
362

Scott committed
363
// extract IDs and prepare for pagination
Scott committed
364

Scott committed
365 366 367
$answerids = array_keys($answerposition);
$countforpages = count($answerids);
$pagesize = qa_opt('page_size_q_as');
Scott committed
368

Scott committed
369
// see if we need to display a particular answer
Scott committed
370

Scott committed
371 372 373
if (isset($showid)) {
	if (isset($commentsfollows[$showid]))
		$showid = $commentsfollows[$showid]['parentid'];
Scott committed
374

Scott committed
375
	$position = array_search($showid, $answerids);
Scott committed
376

Scott committed
377 378 379
	if (is_numeric($position))
		$pagestart = floor($position / $pagesize) * $pagesize;
}
Scott committed
380

Scott committed
381
// set the canonical url based on possible pagination
Scott committed
382

Scott committed
383 384
$qa_content['canonical'] = qa_path_html(qa_q_request($question['postid'], $question['title']),
	($pagestart > 0) ? array('start' => $pagestart) : null, qa_opt('site_url'));
Scott committed
385

Scott committed
386
// build the actual answer list
Scott committed
387

Scott committed
388
$answerids = array_slice($answerids, $pagestart, $pagesize);
Scott committed
389

Scott committed
390 391
foreach ($answerids as $answerid) {
	$answer = $answers[$answerid];
Scott committed
392

Scott committed
393 394
	if (!($formtype == 'a_edit' && $formpostid == $answerid)) {
		$a_view = qa_page_q_answer_view($question, $answer, $answer['isselected'], $usershtml, $formrequested);
Scott committed
395 396 397

		//	Prepare content for comments on this answer, plus add or edit comment forms

Scott committed
398 399 400
		if (($formtype == 'c_add' && $formpostid == $answerid) || ($answer['commentbutton'] && !$formrequested)) { // ...to be added
			$a_view['c_form'] = qa_page_q_add_c_form($qa_content, $question, $answer, 'c' . $answerid,
				$captchareason, @$cnewin[$answerid], @$cnewerrors[$answerid], $formtype == 'c_add');
Scott committed
401

Scott committed
402 403 404 405
			if ($formtype == 'c_add' && $formpostid == $answerid) {
				$jumptoanchor = 'c' . $answerid;
				$commentsall = $answerid;
			}
Scott committed
406

Scott committed
407 408 409
		} elseif ($formtype == 'c_edit' && @$commentsfollows[$formpostid]['parentid'] == $answerid) { // ...being edited
			$a_view['c_form'] = qa_page_q_edit_c_form($qa_content, 'c' . $formpostid, $commentsfollows[$formpostid],
				@$ceditin[$formpostid], @$cediterrors[$formpostid]);
Scott committed
410

Scott committed
411 412 413
			$jumptoanchor = 'c' . $formpostid;
			$commentsall = $answerid;
		}
Scott committed
414

Scott committed
415 416
		$a_view['c_list'] = qa_page_q_comment_follow_list($question, $answer, $commentsfollows,
			$commentsall == $answerid, $usershtml, $formrequested, $formpostid); // ...for viewing
Scott committed
417 418 419

		//	Add the answer to the list

Scott committed
420
		$qa_content['a_list']['as'][] = $a_view;
Scott committed
421
	}
Scott committed
422
}
Scott committed
423

Scott committed
424 425
if ($question['basetype'] == 'Q') {
	$qa_content['a_list']['title_tags'] = 'id="a_list_title"';
Scott committed
426

Scott committed
427 428 429 430
	if ($countfortitle > 0) {
		$split = $countfortitle == 1
			? qa_lang_html_sub_split('question/1_answer_title', '1', '1')
			: qa_lang_html_sub_split('question/x_answers_title', $countfortitle);
Scott committed
431

Scott committed
432 433
		if ($microdata)
			$split['data'] = '<span itemprop="answerCount">' . $split['data'] . '</span>';
Scott committed
434

Scott committed
435 436 437 438
		$qa_content['a_list']['title'] = $split['prefix'] . $split['data'] . $split['suffix'];
	} else
		$qa_content['a_list']['title_tags'] .= ' style="display:none;" ';
}
Scott committed
439

Scott committed
440 441
if (!$formrequested)
	$qa_content['page_links'] = qa_html_page_links(qa_request(), $pagestart, $pagesize, $countforpages, qa_opt('pages_prev_next'), array(), false, 'a_list_title');
Scott committed
442 443 444 445


//	Some generally useful stuff

Scott committed
446 447
if (qa_using_categories() && count($categories))
	$qa_content['navigation']['cat'] = qa_category_navigation($categories, $question['categoryid']);
Scott committed
448

Scott committed
449 450 451 452
if (isset($jumptoanchor))
	$qa_content['script_onloads'][] = array(
		'qa_scroll_page_to($("#"+' . qa_js($jumptoanchor) . ').offset().top);'
	);
Scott committed
453 454


455 456
//	Determine whether this request should be counted for page view statistics.
//	The lastviewip check is now in the hotness query in order to bypass caching.
Scott committed
457

Scott committed
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
if (
	qa_opt('do_count_q_views') &&
	(!$formrequested) &&
	(!qa_is_http_post()) &&
	qa_is_human_probably() &&
	((!$question['views']) || ( // if it has more than zero views
			((@inet_ntop($question['createip']) != qa_remote_ip_address()) || (!isset($question['createip']))) && // and different IP from the creator
			(($question['userid'] != $userid) || (!isset($question['userid']))) && // and different user from the creator
			(($question['cookieid'] != $cookieid) || (!isset($question['cookieid']))) // and different cookieid from the creator
		))
)
	$qa_content['inc_views_postid'] = $questionid;


return $qa_content;
Scott committed
473 474 475 476


/*
	Omit PHP closing tag to help avoid accidental output
477
*/