<?php /* Question2Answer by Gideon Greenspan and contributors http://www.question2answer.org/ Description: Higher-level functions to create and manipulate posts 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 */ if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser header('Location: ../../'); exit; } require_once QA_INCLUDE_DIR . 'qa-db.php'; require_once QA_INCLUDE_DIR . 'db/selects.php'; require_once QA_INCLUDE_DIR . 'app/format.php'; require_once QA_INCLUDE_DIR . 'app/post-create.php'; require_once QA_INCLUDE_DIR . 'app/post-update.php'; require_once QA_INCLUDE_DIR . 'app/users.php'; require_once QA_INCLUDE_DIR . 'util/string.php'; /** * Create a new post in the database, and return its postid. * * Set $type to 'Q' for a new question, 'A' for an answer, or 'C' for a comment. You can also use 'Q_QUEUED', * 'A_QUEUED' or 'C_QUEUED' to create a post which is queued for moderator approval. For questions, set $parentid to * the postid of the answer to which the question is related, or null if (as in most cases) the question is not related * to an answer. For answers, set $parentid to the postid of the question being answered. For comments, set $parentid * to the postid of the question or answer to which the comment relates. The $content and $format parameters go * together - if $format is '' then $content should be in plain UTF-8 text, and if $format is 'html' then $content * should be in UTF-8 HTML. Other values of $format may be allowed if an appropriate viewer module is installed. The * $title, $categoryid and $tags parameters are only relevant when creating a question - $tags can either be an array * of tags, or a string of tags separated by commas. The new post will be assigned to $userid if it is not null, * otherwise it will be by a non-user. If $notify is true then the author will be sent notifications relating to the * post - either to $email if it is specified and valid, or to the current email address of $userid if $email is '@'. * If you're creating a question, the $extravalue parameter will be set as the custom extra field, if not null. For all * post types you can specify the $name of the post's author, which is relevant if the $userid is null. * @param string $type * @param int|null $parentid * @param string $title * @param string $content * @param string $format * @param int|null $categoryid * @param array|null $tags * @param mixed|null $userid * @param string|null $notify * @param string|null $email * @param string|null $extravalue * @param string|null $name * @return mixed */ function qa_post_create($type, $parentid, $title, $content, $format = '', $categoryid = null, $tags = null, $userid = null, $notify = null, $email = null, $extravalue = null, $name = null) { $handle = qa_userid_to_handle($userid); $text = qa_post_content_to_text($content, $format); switch ($type) { case 'Q': case 'Q_QUEUED': $followanswer = isset($parentid) ? qa_post_get_full($parentid, 'A') : null; $tagstring = qa_post_tags_to_tagstring($tags); $postid = qa_question_create($followanswer, $userid, $handle, null, $title, $content, $format, $text, $tagstring, $notify, $email, $categoryid, $extravalue, $type == 'Q_QUEUED', $name); break; case 'A': case 'A_QUEUED': $question = qa_post_get_full($parentid, 'Q'); $postid = qa_answer_create($userid, $handle, null, $content, $format, $text, $notify, $email, $question, $type == 'A_QUEUED', $name); break; case 'C': case 'C_QUEUED': $parent = qa_post_get_full($parentid, 'QA'); $commentsfollows = qa_db_single_select(qa_db_full_child_posts_selectspec(null, $parentid)); $question = qa_post_parent_to_question($parent); $postid = qa_comment_create($userid, $handle, null, $content, $format, $text, $notify, $email, $question, $parent, $commentsfollows, $type == 'C_QUEUED', $name); break; default: qa_fatal_error('Post type not recognized: ' . $type); break; } return $postid; } /** * Change the data stored for post $postid based on any of the $title, $content, $format, $tags, $notify, $email, * $extravalue and $name parameters passed which are not null. The meaning of these parameters is the same as for * qa_post_create() above. Pass the identify of the user making this change in $byuserid (or null for silent). * @param int $postid * @param string|null $title * @param string|null $content * @param string $format * @param array|null $tags * @param string|null $notify * @param string|null $email * @param mixed|null $byuserid * @param string|null $extravalue * @param string $name */ function qa_post_set_content($postid, $title, $content, $format = null, $tags = null, $notify = null, $email = null, $byuserid = null, $extravalue = null, $name = null) { $oldpost = qa_post_get_full($postid, 'QAC'); if (!isset($title)) $title = $oldpost['title']; if (!isset($content)) $content = $oldpost['content']; if (!isset($format)) $format = $oldpost['format']; if (!isset($tags)) $tags = qa_tagstring_to_tags($oldpost['tags']); if (isset($notify) || isset($email)) $setnotify = qa_combine_notify_email($oldpost['userid'], isset($notify) ? $notify : isset($oldpost['notify']), isset($email) ? $email : $oldpost['notify']); else $setnotify = $oldpost['notify']; $byhandle = qa_userid_to_handle($byuserid); $text = qa_post_content_to_text($content, $format); switch ($oldpost['basetype']) { case 'Q': $tagstring = qa_post_tags_to_tagstring($tags); qa_question_set_content($oldpost, $title, $content, $format, $text, $tagstring, $setnotify, $byuserid, $byhandle, null, $extravalue, $name); break; case 'A': $question = qa_post_get_full($oldpost['parentid'], 'Q'); qa_answer_set_content($oldpost, $content, $format, $text, $setnotify, $byuserid, $byhandle, null, $question, $name); break; case 'C': $parent = qa_post_get_full($oldpost['parentid'], 'QA'); $question = qa_post_parent_to_question($parent); qa_comment_set_content($oldpost, $content, $format, $text, $setnotify, $byuserid, $byhandle, null, $question, $parent, $name); break; } } /** * Change the category of $postid to $categoryid. The category of all related posts (shown together on the same * question page) will also be changed. Pass the identify of the user making this change in $byuserid (or null for an * anonymous change). * @param int $postid * @param int $categoryid * @param mixed|null $byuserid */ function qa_post_set_category($postid, $categoryid, $byuserid = null) { $oldpost = qa_post_get_full($postid, 'QAC'); if ($oldpost['basetype'] == 'Q') { $byhandle = qa_userid_to_handle($byuserid); $answers = qa_post_get_question_answers($postid); $commentsfollows = qa_post_get_question_commentsfollows($postid); $closepost = qa_post_get_question_closepost($postid); qa_question_set_category($oldpost, $categoryid, $byuserid, $byhandle, null, $answers, $commentsfollows, $closepost); } else qa_post_set_category($oldpost['parentid'], $categoryid, $byuserid); // keep looking until we find the parent question } /** * Set the selected best answer of $questionid to $answerid (or to none if $answerid is null). Pass the identify of the * user in $byuserid (or null for an anonymous change). * @param int $questionid * @param int|null $answerid * @param mixed|null $byuserid */ function qa_post_set_selchildid($questionid, $answerid, $byuserid = null) { $oldquestion = qa_post_get_full($questionid, 'Q'); $byhandle = qa_userid_to_handle($byuserid); $answers = qa_post_get_question_answers($questionid); if (isset($answerid) && !isset($answers[$answerid])) qa_fatal_error('Answer ID could not be found: ' . $answerid); qa_question_set_selchildid($byuserid, $byhandle, null, $oldquestion, $answerid, $answers); } /** * Close $questionid if $closed is true, otherwise reopen it. If $closed is true, pass either the $originalpostid of * the question that it is a duplicate of, or a $note to explain why it's closed. Pass the identifier of the user in * $byuserid (or null for an anonymous change). * @param int $questionid * @param bool $closed * @param int|null $originalpostid * @param string|null $note * @param mixed|null $byuserid */ function qa_post_set_closed($questionid, $closed = true, $originalpostid = null, $note = null, $byuserid = null) { $oldquestion = qa_post_get_full($questionid, 'Q'); $oldclosepost = qa_post_get_question_closepost($questionid); $byhandle = qa_userid_to_handle($byuserid); if ($closed) { if (isset($originalpostid)) qa_question_close_duplicate($oldquestion, $oldclosepost, $originalpostid, $byuserid, $byhandle, null); elseif (isset($note)) qa_question_close_other($oldquestion, $oldclosepost, $note, $byuserid, $byhandle, null); else qa_fatal_error('Question must be closed as a duplicate or with a note'); } else qa_question_close_clear($oldquestion, $oldclosepost, $byuserid, $byhandle, null); } /** * Return whether the given question is closed. This check takes into account the do_close_on_select option which * considers questions with a selected answer as closed. * @since 1.8.2 * @param array $question * @return bool */ function qa_post_is_closed(array $question) { return isset($question['closedbyid']) || (isset($question['selchildid']) && qa_opt('do_close_on_select')); } /** * Hide $postid if $hidden is true, otherwise show the post. Pass the identify of the user making this change in * $byuserid (or null for a silent change). * @deprecated Replaced by qa_post_set_status. * @param int $postid * @param bool $hidden * @param mixed|null $byuserid */ function qa_post_set_hidden($postid, $hidden = true, $byuserid = null) { qa_post_set_status($postid, $hidden ? QA_POST_STATUS_HIDDEN : QA_POST_STATUS_NORMAL, $byuserid); } /** * Change the status of $postid to $status, which should be one of the QA_POST_STATUS_* constants defined in * /qa-include/app/post-update.php. Pass the identify of the user making this change in $byuserid (or null for a silent change). * @param int $postid * @param int $status * @param mixed|null $byuserid */ function qa_post_set_status($postid, $status, $byuserid = null) { $oldpost = qa_post_get_full($postid, 'QAC'); $byhandle = qa_userid_to_handle($byuserid); switch ($oldpost['basetype']) { case 'Q': $answers = qa_post_get_question_answers($postid); $commentsfollows = qa_post_get_question_commentsfollows($postid); $closepost = qa_post_get_question_closepost($postid); qa_question_set_status($oldpost, $status, $byuserid, $byhandle, null, $answers, $commentsfollows, $closepost); break; case 'A': $question = qa_post_get_full($oldpost['parentid'], 'Q'); $commentsfollows = qa_post_get_answer_commentsfollows($postid); qa_answer_set_status($oldpost, $status, $byuserid, $byhandle, null, $question, $commentsfollows); break; case 'C': $parent = qa_post_get_full($oldpost['parentid'], 'QA'); $question = qa_post_parent_to_question($parent); qa_comment_set_status($oldpost, $status, $byuserid, $byhandle, null, $question, $parent); break; } } /** * Set the created date of $postid to $created, which is a unix timestamp. * @param int $postid * @param int $created */ function qa_post_set_created($postid, $created) { $oldpost = qa_post_get_full($postid); qa_db_post_set_created($postid, $created); switch ($oldpost['basetype']) { case 'Q': qa_db_hotness_update($postid); break; case 'A': qa_db_hotness_update($oldpost['parentid']); break; } } /** * Delete $postid from the database, hiding it first if appropriate. * @param int $postid */ function qa_post_delete($postid) { $oldpost = qa_post_get_full($postid, 'QAC'); if (!$oldpost['hidden']) { qa_post_set_status($postid, QA_POST_STATUS_HIDDEN, null); $oldpost = qa_post_get_full($postid, 'QAC'); } switch ($oldpost['basetype']) { case 'Q': $answers = qa_post_get_question_answers($postid); $commentsfollows = qa_post_get_question_commentsfollows($postid); $closepost = qa_post_get_question_closepost($postid); if (count($answers) || count($commentsfollows)) qa_fatal_error('Could not delete question ID due to dependents: ' . $postid); qa_question_delete($oldpost, null, null, null, $closepost); break; case 'A': $question = qa_post_get_full($oldpost['parentid'], 'Q'); $commentsfollows = qa_post_get_answer_commentsfollows($postid); if (count($commentsfollows)) qa_fatal_error('Could not delete answer ID due to dependents: ' . $postid); qa_answer_delete($oldpost, $question, null, null, null); break; case 'C': $parent = qa_post_get_full($oldpost['parentid'], 'QA'); $question = qa_post_parent_to_question($parent); qa_comment_delete($oldpost, $question, $parent, null, null, null); break; } } /** * Return the full information from the database for $postid in an array. * @param int $postid * @param string|null $requiredbasetypes * @return array */ function qa_post_get_full($postid, $requiredbasetypes = null) { $post = qa_db_single_select(qa_db_full_post_selectspec(null, $postid)); if (!is_array($post)) qa_fatal_error('Post ID could not be found: ' . $postid); if (isset($requiredbasetypes) && !is_numeric(strpos($requiredbasetypes, $post['basetype']))) qa_fatal_error('Post of wrong type: ' . $post['basetype']); return $post; } /** * Return the handle corresponding to $userid, unless it is null in which case return null. * * @deprecated Deprecated from 1.7; use `qa_userid_to_handle($userid)` instead. * @param mixed $userid * @return string|null */ function qa_post_userid_to_handle($userid) { return qa_userid_to_handle($userid); } /** * Return the textual rendition of $content in $format (used for indexing). * @param string $content * @param string $format * @return string */ function qa_post_content_to_text($content, $format) { $viewer = qa_load_viewer($content, $format); if (!isset($viewer)) qa_fatal_error('Content could not be parsed in format: ' . $format); return $viewer->get_text($content, $format, array()); } /** * Return tagstring to store in the database based on $tags as an array or a comma-separated string. * @param array|string $tags * @return string */ function qa_post_tags_to_tagstring($tags) { if (is_array($tags)) $tags = implode(',', $tags); return qa_tags_to_tagstring(array_unique(preg_split('/\s*,\s*/', qa_strtolower(strtr($tags, '/', ' ')), -1, PREG_SPLIT_NO_EMPTY))); } /** * Return the full database records for all answers to question $questionid * @param int $questionid * @return array */ function qa_post_get_question_answers($questionid) { $answers = array(); $childposts = qa_db_single_select(qa_db_full_child_posts_selectspec(null, $questionid)); foreach ($childposts as $postid => $post) { if ($post['basetype'] == 'A') $answers[$postid] = $post; } return $answers; } /** * Return the full database records for all comments or follow-on questions for question $questionid or its answers * @param int $questionid * @return array */ function qa_post_get_question_commentsfollows($questionid) { $commentsfollows = array(); list($childposts, $achildposts) = qa_db_multi_select(array( qa_db_full_child_posts_selectspec(null, $questionid), qa_db_full_a_child_posts_selectspec(null, $questionid), )); foreach ($childposts as $postid => $post) { if ($post['basetype'] == 'C') $commentsfollows[$postid] = $post; } foreach ($achildposts as $postid => $post) { if ($post['basetype'] == 'Q' || $post['basetype'] == 'C') $commentsfollows[$postid] = $post; } return $commentsfollows; } /** * Return the full database record for the post which closed $questionid, if there is any * @param int $questionid * @return array|null */ function qa_post_get_question_closepost($questionid) { return qa_db_single_select(qa_db_post_close_post_selectspec($questionid)); } /** * Return the full database records for all comments or follow-on questions for answer $answerid * @param int $answerid * @return array */ function qa_post_get_answer_commentsfollows($answerid) { $commentsfollows = array(); $childposts = qa_db_single_select(qa_db_full_child_posts_selectspec(null, $answerid)); foreach ($childposts as $postid => $post) { if ($post['basetype'] == 'Q' || $post['basetype'] == 'C') $commentsfollows[$postid] = $post; } return $commentsfollows; } /** * Return $parent if it's the database record for a question, otherwise return the database record for its parent * @param array $parent * @return array */ function qa_post_parent_to_question($parent) { if ($parent['basetype'] == 'Q') $question = $parent; else $question = qa_post_get_full($parent['parentid'], 'Q'); return $question; }