From 7f4bd6b69f280810b793c0154d36176b3ed48e15 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 21 Mar 2011 20:57:19 -0700 Subject: [PATCH] Work on QnA notice display -- in progress --- plugins/QnA/QnAPlugin.php | 168 ++++++++---------------- plugins/QnA/actions/qnashowanswer.php | 10 +- plugins/QnA/actions/qnashowquestion.php | 4 +- plugins/QnA/actions/qnavote.php | 2 +- plugins/QnA/classes/QnA_Answer.php | 129 +++++++++++++++--- plugins/QnA/classes/QnA_Question.php | 5 +- plugins/QnA/lib/qnaansweredform.php | 122 +++++++++++++++++ 7 files changed, 296 insertions(+), 144 deletions(-) create mode 100644 plugins/QnA/lib/qnaansweredform.php diff --git a/plugins/QnA/QnAPlugin.php b/plugins/QnA/QnAPlugin.php index ba3b6e329a..992641b18e 100644 --- a/plugins/QnA/QnAPlugin.php +++ b/plugins/QnA/QnAPlugin.php @@ -88,7 +88,8 @@ class QnAPlugin extends MicroAppPlugin return false; case 'QnaquestionForm': case 'QnaanswerForm': - case 'QnavoteForm'; + case 'QnaansweredForm': + case 'QnavoteForm': include_once $dir . '/lib/' . strtolower($cls).'.php'; break; case 'QnA_Question': @@ -201,24 +202,23 @@ class QnAPlugin extends MicroAppPlugin switch ($activity->verb) { case ActivityVerb::POST: - $notice = Question::saveNew( + $notice = QnA_Question::saveNew( $actor, - $questionObj->title - // null, - // $questionObj->summary, - // $options + $questionObj->title, + $questionObj->summary, + $options ); break; - case Answer::NORMAL: + case Answer::ObjectType: $question = QnA_Question::staticGet('uri', $questionObj->id); if (empty($question)) { // FIXME: save the question throw new Exception("Answer to unknown question."); } - $notice = QnA_Answer::saveNew($actor, $question, $activity->verb, $options); + $notice = QnA_Answer::saveNew($actor, $question, $options); break; default: - throw new Exception("Unknown verb for question"); + throw new Exception("Unknown object type received by QnA Plugin"); } return $notice; @@ -292,127 +292,67 @@ class QnAPlugin extends MicroAppPlugin * @param Notice $notice * @param HTMLOutputter $out */ - function showNotice($notice, $out) { switch ($notice->object_type) { case QnA_Question::OBJECT_TYPE: - $this->showQuestionNotice($notice, $out); - break; + return $this->showNoticeQuestion($notice, $out); case QnA_Answer::OBJECT_TYPE: - $this->showAnswerNotice($notice, $out); - break; + return $this->showNoticeAnswer($notice, $out); + default: + // TRANS: Exception thrown when performing an unexpected action on a question. + // TRANS: %s is the unpexpected object type. + throw new Exception( + sprintf( + _m('Unexpected type for QnA plugin: %s.'), + $notice->object_type + ) + ); } - - // bad craziness - $out->elementStart('div', array('class' => 'question')); - - $profile = $notice->getProfile(); - $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); - - $out->element( - 'img', - array( - 'src' => ($avatar) - ? $avatar->displayUrl() - : Avatar::defaultImage(AVATAR_MINI_SIZE), - 'class' => 'avatar photo question-avatar', - 'width' => AVATAR_MINI_SIZE, - 'height' => AVATAR_MINI_SIZE, - 'alt' => $profile->getBestName() - ) - ); - - $out->raw(' '); // avoid   for AJAX XML compatibility - - // hack for belongsOnTimeline; JS needs to be able to find the author - $out->elementStart('span', 'vcard author'); - $out->element( - 'a', - array( - 'class' => 'url', - 'href' => $profile->profileurl, - 'title' => $profile->getBestName() - ), - $profile->nickname - ); - - $out->elementEnd('span'); } - - function showAnswerNotice($notice, $out) + + function showNoticeQuestion($notice, $out) { - $answer = QnA_Answer::fromNotice($notice); - - assert(!empty($answer)); - - $out->elementStart('div', 'answer'); - $out->raw($answer->asHTML()); - $out->elementEnd('div'); - } - - function showQuestionNotice($notice, $out) - { - $profile = $notice->getProfile(); - $question = QnA_Question::fromNotice($notice); - - assert(!empty($question)); - assert(!empty($profile)); - - $out->elementStart('div', 'question-notice'); - - $out->elementStart('h3'); + $user = common_current_user(); - if (!empty($question->url)) { - $out->element( - 'a', - array( - 'href' => $question->url, - 'class' => 'question-title' - ), - $question->title - ); + // @hack we want regular rendering, then just add stuff after that + $nli = new NoticeListItem($notice, $out); + $nli->showNotice(); + + $out->elementStart('div', array('class' => 'entry-content question-content')); + $question = QnA_Question::getByNotice($notice); + + if ($question) { + if ($user) { + $profile = $user->getProfile(); + $answer = $question->getAnswer($profile); + if ($answer) { + // User has already answer; show the results. + $form = new QnaansweredForm($answer, $out); + } else { + $form = new QnaanswerForm($question, $out); + } + $form->show(); + } } else { - $out->text($question->title); + $out->text(_m('Question data is missing')); } - - if (!empty($question->location)) { - $out->elementStart('div', 'question-location'); - $out->element('strong', null, _('Location: ')); - $out->element('span', 'location', $question->location); - $out->elementEnd('div'); - } - - if (!empty($question->description)) { - $out->elementStart('div', 'question-description'); - $out->element('strong', null, _('Description: ')); - $out->element('span', 'description', $question->description); - $out->elementEnd('div'); - } - - //$answers = $question->getAnswers(); - - $out->elementStart('div', 'question-answers'); - $out->element('strong', null, _('Answer: ')); - $out->element('span', 'question-answer'); - $out->elementEnd('div'); - $user = common_current_user(); - - if (!empty($user)) { - - $answer = $question->getAnswer($user->getProfile()); - - if (empty($answer)) { - $form = new QnaanswerForm($question, $out); - $form->show(); - } + // @fixme + $out->elementStart('div', array('class' => 'entry-content')); + } + function showNoticeAnswer($notice, $out) + { + $user = common_current_user(); - } + // @hack we want regular rendering, then just add stuff after that + $nli = new NoticeListItem($notice, $out); + $nli->showNotice(); - $out->elementEnd('div'); + // @fixme + $out->elementStart('div', array('class' => 'entry-content')); } /** diff --git a/plugins/QnA/actions/qnashowanswer.php b/plugins/QnA/actions/qnashowanswer.php index 68baadfba8..9721f22da3 100644 --- a/plugins/QnA/actions/qnashowanswer.php +++ b/plugins/QnA/actions/qnashowanswer.php @@ -63,7 +63,7 @@ class QnashowanswerAction extends ShownoticeAction $this->id = $this->trimmed('id'); - $this->answer = Answer::staticGet('id', $this->id); + $this->answer = QnA_Answer::staticGet('id', $this->id); if (empty($this->answer)) { throw new ClientException(_('No such answer.'), 404); @@ -117,9 +117,11 @@ class QnashowanswerAction extends ShownoticeAction function showPageTitle() { $this->elementStart('h1'); - $this->element('a', - array('href' => $this->answer->url), - $this->asnwer->title); + $this->element( + 'a', + array('href' => $this->answer->url), + $this->answer->title + ); $this->elementEnd('h1'); } } diff --git a/plugins/QnA/actions/qnashowquestion.php b/plugins/QnA/actions/qnashowquestion.php index e563753a01..6719125354 100644 --- a/plugins/QnA/actions/qnashowquestion.php +++ b/plugins/QnA/actions/qnashowquestion.php @@ -61,7 +61,7 @@ class QnashowquestionAction extends ShownoticeAction $this->id = $this->trimmed('id'); - $this->question = Question::staticGet('id', $this->id); + $this->question = QnA_Question::staticGet('id', $this->id); if (empty($this->question)) { // TRANS: Client exception thrown trying to view a non-existing question. @@ -108,7 +108,7 @@ class QnashowquestionAction extends ShownoticeAction // TRANS: %1$s is the nickname of the user who asked the question, %2$s is the question. return sprintf(_m('%1$s\'s question: %2$s'), $this->user->nickname, - $this->question->question); + $this->question->title); } /** diff --git a/plugins/QnA/actions/qnavote.php b/plugins/QnA/actions/qnavote.php index 94aec41c5b..8098cb87d0 100644 --- a/plugins/QnA/actions/qnavote.php +++ b/plugins/QnA/actions/qnavote.php @@ -90,7 +90,7 @@ class Qnavote extends Action } $id = $this->trimmed('id'); - $this->question = Question::staticGet('id', $id); + $this->question = QnA_Question::staticGet('id', $id); if (empty($this->question)) { // TRANS: Client exception thrown trying to respond to a non-existing question. throw new ClientException(_m('Invalid or missing question.'), 404); diff --git a/plugins/QnA/classes/QnA_Answer.php b/plugins/QnA/classes/QnA_Answer.php index ff11ff8f14..57c08afe4e 100644 --- a/plugins/QnA/classes/QnA_Answer.php +++ b/plugins/QnA/classes/QnA_Answer.php @@ -50,7 +50,9 @@ class QnA_Answer extends Managed_DataObject public $id; // char(36) primary key not null -> UUID public $question_id; // char(36) -> question.id UUID public $profile_id; // int -> question.id - public $best; // (int) boolean -> whether the question asker has marked this as the best answer + public $best; // (boolean) int -> whether the question asker has marked this as the best answer + public $revisions; // int -> count of revisions to this answer + public $text; // text -> response text public $created; // datetime /** @@ -105,14 +107,15 @@ class QnA_Answer extends Managed_DataObject 'description' => 'UUID to the answer notice' ), 'question_id' => array( - 'type' => 'char', - 'length' => 36, - 'not null' => true, + 'type' => 'char', + 'length' => 36, + 'not null' => true, 'description' => 'UUID of question being responded to' ), - 'best' => array('type' => 'int', 'size' => 'tiny'), - 'profile_id' => array('type' => 'int'), - 'created' => array('type' => 'datetime', 'not null' => true), + 'best' => array('type' => 'int', 'size' => 'tiny'), + 'revisions' => array('type' => 'int'), + 'profile_id' => array('type' => 'int'), + 'created' => array('type' => 'datetime', 'not null' => true), ), 'primary key' => array('id'), 'unique keys' => array( @@ -134,7 +137,11 @@ class QnA_Answer extends Managed_DataObject */ function getByNotice($notice) { - return self::staticGet('uri', $notice->uri); + $answer = self::staticGet('uri', $notice->uri); + if (empty($answer)) { + throw new Exception("No answer with URI {$this->notice->uri}"); + } + return $answer; } /** @@ -159,14 +166,93 @@ class QnA_Answer extends Managed_DataObject */ function getQuestion() { - return Question::staticGet('id', $this->question_id); + $question = self::staticGet('id', $this->question_id); + if (empty($question)) { + throw new Exception("No question with ID {$this->question_id}"); + } + return question; + } + + function getProfile() + { + $profile = Profile::staticGet('id', $this->profile_id); + if (empty($profile)) { + throw new Exception("No profile with ID {$this->profile_id}"); + } + return $profile; } - static function fromNotice($notice) + function asHTML() { - return self::staticGet('uri', $notice->uri); + return self::toHTML( + $this->getProfile(), + $this->getQuestion() + ); } + function asString() + { + return self::toString( + $this->getProfile(), + $this->getQuestion() + ); + } + + static function toHTML($profile, $event, $response) + { + $fmt = null; + + $notice = $event->getNotice(); + + switch ($response) { + case 'Y': + $fmt = _("%2s is attending %4s."); + break; + case 'N': + $fmt = _("%2s is not attending %4s."); + break; + case '?': + $fmt = _("%2s might attend %4s."); + break; + default: + throw new Exception("Unknown response code {$response}"); + break; + } + + return sprintf($fmt, + htmlspecialchars($profile->profileurl), + htmlspecialchars($profile->getBestName()), + htmlspecialchars($notice->bestUrl()), + htmlspecialchars($event->title)); + } + + static function toString($profile, $event, $response) + { + $fmt = null; + + $notice = $event->getNotice(); + + switch ($response) { + case 'Y': + $fmt = _("%1s is attending %2s."); + break; + case 'N': + $fmt = _("%1s is not attending %2s."); + break; + case '?': + $fmt = _("%1s might attend %2s.>"); + break; + default: + throw new Exception("Unknown response code {$response}"); + break; + } + + return sprintf($fmt, + $profile->getBestName(), + $event->title); + } + + /** * Save a new answer notice * @@ -176,7 +262,7 @@ class QnA_Answer extends Managed_DataObject * * @return Notice saved notice */ - static function saveNew($profile, $question, $options = null) + static function saveNew($profile, $question, $text, $options = null) { if (empty($options)) { $options = array(); @@ -186,23 +272,24 @@ class QnA_Answer extends Managed_DataObject $answer->id = UUID::gen(); $answer->profile_id = $profile->id; $answer->question_id = $question->id; + $answer->revisions = 0; + $answer->best = 0; + $answer->text = $text; $answer->created = common_sql_now(); $answer->uri = common_local_url( - 'showanswer', + 'qnashowanswer', array('id' => $answer->id) ); common_log(LOG_DEBUG, "Saving answer: $answer->id, $answer->uri"); $answer->insert(); - // TRANS: Notice content answering a question. - // TRANS: %s is the answer $content = sprintf( _m('answered "%s"'), - $answer->uri + $question->title ); - $link = '' . htmlspecialchars($answer) . ''; + $link = '' . htmlspecialchars($question->title) . ''; // TRANS: Rendered version of the notice content answering a question. // TRANS: %s a link to the question with question title as the link content. $rendered = sprintf(_m('answered "%s"'), $link); @@ -213,13 +300,15 @@ class QnA_Answer extends Managed_DataObject $options = array_merge( array( 'urls' => array(), + 'content' => $content, 'rendered' => $rendered, 'tags' => $tags, 'replies' => $replies, 'reply_to' => $question->getNotice()->id, - 'object_type' => self::OBJECT_TYPE), - $options - ); + 'object_type' => self::OBJECT_TYPE + ), + $options + ); if (!array_key_exists('uri', $options)) { $options['uri'] = $answer->uri; diff --git a/plugins/QnA/classes/QnA_Question.php b/plugins/QnA/classes/QnA_Question.php index 308e87b99f..5230923590 100644 --- a/plugins/QnA/classes/QnA_Question.php +++ b/plugins/QnA/classes/QnA_Question.php @@ -113,7 +113,7 @@ class QnA_Question extends Managed_DataObject 'closed' => array('type' => 'int', 'size' => 'tiny'), 'description' => array('type' => 'text'), 'created' => array( - 'type' => 'datetime', + 'type' => 'datetime', 'not null' => true ), ), @@ -175,7 +175,6 @@ class QnA_Question extends Managed_DataObject static function fromNotice($notice) { - common_debug('xxxxxxxxxxxxxxx notice-uri = ' . $notice->uri); return QnA_Question::staticGet('uri', $notice->uri); } @@ -209,7 +208,7 @@ class QnA_Question extends Managed_DataObject $q->uri = $options['uri']; } else { $q->uri = common_local_url( - 'showquestion', + 'qnashowquestion', array('id' => $q->id) ); } diff --git a/plugins/QnA/lib/qnaansweredform.php b/plugins/QnA/lib/qnaansweredform.php new file mode 100644 index 0000000000..a229e7f870 --- /dev/null +++ b/plugins/QnA/lib/qnaansweredform.php @@ -0,0 +1,122 @@ +. + * + * @category QnA + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Form to add a new answer to a question + * + * @category QnA + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class QnaansweredForm extends Form +{ + protected $question; + protected $answer; + + /** + * Construct a new answer form + * + * @param QnA_Answer $answer + * @param HTMLOutputter $out output channel + * + * @return void + */ + function __construct(QnA_Answer $answer, HTMLOutputter $out) + { + parent::__construct($out); + $this->question = $answer->getQuestion(); + $this->answer = $answer; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + function id() + { + return 'answered-form'; + } + + /** + * class of the form + * + * @return string class of the form + */ + function formClass() + { + return 'form_settings ajax'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + function action() + { + return common_local_url('qnareviseanswer', array('id' => $this->question->id)); + } + + /** + * Data elements of the form + * + * @return void + */ + function formData() + { + $question = $this->question; + $out = $this->out; + $id = "question-" . $question->id; + + $out->element('p', 'Your answer to:', $question->title); + $out->element('input', array('type' => 'text', 'name' => 'answer')); + } + + /** + * Action elements + * + * @return void + */ + function formActions() + { + // TRANS: Button text for submitting a poll response. + $this->out->submit('submit', _m('BUTTON', 'Submit')); + } +} -- 2.39.5