question-submit.php 18.3 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-submit.php
	Description: Common functions for question page form submission, either regular or via Ajax


	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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
	header('Location: ../');
	exit;
}


require_once QA_INCLUDE_DIR . 'app/post-create.php';
require_once QA_INCLUDE_DIR . 'app/post-update.php';


/**
 * Checks for a POSTed click on $question by the current user and returns true if it was permitted and processed. Pass
 * in the question's $answers, all $commentsfollows from it or its answers, and its closing $closepost (or null if
 * none). If there is an error to display, it will be passed out in $error.
 * @param $question
 * @param $answers
 * @param $commentsfollows
 * @param $closepost
 * @param $error
 * @return bool
 */
function qa_page_q_single_click_q($question, $answers, $commentsfollows, $closepost, &$error)
{
	require_once QA_INCLUDE_DIR . 'app/post-update.php';
	require_once QA_INCLUDE_DIR . 'app/limits.php';

	$userid = qa_get_logged_in_userid();
	$handle = qa_get_logged_in_handle();
	$cookieid = qa_cookie_get();

	if (qa_clicked('q_doreopen') && $question['reopenable'] && qa_page_q_click_check_form_code($question, $error)) {
		qa_question_close_clear($question, $closepost, $userid, $handle, $cookieid);
		return true;
Scott committed
56 57
	}

Scott committed
58 59 60
	if ((qa_clicked('q_dohide') && $question['hideable']) || (qa_clicked('q_doreject') && $question['moderatable'])) {
		if (qa_page_q_click_check_form_code($question, $error)) {
			qa_question_set_hidden($question, true, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost);
Scott committed
61 62
			return true;
		}
Scott committed
63
	}
Scott committed
64

Scott committed
65 66 67 68
	if ((qa_clicked('q_doreshow') && $question['reshowable']) || (qa_clicked('q_doapprove') && $question['moderatable'])) {
		if (qa_page_q_click_check_form_code($question, $error)) {
			if ($question['moderatable'] || $question['reshowimmed']) {
				$status = QA_POST_STATUS_NORMAL;
Scott committed
69

Scott committed
70 71 72
			} else {
				$in = qa_page_q_prepare_post_for_filters($question);
				$filtermodules = qa_load_modules_with('filter', 'filter_question'); // run through filters but only for queued status
Scott committed
73

Scott committed
74 75 76 77
				foreach ($filtermodules as $filtermodule) {
					$tempin = $in; // always pass original question in because we aren't modifying anything else
					$filtermodule->filter_question($tempin, $temperrors, $question);
					$in['queued'] = $tempin['queued']; // only preserve queued status in loop
Scott committed
78 79
				}

Scott committed
80
				$status = $in['queued'] ? QA_POST_STATUS_QUEUED : QA_POST_STATUS_NORMAL;
Scott committed
81 82
			}

Scott committed
83 84
			qa_question_set_status($question, $status, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost);
			return true;
Scott committed
85
		}
Scott committed
86
	}
Scott committed
87

Scott committed
88 89 90 91
	if (qa_clicked('q_doclaim') && $question['claimable'] && qa_page_q_click_check_form_code($question, $error)) {
		if (qa_user_limits_remaining(QA_LIMIT_QUESTIONS)) { // already checked 'permit_post_q'
			qa_question_set_userid($question, $userid, $handle, $cookieid);
			return true;
Scott committed
92

Scott committed
93 94 95
		} else
			$error = qa_lang_html('question/ask_limit');
	}
Scott committed
96

Scott committed
97 98
	if (qa_clicked('q_doflag') && $question['flagbutton'] && qa_page_q_click_check_form_code($question, $error)) {
		require_once QA_INCLUDE_DIR . 'app/votes.php';
Scott committed
99

Scott committed
100 101 102 103
		$error = qa_flag_error_html($question, $userid, qa_request());
		if (!$error) {
			if (qa_flag_set_tohide($question, $userid, $handle, $cookieid, $question))
				qa_question_set_hidden($question, true, null, null, null, $answers, $commentsfollows, $closepost); // hiding not really by this user so pass nulls
Scott committed
104 105
			return true;
		}
Scott committed
106
	}
Scott committed
107

Scott committed
108 109
	if (qa_clicked('q_dounflag') && $question['unflaggable'] && qa_page_q_click_check_form_code($question, $error)) {
		require_once QA_INCLUDE_DIR . 'app/votes.php';
Scott committed
110

Scott committed
111 112
		qa_flag_clear($question, $userid, $handle, $cookieid);
		return true;
Scott committed
113 114
	}

Scott committed
115 116
	if (qa_clicked('q_doclearflags') && $question['clearflaggable'] && qa_page_q_click_check_form_code($question, $error)) {
		require_once QA_INCLUDE_DIR . 'app/votes.php';
Scott committed
117

Scott committed
118 119 120
		qa_flags_clear_all($question, $userid, $handle, $cookieid);
		return true;
	}
Scott committed
121

Scott committed
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
	return false;
}


/**
 * Checks for a POSTed click on $answer by the current user and returns true if it was permitted and processed. Pass in
 * the $question, all of its $answers, and all $commentsfollows from it or its answers. Set $allowselectmove to whether
 * it is legitimate to change the selected answer for the question from one to another (this can't be done via Ajax).
 * If there is an error to display, it will be passed out in $error.
 * @param $answer
 * @param $question
 * @param $answers
 * @param $commentsfollows
 * @param $allowselectmove
 * @param $error
 * @return bool
 */
function qa_page_q_single_click_a($answer, $question, $answers, $commentsfollows, $allowselectmove, &$error)
{
	$userid = qa_get_logged_in_userid();
	$handle = qa_get_logged_in_handle();
	$cookieid = qa_cookie_get();

	$prefix = 'a' . $answer['postid'] . '_';

	if (qa_clicked($prefix . 'doselect') && $question['aselectable'] && ($allowselectmove || ((!isset($question['selchildid'])) && !qa_opt('do_close_on_select'))) && qa_page_q_click_check_form_code($answer, $error)) {
		qa_question_set_selchildid($userid, $handle, $cookieid, $question, $answer['postid'], $answers);
		return true;
	}
Scott committed
151

Scott committed
152 153 154 155
	if (qa_clicked($prefix . 'dounselect') && $question['aselectable'] && ($question['selchildid'] == $answer['postid']) && ($allowselectmove || !qa_opt('do_close_on_select')) && qa_page_q_click_check_form_code($answer, $error)) {
		qa_question_set_selchildid($userid, $handle, $cookieid, $question, null, $answers);
		return true;
	}
Scott committed
156

Scott committed
157 158 159
	if ((qa_clicked($prefix . 'dohide') && $answer['hideable']) || (qa_clicked($prefix . 'doreject') && $answer['moderatable'])) {
		if (qa_page_q_click_check_form_code($answer, $error)) {
			qa_answer_set_hidden($answer, true, $userid, $handle, $cookieid, $question, $commentsfollows);
Scott committed
160 161
			return true;
		}
Scott committed
162
	}
Scott committed
163

Scott committed
164 165 166 167
	if ((qa_clicked($prefix . 'doreshow') && $answer['reshowable']) || (qa_clicked($prefix . 'doapprove') && $answer['moderatable'])) {
		if (qa_page_q_click_check_form_code($answer, $error)) {
			if ($answer['moderatable'] || $answer['reshowimmed']) {
				$status = QA_POST_STATUS_NORMAL;
Scott committed
168

Scott committed
169 170 171
			} else {
				$in = qa_page_q_prepare_post_for_filters($answer);
				$filtermodules = qa_load_modules_with('filter', 'filter_answer'); // run through filters but only for queued status
Scott committed
172

Scott committed
173 174 175 176
				foreach ($filtermodules as $filtermodule) {
					$tempin = $in; // always pass original answer in because we aren't modifying anything else
					$filtermodule->filter_answer($tempin, $temperrors, $question, $answer);
					$in['queued'] = $tempin['queued']; // only preserve queued status in loop
Scott committed
177 178
				}

Scott committed
179
				$status = $in['queued'] ? QA_POST_STATUS_QUEUED : QA_POST_STATUS_NORMAL;
Scott committed
180 181
			}

Scott committed
182
			qa_answer_set_status($answer, $status, $userid, $handle, $cookieid, $question, $commentsfollows);
Scott committed
183 184
			return true;
		}
Scott committed
185
	}
Scott committed
186

Scott committed
187 188 189 190
	if (qa_clicked($prefix . 'dodelete') && $answer['deleteable'] && qa_page_q_click_check_form_code($answer, $error)) {
		qa_answer_delete($answer, $question, $userid, $handle, $cookieid);
		return true;
	}
Scott committed
191

Scott committed
192 193 194 195
	if (qa_clicked($prefix . 'doclaim') && $answer['claimable'] && qa_page_q_click_check_form_code($answer, $error)) {
		if (qa_user_limits_remaining(QA_LIMIT_ANSWERS)) { // already checked 'permit_post_a'
			qa_answer_set_userid($answer, $userid, $handle, $cookieid);
			return true;
Scott committed
196

Scott committed
197 198 199
		} else
			$error = qa_lang_html('question/answer_limit');
	}
Scott committed
200

Scott committed
201 202
	if (qa_clicked($prefix . 'doflag') && $answer['flagbutton'] && qa_page_q_click_check_form_code($answer, $error)) {
		require_once QA_INCLUDE_DIR . 'app/votes.php';
Scott committed
203

Scott committed
204 205 206 207
		$error = qa_flag_error_html($answer, $userid, qa_request());
		if (!$error) {
			if (qa_flag_set_tohide($answer, $userid, $handle, $cookieid, $question))
				qa_answer_set_hidden($answer, true, null, null, null, $question, $commentsfollows); // hiding not really by this user so pass nulls
Scott committed
208 209 210

			return true;
		}
Scott committed
211
	}
Scott committed
212

Scott committed
213 214
	if (qa_clicked($prefix . 'dounflag') && $answer['unflaggable'] && qa_page_q_click_check_form_code($answer, $error)) {
		require_once QA_INCLUDE_DIR . 'app/votes.php';
Scott committed
215

Scott committed
216 217
		qa_flag_clear($answer, $userid, $handle, $cookieid);
		return true;
Scott committed
218 219
	}

Scott committed
220 221
	if (qa_clicked($prefix . 'doclearflags') && $answer['clearflaggable'] && qa_page_q_click_check_form_code($answer, $error)) {
		require_once QA_INCLUDE_DIR . 'app/votes.php';
Scott committed
222

Scott committed
223 224 225
		qa_flags_clear_all($answer, $userid, $handle, $cookieid);
		return true;
	}
Scott committed
226

Scott committed
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
	return false;
}


/**
 * Checks for a POSTed click on $comment by the current user and returns true if it was permitted and processed. Pass
 * in the antecedent $question and the comment's $parent post. If there is an error to display, it will be passed out
 * in $error.
 * @param $comment
 * @param $question
 * @param $parent
 * @param $error
 * @return bool
 */
function qa_page_q_single_click_c($comment, $question, $parent, &$error)
{
	$userid = qa_get_logged_in_userid();
	$handle = qa_get_logged_in_handle();
	$cookieid = qa_cookie_get();

	$prefix = 'c' . $comment['postid'] . '_';

	if ((qa_clicked($prefix . 'dohide') && $comment['hideable']) || (qa_clicked($prefix . 'doreject') && $comment['moderatable'])) {
		if (qa_page_q_click_check_form_code($parent, $error)) {
			qa_comment_set_hidden($comment, true, $userid, $handle, $cookieid, $question, $parent);
			return true;
		}
	}
Scott committed
255

Scott committed
256 257 258 259
	if ((qa_clicked($prefix . 'doreshow') && $comment['reshowable']) || (qa_clicked($prefix . 'doapprove') && $comment['moderatable'])) {
		if (qa_page_q_click_check_form_code($parent, $error)) {
			if ($comment['moderatable'] || $comment['reshowimmed']) {
				$status = QA_POST_STATUS_NORMAL;
Scott committed
260

Scott committed
261 262 263
			} else {
				$in = qa_page_q_prepare_post_for_filters($comment);
				$filtermodules = qa_load_modules_with('filter', 'filter_comment'); // run through filters but only for queued status
Scott committed
264

Scott committed
265 266 267 268
				foreach ($filtermodules as $filtermodule) {
					$tempin = $in; // always pass original comment in because we aren't modifying anything else
					$filtermodule->filter_comment($tempin, $temperrors, $question, $parent, $comment);
					$in['queued'] = $tempin['queued']; // only preserve queued status in loop
Scott committed
269 270
				}

Scott committed
271
				$status = $in['queued'] ? QA_POST_STATUS_QUEUED : QA_POST_STATUS_NORMAL;
Scott committed
272 273
			}

Scott committed
274
			qa_comment_set_status($comment, $status, $userid, $handle, $cookieid, $question, $parent);
Scott committed
275 276
			return true;
		}
Scott committed
277
	}
Scott committed
278

Scott committed
279 280 281 282
	if (qa_clicked($prefix . 'dodelete') && $comment['deleteable'] && qa_page_q_click_check_form_code($parent, $error)) {
		qa_comment_delete($comment, $question, $parent, $userid, $handle, $cookieid);
		return true;
	}
Scott committed
283

Scott committed
284 285 286 287
	if (qa_clicked($prefix . 'doclaim') && $comment['claimable'] && qa_page_q_click_check_form_code($parent, $error)) {
		if (qa_user_limits_remaining(QA_LIMIT_COMMENTS)) {
			qa_comment_set_userid($comment, $userid, $handle, $cookieid);
			return true;
Scott committed
288

Scott committed
289 290 291
		} else
			$error = qa_lang_html('question/comment_limit');
	}
Scott committed
292

Scott committed
293 294
	if (qa_clicked($prefix . 'doflag') && $comment['flagbutton'] && qa_page_q_click_check_form_code($parent, $error)) {
		require_once QA_INCLUDE_DIR . 'app/votes.php';
Scott committed
295

Scott committed
296 297 298 299
		$error = qa_flag_error_html($comment, $userid, qa_request());
		if (!$error) {
			if (qa_flag_set_tohide($comment, $userid, $handle, $cookieid, $question))
				qa_comment_set_hidden($comment, true, null, null, null, $question, $parent); // hiding not really by this user so pass nulls
Scott committed
300 301 302 303 304

			return true;
		}
	}

Scott committed
305 306
	if (qa_clicked($prefix . 'dounflag') && $comment['unflaggable'] && qa_page_q_click_check_form_code($parent, $error)) {
		require_once QA_INCLUDE_DIR . 'app/votes.php';
Scott committed
307

Scott committed
308 309
		qa_flag_clear($comment, $userid, $handle, $cookieid);
		return true;
Scott committed
310 311
	}

Scott committed
312 313
	if (qa_clicked($prefix . 'doclearflags') && $comment['clearflaggable'] && qa_page_q_click_check_form_code($parent, $error)) {
		require_once QA_INCLUDE_DIR . 'app/votes.php';
Scott committed
314

Scott committed
315 316 317
		qa_flags_clear_all($comment, $userid, $handle, $cookieid);
		return true;
	}
Scott committed
318

Scott committed
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
	return false;
}


/**
 * Check the form security (anti-CSRF protection) for one of the buttons shown for post $post. Return true if the
 * security passed, otherwise return false and set an error message in $error
 * @param $post
 * @param $error
 * @return bool
 */
function qa_page_q_click_check_form_code($post, &$error)
{
	$result = qa_check_form_security_code('buttons-' . $post['postid'], qa_post_text('code'));

	if (!$result)
		$error = qa_lang_html('misc/form_security_again');

	return $result;
}


/**
 * Processes a POSTed form to add an answer to $question, returning the postid if successful, otherwise null. Pass in
 * other $answers to the question and whether a $usecaptcha is required. The form fields submitted will be passed out
 * as an array in $in, as well as any $errors on those fields.
 * @param $question
 * @param $answers
 * @param $usecaptcha
 * @param $in
 * @param $errors
 * @return mixed|null
 */
function qa_page_q_add_a_submit($question, $answers, $usecaptcha, &$in, &$errors)
{
	$in = array(
355
		'name' => qa_opt('allow_anonymous_naming') ? qa_post_text('a_name') : null,
Scott committed
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
		'notify' => qa_post_text('a_notify') !== null,
		'email' => qa_post_text('a_email'),
		'queued' => qa_user_moderation_reason(qa_user_level_for_post($question)) !== false,
	);

	qa_get_post_content('a_editor', 'a_content', $in['editor'], $in['content'], $in['format'], $in['text']);

	$errors = array();

	if (!qa_check_form_security_code('answer-' . $question['postid'], qa_post_text('code')))
		$errors['content'] = qa_lang_html('misc/form_security_again');

	else {
		// call any filter plugins
		$filtermodules = qa_load_modules_with('filter', 'filter_answer');
		foreach ($filtermodules as $filtermodule) {
			$oldin = $in;
			$filtermodule->filter_answer($in, $errors, $question, null);
			qa_update_post_text($in, $oldin);
		}
Scott committed
376

Scott committed
377 378 379
		// check CAPTCHA
		if ($usecaptcha)
			qa_captcha_validate_post($errors);
Scott committed
380

Scott committed
381 382 383
		// check for duplicate posts
		if (empty($errors)) {
			$testwords = implode(' ', qa_string_to_words($in['content']));
384

Scott committed
385 386 387 388
			foreach ($answers as $answer) {
				if (!$answer['hidden']) {
					if (implode(' ', qa_string_to_words($answer['content'])) == $testwords) {
						$errors['content'] = qa_lang_html('question/duplicate_content');
389 390 391 392
						break;
					}
				}
			}
Scott committed
393
		}
394

Scott committed
395
		$userid = qa_get_logged_in_userid();
Scott committed
396

Scott committed
397 398 399 400 401 402 403
		// if this is an additional answer, check we can add it
		if (empty($errors) && !qa_opt('allow_multi_answers')) {
			foreach ($answers as $answer) {
				if (qa_post_is_by_user($answer, $userid, qa_cookie_get())) {
					$errors[] = '';
					break;
				}
Scott committed
404 405 406
			}
		}

Scott committed
407 408 409 410
		// create the answer
		if (empty($errors)) {
			$handle = qa_get_logged_in_handle();
			$cookieid = isset($userid) ? qa_cookie_get() : qa_cookie_get_create(); // create a new cookie if necessary
Scott committed
411

Scott committed
412 413
			$answerid = qa_answer_create($userid, $handle, $cookieid, $in['content'], $in['format'], $in['text'], $in['notify'], $in['email'],
				$question, $in['queued'], $in['name']);
Scott committed
414

Scott committed
415 416 417
			return $answerid;
		}
	}
Scott committed
418

Scott committed
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
	return null;
}


/**
 * Processes a POSTed form to add a comment, returning the postid if successful, otherwise null. Pass in the antecedent
 * $question and the comment's $parent post. Set $usecaptcha to whether a captcha is required. Pass an array which
 * includes the other comments with the same parent in $commentsfollows (it can contain other posts which are ignored).
 * The form fields submitted will be passed out as an array in $in, as well as any $errors on those fields.
 * @param $question
 * @param $parent
 * @param $commentsfollows
 * @param $usecaptcha
 * @param $in
 * @param $errors
 * @return mixed|null
 */
function qa_page_q_add_c_submit($question, $parent, $commentsfollows, $usecaptcha, &$in, &$errors)
{
	$parentid = $parent['postid'];

	$prefix = 'c' . $parentid . '_';

	$in = array(
443
		'name' => qa_opt('allow_anonymous_naming') ? qa_post_text($prefix . 'name') : null,
Scott committed
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
		'notify' => qa_post_text($prefix . 'notify') !== null,
		'email' => qa_post_text($prefix . 'email'),
		'queued' => qa_user_moderation_reason(qa_user_level_for_post($parent)) !== false,
	);

	qa_get_post_content($prefix . 'editor', $prefix . 'content', $in['editor'], $in['content'], $in['format'], $in['text']);

	$errors = array();

	if (!qa_check_form_security_code('comment-' . $parent['postid'], qa_post_text($prefix . 'code')))
		$errors['content'] = qa_lang_html('misc/form_security_again');

	else {
		$filtermodules = qa_load_modules_with('filter', 'filter_comment');
		foreach ($filtermodules as $filtermodule) {
			$oldin = $in;
			$filtermodule->filter_comment($in, $errors, $question, $parent, null);
			qa_update_post_text($in, $oldin);
		}
Scott committed
463

Scott committed
464 465
		if ($usecaptcha)
			qa_captcha_validate_post($errors);
Scott committed
466

Scott committed
467 468
		if (empty($errors)) {
			$testwords = implode(' ', qa_string_to_words($in['content']));
Scott committed
469

Scott committed
470
			foreach ($commentsfollows as $comment) {
Scott committed
471
				if ($comment['basetype'] == 'C' && $comment['parentid'] == $parentid && !$comment['hidden']) {
Scott committed
472 473 474 475 476
					if (implode(' ', qa_string_to_words($comment['content'])) == $testwords) {
						$errors['content'] = qa_lang_html('question/duplicate_content');
						break;
					}
				}
Scott committed
477 478 479
			}
		}

Scott committed
480 481 482 483
		if (empty($errors)) {
			$userid = qa_get_logged_in_userid();
			$handle = qa_get_logged_in_handle();
			$cookieid = isset($userid) ? qa_cookie_get() : qa_cookie_get_create(); // create a new cookie if necessary
Scott committed
484

Scott committed
485 486
			$commentid = qa_comment_create($userid, $handle, $cookieid, $in['content'], $in['format'], $in['text'], $in['notify'], $in['email'],
				$question, $parent, $commentsfollows, $in['queued'], $in['name']);
Scott committed
487

Scott committed
488
			return $commentid;
Scott committed
489 490 491
		}
	}

Scott committed
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
	return null;
}


/**
 * Return the array of information to be passed to filter modules for the post in $post (from the database)
 * @param $post
 * @return array
 */
function qa_page_q_prepare_post_for_filters($post)
{
	$in = array(
		'content' => $post['content'],
		'format' => $post['format'],
		'text' => qa_viewer_text($post['content'], $post['format']),
		'notify' => isset($post['notify']),
		'email' => qa_email_validate($post['notify']) ? $post['notify'] : null,
		'queued' => qa_user_moderation_reason(qa_user_level_for_post($post)) !== false,
	);

	if ($post['basetype'] == 'Q') {
		$in['title'] = $post['title'];
		$in['tags'] = qa_tagstring_to_tags($post['tags']);
		$in['categoryid'] = $post['categoryid'];
		$in['extra'] = $post['extra'];
	}
Scott committed
518

Scott committed
519 520
	return $in;
}