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

	Description: Database functions for creating a question, answer or comment


	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
	exit;
}


/**
 * Create a new post in the database and return its ID (based on auto-incrementing)
30 31 32 33 34 35 36 37 38 39 40 41 42
 * @param string $type
 * @param int $parentid
 * @param mixed $userid
 * @param string $cookieid
 * @param string $ip
 * @param string $title
 * @param string $content
 * @param string $format
 * @param string $tagstring
 * @param bool $notify
 * @param int|null $categoryid
 * @param string|null $name
 * @return string
Scott committed
43 44 45 46 47
 */
function qa_db_post_create($type, $parentid, $userid, $cookieid, $ip, $title, $content, $format, $tagstring, $notify, $categoryid = null, $name = null)
{
	qa_db_query_sub(
		'INSERT INTO ^posts (categoryid, type, parentid, userid, cookieid, createip, title, content, format, tags, notify, name, created) ' .
48 49
		'VALUES (#, $, #, $, #, UNHEX($), $, $, $, $, $, $, NOW())',
		$categoryid, $type, $parentid, $userid, $cookieid, bin2hex(@inet_pton($ip)), $title, $content, $format, $tagstring, $notify, $name
Scott committed
50 51 52 53 54 55 56 57
	);

	return qa_db_last_insert_id();
}


/**
 * Recalculate the full category path (i.e. columns catidpath1/2/3) for posts from $firstpostid to $lastpostid (if specified)
58 59
 * @param int $firstpostid
 * @param int|null $lastpostid
Scott committed
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
 */
function qa_db_posts_calc_category_path($firstpostid, $lastpostid = null)
{
	if (!isset($lastpostid))
		$lastpostid = $firstpostid;

	qa_db_query_sub(
		"UPDATE ^posts AS x, (SELECT ^posts.postid, " .
		"COALESCE(parent2.parentid, parent1.parentid, parent0.parentid, parent0.categoryid) AS catidpath1, " .
		"IF (parent2.parentid IS NOT NULL, parent1.parentid, IF (parent1.parentid IS NOT NULL, parent0.parentid, IF (parent0.parentid IS NOT NULL, parent0.categoryid, NULL))) AS catidpath2, " .
		"IF (parent2.parentid IS NOT NULL, parent0.parentid, IF (parent1.parentid IS NOT NULL, parent0.categoryid, NULL)) AS catidpath3 " .
		"FROM ^posts LEFT JOIN ^categories AS parent0 ON ^posts.categoryid=parent0.categoryid LEFT JOIN ^categories AS parent1 ON parent0.parentid=parent1.categoryid LEFT JOIN ^categories AS parent2 ON parent1.parentid=parent2.categoryid WHERE ^posts.postid BETWEEN # AND #) AS a SET x.catidpath1=a.catidpath1, x.catidpath2=a.catidpath2, x.catidpath3=a.catidpath3 WHERE x.postid=a.postid",
		$firstpostid, $lastpostid
	); // requires QA_CATEGORY_DEPTH=4
}


/**
 * Get the full category path (including categoryid) for $postid
79 80
 * @param int $postid
 * @return array
Scott committed
81 82 83 84 85 86 87 88 89 90 91 92
 */
function qa_db_post_get_category_path($postid)
{
	return qa_db_read_one_assoc(qa_db_query_sub(
		'SELECT categoryid, catidpath1, catidpath2, catidpath3 FROM ^posts WHERE postid=#',
		$postid
	)); // requires QA_CATEGORY_DEPTH=4
}


/**
 * Update the cached number of answers for $questionid in the database, along with the highest netvotes of any of its answers
93
 * @param int $questionid
Scott committed
94 95 96 97
 */
function qa_db_post_acount_update($questionid)
{
	if (qa_should_update_counts()) {
Scott committed
98
		qa_db_query_sub(
Scott committed
99 100
			"UPDATE ^posts AS x, (SELECT COUNT(*) AS acount, COALESCE(GREATEST(MAX(netvotes), 0), 0) AS amaxvote FROM ^posts WHERE parentid=# AND type='A') AS a SET x.acount=a.acount, x.amaxvote=a.amaxvote WHERE x.postid=#",
			$questionid, $questionid
Scott committed
101 102
		);
	}
Scott committed
103 104 105 106 107
}


/**
 * Recalculate the number of questions for each category in $path retrieved via qa_db_post_get_category_path()
108
 * @param array $path
Scott committed
109 110 111 112 113 114 115 116 117 118 119 120
 */
function qa_db_category_path_qcount_update($path)
{
	qa_db_ifcategory_qcount_update($path['categoryid']); // requires QA_CATEGORY_DEPTH=4
	qa_db_ifcategory_qcount_update($path['catidpath1']);
	qa_db_ifcategory_qcount_update($path['catidpath2']);
	qa_db_ifcategory_qcount_update($path['catidpath3']);
}


/**
 * Update the cached number of questions for category $categoryid in the database, including its subcategories
121
 * @param int|null $categoryid
Scott committed
122 123 124 125 126
 */
function qa_db_ifcategory_qcount_update($categoryid)
{
	if (qa_should_update_counts() && isset($categoryid)) {
		// This seemed like the most sensible approach which avoids explicitly calculating the category's depth in the hierarchy
Scott committed
127 128

		qa_db_query_sub(
Scott committed
129 130
			"UPDATE ^categories SET qcount=GREATEST( (SELECT COUNT(*) FROM ^posts WHERE categoryid=# AND type='Q'), (SELECT COUNT(*) FROM ^posts WHERE catidpath1=# AND type='Q'), (SELECT COUNT(*) FROM ^posts WHERE catidpath2=# AND type='Q'), (SELECT COUNT(*) FROM ^posts WHERE catidpath3=# AND type='Q') ) WHERE categoryid=#",
			$categoryid, $categoryid, $categoryid, $categoryid, $categoryid
Scott committed
131 132
		); // requires QA_CATEGORY_DEPTH=4
	}
Scott committed
133 134 135 136 137 138
}


/**
 * Add rows into the database title index, where $postid contains the words $wordids - this does the same sort
 * of thing as qa_db_posttags_add_post_wordids() in a different way, for no particularly good reason.
139 140
 * @param int $postid
 * @param array $wordids
Scott committed
141 142 143
 */
function qa_db_titlewords_add_post_wordids($postid, $wordids)
{
144
	if (!empty($wordids)) {
Scott committed
145 146 147
		$rowstoadd = array();
		foreach ($wordids as $wordid)
			$rowstoadd[] = array($postid, $wordid);
Scott committed
148

Scott committed
149 150 151 152
		qa_db_query_sub(
			'INSERT INTO ^titlewords (postid, wordid) VALUES #',
			$rowstoadd
		);
Scott committed
153
	}
Scott committed
154 155 156 157 158 159
}


/**
 * Add rows into the database content index, where $postid (of $type, with the antecedent $questionid)
 * has words as per the keys of $wordidcounts, and the corresponding number of those words in the values.
160 161 162 163
 * @param int $postid
 * @param string $type
 * @param int $questionid
 * @param array $wordidcounts
Scott committed
164 165 166
 */
function qa_db_contentwords_add_post_wordidcounts($postid, $type, $questionid, $wordidcounts)
{
167
	if (!empty($wordidcounts)) {
Scott committed
168
		$rowstoadd = array();
169 170 171 172
		foreach ($wordidcounts as $wordid => $count) {
			if ($count > QA_DB_MAX_WORD_COUNT) {
				$count = QA_DB_MAX_WORD_COUNT;
			}
Scott committed
173
			$rowstoadd[] = array($postid, $wordid, $count, $type, $questionid);
174
		}
Scott committed
175

Scott committed
176 177 178 179
		qa_db_query_sub(
			'INSERT INTO ^contentwords (postid, wordid, count, type, questionid) VALUES #',
			$rowstoadd
		);
Scott committed
180
	}
Scott committed
181
}
Scott committed
182 183


Scott committed
184 185
/**
 * Add rows into the database index of individual tag words, where $postid contains the words $wordids
186 187
 * @param int $postid
 * @param array $wordids
Scott committed
188 189 190
 */
function qa_db_tagwords_add_post_wordids($postid, $wordids)
{
191
	if (!empty($wordids)) {
Scott committed
192 193 194
		$rowstoadd = array();
		foreach ($wordids as $wordid)
			$rowstoadd[] = array($postid, $wordid);
Scott committed
195

Scott committed
196 197 198 199
		qa_db_query_sub(
			'INSERT INTO ^tagwords (postid, wordid) VALUES #',
			$rowstoadd
		);
Scott committed
200
	}
Scott committed
201
}
Scott committed
202 203


Scott committed
204 205
/**
 * Add rows into the database index of whole tags, where $postid contains the tags $wordids
206 207
 * @param int $postid
 * @param array $wordids
Scott committed
208 209 210
 */
function qa_db_posttags_add_post_wordids($postid, $wordids)
{
211
	if (!empty($wordids)) {
Scott committed
212 213 214 215
		qa_db_query_sub(
			'INSERT INTO ^posttags (postid, wordid, postcreated) SELECT postid, wordid, created FROM ^words, ^posts WHERE postid=# AND wordid IN ($)',
			$postid, $wordids
		);
Scott committed
216
	}
Scott committed
217 218 219 220 221
}


/**
 * Return an array mapping each word in $words to its corresponding wordid in the database
222
 * @param array $words
Scott committed
223 224 225 226
 * @return array
 */
function qa_db_word_mapto_ids($words)
{
227
	if (!empty($words)) {
Scott committed
228 229 230
		return qa_db_read_all_assoc(qa_db_query_sub(
			'SELECT wordid, word FROM ^words WHERE word IN ($)', $words
		), 'word', 'wordid');
Scott committed
231 232
	}

Scott committed
233 234
	return array();
}
Scott committed
235 236


Scott committed
237 238
/**
 * Return an array mapping each word in $words to its corresponding wordid in the database, adding any that are missing
239
 * @param array $words
Scott committed
240 241 242 243 244
 * @return array
 */
function qa_db_word_mapto_ids_add($words)
{
	$wordtoid = qa_db_word_mapto_ids($words);
Scott committed
245

Scott committed
246 247 248 249
	$wordstoadd = array();
	foreach ($words as $word) {
		if (!isset($wordtoid[$word]))
			$wordstoadd[] = $word;
Scott committed
250 251
	}

252
	if (!empty($wordstoadd)) {
Scott committed
253
		qa_db_query_sub('LOCK TABLES ^words WRITE'); // to prevent two requests adding the same word
Scott committed
254

Scott committed
255
		$wordtoid = qa_db_word_mapto_ids($words); // map it again in case table content changed before it was locked
Scott committed
256

Scott committed
257 258
		$rowstoadd = array();
		foreach ($words as $word) {
Scott committed
259
			if (!isset($wordtoid[$word]))
Scott committed
260 261
				$rowstoadd[] = array($word);
		}
Scott committed
262

Scott committed
263
		qa_db_query_sub('INSERT IGNORE INTO ^words (word) VALUES $', $rowstoadd);
Scott committed
264

Scott committed
265
		qa_db_query_sub('UNLOCK TABLES');
Scott committed
266

Scott committed
267
		$wordtoid = qa_db_word_mapto_ids($words); // do it one last time
Scott committed
268 269
	}

Scott committed
270 271
	return $wordtoid;
}
Scott committed
272

Scott committed
273 274 275

/**
 * Update the titlecount column in the database for the words in $wordids, based on how many posts they appear in the title of
276
 * @param array $wordids
Scott committed
277 278 279 280 281 282 283 284
 */
function qa_db_word_titlecount_update($wordids)
{
	if (qa_should_update_counts() && count($wordids)) {
		qa_db_query_sub(
			'UPDATE ^words AS x, (SELECT ^words.wordid, COUNT(^titlewords.wordid) AS titlecount FROM ^words LEFT JOIN ^titlewords ON ^titlewords.wordid=^words.wordid WHERE ^words.wordid IN (#) GROUP BY wordid) AS a SET x.titlecount=a.titlecount WHERE x.wordid=a.wordid',
			$wordids
		);
Scott committed
285
	}
Scott committed
286
}
Scott committed
287 288


Scott committed
289 290
/**
 * Update the contentcount column in the database for the words in $wordids, based on how many posts they appear in the content of
291
 * @param array $wordids
Scott committed
292 293 294 295 296 297 298 299
 */
function qa_db_word_contentcount_update($wordids)
{
	if (qa_should_update_counts() && count($wordids)) {
		qa_db_query_sub(
			'UPDATE ^words AS x, (SELECT ^words.wordid, COUNT(^contentwords.wordid) AS contentcount FROM ^words LEFT JOIN ^contentwords ON ^contentwords.wordid=^words.wordid WHERE ^words.wordid IN (#) GROUP BY wordid) AS a SET x.contentcount=a.contentcount WHERE x.wordid=a.wordid',
			$wordids
		);
Scott committed
300
	}
Scott committed
301
}
Scott committed
302 303


Scott committed
304 305
/**
 * Update the tagwordcount column in the database for the individual tag words in $wordids, based on how many posts they appear in the tags of
306
 * @param array $wordids
Scott committed
307 308 309 310 311 312 313 314
 */
function qa_db_word_tagwordcount_update($wordids)
{
	if (qa_should_update_counts() && count($wordids)) {
		qa_db_query_sub(
			'UPDATE ^words AS x, (SELECT ^words.wordid, COUNT(^tagwords.wordid) AS tagwordcount FROM ^words LEFT JOIN ^tagwords ON ^tagwords.wordid=^words.wordid WHERE ^words.wordid IN (#) GROUP BY wordid) AS a SET x.tagwordcount=a.tagwordcount WHERE x.wordid=a.wordid',
			$wordids
		);
Scott committed
315
	}
Scott committed
316
}
Scott committed
317 318


Scott committed
319 320
/**
 * Update the tagcount column in the database for the whole tags in $wordids, based on how many posts they appear as tags of
321
 * @param array $wordids
Scott committed
322 323 324 325 326 327 328 329
 */
function qa_db_word_tagcount_update($wordids)
{
	if (qa_should_update_counts() && count($wordids)) {
		qa_db_query_sub(
			'UPDATE ^words AS x, (SELECT ^words.wordid, COUNT(^posttags.wordid) AS tagcount FROM ^words LEFT JOIN ^posttags ON ^posttags.wordid=^words.wordid WHERE ^words.wordid IN (#) GROUP BY wordid) AS a SET x.tagcount=a.tagcount WHERE x.wordid=a.wordid',
			$wordids
		);
Scott committed
330
	}
Scott committed
331
}
Scott committed
332 333


Scott committed
334 335 336 337 338 339 340 341 342 343 344 345
/**
 * Update the cached count in the database of the number of questions (excluding hidden/queued)
 */
function qa_db_qcount_update()
{
	if (qa_should_update_counts()) {
		qa_db_query_sub(
			"INSERT INTO ^options (title, content) " .
			"SELECT 'cache_qcount', COUNT(*) FROM ^posts " .
			"WHERE type = 'Q' " .
			"ON DUPLICATE KEY UPDATE content = VALUES(content)"
		);
Scott committed
346
	}
Scott committed
347
}
Scott committed
348 349


Scott committed
350 351 352 353 354 355 356 357 358 359 360 361
/**
 * Update the cached count in the database of the number of answers (excluding hidden/queued)
 */
function qa_db_acount_update()
{
	if (qa_should_update_counts()) {
		qa_db_query_sub(
			"INSERT INTO ^options (title, content) " .
			"SELECT 'cache_acount', COUNT(*) FROM ^posts " .
			"WHERE type = 'A' " .
			"ON DUPLICATE KEY UPDATE content = VALUES(content)"
		);
Scott committed
362
	}
Scott committed
363
}
Scott committed
364 365


Scott committed
366 367 368 369 370 371 372 373 374 375 376 377
/**
 * Update the cached count in the database of the number of comments (excluding hidden/queued)
 */
function qa_db_ccount_update()
{
	if (qa_should_update_counts()) {
		qa_db_query_sub(
			"INSERT INTO ^options (title, content) " .
			"SELECT 'cache_ccount', COUNT(*) FROM ^posts " .
			"WHERE type = 'C' " .
			"ON DUPLICATE KEY UPDATE content = VALUES(content)"
		);
Scott committed
378
	}
Scott committed
379
}
Scott committed
380 381


Scott committed
382 383 384 385 386 387 388 389 390 391 392 393
/**
 * Update the cached count in the database of the number of different tags used
 */
function qa_db_tagcount_update()
{
	if (qa_should_update_counts()) {
		qa_db_query_sub(
			"INSERT INTO ^options (title, content) " .
			"SELECT 'cache_tagcount', COUNT(*) FROM ^words " .
			"WHERE tagcount > 0 " .
			"ON DUPLICATE KEY UPDATE content = VALUES(content)"
		);
Scott committed
394
	}
Scott committed
395
}
Scott committed
396 397


Scott committed
398 399 400 401 402 403 404 405 406 407 408 409
/**
 * Update the cached count in the database of the number of unanswered questions (excluding hidden/queued)
 */
function qa_db_unaqcount_update()
{
	if (qa_should_update_counts()) {
		qa_db_query_sub(
			"INSERT INTO ^options (title, content) " .
			"SELECT 'cache_unaqcount', COUNT(*) FROM ^posts " .
			"WHERE type = 'Q' AND acount = 0 AND closedbyid IS NULL " .
			"ON DUPLICATE KEY UPDATE content = VALUES(content)"
		);
Scott committed
410
	}
Scott committed
411
}
Scott committed
412 413


Scott committed
414 415 416 417 418 419 420 421 422 423 424 425
/**
 * Update the cached count in the database of the number of questions with no answer selected (excluding hidden/queued)
 */
function qa_db_unselqcount_update()
{
	if (qa_should_update_counts()) {
		qa_db_query_sub(
			"INSERT INTO ^options (title, content) " .
			"SELECT 'cache_unselqcount', COUNT(*) FROM ^posts " .
			"WHERE type = 'Q' AND selchildid IS NULL AND closedbyid IS NULL " .
			"ON DUPLICATE KEY UPDATE content = VALUES(content)"
		);
Scott committed
426
	}
Scott committed
427
}
Scott committed
428 429


Scott committed
430 431 432 433 434 435 436 437 438 439 440 441
/**
 * Update the cached count in the database of the number of questions with no upvoted answers (excluding hidden/queued)
 */
function qa_db_unupaqcount_update()
{
	if (qa_should_update_counts()) {
		qa_db_query_sub(
			"INSERT INTO ^options (title, content) " .
			"SELECT 'cache_unupaqcount', COUNT(*) FROM ^posts " .
			"WHERE type = 'Q' AND amaxvote = 0 AND closedbyid IS NULL " .
			"ON DUPLICATE KEY UPDATE content = VALUES(content)"
		);
Scott committed
442
	}
Scott committed
443
}
Scott committed
444 445


Scott committed
446 447 448 449 450 451 452 453 454 455 456 457
/**
 * Update the cached count in the database of the number of posts which are queued for moderation
 */
function qa_db_queuedcount_update()
{
	if (qa_should_update_counts()) {
		qa_db_query_sub(
			"INSERT INTO ^options (title, content) " .
			"SELECT 'cache_queuedcount', COUNT(*) FROM ^posts " .
			"WHERE type IN ('Q_QUEUED', 'A_QUEUED', 'C_QUEUED') " .
			"ON DUPLICATE KEY UPDATE content = VALUES(content)"
		);
Scott committed
458
	}
Scott committed
459
}
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475


/**
 * Update the cached count in the database of the number of posts which are hidden
 */
function qa_db_hiddencount_update()
{
	if (qa_should_update_counts()) {
		qa_db_query_sub(
			"INSERT INTO ^options (title, content) " .
			"SELECT 'cache_hiddencount', COUNT(*) FROM ^posts " .
			"WHERE type IN ('Q_HIDDEN', 'A_HIDDEN', 'C_HIDDEN') " .
			"ON DUPLICATE KEY UPDATE content = VALUES(content)"
		);
	}
}