diff --git a/plugins/QuestionAndAnswer/QuestionAndAnswerPlugin.php b/plugins/QnA/QnAPlugin similarity index 85% rename from plugins/QuestionAndAnswer/QuestionAndAnswerPlugin.php rename to plugins/QnA/QnAPlugin index e519dac64f..76bd304a87 100644 --- a/plugins/QuestionAndAnswer/QuestionAndAnswerPlugin.php +++ b/plugins/QnA/QnAPlugin @@ -20,7 +20,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @copyright 2011 StatusNet, Inc. @@ -44,8 +44,13 @@ if (!defined('STATUSNET')) { * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @link http://status.net/ */ -class QuestionAndAnswerPlugin extends MicroappPlugin +class QnAPlugin extends MicroAppPlugin { + + // @fixme which domain should we use for these namespaces? + const QUESTION_OBJECT = 'http://activityschema.org/object/question'; + const ANSWER_OBJECT = 'http://activityschema.org/object/answer'; + /** * Set up our tables (question and answer) * @@ -58,9 +63,10 @@ class QuestionAndAnswerPlugin extends MicroappPlugin { $schema = Schema::get(); - $schema->ensureTable('question', Question::schemaDef()); - $schema->ensureTable('answer', Answer::schemaDef()); - + $schema->ensureTable('qna_question', QnA_Question::schemaDef()); + $schema->ensureTable('qna_answer', QnA_Answer::schemaDef()); + $schema->ensureTable('qna_vote', QnA_Vote::schemaDef()); + return true; } @@ -81,15 +87,18 @@ class QuestionAndAnswerPlugin extends MicroappPlugin case 'NewanswerAction': case 'ShowquestionAction': case 'ShowanswerAction': + case 'QnavoteAction': include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; case 'QuestionForm': case 'AnswerForm': + case 'VoteForm'; include_once $dir . '/lib/' . strtolower($cls).'.php'; break; - case 'Question': - case 'Answer': + case 'QnA_Question': + case 'QnA_Answer': + case 'QnA_Vote': include_once $dir . '/classes/' . $cls.'.php'; return false; break; @@ -108,26 +117,47 @@ class QuestionAndAnswerPlugin extends MicroappPlugin function onRouterInitialized($m) { - $m->connect('main/question/new', - array('action' => 'newquestion')); - $m->connect('main/question/answer', - array('action' => 'newanswer')); - $m->connect('question/:id', - array('action' => 'showquestion'), - array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')); - $m->connect('answer/:id', - array('action' => 'showanswer'), - array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')); + $regexId = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'; + + $m->connect( + 'main/question/new', + array('action' => 'newquestion') + ); + $m->connect( + 'main/question/answer', + array('action' => 'newanswer') + ); + $m->connect( + 'question/vote/:id', + array('action' => 'qnavote', 'type' => 'question'), + array('id' => $regexId) + ); + $m->connect( + 'question/:id', + array('action' => 'showquestion'), + array('id' => $regexId) + ); + $m->connect( + 'answer/vote/:id', + array('action' => 'qnavote', 'type' => 'answer'), + array('id' => $regexId) + ); + $m->connect( + 'answer/:id', + array('action' => 'showanswer'), + array('id' => $regexId) + ); + return true; } function onPluginVersion(&$versions) { $versions[] = array( - 'name' => 'QuestionAndAnswer', + 'name' => 'QnA', 'version' => STATUSNET_VERSION, 'author' => 'Zach Copley', - 'homepage' => 'http://status.net/wiki/Plugin:QuestionAndAnswer', + 'homepage' => 'http://status.net/wiki/Plugin:QnA', 'description' => _m('Question and Answers micro-app.') ); @@ -167,7 +197,7 @@ class QuestionAndAnswerPlugin extends MicroappPlugin $questionObj = $activity->objects[0]; - if ($questinoObj->type != Question::OBJECT_TYPE) { + if ($questinoObj->type != QnA_Question::OBJECT_TYPE) { throw new Exception('Wrong type for object.'); } @@ -184,13 +214,12 @@ class QuestionAndAnswerPlugin extends MicroappPlugin ); break; case Answer::NORMAL: - case Answer::ANONYMOUS: - $question = Question::staticGet('uri', $questionObj->id); + $question = QnA_Question::staticGet('uri', $questionObj->id); if (empty($question)) { // FIXME: save the question throw new Exception("Answer to unknown question."); } - $notice = Answer::saveNew($actor, $question, $activity->verb, $options); + $notice = QnA_Answer::saveNew($actor, $question, $activity->verb, $options); break; default: throw new Exception("Unknown verb for question"); diff --git a/plugins/QuestionAndAnswer/actions/answer.php b/plugins/QnA/actions/answer.php similarity index 99% rename from plugins/QuestionAndAnswer/actions/answer.php rename to plugins/QnA/actions/answer.php index 49bb73aa54..17e841e545 100644 --- a/plugins/QuestionAndAnswer/actions/answer.php +++ b/plugins/QnA/actions/answer.php @@ -36,7 +36,7 @@ if (!defined('STATUSNET')) { /** * Answer a question * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @copyright 2010 StatusNet, Inc. diff --git a/plugins/QuestionAndAnswer/actions/Newquestion.php b/plugins/QnA/actions/newquestion.php similarity index 99% rename from plugins/QuestionAndAnswer/actions/Newquestion.php rename to plugins/QnA/actions/newquestion.php index cd1c2ffb13..83b1022d6b 100644 --- a/plugins/QuestionAndAnswer/actions/Newquestion.php +++ b/plugins/QnA/actions/newquestion.php @@ -20,7 +20,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @copyright 2011 StatusNet, Inc. diff --git a/plugins/QnA/actions/qnavote.php b/plugins/QnA/actions/qnavote.php new file mode 100644 index 0000000000..17e841e545 --- /dev/null +++ b/plugins/QnA/actions/qnavote.php @@ -0,0 +1,198 @@ +. + * + * @category QuestonAndAnswer + * @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); +} + +/** + * Answer a question + * + * @category QnA + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class AnswerAction extends Action +{ + protected $user = null; + protected $error = null; + protected $complete = null; + + protected $qustion = null; + protected $answer = null; + + /** + * Returns the title of the action + * + * @return string Action title + */ + function title() + { + // TRANS: Page title for and answer to a question. + return _m('Answer'); + } + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + function prepare($argarray) + { + parent::prepare($argarray); + if ($this->boolean('ajax')) { + StatusNet::setApi(true); + } + + $this->user = common_current_user(); + + if (empty($this->user)) { + // TRANS: Client exception thrown trying to answer a question while not logged in. + throw new ClientException(_m("You must be logged in to answer to a question."), + 403); + } + + if ($this->isPost()) { + $this->checkSessionToken(); + } + + $id = $this->trimmed('id'); + $this->question = 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); + } + + $answer = $this->trimmed('answer'); + + + return true; + } + + /** + * Handler method + * + * @param array $argarray is ignored since it's now passed in in prepare() + * + * @return void + */ + function handle($argarray=null) + { + parent::handle($argarray); + + if ($this->isPost()) { + $this->answer(); + } else { + $this->showPage(); + } + + return; + } + + /** + * Add a new answer + * + * @return void + */ + function answer() + { + try { + $notice = Answer::saveNew( + $this->user->getProfile(), + $this->question, + $this->answer + ); + } catch (ClientException $ce) { + $this->error = $ce->getMessage(); + $this->showPage(); + return; + } + + if ($this->boolean('ajax')) { + header('Content-Type: text/xml;charset=utf-8'); + $this->xw->startDocument('1.0', 'UTF-8'); + $this->elementStart('html'); + $this->elementStart('head'); + // TRANS: Page title after sending an answer. + $this->element('title', null, _m('Answers')); + $this->elementEnd('head'); + $this->elementStart('body'); + $form = new Answer($this->question, $this); + $form->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + common_redirect($this->question->bestUrl(), 303); + } + } + + /** + * Show the Answer form + * + * @return void + */ + function showContent() + { + if (!empty($this->error)) { + $this->element('p', 'error', $this->error); + } + + $form = new AnswerForm($this->question, $this); + + $form->show(); + + return; + } + + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + function isReadOnly($args) + { + if ($_SERVER['REQUEST_METHOD'] == 'GET' || + $_SERVER['REQUEST_METHOD'] == 'HEAD') { + return true; + } else { + return false; + } + } +} diff --git a/plugins/QuestionAndAnswer/actions/showanswer.php b/plugins/QnA/actions/showanswer.php similarity index 98% rename from plugins/QuestionAndAnswer/actions/showanswer.php rename to plugins/QnA/actions/showanswer.php index d3202cd51d..7686d6d566 100644 --- a/plugins/QuestionAndAnswer/actions/showanswer.php +++ b/plugins/QnA/actions/showanswer.php @@ -20,7 +20,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @copyright 2010 StatusNet, Inc. @@ -37,7 +37,7 @@ if (!defined('STATUSNET')) { /** * Show an answer to a question, and associated data * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @copyright 2010 StatusNet, Inc. diff --git a/plugins/QuestionAndAnswer/actions/showquestion.php b/plugins/QnA/actions/showquestion.php similarity index 98% rename from plugins/QuestionAndAnswer/actions/showquestion.php rename to plugins/QnA/actions/showquestion.php index 50f56fd161..41c1d809fe 100644 --- a/plugins/QuestionAndAnswer/actions/showquestion.php +++ b/plugins/QnA/actions/showquestion.php @@ -20,7 +20,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @copyright 2011 StatusNet, Inc. @@ -37,7 +37,7 @@ if (!defined('STATUSNET')) { /** * Show a question * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @copyright 2011 StatusNet, Inc. diff --git a/plugins/QuestionAndAnswer/classes/Answer.php b/plugins/QnA/classes/QnA_Answer.php similarity index 68% rename from plugins/QuestionAndAnswer/classes/Answer.php rename to plugins/QnA/classes/QnA_Answer.php index 45e52d0d39..d88e6bda41 100644 --- a/plugins/QuestionAndAnswer/classes/Answer.php +++ b/plugins/QnA/classes/QnA_Answer.php @@ -4,7 +4,7 @@ * * PHP version 5 * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 @@ -34,7 +34,7 @@ if (!defined('STATUSNET')) { /** * For storing answers * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 @@ -42,13 +42,14 @@ if (!defined('STATUSNET')) { * * @see DB_DataObject */ -class Answer extends Managed_DataObject +class QnA_Answer extends Managed_DataObject { - public $__table = 'answer'; // table name + CONST ANSWER = 'http://activityschema.org/object/answer'; + + public $__table = 'qna_answer'; // table name public $id; // char(36) primary key not null -> UUID public $question_id; // char(36) -> question.id UUID public $profile_id; // int -> question.id - public $votes; // int -> total number of votes (up & down) public $best; // (int) boolean -> whether the question asker has marked this as the best answer public $created; // datetime @@ -57,15 +58,15 @@ class Answer extends Managed_DataObject * * This is a utility method to get a single instance with a given key value. * - * @param string $k Key to use to lookup (usually 'user_id' for this class) + * @param string $k Key to use to lookup * @param mixed $v Value to lookup * - * @return User_greeting_count object found, or null for no hits + * @return QnA_Answer object found, or null for no hits * */ function staticGet($k, $v=null) { - return Memcached_DataObject::staticGet('Answer', $k, $v); + return Memcached_DataObject::staticGet('QnA_Answer', $k, $v); } /** @@ -77,12 +78,12 @@ class Answer extends Managed_DataObject * * @param array $kv array of key-value mappings * - * @return Bookmark object found, or null for no hits + * @return QA_Answer object found, or null for no hits * */ function pkeyGet($kv) { - return Memcached_DataObject::pkeyGet('Answer', $kv); + return Memcached_DataObject::pkeyGet('QnA_Answer', $kv); } /** @@ -93,13 +94,25 @@ class Answer extends Managed_DataObject return array( 'description' => 'Record of answers to questions', 'fields' => array( - 'id' => array('type' => 'char', 'length' => 36, 'not null' => true, 'description' => 'UUID of the response'), - 'uri' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'UUID to the answer notice'), - 'question_id' => array('type' => 'char', 'length' => 36, 'not null' => true, 'description' => 'UUID of question being responded to'), - 'votes' => array('type' => 'int'), - 'best' => array('type' => 'int'), - 'profile_id' => array('type' => 'int'), - 'created' => array('type' => 'datetime', 'not null' => true), + 'id' => array( + 'type' => 'char', + 'length' => 36, + 'not null' => true, 'description' => 'UUID of the response'), + 'uri' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => true, + 'description' => 'UUID to the answer notice' + ), + 'question_id' => array( + '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), ), 'primary key' => array('id'), 'unique keys' => array( @@ -107,7 +120,7 @@ class Answer extends Managed_DataObject 'question_id_profile_id_key' => array('question_id', 'profile_id'), ), 'indexes' => array( - 'profile_id_question_Id_index' => array('profile_id', 'question_id'), + 'profile_id_question_id_index' => array('profile_id', 'question_id'), ) ); } @@ -117,7 +130,7 @@ class Answer extends Managed_DataObject * * @param Notice $notice Notice to check for * - * @return Answer found response or null + * @return QnA_Answer found response or null */ function getByNotice($notice) { @@ -142,12 +155,13 @@ class Answer extends Managed_DataObject /** * Get the Question this is an answer to * - * @return Question + * @return QnA_Question */ function getQuestion() { return Question::staticGet('id', $this->question_id); } + /** * Save a new answer notice * @@ -184,29 +198,34 @@ class Answer extends Managed_DataObject ); $link = '' . htmlspecialchars($answer) . ''; // TRANS: Rendered version of the notice content answering a question. - // TRANS: %s a link to the question with the chosen option as link description. + // TRANS: %s a link to the question with question title as the link content. $rendered = sprintf(_m('answered "%s"'), $link); $tags = array(); $replies = array(); - $options = array_merge(array('urls' => array(), - 'rendered' => $rendered, - 'tags' => $tags, - 'replies' => $replies, - 'reply_to' => $question->getNotice()->id, - 'object_type' => QuestionAndAnswer::ANSWER_OBJECT), - $options); + $options = array_merge( + array( + 'urls' => array(), + 'rendered' => $rendered, + 'tags' => $tags, + 'replies' => $replies, + 'reply_to' => $question->getNotice()->id, + 'object_type' => QnA::ANSWER_OBJECT), + $options + ); if (!array_key_exists('uri', $options)) { $options['uri'] = $pr->uri; } - $saved = Notice::saveNew($profile->id, - $content, - array_key_exists('source', $options) ? - $options['source'] : 'web', - $options); + $saved = Notice::saveNew( + $profile->id, + $content, + array_key_exists('source', $options) ? + $options['source'] : 'web', + $options + ); return $saved; } diff --git a/plugins/QuestionAndAnswer/classes/Question.php b/plugins/QnA/classes/QnA_Question.php similarity index 79% rename from plugins/QuestionAndAnswer/classes/Question.php rename to plugins/QnA/classes/QnA_Question.php index 95ceeb45e2..1a298ae4e9 100644 --- a/plugins/QuestionAndAnswer/classes/Question.php +++ b/plugins/QnA/classes/QnA_Question.php @@ -4,7 +4,7 @@ * * PHP version 5 * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 @@ -34,7 +34,7 @@ if (!defined('STATUSNET')) { /** * For storing a question * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 @@ -43,14 +43,18 @@ if (!defined('STATUSNET')) { * @see DB_DataObject */ -class Question extends Managed_DataObject +class QnA_Question extends Managed_DataObject { - public $__table = 'question'; // table name + + const QUESTION = 'http://activityschema.org/object/question'; + + public $__table = 'qna_question'; // table name public $id; // char(36) primary key not null -> UUID public $uri; public $profile_id; // int -> profile.id public $title; // text public $description; // text + public $closed; // int (boolean) whether a question is closed public $created; // datetime /** @@ -58,15 +62,15 @@ class Question extends Managed_DataObject * * This is a utility method to get a single instance with a given key value. * - * @param string $k Key to use to lookup (usually 'user_id' for this class) + * @param string $k Key to use to lookup * @param mixed $v Value to lookup * - * @return User_greeting_count object found, or null for no hits + * @return QnA_Question object found, or null for no hits * */ function staticGet($k, $v=null) { - return Memcached_DataObject::staticGet('Question', $k, $v); + return Memcached_DataObject::staticGet('QnA_Question', $k, $v); } /** @@ -83,7 +87,7 @@ class Question extends Managed_DataObject */ function pkeyGet($kv) { - return Memcached_DataObject::pkeyGet('Question', $kv); + return Memcached_DataObject::pkeyGet('QnA_Question', $kv); } /** @@ -92,14 +96,27 @@ class Question extends Managed_DataObject public static function schemaDef() { return array( - 'description' => 'Per-notice question data for QuestionAndAnswer plugin', + 'description' => 'Per-notice question data for QNA plugin', 'fields' => array( - 'id' => array('type' => 'char', 'length' => 36, 'not null' => true, 'description' => 'UUID'), - 'uri' => array('type' => 'varchar', 'length' => 255, 'not null' => true), - 'profile_id' => array('type' => 'int'), - 'title' => array('type' => 'text'), + 'id' => array( + 'type' => 'char', + 'length' => 36, + 'not null' => true, + 'description' => 'UUID' + ), + 'uri' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => true + ), + 'profile_id' => array('type' => 'int'), + 'title' => array('type' => 'text'), + 'closed' => array('type' => 'int', size => 'tiny'), 'description' => array('type' => 'text'), - 'created' => array('type' => 'datetime', 'not null' => true), + 'created' => array( + 'type' => 'datetime', + 'not null' => true + ), ), 'primary key' => array('id'), 'unique keys' => array( @@ -139,7 +156,7 @@ class Question extends Managed_DataObject */ function getAnswer(Profile $profile) { - $a = new Answer(); + $a = new QnA_Answer(); $a->question_id = $this->id; $a->profile_id = $profile->id; $a->find(); @@ -152,8 +169,7 @@ class Question extends Managed_DataObject function countAnswers() { - $a = new Answer(); - + $a = new QnA_Answer(); $a->question_id = $this->id; return $a-count(); } @@ -171,7 +187,7 @@ class Question extends Managed_DataObject */ static function saveNew($profile, $question, $title, $description, $options = array()) { - $q = new Question(); + $q = new QnA_Question(); $q->id = UUID::gen(); $q->profile_id = $profile->id; @@ -218,7 +234,7 @@ class Question extends Managed_DataObject 'rendered' => $rendered, 'tags' => $tags, 'replies' => $replies, - 'object_type' => QuestionAndAnswerPlugin::QUESTION_OBJECT + 'object_type' => QnAPlugin::QUESTION_OBJECT ), $options ); diff --git a/plugins/QnA/classes/QnA_Vote.php b/plugins/QnA/classes/QnA_Vote.php new file mode 100644 index 0000000000..ec2e75afbb --- /dev/null +++ b/plugins/QnA/classes/QnA_Vote.php @@ -0,0 +1,160 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2011, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * For storing votes on question and answers + * + * @category QnA + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * @see DB_DataObject + */ +class QnA_Vote extends Managed_DataObject +{ + const UP = 'http://activitystrea.ms/schema/1.0/like'; + const DOWN = 'http://activityschema.org/object/dislike'; // Gar! + + public $__table = 'qa_vote'; // table name + public $id; // char(36) primary key not null -> UUID + public $question_id; // char(36) -> question.id UUID + public $answer_id; // char(36) -> question.id UUID + public $type // tinyint -> vote: up (1) or down (-1) + public $profile_id; // int -> question.id + public $created; // datetime + + /** + * Get an instance by key + * + * This is a utility method to get a single instance with a given key value. + * + * @param string $k Key to use to lookup + * @param mixed $v Value to lookup + * + * @return QnA_Vote object found, or null for no hits + * + */ + function staticGet($k, $v=null) + { + return Memcached_DataObject::staticGet('QnA_Vote', $k, $v); + } + + /** + * Get an instance by compound key + * + * This is a utility method to get a single instance with a given set of + * key-value pairs. Usually used for the primary key for a compound key; thus + * the name. + * + * @param array $kv array of key-value mappings + * + * @return QnA_Vote object found, or null for no hits + * + */ + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('QnA_Vote', $kv); + } + + /** + * The One True Thingy that must be defined and declared. + */ + public static function schemaDef() + { + return array( + 'description' => 'For storing votes on questions and answers', + 'fields' => array( + 'id' => array( + 'type' => 'char', + 'length' => 36, + 'not null' => true, + 'description' => 'UUID of the vote' + ), + 'question_id' => array( + 'type' => 'char', + 'length' => 36, + 'not null' => true, + 'description' => 'UUID of question being voted on' + ), + 'answer_id' => array( + 'type' => 'char', + 'length' => 36, + 'not null' => true, + 'description' => 'UUID of answer being voted on' + ), + 'vote' => array('type' => 'int', 'size' => 'tiny'), + 'profile_id' => array('type' => 'int'), + 'created' => array('type' => 'datetime', 'not null' => true), + ), + 'primary key' => array('id'), + 'indexes' => array( + 'profile_id_question_Id_index' => array( + 'profile_id', + 'question_id' + ), + 'profile_id_question_Id_index' => array( + 'profile_id', + 'answer_id' + ) + ) + ); + } + + /** + * Save a vote on a question or answer + * + * @param Profile $profile + * @param QnA_Question the question being voted on + * @param QnA_Answer the answer being voted on + * @param vote + * @param array + * + * @return Void + */ + static function save($profile, $question, $answer, $vote) + { + $v = new QnA_Vote(); + $v->id = UUID::gen(); + $v->profile_id = $profile->id; + $v->question_id = $question->id; + $v->answer_id = $answer->id; + $v->vote = $vote; + $v->created = common_sql_now(); + + common_log(LOG_DEBUG, "Saving vote: $v->id $v->vote"); + + $v->insert(); + } +} diff --git a/plugins/QuestionAndAnswer/css/questionandanswer.css b/plugins/QnA/css/qna.css similarity index 100% rename from plugins/QuestionAndAnswer/css/questionandanswer.css rename to plugins/QnA/css/qna.css diff --git a/plugins/QuestionAndAnswer/lib/answerform.php b/plugins/QnA/lib/answerform.php similarity index 92% rename from plugins/QuestionAndAnswer/lib/answerform.php rename to plugins/QnA/lib/answerform.php index d093863708..554f698d99 100644 --- a/plugins/QuestionAndAnswer/lib/answerform.php +++ b/plugins/QnA/lib/answerform.php @@ -20,7 +20,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @copyright 2011 StatusNet, Inc. @@ -37,7 +37,7 @@ if (!defined('STATUSNET')) { /** * Form to add a new answer to a question * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @copyright 2011 StatusNet, Inc. @@ -51,12 +51,12 @@ class AnswerForm extends Form /** * Construct a new answer form * - * @param Question $question + * @param QnA_Question $question * @param HTMLOutputter $out output channel * * @return void */ - function __construct(Question $question, HTMLOutputter $out) + function __construct(QnA_Question $question, HTMLOutputter $out) { parent::__construct($out); $this->question = $question; @@ -100,12 +100,11 @@ class AnswerForm extends Form function formData() { $question = $this->question; - $out = $this->out; - $id = "question-" . $question->id; + $out = $this->out; + $id = "question-" . $question->id; $out->element('p', 'answer', $question->question); $out->element('input', array('type' => 'text', 'name' => 'answer')); - } /** diff --git a/plugins/QuestionAndAnswer/lib/questionform.php b/plugins/QnA/lib/questionform.php similarity index 99% rename from plugins/QuestionAndAnswer/lib/questionform.php rename to plugins/QnA/lib/questionform.php index 5892464218..4f9ea6d808 100644 --- a/plugins/QuestionAndAnswer/lib/questionform.php +++ b/plugins/QnA/lib/questionform.php @@ -37,7 +37,7 @@ if (!defined('STATUSNET')) { /** * Form to add a new question * - * @category QuestionAndAnswer + * @category QnA * @package StatusNet * @author Zach Copley * @copyright 2011 StatusNet, Inc. diff --git a/plugins/QnA/lib/voteform.php b/plugins/QnA/lib/voteform.php new file mode 100644 index 0000000000..554f698d99 --- /dev/null +++ b/plugins/QnA/lib/voteform.php @@ -0,0 +1,121 @@ +. + * + * @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 AnswerForm extends Form +{ + protected $question; + + /** + * Construct a new answer form + * + * @param QnA_Question $question + * @param HTMLOutputter $out output channel + * + * @return void + */ + function __construct(QnA_Question $question, HTMLOutputter $out) + { + parent::__construct($out); + $this->question = $question; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + function id() + { + return 'answer-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('answer', 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', 'answer', $question->question); + $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')); + } +} +