QnA_Question.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Data class to mark a notice as a question
  18. *
  19. * @category QnA
  20. * @package GNUsocial
  21. * @author Zach Copley <zach@status.net>
  22. * @copyright 2011 StatusNet, Inc.
  23. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  24. */
  25. defined('GNUSOCIAL') || die();
  26. /**
  27. * For storing a question
  28. *
  29. * @copyright 2011 StatusNet, Inc.
  30. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  31. *
  32. * @see DB_DataObject
  33. */
  34. class QnA_Question extends Managed_DataObject
  35. {
  36. const OBJECT_TYPE = 'http://activityschema.org/object/question';
  37. public $__table = 'qna_question'; // table name
  38. public $id; // char(36) primary key not null -> UUID
  39. public $uri; // varchar(191) not 255 because utf8mb4 takes more space
  40. public $profile_id; // int -> profile.id
  41. public $title; // text
  42. public $description; // text
  43. public $closed; // bool -> whether a question is closed
  44. public $created; // datetime
  45. /**
  46. * The One True Thingy that must be defined and declared.
  47. */
  48. public static function schemaDef()
  49. {
  50. return array(
  51. 'description' => 'Per-notice question data for QNA plugin',
  52. 'fields' => array(
  53. 'id' => array(
  54. 'type' => 'char',
  55. 'length' => 36,
  56. 'not null' => true,
  57. 'description' => 'UUID'
  58. ),
  59. 'uri' => array(
  60. 'type' => 'varchar',
  61. 'length' => 191,
  62. 'not null' => true
  63. ),
  64. 'profile_id' => array('type' => 'int'),
  65. 'title' => array('type' => 'text'),
  66. 'closed' => array('type' => 'bool'),
  67. 'description' => array('type' => 'text'),
  68. 'created' => array(
  69. 'type' => 'datetime',
  70. 'not null' => true
  71. ),
  72. ),
  73. 'primary key' => array('id'),
  74. 'unique keys' => array(
  75. 'qna_question_uri_key' => array('uri'),
  76. ),
  77. );
  78. }
  79. /**
  80. * Get a question based on a notice
  81. *
  82. * @param Notice $notice Notice to check for
  83. *
  84. * @return Question found question or null
  85. */
  86. public static function getByNotice($notice)
  87. {
  88. return self::getKV('uri', $notice->uri);
  89. }
  90. public function getNotice()
  91. {
  92. return Notice::getKV('uri', $this->uri);
  93. }
  94. public function getUrl()
  95. {
  96. return $this->getNotice()->getUrl();
  97. }
  98. public function getProfile()
  99. {
  100. $profile = Profile::getKV('id', $this->profile_id);
  101. if (empty($profile)) {
  102. // TRANS: Exception trown when getting a profile for a non-existing ID.
  103. // TRANS: %s is the provided profile ID.
  104. throw new Exception(sprintf(_m('No profile with ID %s'), $this->profile_id));
  105. }
  106. return $profile;
  107. }
  108. /**
  109. * Get the answer from a particular user to this question, if any.
  110. *
  111. * @param Profile $profile
  112. *
  113. * @return Answer object or null
  114. */
  115. public function getAnswer(Profile $profile)
  116. {
  117. $a = new QnA_Answer();
  118. $a->question_id = $this->id;
  119. $a->profile_id = $profile->id;
  120. $a->find();
  121. if ($a->fetch()) {
  122. return $a;
  123. } else {
  124. return null;
  125. }
  126. }
  127. public function getAnswers()
  128. {
  129. $a = new QnA_Answer();
  130. $a->question_id = $this->id;
  131. $cnt = $a->find();
  132. if (!empty($cnt)) {
  133. return $a;
  134. } else {
  135. return null;
  136. }
  137. }
  138. public function countAnswers()
  139. {
  140. $a = new QnA_Answer();
  141. $a->question_id = $this->id;
  142. return $a->count();
  143. }
  144. public static function fromNotice($notice)
  145. {
  146. return QnA_Question::getKV('uri', $notice->uri);
  147. }
  148. public function asHTML()
  149. {
  150. return self::toHTML($this->getProfile(), $this);
  151. }
  152. public function asString()
  153. {
  154. return self::toString($this->getProfile(), $this);
  155. }
  156. public static function toHTML($profile, $question)
  157. {
  158. $notice = $question->getNotice();
  159. $out = new XMLStringer();
  160. $cls = array('qna_question');
  161. if (!empty($question->closed)) {
  162. $cls[] = 'closed';
  163. }
  164. $out->elementStart('p', array('class' => implode(' ', $cls)));
  165. if (!empty($question->description)) {
  166. $out->elementStart('span', 'question-description');
  167. $out->raw(common_render_text($question->description));
  168. $out->elementEnd('span');
  169. }
  170. $cnt = $question->countAnswers();
  171. if (!empty($cnt)) {
  172. $out->elementStart('span', 'answer-count');
  173. // TRANS: Number of given answers to a question.
  174. // TRANS: %s is the number of given answers.
  175. $out->text(sprintf(_m('%s answer', '%s answers', $cnt), $cnt));
  176. $out->elementEnd('span');
  177. }
  178. if (!empty($question->closed)) {
  179. $out->elementStart('span', 'question-closed');
  180. // TRANS: Notification that a question cannot be answered anymore because it is closed.
  181. $out->text(_m('This question is closed.'));
  182. $out->elementEnd('span');
  183. }
  184. $out->elementEnd('p');
  185. return $out->getString();
  186. }
  187. public static function toString($profile, $question, $answers)
  188. {
  189. return sprintf(htmlspecialchars($question->description));
  190. }
  191. /**
  192. * Save a new question notice
  193. *
  194. * @param Profile $profile
  195. * @param string $question
  196. * @param string $title
  197. * @param string $description
  198. * @param array $option // and whatnot
  199. *
  200. * @return Notice saved notice
  201. */
  202. public static function saveNew($profile, $title, $description, $options = [])
  203. {
  204. $q = new QnA_Question();
  205. $q->id = UUID::gen();
  206. $q->profile_id = $profile->id;
  207. $q->title = $title;
  208. $q->description = $description;
  209. if (array_key_exists('created', $options)) {
  210. $q->created = $options['created'];
  211. } else {
  212. $q->created = common_sql_now();
  213. }
  214. if (array_key_exists('uri', $options)) {
  215. $q->uri = $options['uri'];
  216. } else {
  217. $q->uri = common_local_url(
  218. 'qnashowquestion',
  219. array('id' => $q->id)
  220. );
  221. }
  222. common_log(LOG_DEBUG, "Saving question: $q->id $q->uri");
  223. $q->insert();
  224. if (Notice::contentTooLong($q->title . ' ' . $q->uri)) {
  225. $max = Notice::maxContent();
  226. $uriLen = mb_strlen($q->uri);
  227. $targetLen = $max - ($uriLen + 15);
  228. $title = mb_substr($q->title, 0, $targetLen) . '…';
  229. }
  230. $content = $title . ' ' . $q->uri;
  231. $link = '<a href="' . htmlspecialchars($q->uri) . '">' . htmlspecialchars($q->title) . '</a>';
  232. // TRANS: Rendered version of the notice content creating a question.
  233. // TRANS: %s a link to the question as link description.
  234. $rendered = sprintf(_m('Question: %s'), $link);
  235. $tags = array('question');
  236. $replies = array();
  237. $options = array_merge(
  238. array(
  239. 'urls' => array(),
  240. 'rendered' => $rendered,
  241. 'tags' => $tags,
  242. 'replies' => $replies,
  243. 'object_type' => self::OBJECT_TYPE
  244. ),
  245. $options
  246. );
  247. if (!array_key_exists('uri', $options)) {
  248. $options['uri'] = $q->uri;
  249. }
  250. $saved = Notice::saveNew(
  251. $profile->id,
  252. $content,
  253. array_key_exists('source', $options) ?
  254. $options['source'] : 'web',
  255. $options
  256. );
  257. return $saved;
  258. }
  259. }