question-submit.php 18.6 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: 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
22
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
23
	header('Location: ../../');
Scott committed
24 25 26 27 28 29 30 31 32 33 34 35
	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.
36 37 38 39 40
 * @param array $question
 * @param array $answers
 * @param array $commentsfollows
 * @param array $closepost
 * @param string $error
Scott committed
41 42 43 44 45 46 47 48 49 50 51 52 53 54
 * @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
55 56
	}

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

Scott committed
64 65 66 67
	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
68

Scott committed
69 70 71
			} 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
72

Scott committed
73 74 75 76
				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
77 78
				}

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

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

Scott committed
87 88 89 90
	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
91

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

Scott committed
96 97
	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
98

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

Scott committed
107 108
	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
109

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

Scott committed
114 115
	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
116

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

Scott committed
121 122 123 124 125 126 127 128 129
	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.
130 131 132 133 134 135
 * @param array $answer
 * @param array $question
 * @param array $answers
 * @param array $commentsfollows
 * @param bool $allowselectmove
 * @param string $error
Scott committed
136 137 138 139 140 141 142 143 144 145 146 147 148 149
 * @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
150

Scott committed
151 152 153 154
	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
155

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

Scott committed
163 164 165 166
	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
167

Scott committed
168 169 170
			} 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
171

Scott committed
172 173 174 175
				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
176 177
				}

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

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

Scott committed
186 187 188 189
	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
190

Scott committed
191 192 193 194
	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
195

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

Scott committed
200 201
	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
202

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

			return true;
		}
Scott committed
210
	}
Scott committed
211

Scott committed
212 213
	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
214

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

Scott committed
219 220
	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
221

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

Scott committed
226 227 228 229 230 231 232 233
	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.
234 235 236 237
 * @param array $comment
 * @param array $question
 * @param array $parent
 * @param string $error
Scott committed
238 239 240 241 242 243 244 245 246 247 248 249
 * @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)) {
250
			qa_comment_set_status($comment, QA_POST_STATUS_HIDDEN, $userid, $handle, $cookieid, $question, $parent);
Scott committed
251 252 253
			return true;
		}
	}
Scott committed
254

Scott committed
255 256 257 258
	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
259

Scott committed
260 261 262
			} 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
263

Scott committed
264 265 266 267
				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
268 269
				}

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

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

Scott committed
278 279 280 281
	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
282

Scott committed
283 284 285 286
	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
287

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

Scott committed
292 293
	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
294

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

			return true;
		}
	}

Scott committed
304 305
	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
306

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

Scott committed
311 312
	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
313

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

Scott committed
318 319 320 321 322 323 324
	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
325 326
 * @param array $post
 * @param string $error
Scott committed
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
 * @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.
344 345 346 347 348 349
 * @param array $question
 * @param array $answers
 * @param bool $usecaptcha
 * @param array $in
 * @param array $errors
 * @return int|null
Scott committed
350 351 352 353
 */
function qa_page_q_add_a_submit($question, $answers, $usecaptcha, &$in, &$errors)
{
	$in = array(
354
		'name' => qa_opt('allow_anonymous_naming') ? qa_post_text('a_name') : null,
Scott committed
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
		'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
375

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

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

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

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

Scott committed
396 397 398 399 400 401 402
		// 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
403 404 405
			}
		}

Scott committed
406 407 408 409
		// 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
410

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

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

Scott committed
418 419 420 421 422 423 424 425 426
	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.
427 428 429 430 431 432 433
 * @param array $question
 * @param array $parent
 * @param array $commentsfollows
 * @param bool $usecaptcha
 * @param array $in
 * @param array $errors
 * @return int|null
Scott committed
434 435 436 437 438 439 440 441
 */
function qa_page_q_add_c_submit($question, $parent, $commentsfollows, $usecaptcha, &$in, &$errors)
{
	$parentid = $parent['postid'];

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

	$in = array(
442
		'name' => qa_opt('allow_anonymous_naming') ? qa_post_text($prefix . 'name') : null,
Scott committed
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
		'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
462

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

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

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

Scott committed
479 480 481 482
		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
483

Scott committed
484 485
			$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
486

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

Scott committed
491 492 493 494 495 496
	return null;
}


/**
 * Return the array of information to be passed to filter modules for the post in $post (from the database)
497
 * @param array $post
Scott committed
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
 * @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
517

Scott committed
518 519
	return $in;
}