]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Renamed QuestionAndAnswerPlugin to QnAPlugin
authorZach Copley <zach@status.net>
Mon, 21 Mar 2011 02:24:35 +0000 (19:24 -0700)
committerZach Copley <zach@status.net>
Mon, 21 Mar 2011 02:24:35 +0000 (19:24 -0700)
23 files changed:
plugins/QnA/QnAPlugin [new file with mode: 0644]
plugins/QnA/actions/answer.php [new file with mode: 0644]
plugins/QnA/actions/newquestion.php [new file with mode: 0644]
plugins/QnA/actions/qnavote.php [new file with mode: 0644]
plugins/QnA/actions/showanswer.php [new file with mode: 0644]
plugins/QnA/actions/showquestion.php [new file with mode: 0644]
plugins/QnA/classes/QnA_Answer.php [new file with mode: 0644]
plugins/QnA/classes/QnA_Question.php [new file with mode: 0644]
plugins/QnA/classes/QnA_Vote.php [new file with mode: 0644]
plugins/QnA/css/qna.css [new file with mode: 0644]
plugins/QnA/lib/answerform.php [new file with mode: 0644]
plugins/QnA/lib/questionform.php [new file with mode: 0644]
plugins/QnA/lib/voteform.php [new file with mode: 0644]
plugins/QuestionAndAnswer/QuestionAndAnswerPlugin.php [deleted file]
plugins/QuestionAndAnswer/actions/Newquestion.php [deleted file]
plugins/QuestionAndAnswer/actions/answer.php [deleted file]
plugins/QuestionAndAnswer/actions/showanswer.php [deleted file]
plugins/QuestionAndAnswer/actions/showquestion.php [deleted file]
plugins/QuestionAndAnswer/classes/Answer.php [deleted file]
plugins/QuestionAndAnswer/classes/Question.php [deleted file]
plugins/QuestionAndAnswer/css/questionandanswer.css [deleted file]
plugins/QuestionAndAnswer/lib/answerform.php [deleted file]
plugins/QuestionAndAnswer/lib/questionform.php [deleted file]

diff --git a/plugins/QnA/QnAPlugin b/plugins/QnA/QnAPlugin
new file mode 100644 (file)
index 0000000..76bd304
--- /dev/null
@@ -0,0 +1,457 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Microapp plugin for Questions and Answers
+ *
+ * PHP version 5
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category  QnA
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @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);
+}
+
+/**
+ * Question and Answer plugin
+ *
+ * @category  Plugin
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+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)
+     *
+     * @see Schema
+     * @see ColumnDef
+     *
+     * @return boolean hook value; true means continue processing, false means stop.
+     */
+    function onCheckSchema()
+    {
+        $schema = Schema::get();
+
+        $schema->ensureTable('qna_question', QnA_Question::schemaDef());
+        $schema->ensureTable('qna_answer', QnA_Answer::schemaDef());
+        $schema->ensureTable('qna_vote', QnA_Vote::schemaDef());
+        
+        return true;
+    }
+
+    /**
+     * Load related modules when needed
+     *
+     * @param string $cls Name of the class to be loaded
+     *
+     * @return boolean hook value; true means continue processing, false means stop.
+     */
+    function onAutoload($cls)
+    {
+        $dir = dirname(__FILE__);
+
+        switch ($cls)
+        {
+        case 'NewquestionAction':
+        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 'QnA_Question':
+        case 'QnA_Answer':
+        case 'QnA_Vote':
+            include_once $dir . '/classes/' . $cls.'.php';
+            return false;
+            break;
+        default:
+            return true;
+        }
+    }
+
+    /**
+     * Map URLs to actions
+     *
+     * @param Net_URL_Mapper $m path-to-action mapper
+     *
+     * @return boolean hook value; true means continue processing, false means stop.
+     */
+
+    function onRouterInitialized($m)
+    {
+        $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'        => 'QnA',
+            'version'     => STATUSNET_VERSION,
+            'author'      => 'Zach Copley',
+            'homepage'    => 'http://status.net/wiki/Plugin:QnA',
+            'description' =>
+             _m('Question and Answers micro-app.')
+        );
+        return true;
+    }
+
+    function appTitle() {
+        return _m('Question');
+    }
+
+    function tag() {
+        return 'question';
+    }
+
+    function types() {
+        return array(
+            Question::OBJECT_TYPE,
+            Answer::NORMAL
+        );
+    }
+
+    /**
+     * Given a parsed ActivityStreams activity, save it into a notice
+     * and other data structures.
+     *
+     * @param Activity $activity
+     * @param Profile $actor
+     * @param array $options=array()
+     *
+     * @return Notice the resulting notice
+     */
+    function saveNoticeFromActivity($activity, $actor, $options=array())
+    {
+        if (count($activity->objects) != 1) {
+            throw new Exception('Too many activity objects.');
+        }
+
+        $questionObj = $activity->objects[0];
+
+        if ($questinoObj->type != QnA_Question::OBJECT_TYPE) {
+            throw new Exception('Wrong type for object.');
+        }
+
+        $notice = null;
+
+        switch ($activity->verb) {
+        case ActivityVerb::POST:
+            $notice = Question::saveNew(
+                $actor,
+                $questionObj->title
+               // null,
+               // $questionObj->summary,
+               // $options
+            );
+            break;
+        case Answer::NORMAL:
+            $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);
+            break;
+        default:
+            throw new Exception("Unknown verb for question");
+        }
+
+        return $notice;
+    }
+
+    /**
+     * Turn a Notice into an activity object
+     *
+     * @param Notice $notice
+     *
+     * @return ActivityObject
+     */
+
+    function activityObjectFromNotice($notice)
+    {
+        $question = null;
+
+        switch ($notice->object_type) {
+        case Question::OBJECT_TYPE:
+            $question = Qeustion::fromNotice($notice);
+            break;
+        case Answer::NORMAL:
+        case Answer::ANONYMOUS:
+            $answer   = Answer::fromNotice($notice);
+            $question = $answer->getQuestion();
+            break;
+        }
+
+        if (empty($question)) {
+            throw new Exception("Unknown object type.");
+        }
+
+        $notice = $question->getNotice();
+
+        if (empty($notice)) {
+            throw new Exception("Unknown question notice.");
+        }
+
+        $obj = new ActivityObject();
+
+        $obj->id      = $question->uri;
+        $obj->type    = Question::OBJECT_TYPE;
+        $obj->title   = $question->title;
+        $obj->link    = $notice->bestUrl();
+
+        // XXX: probably need other stuff here
+
+        return $obj;
+    }
+
+    /**
+     * Change the verb on Answer notices
+     *
+     * @param Notice $notice
+     *
+     * @return ActivityObject
+     */
+
+    function onEndNoticeAsActivity($notice, &$act) {
+        switch ($notice->object_type) {
+        case Answer::NORMAL:
+        case Answer::ANONYMOUS:
+            $act->verb = $notice->object_type;
+            break;
+        }
+        return true;
+    }
+
+    /**
+     * Custom HTML output for our notices
+     *
+     * @param Notice $notice
+     * @param HTMLOutputter $out
+     */
+
+    function showNotice($notice, $out)
+    {
+        switch ($notice->object_type) {
+        case Question::OBJECT_TYPE:
+            $this->showQuestionNotice($notice, $out);
+            break;
+        case Answer::NORMAL:
+        case Answer::ANONYMOUS:
+        case RSVP::POSSIBLE:
+            $this->showAnswerNotice($notice, $out);
+            break;
+        }
+
+        $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 bookmark-avatar',
+                            'width' => AVATAR_MINI_SIZE,
+                            'height' => AVATAR_MINI_SIZE,
+                            'alt' => $profile->getBestName()));
+
+        $out->raw('&#160;'); // avoid &nbsp; for AJAX XML compatibility
+
+        $out->elementStart('span', 'vcard author'); // hack for belongsOnTimeline; JS needs to be able to find the author
+        $out->element('a',
+                      array('class' => 'url',
+                            'href' => $profile->profileurl,
+                            'title' => $profile->getBestName()),
+                      $profile->nickname);
+        $out->elementEnd('span');
+    }
+
+    function showAnswerNotice($notice, $out)
+    {
+        $rsvp = Answer::fromNotice($notice);
+
+        $out->elementStart('div', 'answer');
+        $out->raw($answer->asHTML());
+        $out->elementEnd('div');
+        return;
+    }
+
+    function showQuestionNotice($notice, $out)
+    {
+        $profile  = $notice->getProfile();
+        $question = Question::fromNotice($notice);
+
+        assert(!empty($question));
+        assert(!empty($profile));
+
+        $out->elementStart('div', 'question-notice');
+
+        $out->elementStart('h3');
+
+        if (!empty($question->url)) {
+            $out->element('a',
+                          array('href' => $question->url,
+                                'class' => 'question-title'),
+                          $question->title);
+        } else {
+            $out->text($question->title);
+        }
+
+        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');
+
+        // XXX I dunno
+
+        $out->elementEnd('div');
+
+        $user = common_current_user();
+
+        if (!empty($user)) {
+            $question = $question->getAnswer($user->getProfile());
+
+            if (empty($answer)) {
+                $form = new AnswerForm($question, $out);
+            }
+
+            $form->show();
+        }
+
+        $out->elementEnd('div');
+    }
+
+    /**
+     * Form for our app
+     *
+     * @param HTMLOutputter $out
+     * @return Widget
+     */
+
+    function entryForm($out)
+    {
+        return new QuestionForm($out);
+    }
+
+    /**
+     * When a notice is deleted, clean up related tables.
+     *
+     * @param Notice $notice
+     */
+
+    function deleteRelated($notice)
+    {
+        switch ($notice->object_type) {
+        case Question::OBJECT_TYPE:
+            common_log(LOG_DEBUG, "Deleting question from notice...");
+            $question = Question::fromNotice($notice);
+            $question->delete();
+            break;
+        case Answer::NORMAL:
+        case Answer::ANONYMOUS:
+            common_log(LOG_DEBUG, "Deleting answer from notice...");
+            $answer = Answer::fromNotice($notice);
+            common_log(LOG_DEBUG, "to delete: $answer->id");
+            $answer->delete();
+            break;
+        default:
+            common_log(LOG_DEBUG, "Not deleting related, wtf...");
+        }
+    }
+
+    function onEndShowScripts($action)
+    {
+        // XXX maybe some cool shiz here
+    }
+
+    function onEndShowStyles($action)
+    {
+        $action->cssLink($this->path('css/questionandanswer.css'));
+        return true;
+    }
+}
diff --git a/plugins/QnA/actions/answer.php b/plugins/QnA/actions/answer.php
new file mode 100644 (file)
index 0000000..17e841e
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Answer a question
+ *
+ * PHP version 5
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category  QuestonAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @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 <zach@status.net>
+ * @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/QnA/actions/newquestion.php b/plugins/QnA/actions/newquestion.php
new file mode 100644 (file)
index 0000000..83b1022
--- /dev/null
@@ -0,0 +1,211 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Add a new Question
+ *
+ * PHP version 5
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category  QnA
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @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);
+}
+
+/**
+ * Add a new Question
+ *
+ * @category  Plugin
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+class NewquestionAction extends Action
+{
+    protected $user        = null;
+    protected $error       = null;
+    protected $complete    = null;
+
+    protected $question    = null;
+
+    /**
+     * Returns the title of the action
+     *
+     * @return string Action title
+     */
+    function title()
+    {
+        // TRANS: Title for Question page.
+        return _m('New question');
+    }
+
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+    function prepare($argarray)
+    {
+        parent::prepare($argarray);
+
+        $this->user = common_current_user();
+
+        if (empty($this->user)) {
+            // TRANS: Client exception thrown trying to create a Question while not logged in.
+            throw new ClientException(_m('You must be logged in to post a question.'),
+                                      403);
+        }
+
+        if ($this->isPost()) {
+            $this->checkSessionToken();
+        }
+
+        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->newQuestion();
+        } else {
+            $this->showPage();
+        }
+
+        return;
+    }
+
+    /**
+     * Add a new Question
+     *
+     * @return void
+     */
+    function newQuestion()
+    {
+        if ($this->boolean('ajax')) {
+            StatusNet::setApi(true);
+        }
+        try {
+            if (empty($this->question)) {
+            // TRANS: Client exception thrown trying to create a Question without a question.
+                throw new ClientException(_m('Question must have a question.'));
+            }
+
+            $saved = Question::saveNew(
+                $this->user->getProfile(),
+                $this->question
+            );
+        } 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 a notice.
+            $this->element('title', null, _m('Notice posted'));
+            $this->elementEnd('head');
+            $this->elementStart('body');
+            $this->showNotice($saved);
+            $this->elementEnd('body');
+            $this->elementEnd('html');
+        } else {
+            common_redirect($saved->bestUrl(), 303);
+        }
+    }
+
+    /**
+     * Output a notice
+     *
+     * Used to generate the notice code for Ajax results.
+     *
+     * @param Notice $notice Notice that was saved
+     *
+     * @return void
+     */
+    function showNotice($notice)
+    {
+        class_exists('NoticeList'); // @fixme hack for autoloader
+        $nli = new NoticeListItem($notice, $this);
+        $nli->show();
+    }
+
+    /**
+     * Show the Question form
+     *
+     * @return void
+     */
+    function showContent()
+    {
+        if (!empty($this->error)) {
+            $this->element('p', 'error', $this->error);
+        }
+
+        $form = new NewQuestionForm(
+            $this,
+            $this->question,
+            $this->options
+        );
+
+        $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/QnA/actions/qnavote.php b/plugins/QnA/actions/qnavote.php
new file mode 100644 (file)
index 0000000..17e841e
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Answer a question
+ *
+ * PHP version 5
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category  QuestonAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @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 <zach@status.net>
+ * @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/QnA/actions/showanswer.php b/plugins/QnA/actions/showanswer.php
new file mode 100644 (file)
index 0000000..7686d6d
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * Show an answer to a question
+ *
+ * PHP version 5
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category  QnA
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 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);
+}
+
+/**
+ * Show an answer to a question, and associated data
+ *
+ * @category  QnA
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+class ShowAnswerAction extends ShownoticeAction
+{
+    protected $answer = null;
+
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+
+    function prepare($argarray)
+    {
+        OwnerDesignAction::prepare($argarray);
+
+        $this->id = $this->trimmed('id');
+
+        $this->answer = Answer::staticGet('id', $this->id);
+
+        if (empty($this->answer)) {
+            throw new ClientException(_('No such answer.'), 404);
+        }
+
+        $this->notice = Notice::staticGet('uri', $this->answer->uri);
+
+        if (empty($this->notice)) {
+            // Did we used to have it, and it got deleted?
+            throw new ClientException(_('No such answer.'), 404);
+        }
+
+        $this->user = User::staticGet('id', $this->answer->profile_id);
+
+        if (empty($this->user)) {
+            throw new ClientException(_('No such user.'), 404);
+        }
+
+        $this->profile = $this->user->getProfile();
+
+        if (empty($this->profile)) {
+            throw new ServerException(_('User without a profile.'));
+        }
+
+        $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
+
+        return true;
+    }
+
+    /**
+     * Title of the page
+     *
+     * Used by Action class for layout.
+     *
+     * @return string page tile
+     */
+
+    function title()
+    {
+        return sprintf(_('%s\'s answer to "%s"'),
+                       $this->user->nickname,
+                       $this->answer->title);
+    }
+
+    /**
+     * Overload page title display to show answer link
+     *
+     * @return void
+     */
+
+    function showPageTitle()
+    {
+        $this->elementStart('h1');
+        $this->element('a',
+                       array('href' => $this->answer->url),
+                       $this->asnwer->title);
+        $this->elementEnd('h1');
+    }
+}
diff --git a/plugins/QnA/actions/showquestion.php b/plugins/QnA/actions/showquestion.php
new file mode 100644 (file)
index 0000000..41c1d80
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Show a question
+ *
+ * PHP version 5
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category  QnA
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @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);
+}
+
+/**
+ * Show a question
+ *
+ * @category  QnA
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+class ShowquestionAction extends ShownoticeAction
+{
+    protected $question = null;
+
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+    function prepare($argarray)
+    {
+        OwnerDesignAction::prepare($argarray);
+
+        $this->id = $this->trimmed('id');
+
+        $this->question = Question::staticGet('id', $this->id);
+
+        if (empty($this->question)) {
+            // TRANS: Client exception thrown trying to view a non-existing question.
+            throw new ClientException(_m('No such question.'), 404);
+        }
+
+        $this->notice = $this->question->getNotice();
+
+        if (empty($this->notice)) {
+            // Did we used to have it, and it got deleted?
+            // TRANS: Client exception thrown trying to view a non-existing question notice.
+            throw new ClientException(_m('No such question notice.'), 404);
+        }
+
+        $this->user = User::staticGet('id', $this->question->profile_id);
+
+        if (empty($this->user)) {
+            // TRANS: Client exception thrown trying to view a question of a non-existing user.
+            throw new ClientException(_m('No such user.'), 404);
+        }
+
+        $this->profile = $this->user->getProfile();
+
+        if (empty($this->profile)) {
+            // TRANS: Server exception thrown trying to view a question for a user for which the profile could not be loaded.
+            throw new ServerException(_m('User without a profile.'));
+        }
+
+        $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
+
+        return true;
+    }
+
+    /**
+     * Title of the page
+     *
+     * Used by Action class for layout.
+     *
+     * @return string page tile
+     */
+    function title()
+    {
+        // TRANS: Page title for a question.
+        // 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);
+    }
+
+    /**
+     * @fixme combine the notice time with question update time
+     */
+    function lastModified()
+    {
+        return Action::lastModified();
+    }
+
+
+    /**
+     * @fixme combine the notice time with question update time
+     */
+    function etag()
+    {
+        return Action::etag();
+    }
+}
diff --git a/plugins/QnA/classes/QnA_Answer.php b/plugins/QnA/classes/QnA_Answer.php
new file mode 100644 (file)
index 0000000..d88e6bd
--- /dev/null
@@ -0,0 +1,232 @@
+<?php
+/**
+ * Data class to save answers to questions
+ *
+ * PHP version 5
+ *
+ * @category QnA
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * For storing answers
+ *
+ * @category QnA
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * @see      DB_DataObject
+ */
+class QnA_Answer extends Managed_DataObject
+{
+    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 $best;        // (int) boolean -> whether the question asker has marked this as the best answer
+    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_Answer object found, or null for no hits
+     *
+     */
+    function staticGet($k, $v=null)
+    {
+        return Memcached_DataObject::staticGet('QnA_Answer', $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 QA_Answer object found, or null for no hits
+     *
+     */
+    function pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('QnA_Answer', $kv);
+    }
+
+    /**
+     * The One True Thingy that must be defined and declared.
+     */
+    public static function schemaDef()
+    {
+        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'
+                    ),
+                    '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(
+                'question_uri_key' => array('uri'),
+                'question_id_profile_id_key' => array('question_id', 'profile_id'),
+            ),
+            'indexes' => array(
+                'profile_id_question_id_index' => array('profile_id', 'question_id'),
+            )
+        );
+    }
+
+    /**
+     * Get an answer based on a notice
+     *
+     * @param Notice $notice Notice to check for
+     *
+     * @return QnA_Answer found response or null
+     */
+    function getByNotice($notice)
+    {
+        return self::staticGet('uri', $notice->uri);
+    }
+
+    /**
+     * Get the notice that belongs to this answer
+     *
+     * @return Notice
+     */
+    function getNotice()
+    {
+        return Notice::staticGet('uri', $this->uri);
+    }
+
+    function bestUrl()
+    {
+        return $this->getNotice()->bestUrl();
+    }
+
+    /**
+     * Get the Question this is an answer to
+     *
+     * @return QnA_Question
+     */
+    function getQuestion()
+    {
+        return Question::staticGet('id', $this->question_id);
+    }
+
+    /**
+     * Save a new answer notice
+     *
+     * @param Profile  $profile
+     * @param Question $Question the question being answered
+     * @param array
+     *
+     * @return Notice saved notice
+     */
+    static function saveNew($profile, $question, $options=null)
+    {
+        if (empty($options)) {
+            $options = array();
+        }
+
+        $a = new Answer();
+        $a->id          = UUID::gen();
+        $a->profile_id  = $profile->id;
+        $a->question_id = $question->id;
+        $a->created     = common_sql_now();
+        $a->uri         = common_local_url(
+            'showanswer',
+            array('id' => $pr->id)
+        );
+        
+        common_log(LOG_DEBUG, "Saving answer: $pr->id $pr->uri");
+        $a->insert();
+
+        // TRANS: Notice content answering a question.
+        // TRANS: %s is the answer
+        $content  = sprintf(
+            _m('answered "%s"'),
+            $answer
+        );
+        $link = '<a href="' . htmlspecialchars($question->uri) . '">' . htmlspecialchars($answer) . '</a>';
+        // 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);
+
+        $tags    = array();
+        $replies = array();
+
+        $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
+        );
+
+        return $saved;
+    }
+}
diff --git a/plugins/QnA/classes/QnA_Question.php b/plugins/QnA/classes/QnA_Question.php
new file mode 100644 (file)
index 0000000..1a298ae
--- /dev/null
@@ -0,0 +1,256 @@
+<?php
+/**
+ * Data class to mark a notice as a question
+ *
+ * PHP version 5
+ *
+ * @category QnA
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * For storing a question
+ *
+ * @category QnA
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * @see      DB_DataObject
+ */
+
+class QnA_Question extends Managed_DataObject
+{
+    
+    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
+
+    /**
+     * 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_Question object found, or null for no hits
+     *
+     */
+    function staticGet($k, $v=null)
+    {
+        return Memcached_DataObject::staticGet('QnA_Question', $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 Bookmark object found, or null for no hits
+     *
+     */
+    function pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('QnA_Question', $kv);
+    }
+
+    /**
+     * The One True Thingy that must be defined and declared.
+     */
+    public static function schemaDef()
+    {
+        return array(
+            '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'),
+                'closed'      => array('type' => 'int', size => 'tiny'),
+                'description' => array('type' => 'text'),
+                'created'     => array(
+                    'type' => 'datetime', 
+                    'not null' => true
+                ),
+            ),
+            'primary key' => array('id'),
+            'unique keys' => array(
+                'question_uri_key' => array('uri'),
+            ),
+        );
+    }
+
+    /**
+     * Get a question based on a notice
+     *
+     * @param Notice $notice Notice to check for
+     *
+     * @return Question found question or null
+     */
+    function getByNotice($notice)
+    {
+        return self::staticGet('uri', $notice->uri);
+    }
+
+    function getNotice()
+    {
+        return Notice::staticGet('uri', $this->uri);
+    }
+
+    function bestUrl()
+    {
+        return $this->getNotice()->bestUrl();
+    }
+
+    /**
+     * Get the answer from a particular user to this question, if any.
+     *
+     * @param Profile $profile
+     *
+     * @return Answer object or null
+     */
+    function getAnswer(Profile $profile)
+    {
+        $a = new QnA_Answer();
+        $a->question_id = $this->id;
+        $a->profile_id = $profile->id;
+        $a->find();
+        if ($a->fetch()) {
+            return $a;
+        } else {
+            return null;
+        }
+    }
+
+    function countAnswers()
+    {
+        $a              = new QnA_Answer();
+        $a->question_id = $this->id;
+        return $a-count();
+    }
+
+    /**
+     * Save a new question notice
+     *
+     * @param Profile $profile
+     * @param string  $question
+     * @param string  $title
+     * @param string  $description
+     * @param array   $option // and whatnot
+     *
+     * @return Notice saved notice
+     */
+    static function saveNew($profile, $question, $title, $description, $options = array())
+    {
+        $q = new QnA_Question();
+
+        $q->id          = UUID::gen();
+        $q->profile_id  = $profile->id;
+        $q->title       = $title;
+        $q->description = $description;
+
+        if (array_key_exists('created', $options)) {
+            $q->created = $options['created'];
+        } else {
+            $q->created = common_sql_now();
+        }
+
+        if (array_key_exists('uri', $options)) {
+            $q->uri = $options['uri'];
+        } else {
+            $q->uri = common_local_url(
+                'showquestion',
+                array('id' => $q->id)
+            );
+        }
+
+        common_log(LOG_DEBUG, "Saving question: $q->id $q->uri");
+        $q->insert();
+
+        // TRANS: Notice content creating a question.
+        // TRANS: %1$s is the title of the question, %2$s is a link to the question.
+        $content  = sprintf(
+            _m('question: %1$s %2$s'),
+            $title,
+            $q->uri
+        );
+        
+        $link = '<a href="' . htmlspecialchars($q->uri) . '">' . htmlspecialchars($title) . '</a>';
+        // TRANS: Rendered version of the notice content creating a question.
+        // TRANS: %s a link to the question as link description.
+        $rendered = sprintf(_m('Question: %s'), $link);
+
+        $tags    = array('question');
+        $replies = array();
+
+        $options = array_merge(
+            array(
+                'urls'        => array(),
+                'rendered'    => $rendered,
+                'tags'        => $tags,
+                'replies'     => $replies,
+                'object_type' => QnAPlugin::QUESTION_OBJECT
+            ),
+            $options
+        );
+
+        if (!array_key_exists('uri', $options)) {
+            $options['uri'] = $p->uri;
+        }
+
+        $saved = Notice::saveNew(
+            $profile->id,
+            $content,
+            array_key_exists('source', $options) ?
+            $options['source'] : 'web',
+            $options
+        );
+
+        return $saved;
+    }
+}
diff --git a/plugins/QnA/classes/QnA_Vote.php b/plugins/QnA/classes/QnA_Vote.php
new file mode 100644 (file)
index 0000000..ec2e75a
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+/**
+ * Data class to save users votes for 
+ *
+ * PHP version 5
+ *
+ * @category QnA
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * For storing votes on question and answers
+ *
+ * @category QnA
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @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/QnA/css/qna.css b/plugins/QnA/css/qna.css
new file mode 100644 (file)
index 0000000..4701b5a
--- /dev/null
@@ -0,0 +1 @@
+/* stubb for q&a css */
diff --git a/plugins/QnA/lib/answerform.php b/plugins/QnA/lib/answerform.php
new file mode 100644 (file)
index 0000000..554f698
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Form for answering a question
+ *
+ * PHP version 5
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category  QnA
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @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 <zach@status.net>
+ * @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'));
+    }
+}
+
diff --git a/plugins/QnA/lib/questionform.php b/plugins/QnA/lib/questionform.php
new file mode 100644 (file)
index 0000000..4f9ea6d
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Form for adding a new question
+ *
+ * PHP version 5
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category  QuestonAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@copley.name>
+ * @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 question
+ *
+ * @category  QnA
+ * @package   StatusNet
+ * @author    Zach Copley <zach@copley.name>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+class NewQuestionForm extends Form
+{
+    protected $question = null;
+
+    /**
+     * Construct a new question form
+     *
+     * @param HTMLOutputter $out output channel
+     *
+     * @return void
+     */
+    function __construct($out=null, $question=null, $options=null)
+    {
+        parent::__construct($out);
+    }
+
+    /**
+     * ID of the form
+     *
+     * @return int ID of the form
+     */
+    function id()
+    {
+        return 'newquestion-form';
+    }
+
+    /**
+     * class of the form
+     *
+     * @return string class of the form
+     */
+    function formClass()
+    {
+        return 'form_settings ajax-notice';
+    }
+
+    /**
+     * Action of the form
+     *
+     * @return string URL of the action
+     */
+    function action()
+    {
+        return common_local_url('newquestion');
+    }
+
+    /**
+     * Data elements of the form
+     *
+     * @return void
+     */
+    function formData()
+    {
+        $this->out->elementStart('fieldset', array('id' => 'newquestion-data'));
+        $this->out->elementStart('ul', 'form_data');
+
+        $this->li();
+        $this->out->input('question',
+                          // TRANS: Field label on the page to create a question.
+                          _m('Question'),
+                          $this->question,
+                          // TRANS: Field title on the page to create a question.
+                          _m('What is your question?'));
+        $this->unli();
+
+        $this->out->elementEnd('ul');
+        $this->out->elementEnd('fieldset');
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+    function formActions()
+    {
+        // TRANS: Button text for saving a new question.
+        $this->out->submit('submit', _m('BUTTON', 'Save'));
+    }
+}
diff --git a/plugins/QnA/lib/voteform.php b/plugins/QnA/lib/voteform.php
new file mode 100644 (file)
index 0000000..554f698
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Form for answering a question
+ *
+ * PHP version 5
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * @category  QnA
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @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 <zach@status.net>
+ * @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'));
+    }
+}
+
diff --git a/plugins/QuestionAndAnswer/QuestionAndAnswerPlugin.php b/plugins/QuestionAndAnswer/QuestionAndAnswerPlugin.php
deleted file mode 100644 (file)
index e519dac..0000000
+++ /dev/null
@@ -1,428 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2011, StatusNet, Inc.
- *
- * Microapp plugin for Questions and Answers
- *
- * PHP version 5
- *
- * 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 <http://www.gnu.org/licenses/>.
- *
- * @category  QuestionAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @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);
-}
-
-/**
- * Question and Answer plugin
- *
- * @category  Plugin
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @copyright 2011 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
- * @link      http://status.net/
- */
-class QuestionAndAnswerPlugin extends MicroappPlugin
-{
-    /**
-     * Set up our tables (question and answer)
-     *
-     * @see Schema
-     * @see ColumnDef
-     *
-     * @return boolean hook value; true means continue processing, false means stop.
-     */
-    function onCheckSchema()
-    {
-        $schema = Schema::get();
-
-        $schema->ensureTable('question', Question::schemaDef());
-        $schema->ensureTable('answer', Answer::schemaDef());
-
-        return true;
-    }
-
-    /**
-     * Load related modules when needed
-     *
-     * @param string $cls Name of the class to be loaded
-     *
-     * @return boolean hook value; true means continue processing, false means stop.
-     */
-    function onAutoload($cls)
-    {
-        $dir = dirname(__FILE__);
-
-        switch ($cls)
-        {
-        case 'NewquestionAction':
-        case 'NewanswerAction':
-        case 'ShowquestionAction':
-        case 'ShowanswerAction':
-            include_once $dir . '/actions/'
-                . strtolower(mb_substr($cls, 0, -6)) . '.php';
-            return false;
-        case 'QuestionForm':
-        case 'AnswerForm':
-            include_once $dir . '/lib/' . strtolower($cls).'.php';
-            break;
-        case 'Question':
-        case 'Answer':
-            include_once $dir . '/classes/' . $cls.'.php';
-            return false;
-            break;
-        default:
-            return true;
-        }
-    }
-
-    /**
-     * Map URLs to actions
-     *
-     * @param Net_URL_Mapper $m path-to-action mapper
-     *
-     * @return boolean hook value; true means continue processing, false means stop.
-     */
-
-    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}'));
-        return true;
-    }
-
-    function onPluginVersion(&$versions)
-    {
-        $versions[] = array(
-            'name'        => 'QuestionAndAnswer',
-            'version'     => STATUSNET_VERSION,
-            'author'      => 'Zach Copley',
-            'homepage'    => 'http://status.net/wiki/Plugin:QuestionAndAnswer',
-            'description' =>
-             _m('Question and Answers micro-app.')
-        );
-        return true;
-    }
-
-    function appTitle() {
-        return _m('Question');
-    }
-
-    function tag() {
-        return 'question';
-    }
-
-    function types() {
-        return array(
-            Question::OBJECT_TYPE,
-            Answer::NORMAL
-        );
-    }
-
-    /**
-     * Given a parsed ActivityStreams activity, save it into a notice
-     * and other data structures.
-     *
-     * @param Activity $activity
-     * @param Profile $actor
-     * @param array $options=array()
-     *
-     * @return Notice the resulting notice
-     */
-    function saveNoticeFromActivity($activity, $actor, $options=array())
-    {
-        if (count($activity->objects) != 1) {
-            throw new Exception('Too many activity objects.');
-        }
-
-        $questionObj = $activity->objects[0];
-
-        if ($questinoObj->type != Question::OBJECT_TYPE) {
-            throw new Exception('Wrong type for object.');
-        }
-
-        $notice = null;
-
-        switch ($activity->verb) {
-        case ActivityVerb::POST:
-            $notice = Question::saveNew(
-                $actor,
-                $questionObj->title
-               // null,
-               // $questionObj->summary,
-               // $options
-            );
-            break;
-        case Answer::NORMAL:
-        case Answer::ANONYMOUS:
-            $question = 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);
-            break;
-        default:
-            throw new Exception("Unknown verb for question");
-        }
-
-        return $notice;
-    }
-
-    /**
-     * Turn a Notice into an activity object
-     *
-     * @param Notice $notice
-     *
-     * @return ActivityObject
-     */
-
-    function activityObjectFromNotice($notice)
-    {
-        $question = null;
-
-        switch ($notice->object_type) {
-        case Question::OBJECT_TYPE:
-            $question = Qeustion::fromNotice($notice);
-            break;
-        case Answer::NORMAL:
-        case Answer::ANONYMOUS:
-            $answer   = Answer::fromNotice($notice);
-            $question = $answer->getQuestion();
-            break;
-        }
-
-        if (empty($question)) {
-            throw new Exception("Unknown object type.");
-        }
-
-        $notice = $question->getNotice();
-
-        if (empty($notice)) {
-            throw new Exception("Unknown question notice.");
-        }
-
-        $obj = new ActivityObject();
-
-        $obj->id      = $question->uri;
-        $obj->type    = Question::OBJECT_TYPE;
-        $obj->title   = $question->title;
-        $obj->link    = $notice->bestUrl();
-
-        // XXX: probably need other stuff here
-
-        return $obj;
-    }
-
-    /**
-     * Change the verb on Answer notices
-     *
-     * @param Notice $notice
-     *
-     * @return ActivityObject
-     */
-
-    function onEndNoticeAsActivity($notice, &$act) {
-        switch ($notice->object_type) {
-        case Answer::NORMAL:
-        case Answer::ANONYMOUS:
-            $act->verb = $notice->object_type;
-            break;
-        }
-        return true;
-    }
-
-    /**
-     * Custom HTML output for our notices
-     *
-     * @param Notice $notice
-     * @param HTMLOutputter $out
-     */
-
-    function showNotice($notice, $out)
-    {
-        switch ($notice->object_type) {
-        case Question::OBJECT_TYPE:
-            $this->showQuestionNotice($notice, $out);
-            break;
-        case Answer::NORMAL:
-        case Answer::ANONYMOUS:
-        case RSVP::POSSIBLE:
-            $this->showAnswerNotice($notice, $out);
-            break;
-        }
-
-        $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 bookmark-avatar',
-                            'width' => AVATAR_MINI_SIZE,
-                            'height' => AVATAR_MINI_SIZE,
-                            'alt' => $profile->getBestName()));
-
-        $out->raw('&#160;'); // avoid &nbsp; for AJAX XML compatibility
-
-        $out->elementStart('span', 'vcard author'); // hack for belongsOnTimeline; JS needs to be able to find the author
-        $out->element('a',
-                      array('class' => 'url',
-                            'href' => $profile->profileurl,
-                            'title' => $profile->getBestName()),
-                      $profile->nickname);
-        $out->elementEnd('span');
-    }
-
-    function showAnswerNotice($notice, $out)
-    {
-        $rsvp = Answer::fromNotice($notice);
-
-        $out->elementStart('div', 'answer');
-        $out->raw($answer->asHTML());
-        $out->elementEnd('div');
-        return;
-    }
-
-    function showQuestionNotice($notice, $out)
-    {
-        $profile  = $notice->getProfile();
-        $question = Question::fromNotice($notice);
-
-        assert(!empty($question));
-        assert(!empty($profile));
-
-        $out->elementStart('div', 'question-notice');
-
-        $out->elementStart('h3');
-
-        if (!empty($question->url)) {
-            $out->element('a',
-                          array('href' => $question->url,
-                                'class' => 'question-title'),
-                          $question->title);
-        } else {
-            $out->text($question->title);
-        }
-
-        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');
-
-        // XXX I dunno
-
-        $out->elementEnd('div');
-
-        $user = common_current_user();
-
-        if (!empty($user)) {
-            $question = $question->getAnswer($user->getProfile());
-
-            if (empty($answer)) {
-                $form = new AnswerForm($question, $out);
-            }
-
-            $form->show();
-        }
-
-        $out->elementEnd('div');
-    }
-
-    /**
-     * Form for our app
-     *
-     * @param HTMLOutputter $out
-     * @return Widget
-     */
-
-    function entryForm($out)
-    {
-        return new QuestionForm($out);
-    }
-
-    /**
-     * When a notice is deleted, clean up related tables.
-     *
-     * @param Notice $notice
-     */
-
-    function deleteRelated($notice)
-    {
-        switch ($notice->object_type) {
-        case Question::OBJECT_TYPE:
-            common_log(LOG_DEBUG, "Deleting question from notice...");
-            $question = Question::fromNotice($notice);
-            $question->delete();
-            break;
-        case Answer::NORMAL:
-        case Answer::ANONYMOUS:
-            common_log(LOG_DEBUG, "Deleting answer from notice...");
-            $answer = Answer::fromNotice($notice);
-            common_log(LOG_DEBUG, "to delete: $answer->id");
-            $answer->delete();
-            break;
-        default:
-            common_log(LOG_DEBUG, "Not deleting related, wtf...");
-        }
-    }
-
-    function onEndShowScripts($action)
-    {
-        // XXX maybe some cool shiz here
-    }
-
-    function onEndShowStyles($action)
-    {
-        $action->cssLink($this->path('css/questionandanswer.css'));
-        return true;
-    }
-}
diff --git a/plugins/QuestionAndAnswer/actions/Newquestion.php b/plugins/QuestionAndAnswer/actions/Newquestion.php
deleted file mode 100644 (file)
index cd1c2ff..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2011, StatusNet, Inc.
- *
- * Add a new Question
- *
- * PHP version 5
- *
- * 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 <http://www.gnu.org/licenses/>.
- *
- * @category  QuestionAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @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);
-}
-
-/**
- * Add a new Question
- *
- * @category  Plugin
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @copyright 2010 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
- * @link      http://status.net/
- */
-class NewquestionAction extends Action
-{
-    protected $user        = null;
-    protected $error       = null;
-    protected $complete    = null;
-
-    protected $question    = null;
-
-    /**
-     * Returns the title of the action
-     *
-     * @return string Action title
-     */
-    function title()
-    {
-        // TRANS: Title for Question page.
-        return _m('New question');
-    }
-
-    /**
-     * For initializing members of the class.
-     *
-     * @param array $argarray misc. arguments
-     *
-     * @return boolean true
-     */
-    function prepare($argarray)
-    {
-        parent::prepare($argarray);
-
-        $this->user = common_current_user();
-
-        if (empty($this->user)) {
-            // TRANS: Client exception thrown trying to create a Question while not logged in.
-            throw new ClientException(_m('You must be logged in to post a question.'),
-                                      403);
-        }
-
-        if ($this->isPost()) {
-            $this->checkSessionToken();
-        }
-
-        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->newQuestion();
-        } else {
-            $this->showPage();
-        }
-
-        return;
-    }
-
-    /**
-     * Add a new Question
-     *
-     * @return void
-     */
-    function newQuestion()
-    {
-        if ($this->boolean('ajax')) {
-            StatusNet::setApi(true);
-        }
-        try {
-            if (empty($this->question)) {
-            // TRANS: Client exception thrown trying to create a Question without a question.
-                throw new ClientException(_m('Question must have a question.'));
-            }
-
-            $saved = Question::saveNew(
-                $this->user->getProfile(),
-                $this->question
-            );
-        } 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 a notice.
-            $this->element('title', null, _m('Notice posted'));
-            $this->elementEnd('head');
-            $this->elementStart('body');
-            $this->showNotice($saved);
-            $this->elementEnd('body');
-            $this->elementEnd('html');
-        } else {
-            common_redirect($saved->bestUrl(), 303);
-        }
-    }
-
-    /**
-     * Output a notice
-     *
-     * Used to generate the notice code for Ajax results.
-     *
-     * @param Notice $notice Notice that was saved
-     *
-     * @return void
-     */
-    function showNotice($notice)
-    {
-        class_exists('NoticeList'); // @fixme hack for autoloader
-        $nli = new NoticeListItem($notice, $this);
-        $nli->show();
-    }
-
-    /**
-     * Show the Question form
-     *
-     * @return void
-     */
-    function showContent()
-    {
-        if (!empty($this->error)) {
-            $this->element('p', 'error', $this->error);
-        }
-
-        $form = new NewQuestionForm(
-            $this,
-            $this->question,
-            $this->options
-        );
-
-        $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/answer.php b/plugins/QuestionAndAnswer/actions/answer.php
deleted file mode 100644 (file)
index 49bb73a..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2011, StatusNet, Inc.
- *
- * Answer a question
- *
- * PHP version 5
- *
- * 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 <http://www.gnu.org/licenses/>.
- *
- * @category  QuestonAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @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  QuestionAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @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/QuestionAndAnswer/actions/showanswer.php
deleted file mode 100644 (file)
index d3202cd..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
- *
- * Show an answer to a question
- *
- * PHP version 5
- *
- * 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 <http://www.gnu.org/licenses/>.
- *
- * @category  QuestionAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @copyright 2010 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);
-}
-
-/**
- * Show an answer to a question, and associated data
- *
- * @category  QuestionAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @copyright 2010 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
- * @link      http://status.net/
- */
-
-class ShowAnswerAction extends ShownoticeAction
-{
-    protected $answer = null;
-
-    /**
-     * For initializing members of the class.
-     *
-     * @param array $argarray misc. arguments
-     *
-     * @return boolean true
-     */
-
-    function prepare($argarray)
-    {
-        OwnerDesignAction::prepare($argarray);
-
-        $this->id = $this->trimmed('id');
-
-        $this->answer = Answer::staticGet('id', $this->id);
-
-        if (empty($this->answer)) {
-            throw new ClientException(_('No such answer.'), 404);
-        }
-
-        $this->notice = Notice::staticGet('uri', $this->answer->uri);
-
-        if (empty($this->notice)) {
-            // Did we used to have it, and it got deleted?
-            throw new ClientException(_('No such answer.'), 404);
-        }
-
-        $this->user = User::staticGet('id', $this->answer->profile_id);
-
-        if (empty($this->user)) {
-            throw new ClientException(_('No such user.'), 404);
-        }
-
-        $this->profile = $this->user->getProfile();
-
-        if (empty($this->profile)) {
-            throw new ServerException(_('User without a profile.'));
-        }
-
-        $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
-
-        return true;
-    }
-
-    /**
-     * Title of the page
-     *
-     * Used by Action class for layout.
-     *
-     * @return string page tile
-     */
-
-    function title()
-    {
-        return sprintf(_('%s\'s answer to "%s"'),
-                       $this->user->nickname,
-                       $this->answer->title);
-    }
-
-    /**
-     * Overload page title display to show answer link
-     *
-     * @return void
-     */
-
-    function showPageTitle()
-    {
-        $this->elementStart('h1');
-        $this->element('a',
-                       array('href' => $this->answer->url),
-                       $this->asnwer->title);
-        $this->elementEnd('h1');
-    }
-}
diff --git a/plugins/QuestionAndAnswer/actions/showquestion.php b/plugins/QuestionAndAnswer/actions/showquestion.php
deleted file mode 100644 (file)
index 50f56fd..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2011, StatusNet, Inc.
- *
- * Show a question
- *
- * PHP version 5
- *
- * 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 <http://www.gnu.org/licenses/>.
- *
- * @category  QuestionAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @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);
-}
-
-/**
- * Show a question
- *
- * @category  QuestionAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @copyright 2011 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
- * @link      http://status.net/
- */
-class ShowquestionAction extends ShownoticeAction
-{
-    protected $question = null;
-
-    /**
-     * For initializing members of the class.
-     *
-     * @param array $argarray misc. arguments
-     *
-     * @return boolean true
-     */
-    function prepare($argarray)
-    {
-        OwnerDesignAction::prepare($argarray);
-
-        $this->id = $this->trimmed('id');
-
-        $this->question = Question::staticGet('id', $this->id);
-
-        if (empty($this->question)) {
-            // TRANS: Client exception thrown trying to view a non-existing question.
-            throw new ClientException(_m('No such question.'), 404);
-        }
-
-        $this->notice = $this->question->getNotice();
-
-        if (empty($this->notice)) {
-            // Did we used to have it, and it got deleted?
-            // TRANS: Client exception thrown trying to view a non-existing question notice.
-            throw new ClientException(_m('No such question notice.'), 404);
-        }
-
-        $this->user = User::staticGet('id', $this->question->profile_id);
-
-        if (empty($this->user)) {
-            // TRANS: Client exception thrown trying to view a question of a non-existing user.
-            throw new ClientException(_m('No such user.'), 404);
-        }
-
-        $this->profile = $this->user->getProfile();
-
-        if (empty($this->profile)) {
-            // TRANS: Server exception thrown trying to view a question for a user for which the profile could not be loaded.
-            throw new ServerException(_m('User without a profile.'));
-        }
-
-        $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
-
-        return true;
-    }
-
-    /**
-     * Title of the page
-     *
-     * Used by Action class for layout.
-     *
-     * @return string page tile
-     */
-    function title()
-    {
-        // TRANS: Page title for a question.
-        // 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);
-    }
-
-    /**
-     * @fixme combine the notice time with question update time
-     */
-    function lastModified()
-    {
-        return Action::lastModified();
-    }
-
-
-    /**
-     * @fixme combine the notice time with question update time
-     */
-    function etag()
-    {
-        return Action::etag();
-    }
-}
diff --git a/plugins/QuestionAndAnswer/classes/Answer.php b/plugins/QuestionAndAnswer/classes/Answer.php
deleted file mode 100644 (file)
index 45e52d0..0000000
+++ /dev/null
@@ -1,213 +0,0 @@
-<?php
-/**
- * Data class to save answers to questions
- *
- * PHP version 5
- *
- * @category QuestionAndAnswer
- * @package  StatusNet
- * @author   Zach Copley <zach@status.net>
- * @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 <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET')) {
-    exit(1);
-}
-
-/**
- * For storing answers
- *
- * @category QuestionAndAnswer
- * @package  StatusNet
- * @author   Zach Copley <zach@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://status.net/
- *
- * @see      DB_DataObject
- */
-class Answer extends Managed_DataObject
-{
-    public $__table = '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
-
-    /**
-     * 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 (usually 'user_id' for this class)
-     * @param mixed  $v Value to lookup
-     *
-     * @return User_greeting_count object found, or null for no hits
-     *
-     */
-    function staticGet($k, $v=null)
-    {
-        return Memcached_DataObject::staticGet('Answer', $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 Bookmark object found, or null for no hits
-     *
-     */
-    function pkeyGet($kv)
-    {
-        return Memcached_DataObject::pkeyGet('Answer', $kv);
-    }
-
-    /**
-     * The One True Thingy that must be defined and declared.
-     */
-    public static function schemaDef()
-    {
-        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),
-            ),
-            'primary key' => array('id'),
-            'unique keys' => array(
-                'question_uri_key' => array('uri'),
-                'question_id_profile_id_key' => array('question_id', 'profile_id'),
-            ),
-            'indexes' => array(
-                'profile_id_question_Id_index' => array('profile_id', 'question_id'),
-            )
-        );
-    }
-
-    /**
-     * Get an answer based on a notice
-     *
-     * @param Notice $notice Notice to check for
-     *
-     * @return Answer found response or null
-     */
-    function getByNotice($notice)
-    {
-        return self::staticGet('uri', $notice->uri);
-    }
-
-    /**
-     * Get the notice that belongs to this answer
-     *
-     * @return Notice
-     */
-    function getNotice()
-    {
-        return Notice::staticGet('uri', $this->uri);
-    }
-
-    function bestUrl()
-    {
-        return $this->getNotice()->bestUrl();
-    }
-
-    /**
-     * Get the Question this is an answer to
-     *
-     * @return Question
-     */
-    function getQuestion()
-    {
-        return Question::staticGet('id', $this->question_id);
-    }
-    /**
-     * Save a new answer notice
-     *
-     * @param Profile  $profile
-     * @param Question $Question the question being answered
-     * @param array
-     *
-     * @return Notice saved notice
-     */
-    static function saveNew($profile, $question, $options=null)
-    {
-        if (empty($options)) {
-            $options = array();
-        }
-
-        $a = new Answer();
-        $a->id          = UUID::gen();
-        $a->profile_id  = $profile->id;
-        $a->question_id = $question->id;
-        $a->created     = common_sql_now();
-        $a->uri         = common_local_url(
-            'showanswer',
-            array('id' => $pr->id)
-        );
-        
-        common_log(LOG_DEBUG, "Saving answer: $pr->id $pr->uri");
-        $a->insert();
-
-        // TRANS: Notice content answering a question.
-        // TRANS: %s is the answer
-        $content  = sprintf(
-            _m('answered "%s"'),
-            $answer
-        );
-        $link = '<a href="' . htmlspecialchars($question->uri) . '">' . htmlspecialchars($answer) . '</a>';
-        // TRANS: Rendered version of the notice content answering a question.
-        // TRANS: %s a link to the question with the chosen option as link description.
-        $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);
-
-        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);
-
-        return $saved;
-    }
-}
diff --git a/plugins/QuestionAndAnswer/classes/Question.php b/plugins/QuestionAndAnswer/classes/Question.php
deleted file mode 100644 (file)
index 95ceeb4..0000000
+++ /dev/null
@@ -1,240 +0,0 @@
-<?php
-/**
- * Data class to mark a notice as a question
- *
- * PHP version 5
- *
- * @category QuestionAndAnswer
- * @package  StatusNet
- * @author   Zach Copley <zach@status.net>
- * @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 <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET')) {
-    exit(1);
-}
-
-/**
- * For storing a question
- *
- * @category QuestionAndAnswer
- * @package  StatusNet
- * @author   Zach Copley <zach@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://status.net/
- *
- * @see      DB_DataObject
- */
-
-class Question extends Managed_DataObject
-{
-    public $__table = '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 $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 (usually 'user_id' for this class)
-     * @param mixed  $v Value to lookup
-     *
-     * @return User_greeting_count object found, or null for no hits
-     *
-     */
-    function staticGet($k, $v=null)
-    {
-        return Memcached_DataObject::staticGet('Question', $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 Bookmark object found, or null for no hits
-     *
-     */
-    function pkeyGet($kv)
-    {
-        return Memcached_DataObject::pkeyGet('Question', $kv);
-    }
-
-    /**
-     * The One True Thingy that must be defined and declared.
-     */
-    public static function schemaDef()
-    {
-        return array(
-            'description' => 'Per-notice question data for QuestionAndAnswer 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'),
-                'description' => array('type' => 'text'),
-                'created' => array('type' => 'datetime', 'not null' => true),
-            ),
-            'primary key' => array('id'),
-            'unique keys' => array(
-                'question_uri_key' => array('uri'),
-            ),
-        );
-    }
-
-    /**
-     * Get a question based on a notice
-     *
-     * @param Notice $notice Notice to check for
-     *
-     * @return Question found question or null
-     */
-    function getByNotice($notice)
-    {
-        return self::staticGet('uri', $notice->uri);
-    }
-
-    function getNotice()
-    {
-        return Notice::staticGet('uri', $this->uri);
-    }
-
-    function bestUrl()
-    {
-        return $this->getNotice()->bestUrl();
-    }
-
-    /**
-     * Get the answer from a particular user to this question, if any.
-     *
-     * @param Profile $profile
-     *
-     * @return Answer object or null
-     */
-    function getAnswer(Profile $profile)
-    {
-        $a = new Answer();
-        $a->question_id = $this->id;
-        $a->profile_id = $profile->id;
-        $a->find();
-        if ($a->fetch()) {
-            return $a;
-        } else {
-            return null;
-        }
-    }
-
-    function countAnswers()
-    {
-        $a = new Answer();
-        
-        $a->question_id = $this->id;
-        return $a-count();
-    }
-
-    /**
-     * Save a new question notice
-     *
-     * @param Profile $profile
-     * @param string  $question
-     * @param string  $title
-     * @param string  $description
-     * @param array   $option // and whatnot
-     *
-     * @return Notice saved notice
-     */
-    static function saveNew($profile, $question, $title, $description, $options = array())
-    {
-        $q = new Question();
-
-        $q->id          = UUID::gen();
-        $q->profile_id  = $profile->id;
-        $q->title       = $title;
-        $q->description = $description;
-
-        if (array_key_exists('created', $options)) {
-            $q->created = $options['created'];
-        } else {
-            $q->created = common_sql_now();
-        }
-
-        if (array_key_exists('uri', $options)) {
-            $q->uri = $options['uri'];
-        } else {
-            $q->uri = common_local_url(
-                'showquestion',
-                array('id' => $q->id)
-            );
-        }
-
-        common_log(LOG_DEBUG, "Saving question: $q->id $q->uri");
-        $q->insert();
-
-        // TRANS: Notice content creating a question.
-        // TRANS: %1$s is the title of the question, %2$s is a link to the question.
-        $content  = sprintf(
-            _m('question: %1$s %2$s'),
-            $title,
-            $q->uri
-        );
-        
-        $link = '<a href="' . htmlspecialchars($q->uri) . '">' . htmlspecialchars($title) . '</a>';
-        // TRANS: Rendered version of the notice content creating a question.
-        // TRANS: %s a link to the question as link description.
-        $rendered = sprintf(_m('Question: %s'), $link);
-
-        $tags    = array('question');
-        $replies = array();
-
-        $options = array_merge(
-            array(
-                'urls'        => array(),
-                'rendered'    => $rendered,
-                'tags'        => $tags,
-                'replies'     => $replies,
-                'object_type' => QuestionAndAnswerPlugin::QUESTION_OBJECT
-            ),
-            $options
-        );
-
-        if (!array_key_exists('uri', $options)) {
-            $options['uri'] = $p->uri;
-        }
-
-        $saved = Notice::saveNew(
-            $profile->id,
-            $content,
-            array_key_exists('source', $options) ?
-            $options['source'] : 'web',
-            $options
-        );
-
-        return $saved;
-    }
-}
diff --git a/plugins/QuestionAndAnswer/css/questionandanswer.css b/plugins/QuestionAndAnswer/css/questionandanswer.css
deleted file mode 100644 (file)
index 4701b5a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* stubb for q&a css */
diff --git a/plugins/QuestionAndAnswer/lib/answerform.php b/plugins/QuestionAndAnswer/lib/answerform.php
deleted file mode 100644 (file)
index d093863..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2011, StatusNet, Inc.
- *
- * Form for answering a question
- *
- * PHP version 5
- *
- * 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 <http://www.gnu.org/licenses/>.
- *
- * @category  QuestionAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @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  QuestionAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @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 Question $question
-     * @param HTMLOutputter $out output channel
-     *
-     * @return void
-     */
-    function __construct(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'));
-    }
-}
-
diff --git a/plugins/QuestionAndAnswer/lib/questionform.php b/plugins/QuestionAndAnswer/lib/questionform.php
deleted file mode 100644 (file)
index 5892464..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2011, StatusNet, Inc.
- *
- * Form for adding a new question
- *
- * PHP version 5
- *
- * 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 <http://www.gnu.org/licenses/>.
- *
- * @category  QuestonAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@copley.name>
- * @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 question
- *
- * @category  QuestionAndAnswer
- * @package   StatusNet
- * @author    Zach Copley <zach@copley.name>
- * @copyright 2011 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
- * @link      http://status.net/
- */
-class NewQuestionForm extends Form
-{
-    protected $question = null;
-
-    /**
-     * Construct a new question form
-     *
-     * @param HTMLOutputter $out output channel
-     *
-     * @return void
-     */
-    function __construct($out=null, $question=null, $options=null)
-    {
-        parent::__construct($out);
-    }
-
-    /**
-     * ID of the form
-     *
-     * @return int ID of the form
-     */
-    function id()
-    {
-        return 'newquestion-form';
-    }
-
-    /**
-     * class of the form
-     *
-     * @return string class of the form
-     */
-    function formClass()
-    {
-        return 'form_settings ajax-notice';
-    }
-
-    /**
-     * Action of the form
-     *
-     * @return string URL of the action
-     */
-    function action()
-    {
-        return common_local_url('newquestion');
-    }
-
-    /**
-     * Data elements of the form
-     *
-     * @return void
-     */
-    function formData()
-    {
-        $this->out->elementStart('fieldset', array('id' => 'newquestion-data'));
-        $this->out->elementStart('ul', 'form_data');
-
-        $this->li();
-        $this->out->input('question',
-                          // TRANS: Field label on the page to create a question.
-                          _m('Question'),
-                          $this->question,
-                          // TRANS: Field title on the page to create a question.
-                          _m('What is your question?'));
-        $this->unli();
-
-        $this->out->elementEnd('ul');
-        $this->out->elementEnd('fieldset');
-    }
-
-    /**
-     * Action elements
-     *
-     * @return void
-     */
-    function formActions()
-    {
-        // TRANS: Button text for saving a new question.
-        $this->out->submit('submit', _m('BUTTON', 'Save'));
-    }
-}