code:
<?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
{
    const PLUGIN_VERSION = '2.0.0';

    var $oldSaveNew = true;

    /**
     * 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;
    }

    public function newFormAction() {
        return 'qnanewquestion';
    }

    /**
     * Map URLs to actions
     *
     * @param URLMapper $m path-to-action mapper
     *
     * @return boolean hook value; true means continue processing, false means stop.
     */

    public function onRouterInitialized(URLMapper $m)
    {
        $UUIDregex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';

        $m->connect('main/qna/newquestion',
                    ['action' => 'qnanewquestion']);

        $m->connect('answer/qna/closequestion',
                    ['action' => 'qnaclosequestion']);

        $m->connect('main/qna/newanswer',
                    ['action' => 'qnanewanswer']);

        $m->connect('main/qna/reviseanswer',
                    ['action' => 'qnareviseanswer']);

        $m->connect('question/vote/:id',
                    ['action' => 'qnavote',
                     'type'   => 'question'],
                    ['id' => $UUIDregex]);

        $m->connect('question/:id',
                    ['action' => 'qnashowquestion'],
                    ['id' => $UUIDregex]);

        $m->connect('answer/vote/:id',
                    ['action' => 'qnavote',
                     'type'   => 'answer'],
                    ['id' => $UUIDregex]);

        $m->connect('answer/:id',
                    ['action' => 'qnashowanswer'],
                    ['id' => $UUIDregex]);

        return true;
    }

    function onPluginVersion(array &$versions)
    {
        $versions[] = array(
            'name'        => 'QnA',
            'version'     => self::PLUGIN_VERSION,
            'author'      => 'Zach Copley',
            'homepage'    => 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/QnA',
            'description' =>
             // TRANS: Plugin description.
             _m('Question and Answers micro-app.')
        );
        return true;
    }

    function appTitle() {
        // TRANS: Application title.
        return _m('TITLE','Question');
    }

    function tag() {
        return 'question';
    }

    function types() {
        return array(
            QnA_Question::OBJECT_TYPE,
            QnA_Answer::OBJECT_TYPE
        );
    }

    /**
     * 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 $activity, Profile $actor, array $options=array())
    {
        if (count($activity->objects) != 1) {
            // TRANS: Exception thrown when there are too many activity objects.
            throw new Exception(_m('Too many activity objects.'));
        }

        $questionObj = $activity->objects[0];

        if ($questionObj->type != QnA_Question::OBJECT_TYPE) {
            // TRANS: Exception thrown when an incorrect object type is encountered.
            throw new Exception(_m('Wrong type for object.'));
        }

        $notice = null;

        switch ($activity->verb) {
        case ActivityVerb::POST:
            $notice = QnA_Question::saveNew(
                $actor,
                $questionObj->title,
                $questionObj->summary,
                $options
            );
            break;
        case Answer::ObjectType:
            $question = QnA_Question::getKV('uri', $questionObj->id);
            if (empty($question)) {
                // FIXME: save the question
                // TRANS: Exception thrown when answering a non-existing question.
                throw new Exception(_m('Answer to unknown question.'));
            }
            $notice = QnA_Answer::saveNew($actor, $question, $options);
            break;
        default:
            // TRANS: Exception thrown when an object type is encountered that cannot be handled.
            throw new Exception(_m('Unknown object type.'));
        }

        return $notice;
    }

    /**
     * Turn a Notice into an activity object
     *
     * @param Notice $notice
     *
     * @return ActivityObject
     */

    function activityObjectFromNotice(Notice $notice)
    {
        $question = null;

        switch ($notice->object_type) {
        case QnA_Question::OBJECT_TYPE:
            $question = QnA_Question::fromNotice($notice);
            break;
        case QnA_Answer::OBJECT_TYPE:
            $answer   = QnA_Answer::fromNotice($notice);
            $question = $answer->getQuestion();
            break;
        }

        if (empty($question)) {
            // TRANS: Exception thrown when an object type is encountered that cannot be handled.
            throw new Exception(_m('Unknown object type.'));
        }

        $notice = $question->getNotice();

        if (empty($notice)) {
            // TRANS: Exception thrown when requesting a non-existing question notice.
            throw new Exception(_m('Unknown question notice.'));
        }

        $obj = new ActivityObject();

        $obj->id      = $question->uri;
        $obj->type    = QnA_Question::OBJECT_TYPE;
        $obj->title   = $question->title;
        $obj->link    = $notice->getUrl();

        // XXX: probably need other stuff here

        return $obj;
    }

    /**
     * Output our CSS class for QnA notice list elements
     *
     * @param NoticeListItem $nli The item being shown
     *
     * @return boolean hook value
     */

    function onStartOpenNoticeListItemElement(NoticeListItem $nli)
    {
        $type = $nli->notice->object_type;

        switch($type)
        {
        case QnA_Question::OBJECT_TYPE:
            $id = (empty($nli->repeat)) ? $nli->notice->id : $nli->repeat->id;
            $class = 'h-entry notice question';
            if ($nli->notice->scope != 0 && $nli->notice->scope != 1) {
                $class .= ' limited-scope';
            }

            $question = QnA_Question::getKV('uri', $nli->notice->uri);

            if (!empty($question->closed)) {
                $class .= ' closed';
            }

            $nli->out->elementStart(
                'li', array(
                    'class' => $class,
                    'id'    => 'notice-' . $id
                )
            );
            Event::handle('EndOpenNoticeListItemElement', array($nli));
            return false;
            break;
        case QnA_Answer::OBJECT_TYPE:
            $id = (empty($nli->repeat)) ? $nli->notice->id : $nli->repeat->id;

            $cls = array('h-entry', 'notice', 'answer');

            $answer = QnA_Answer::getKV('uri', $nli->notice->uri);

            if (!empty($answer) && !empty($answer->best)) {
                $cls[] = 'best';
            }

            $nli->out->elementStart(
                'li',
                array(
                    'class' => implode(' ', $cls),
                    'id'    => 'notice-' . $id
                )
            );
            Event::handle('EndOpenNoticeListItemElement', array($nli));
            return false;
            break;
        default:
            return true;
        }

        return true;
    }

    /**
     * Output the HTML for this kind of object in a list
     *
     * @param NoticeListItem $nli The list item being shown.
     *
     * @return boolean hook value
     *
     * @todo FIXME: WARNING WARNING WARNING this closes a 'div' that is implicitly opened in BookmarkPlugin's showNotice implementation
     */
    function onStartShowNoticeItem(NoticeListItem $nli)
    {
        if (!$this->isMyNotice($nli->notice)) {
            return true;
        }

        $out = $nli->out;
        $notice = $nli->notice;

        $nli->showNotice($notice, $out);

        $nli->showNoticeLink();
        $nli->showNoticeSource();
        $nli->showNoticeLocation();
        $nli->showPermalink();

        $nli->showNoticeOptions();

        if ($notice->object_type == QnA_Question::OBJECT_TYPE) {
            $user = common_current_user();
            $question = QnA_Question::getByNotice($notice);

            if (!empty($user) and !empty($question)) {
                $profile = $user->getProfile();
                $answer = $question->getAnswer($profile);

                // Output a placeholder input -- clicking on it will
                // bring up a real answer form

                // NOTE: this whole ul is just a placeholder
                if (empty($question->closed) && empty($answer)) {
                    $out->elementStart('ul', 'notices qna-dummy');
                    $out->elementStart('li', 'qna-dummy-placeholder');
                    $out->element(
                        'input',
                        array(
                            'class' => 'placeholder',
                            // TRANS: Placeholder value for a possible answer to a question
                            // TRANS: by the logged in user.
                            'value' => _m('Your answer...')
                        )
                    );
                    $out->elementEnd('li');
                    $out->elementEnd('ul');
                }
            }
        }

        return false;
    }

    function adaptNoticeListItem($nli) {
        return new QnAListItem($nli);
    }

    static function shorten($content, $notice)
    {
        $short = null;

        if (Notice::contentTooLong($content)) {
            common_debug("content too long");
            $max = Notice::maxContent();
            // TRANS: Link description for link to full notice text if it is longer than
            // TRANS: what will be dispplayed.
            $ellipsis = _m('…');
            $short = mb_substr($content, 0, $max - 1);
            $short .= sprintf('%3$s',
                              $notice->getUrl(),
                              // TRANS: Title for link that is an ellipsis in English.
                              _m('more...'),
                              $ellipsis);
        } else {
            $short = $content;
        }

        return $short;
    }

    /**
     * Form for our app
     *
     * @param HTMLOutputter $out
     * @return Widget
     */
    function entryForm($out)
    {
        return new QnanewquestionForm($out);
    }

    /**
     * When a notice is deleted, clean up related tables.
     *
     * @param Notice $notice
     */
    function deleteRelated(Notice $notice)
    {
        switch ($notice->object_type) {
        case QnA_Question::OBJECT_TYPE:
            common_log(LOG_DEBUG, "Deleting question from notice...");
            $question = QnA_Question::fromNotice($notice);
            $question->delete();
            break;
        case QnA_Answer::OBJECT_TYPE:
            common_log(LOG_DEBUG, "Deleting answer from notice...");
            $answer = QnA_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)
    {
        $action->script($this->path('js/qna.js'));
        return true;
    }

    function onEndShowStyles($action)
    {
        $action->cssLink($this->path('css/qna.css'));
        return true;
    }
}


code:
The QnA plugin enables Questions and Answers type of notices

Installation
============
This plugin is enabled by default

Settings
========
none

path: plugins/QnA/README

license: mit


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Close a question to further 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);
}

/**
 * Close a question to new answers
 *
 * @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 QnaclosequestionAction extends Action
{
    protected $user = null;
    protected $error = null;
    protected $complete = null;

    protected $question = null;
    protected $answer = null;

    /**
     * Returns the title of the action
     *
     * @return string Action title
     */
    function title()
    {
        // TRANS: Page title for close a question
        return _m('Close question');
    }

    /**
     * For initializing members of the class.
     *
     * @param array $args misc. arguments
     *
     * @return boolean true
     * @throws ClientException
     */
    function prepare(array $args = [])
    {
        parent::prepare($args);
        if ($this->boolean('ajax')) {
            GNUsocial::setApi(true);
        }

        $this->user = common_current_user();

        if (empty($this->user)) {
            throw new ClientException(
            // TRANS: Client exception thrown trying to close a question when not logged in
                _m("You must be logged in to close a question."),
                403
            );
        }

        if ($this->isPost()) {
            $this->checkSessionToken();
        }

        $id = substr($this->trimmed('id'), 9);
        $this->question = QnA_Question::getKV('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);
        }

        return true;
    }

    /**
     * Handler method
     *
     * @return void
     */
    function handle()
    {
        parent::handle();

        if ($this->isPost()) {
            $this->closeQuestion();
        } else {
            $this->showPage();
        }

        return;
    }

    /**
     * Close a question
     *
     * @return void
     */
    function closeQuestion()
    {
        $user = common_current_user();

        try {
            if ($user->id != $this->question->profile_id) {
                // TRANS: Exception thrown trying to close another user's question.
                throw new Exception(_m('You did not ask this question.'));
            }

            $orig = clone($this->question);
            $this->question->closed = 1;
            $this->question->update($orig);

        } catch (ClientException $ce) {
            $this->error = $ce->getMessage();
            $this->showPage();
            return;
        }

        if ($this->boolean('ajax')) {
            $this->startHTML('text/xml;charset=utf-8');
            $this->elementStart('head');
            // TRANS: Page title after sending an answer.
            $this->element('title', null, _m('Answers'));
            $this->elementEnd('head');
            $this->elementStart('body');
            $form = new QnashowquestionForm($this, $this->question);
            $form->show();
            $this->elementEnd('body');
            $this->endHTML();
        } else {
            common_redirect($this->question->getUrl(), 303);
        }
    }

    /**
     * Show the close question form
     *
     * @return void
     */
    function showContent()
    {
        if (!empty($this->error)) {
            $this->element('p', 'error', $this->error);
        }

        // blar
    }

    /**
     * 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;
        }
    }
}


code:
<?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  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);
}

/**
 * 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 QnanewanswerAction extends Action
{
    public $question = null;
    protected $user = null;
    protected $error = null;
    protected $complete = null;
    protected $content = 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 $args misc. arguments
     *
     * @return boolean true
     * @throws ClientException
     */
    function prepare(array $args = [])
    {
        parent::prepare($args);
        if ($this->boolean('ajax')) {
            GNUsocial::setApi(true);
        }
        common_debug("in qnanewanswer");
        $this->user = common_current_user();

        if (empty($this->user)) {
            throw new ClientException(
            // TRANS: Client exception thrown trying to answer a question while not logged in.
                _m("You must be logged in to answer to a question."),
                403
            );
        }

        if ($this->isPost()) {
            $this->checkSessionToken();
        }

        $id = substr($this->trimmed('id'), 9);

        $this->question = QnA_Question::getKV('id', $id);

        if (empty($this->question)) {
            throw new ClientException(
            // TRANS: Client exception thrown trying to respond to a non-existing question.
                _m('Invalid or missing question.'),
                404
            );
        }

        $this->answerText = $this->trimmed('answer');

        return true;
    }

    /**
     * Handler method
     *
     * @return void
     */
    function handle()
    {
        parent::handle();

        if ($this->isPost()) {
            $this->newAnswer();
        } else {
            $this->showForm();
        }

        return;
    }

    /**
     * Add a new answer
     *
     * @return void
     */
    function newAnswer()
    {
        $profile = $this->user->getProfile();

        try {
            $notice = QnA_Answer::saveNew(
                $profile,
                $this->question,
                $this->answerText
            );
        } catch (ClientException $ce) {
            $this->error = $ce->getMessage();
            $this->showForm($this->error);
            return;
        }
        if ($this->boolean('ajax')) {
            common_debug("ajaxy part");
            $answer = $this->question->getAnswer($profile);

            $this->startHTML('text/xml;charset=utf-8');
            $this->elementStart('head');
            // TRANS: Page title after sending an answer.
            $this->element('title', null, _m('Answers'));
            $this->elementEnd('head');

            $this->elementStart('body');

            $nli = new NoticeAnswerListItem($notice, $this, $this->question, $answer);
            $nli->show();

            $this->elementEnd('body');
            $this->endHTML();
        } else {
            common_debug("not ajax");
            common_redirect($this->question->getUrl(), 303);
        }
    }

    /**
     * @param string $msg An error message, if any
     *
     * @return void
     */
    function showForm($msg = null)
    {
        common_debug("show form - msg = $msg");
        if ($this->boolean('ajax')) {
            if ($msg) {
                $this->ajaxErrorMsg($msg);
            } else {
                $this->ajaxShowForm();
            }
            return;
        }

        $this->msg = $msg;
        $this->showPage();
    }

    /**
     * Show an Ajax-y error message
     *
     * Goes back to the browser, where it's shown in a popup.
     *
     * @param string $msg Message to show
     *
     * @return void
     */
    function ajaxErrorMsg($msg)
    {
        $this->startHTML('text/xml;charset=utf-8', true);
        $this->elementStart('head');
        // TRANS: Page title after an AJAX error occurs on the post answer page.
        $this->element('title', null, _m('Ajax Error'));
        $this->elementEnd('head');
        $this->elementStart('body');
        $this->element('p', array('id' => 'error'), $msg);
        $this->elementEnd('body');
        $this->endHTML();
    }

    /**
     * Show an Ajax-y answer form
     *
     * Goes back to the browser, where it's shown in a popup.
     *
     * @param string $msg Message to show
     *
     * @return void
     */
    function ajaxShowForm()
    {
        common_debug('ajaxShowForm()');
        $this->startHTML('text/xml;charset=utf-8', true);
        $this->elementStart('head');
        // TRANS: Title for form to send answer to a question.
        $this->element('title', null, _m('TITLE', 'Your answer'));
        $this->elementEnd('head');
        $this->elementStart('body');

        $form = new QnanewanswerForm($this, $this->question);
        $form->show();

        $this->elementEnd('body');
        $this->endHTML();
    }

    /**
     * Show the Answer form
     *
     * @return void
     */
    function showContent()
    {
        if (!empty($this->error)) {
            $this->element('p', 'error', $this->error);
        }

        $form = new QnanewanswerForm($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;
        }
    }
}

class NoticeAnswerListItem extends NoticeListItem
{
    protected $question;
    protected $answer;

    /**
     * constructor
     *
     * Also initializes the profile attribute.
     *
     * @param Notice $notice The notice we'll display
     * @param $out
     * @param $question
     * @param $answer
     */
    function __construct($notice, $out, $question, $answer)
    {
        parent::__construct($notice, $out);
        $this->question = $question;
        $this->answer = $answer;

    }

    function show()
    {
        if (empty($this->notice)) {
            common_log(LOG_WARNING, "Trying to show missing notice; skipping.");
            return;
        } else if (empty($this->profile)) {
            common_log(LOG_WARNING, "Trying to show missing profile (" . $this->notice->profile_id . "); skipping.");
            return;
        }

        $this->showStart();
        $this->showNotice();
        $this->showNoticeInfo();
        $notice = $this->question->getNotice();
        $this->out->hidden('inreplyto', $notice->id);
        $this->showEnd();
    }

    /**
     * show the content of the notice
     *
     * Shows the content of the notice. This is pre-rendered for efficiency
     * at save time. Some very old notices might not be pre-rendered, so
     * they're rendered on the spot.
     *
     * @return void
     */
    function showContent()
    {
        $this->out->elementStart('p', array('class' => 'e-content answer-content'));
        $this->out->raw($this->notice->getRendered());

        if (!empty($this->answer)) {
            $form = new QnashowanswerForm($this->out, $this->answer);
            $form->show();
        } else {
            // TRANS: Error message displayed when an answer has no content.
            $this->out->text(_m('Answer data is missing.'));
        }

        $this->out->elementEnd('p');
    }
}


code:
<?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 QnanewquestionAction extends Action
{
    protected $user = null;
    protected $error = null;
    protected $complete = null;
    protected $title = null;
    protected $description = 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 $args misc. arguments
     *
     * @return boolean true
     * @throws ClientException
     */
    function prepare(array $args = [])
    {
        parent::prepare($args);

        $this->user = common_current_user();

        if (empty($this->user)) {
            throw new ClientException(
            // TRANS: Client exception thrown trying to create a Question while not logged in.
                _m('You must be logged in to post a question.'),
                403
            );
        }

        if ($this->isPost()) {
            $this->checkSessionToken();
        }

        $this->title = $this->trimmed('title');
        $this->description = $this->trimmed('description');

        return true;
    }

    /**
     * Handler method
     *
     * @return void
     */
    function handle()
    {
        parent::handle();

        if ($this->isPost()) {
            $this->newQuestion();
        } else {
            $this->showPage();
        }

        return;
    }

    /**
     * Add a new Question
     *
     * @return void
     */
    function newQuestion()
    {
        if ($this->boolean('ajax')) {
            GNUsocial::setApi(true);
        }
        try {
            if (empty($this->title)) {
                // TRANS: Client exception thrown trying to create a question without a title.
                throw new ClientException(_m('Question must have a title.'));
            }

            // Notice options
            $options = array();

            // Does the heavy-lifting for getting "To:" information
            ToSelector::fillOptions($this, $options);

            $saved = QnA_Question::saveNew(
                $this->user->getProfile(),
                $this->title,
                $this->description,
                $options
            );
        } catch (ClientException $ce) {
            $this->error = $ce->getMessage();
            $this->showPage();
            return;
        }

        if ($this->boolean('ajax')) {
            $this->startHTML('text/xml;charset=utf-8');
            $this->elementStart('head');
            // TRANS: Page title after sending a notice.
            $this->element('title', null, _m('Question posted'));
            $this->elementEnd('head');
            $this->elementStart('body');
            $this->showNotice($saved);
            $this->elementEnd('body');
            $this->endHTML();
        } else {
            common_redirect($saved->getUrl(), 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)
    {
        $nli = new NoticeQuestionListItem($notice, $this);
        $nli->show();
    }

    /**
     * Show the Question form
     *
     * @return void
     */
    function showContent()
    {
        if (!empty($this->error)) {
            $this->element('p', 'error', $this->error);
        }

        $form = new QuestionForm(
            $this,
            $this->title,
            $this->description
        );

        $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;
        }
    }
}

class NoticeQuestionListItem extends NoticeListItem
{
    /**
     * constructor
     *
     * Also initializes the profile attribute.
     *
     * @param Notice $notice The notice we'll display
     */
    function __construct($notice, $out = null)
    {
        parent::__construct($notice, $out);
    }

    function showEnd()
    {
        $this->out->element('ul', 'notices threaded-replies xoxo');
        parent::showEnd();
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Revise an answer
 *
 * 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);
}

/**
 * Revise an answer
 *
 * @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 QnareviseanswerAction extends Action
{
    protected $user = null;
    protected $error = null;
    protected $question = null;
    protected $answer = null;
    protected $content = null;

    /**
     * Returns the title of the action
     *
     * @return string Action title
     */
    function title()
    {
        // TRANS: Page title for revising a question
        return _m('Revise answer');
    }

    /**
     * For initializing members of the class.
     *
     * @param array $args misc. arguments
     *
     * @return boolean true
     * @throws ClientException
     */
    function prepare(array $args = [])
    {
        parent::prepare($args);
        if ($this->boolean('ajax')) {
            GNUsocial::setApi(true);
        }

        $this->user = common_current_user();

        if (empty($this->user)) {
            throw new ClientException(
            // TRANS: Client exception thrown trying to answer a question while not logged in.
                _m("You must be logged in to answer to a question."),
                403
            );
        }

        $id = substr($this->trimmed('id'), 7);

        $this->answer = QnA_Answer::getKV('id', $id);
        $this->question = $this->answer->getQuestion();

        if (empty($this->answer) || empty($this->question)) {
            throw new ClientException(
            // TRANS: Client exception thrown trying to respond to a non-existing question.
                _m('Invalid or missing answer.'),
                404
            );
        }

        $this->answerText = $this->trimmed('answer');

        return true;
    }

    /**
     * Handler method
     *
     * @return void
     */
    function handle()
    {
        parent::handle();

        if ($this->isPost()) {
            $this->checkSessionToken();
            if ($this->arg('revise')) {
                $this->showContent();
                return;
            } else if ($this->arg('best')) {
                if ($this->user->id == $this->question->profile_id) {
                    $this->markBest();
                    return;
                }
            } else {
                $this->reviseAnswer();
                return;
            }
        }

        $this->showPage();
    }

    /**
     * Show the revise answer form
     *
     * @return void
     */
    function showContent()
    {
        if (!empty($this->error)) {
            $this->element('p', 'error', $this->error);
        }

        if ($this->boolean('ajax')) {
            $this->showAjaxReviseForm();
        } else {
            $form = new QnareviseanswerForm($this->answer, $this);
            $form->show();
        }

        return;
    }

    function showAjaxReviseForm()
    {
        $this->startHTML('text/xml;charset=utf-8');
        $this->elementStart('head');
        // TRANS: Form title for sending an answer.
        $this->element('title', null, _m('TITLE', 'Answer'));
        $this->elementEnd('head');
        $this->elementStart('body');
        $form = new QnareviseanswerForm($this->answer, $this);
        $form->show();
        $this->elementEnd('body');
        $this->endHTML();
    }

    /**
     * Mark the answer as the "best" answer
     *
     * @return void
     */
    function markBest()
    {
        $question = $this->question;
        $answer = $this->answer;

        try {
            // close the question to further answers
            $orig = clone($question);
            $question->closed = 1;
            $result = $question->update($orig);

            // mark this answer an the best answer
            $orig = clone($answer);
            $answer->best = 1;
            $result = $answer->update($orig);
        } catch (ClientException $ce) {
            $this->error = $ce->getMessage();
            $this->showPage();
            return;
        }
        if ($this->boolean('ajax')) {
            common_debug("ajaxy part");
            $this->startHTML('text/xml;charset=utf-8');
            $this->elementStart('head');
            // TRANS: Page title after sending an answer.
            $this->element('title', null, _m('Answer'));
            $this->elementEnd('head');
            $this->elementStart('body');
            $form = new QnashowanswerForm($this, $answer);
            $form->show();
            $this->elementEnd('body');
            $this->endHTML();
        } else {
            common_redirect($this->answer->getUrl(), 303);
        }
    }

    /**
     * Revise the answer
     *
     * @return void
     */
    function reviseAnswer()
    {
        $answer = $this->answer;

        try {
            $orig = clone($answer);
            $answer->content = $this->answerText;
            $answer->revisions++;
            $result = $answer->update($orig);
        } catch (ClientException $ce) {
            $this->error = $ce->getMessage();
            $this->showPage();
            return;
        }
        if ($this->boolean('ajax')) {
            common_debug("ajaxy part");
            $this->startHTML('text/xml;charset=utf-8');
            $this->elementStart('head');
            // TRANS: Page title after sending an answer.
            $this->element('title', null, _m('Answer'));
            $this->elementEnd('head');
            $this->elementStart('body');
            $form = new QnashowanswerForm($this, $answer);
            $form->show();
            $this->elementEnd('body');
            $this->endHTML();
        } else {
            common_redirect($this->answer->getUrl(), 303);
        }
    }

    /**
     * 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;
        }
    }
}


code:
<?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 QnashowanswerAction extends ShownoticeAction
{
    protected $answer = null;

    /**
     * For initializing members of the class.
     *
     * @param array $args misc. arguments
     *
     * @return boolean true
     * @throws ClientException
     * @throws ServerException
     */
    function prepare(array $args = [])
    {
        Action::prepare($args);

        $this->id = $this->trimmed('id');

        $this->answer = QnA_Answer::getKV('id', $this->id);

        if (empty($this->answer)) {
            // TRANS: Client exception thrown when requesting a non-existing answer.
            throw new ClientException(_m('No such answer.'), 404);
        }

        $this->question = $this->answer->getQuestion();

        if (empty($this->question)) {
            // TRANS: Client exception thrown when requesting an answer that has no connected question.
            throw new ClientException(_m('No question for this answer.'), 404);
        }

        $this->notice = Notice::getKV('uri', $this->answer->uri);

        if (empty($this->notice)) {
            // TRANS: Did we used to have it, and it got deleted?
            throw new ClientException(_m('No such answer.'), 404);
        }

        $this->user = User::getKV('id', $this->answer->profile_id);

        if (empty($this->user)) {
            // TRANS: Client exception thrown when requesting answer data for a non-existing user.
            throw new ClientException(_m('No such user.'), 404);
        }

        $this->profile = $this->user->getProfile();

        if (empty($this->profile)) {
            // TRANS: Client exception thrown when requesting answer data for a user without a profile.
            throw new ServerException(_m('User without a profile.'));
        }

        try {
            $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
        } catch (Exception $e) {
            $this->avatar = null;
        }

        return true;
    }

    /**
     * Title of the page
     *
     * Used by Action class for layout.
     *
     * @return string page tile
     */
    function title()
    {
        $question = $this->answer->getQuestion();

        return sprintf(
        // TRANS: Page title.
        // TRANS: %1$s is the user who answered a question, %2$s is the question.
            _m('%1$s\'s answer to "%2$s"'),
            $this->user->nickname,
            $question->title
        );
    }

    /**
     * Overload page title display to show answer link
     *
     * @return void
     */
    function showPageTitle()
    {
        $this->elementStart('h1');
        $this->element(
            'a',
            array('href' => $this->answer->uri),
            $this->question->title
        );
        $this->elementEnd('h1');
    }

    function showContent()
    {
        $this->raw($this->answer->asHTML());
    }
}


code:
<?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 QnashowquestionAction extends ShownoticeAction
{
    protected $question = null;

    /**
     * For initializing members of the class.
     *
     * @param array $args misc. arguments
     *
     * @return boolean true
     * @throws ClientException
     * @throws ServerException
     */
    function prepare(array $args = [])
    {
        Action::prepare($args);

        $this->id = $this->trimmed('id');

        $this->question = QnA_Question::getKV('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::getKV('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.'));
        }

        try {
            $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
        } catch (Exception $e) {
            $this->avatar = null;
        }

        return true;
    }

    function showContent()
    {
        $this->elementStart('div', 'qna-full-question');
        $this->raw($this->question->asHTML());

        $answer = $this->question->getAnswers();

        $this->elementStart('div', 'qna-full-question-answers');

        $answerIds = array();

        // @fixme use a filtered stream!

        if (!empty($answer)) {
            while ($answer->fetch()) {
                $answerIds[] = $answer->getNotice()->id;
            }
        }

        if (count($answerIds) > 0) {
            $notice = Notice::multiGet('id', $answerIds);

            $nli = new NoticeList($notice, $this);
            $nli->show();
        }

        $user = common_current_user();

        if (!empty($user)) {
            $profile = $user->getProfile();
            $answer = QnA_Question::getAnswer($profile);
            if (empty($answer)) {
                $form = new QnanewanswerForm($this, $this->question, false);
                $form->show();
            }
        }

        $this->elementEnd('div');
        $this->elementEnd('div');
    }

    /**
     * Title of the page
     *
     * Used by Action class for layout.
     *
     * @return string page tile
     */
    function title()
    {
        return sprintf(
        // TRANS: Page title for a question.
        // TRANS: %1$s is the nickname of the user who asked the question, %2$s is the question.
            _m('%1$s\'s question: %2$s'),
            $this->user->nickname,
            $this->question->title
        );
    }

    /**
     * @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();
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Vote on a question or answer
 *
 * 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);
}

/**
 * Vote on a question or answer
 *
 * @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 Qnavote extends Action
{
    protected $user = null;
    protected $error = null;
    protected $complete = null;

    protected $question = 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 $args misc. arguments
     *
     * @return boolean true
     * @throws ClientException
     */
    function prepare(array $args = [])
    {
        parent::prepare($args);
        if ($this->boolean('ajax')) {
            GNUsocial::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 = QnA_Question::getKV('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
     *
     * @return void
     */
    function handle()
    {
        parent::handle();

        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')) {
            $this->startHTML('text/xml;charset=utf-8');
            $this->elementStart('head');
            // TRANS: Page title after sending in a vote for a question or answer.
            $this->element('title', null, _m('Answers'));
            $this->elementEnd('head');
            $this->elementStart('body');
            $form = new QnA_Answer($this->question, $this);
            $form->show();
            $this->elementEnd('body');
            $this->endHTML();
        } else {
            common_redirect($this->question->getUrl(), 303);
        }
    }

    /**
     * Show the Answer form
     *
     * @return void
     */
    function showContent()
    {
        if (!empty($this->error)) {
            $this->element('p', 'error', $this->error);
        }

        $form = new QnaanswerForm($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;
        }
    }
}


code:
<?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  OBJECT_TYPE = 'http://activityschema.org/object/answer';

    public $__table = 'qna_answer'; // table name
    public $id;          // char(36) primary key not null -> UUID
    public $uri;         // varchar(191)   not 255 because utf8mb4 takes more space
    public $question_id; // char(36) -> question.id UUID
    public $profile_id;  // int -> question.id
    public $best;        // (boolean) int -> whether the question asker has marked this as the best answer
    public $revisions;   // int -> count of revisions to this answer
    public $content;     // text -> response text
    public $created;     // datetime

    /**
     * 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'      => 191,
                    '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',
                ),
                'content'    => array('type' => 'text'), // got a better name?
                'best'       => array('type' => 'int', 'size' => 'tiny'),
                'revisions'  => array('type' => 'int'),
                'profile_id' => array('type' => 'int'),
                'created'    => array('type' => 'datetime', 'not null' => true),
            ),
            'primary key' => array('id'),
            'unique keys' => array(
                '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
     */
    static function getByNotice($notice)
    {
        $answer = self::getKV('uri', $notice->uri);
        if (empty($answer)) {
            throw new Exception("No answer with URI {$notice->uri}");
        }
        return $answer;
    }

    /**
     * Get the notice that belongs to this answer
     *
     * @return Notice
     */
    function getNotice()
    {
        return Notice::getKV('uri', $this->uri);
    }

    static function fromNotice($notice)
    {
        return QnA_Answer::getKV('uri', $notice->uri);
    }

    function getUrl()
    {
        return $this->getNotice()->getUrl();
    }

    /**
     * Get the Question this is an answer to
     *
     * @return QnA_Question
     */
    function getQuestion()
    {
        $question = QnA_Question::getKV('id', $this->question_id);
        if (empty($question)) {
            // TRANS: Exception thown when getting a question with a non-existing ID.
            // TRANS: %s is the non-existing question ID.
            throw new Exception(sprintf(_m('No question with ID %s'),$this->question_id));
        }
        return $question;
    }

    function getProfile()
    {
        $profile = Profile::getKV('id', $this->profile_id);
        if (empty($profile)) {
            // TRANS: Exception thown when getting a profile with a non-existing ID.
            // TRANS: %s is the non-existing profile ID.
            throw new Exception(sprintf(_m('No profile with ID %s'),$this->profile_id));
        }
        return $profile;
    }

    function asHTML()
    {
        return self::toHTML(
            $this->getProfile(),
            $this->getQuestion(),
            $this
        );
    }

    function asString()
    {
        return self::toString(
            $this->getProfile(),
            $this->getQuestion(),
            $this
        );
    }

    static function toHTML($profile, $question, $answer)
    {
        $notice = $question->getNotice();

        $out = new XMLStringer();

        $cls = array('qna_answer');
        if (!empty($answer->best)) {
            $cls[] = 'best';
        }

        $out->elementStart('p', array('class' => implode(' ', $cls)));
        $out->elementStart('span', 'answer-content');
        $out->raw(common_render_text($answer->content));
        $out->elementEnd('span');

        if (!empty($answer->revisions)) {
            $out->elementstart('span', 'answer-revisions');
            $out->text(
                htmlspecialchars(
                    // Notification of how often an answer was revised.
                    // TRANS: %s is the number of answer revisions.
                    sprintf(_m('%s revision','%s revisions',$answer->revisions), $answer->revisions)
                )
            );
            $out->elementEnd('span');
        }

        $out->elementEnd('p');

        return $out->getString();
    }

    static function toString($profile, $question, $answer)
    {
        // @todo FIXME: unused variable?
        $notice = $question->getNotice();

        return sprintf(
            // TRANS: Text for a question that was answered.
            // TRANS: %1$s is the user that answered, %2$s is the question title,
            // TRANS: %2$s is the answer content.
            _m('%1$s answered the question "%2$s": %3$s'),
            htmlspecialchars($profile->getBestName()),
            htmlspecialchars($question->title),
            htmlspecialchars($answer->content)
        );
    }

    /**
     * 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, $text, $options = null)
    {
        if (empty($options)) {
            $options = array();
        }

        $answer              = new QnA_Answer();
        $answer->id          = UUID::gen();
        $answer->profile_id  = $profile->id;
        $answer->question_id = $question->id;
        $answer->revisions   = 0;
        $answer->best        = 0;
        $answer->content     = $text;
        $answer->created     = common_sql_now();
        $answer->uri         = common_local_url(
            'qnashowanswer',
            array('id' => $answer->id)
        );

        common_log(LOG_DEBUG, "Saving answer: $answer->id, $answer->uri");
        $answer->insert();

        $content  = sprintf(
            // TRANS: Text for a question that was answered.
            // TRANS: %s is the question title.
            _m('answered "%s"'),
            $question->title
        );

        $link = 'uri) . '">' . htmlspecialchars($question->title) . '';
        // TRANS: Rendered version of the notice content answering a question.
        // TRANS: %s a link to the question with question title as the link content.
        $rendered = sprintf(_m('answered "%s"'), $link);

        $tags    = array();
        $replies = array();

        $options = array_merge(
            array(
                'urls'        => array(),
                'content'     => $content,
                'rendered'    => $rendered,
                'tags'        => $tags,
                'replies'     => $replies,
                'reply_to'    => $question->getNotice()->id,
                'object_type' => self::OBJECT_TYPE
            ),
            $options
        );

        if (!array_key_exists('uri', $options)) {
            $options['uri'] = $answer->uri;
        }

        $saved = Notice::saveNew(
            $profile->id,
            $content,
            array_key_exists('source', $options) ?
            $options['source'] : 'web',
            $options
        );

        return $saved;
    }
}


code:
<?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 OBJECT_TYPE = 'http://activityschema.org/object/question';

    public $__table = 'qna_question'; // table name
    public $id;          // char(36) primary key not null -> UUID
    public $uri;         // varchar(191)   not 255 because utf8mb4 takes more space
    public $profile_id;  // int -> profile.id
    public $title;       // text
    public $description; // text
    public $closed;      // int (boolean) whether a question is closed
    public $created;     // datetime

    /**
     * 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'   => 191,
                    '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
     */
    static function getByNotice($notice)
    {
        return self::getKV('uri', $notice->uri);
    }

    function getNotice()
    {
        return Notice::getKV('uri', $this->uri);
    }

    function getUrl()
    {
        return $this->getNotice()->getUrl();
    }

    function getProfile()
    {
        $profile = Profile::getKV('id', $this->profile_id);
        if (empty($profile)) {
            // TRANS: Exception trown when getting a profile for a non-existing ID.
            // TRANS: %s is the provided profile ID.
            throw new Exception(sprintf(_m('No profile with ID %s'),$this->profile_id));
        }
        return $profile;
    }

    /**
     * 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 getAnswers()
    {
        $a = new QnA_Answer();
        $a->question_id = $this->id;
        $cnt = $a->find();
        if (!empty($cnt)) {
            return $a;
        } else {
            return null;
        }
    }

    function countAnswers()
    {
        $a = new QnA_Answer();

        $a->question_id = $this->id;

        return $a->count();
    }

    static function fromNotice($notice)
    {
        return QnA_Question::getKV('uri', $notice->uri);
    }

    function asHTML()
    {
        return self::toHTML($this->getProfile(), $this);
    }

    function asString()
    {
        return self::toString($this->getProfile(), $this);
    }

    static function toHTML($profile, $question)
    {
        $notice = $question->getNotice();

        $out = new XMLStringer();

        $cls = array('qna_question');

        if (!empty($question->closed)) {
            $cls[] = 'closed';
        }

        $out->elementStart('p', array('class' => implode(' ', $cls)));

        if (!empty($question->description)) {
            $out->elementStart('span', 'question-description');
            $out->raw(common_render_text($question->description));
            $out->elementEnd('span');
        }

        $cnt = $question->countAnswers();

        if (!empty($cnt)) {
            $out->elementStart('span', 'answer-count');
            // TRANS: Number of given answers to a question.
            // TRANS: %s is the number of given answers.
            $out->text(sprintf(_m('%s answer','%s answers',$cnt), $cnt));
            $out->elementEnd('span');
        }

        if (!empty($question->closed)) {
            $out->elementStart('span', 'question-closed');
            // TRANS: Notification that a question cannot be answered anymore because it is closed.
            $out->text(_m('This question is closed.'));
            $out->elementEnd('span');
        }

        $out->elementEnd('p');

        return $out->getString();
    }

    static function toString($profile, $question, $answers)
    {
        return sprintf(htmlspecialchars($question->description));
    }

    /**
     * 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, $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(
                'qnashowquestion',
                array('id' => $q->id)
            );
        }

        common_log(LOG_DEBUG, "Saving question: $q->id $q->uri");
        $q->insert();

        if (Notice::contentTooLong($q->title . ' ' . $q->uri)) {
            $max       = Notice::maxContent();
            $uriLen    = mb_strlen($q->uri);
            $targetLen = $max - ($uriLen + 15);
            $title = mb_substr($q->title, 0, $targetLen) . '…';
        }

        $content = $title . ' ' . $q->uri;

        $link = 'uri) . '">' . htmlspecialchars($q->title) . '';
        // 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' => self::OBJECT_TYPE
            ),
            $options
        );

        if (!array_key_exists('uri', $options)) {
            $options['uri'] = $q->uri;
        }

        $saved = Notice::saveNew(
            $profile->id,
            $content,
            array_key_exists('source', $options) ?
            $options['source'] : 'web',
            $options
        );

        return $saved;
    }
}


code:
<?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 = 'qna_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

    /**
     * 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();
    }
}


code:
<?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 QnanewanswerForm extends Form
{
    protected $question;
    protected $showQuestion;

    /**
     * Construct a new answer form
     *
     * @param QnA_Question $question
     * @param HTMLOutputter $out output channel
     *
     * @return void
     */
    function __construct(HTMLOutputter $out, QnA_Question $question, $showQuestion = false)
    {
        parent::__construct($out);
        $this->question = $question;
        $this->showQuestion = $showQuestion;
    }

    /**
     * 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 qna_answer_form ajax-notice';
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */
    function action()
    {
        return common_local_url('qnanewanswer');
    }

    /**
     * Data elements of the form
     *
     * @return void
     */
    function formData()
    {
        $question = $this->question;
        $out      = $this->out;
        $id       = "question-" . $question->id;

        if ($this->showQuestion) {
            $out->raw($this->question->asHTML());
        }

        $out->hidden('qna-question-id', $id, 'id');
        // TRANS: Field label.
        $out->textarea('qna-answer', _m('Enter your answer'), null, null, 'answer', true);
    }

    /**
     * Action elements
     *
     * @return void
     */
    function formActions()
    {
        // TRANS: Button text for submitting a poll response.
        $this->out->submit('qna-answer-submit', _m('BUTTON', 'Answer'), 'submit', 'submit');
    }
}


code:
<?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  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/
 */

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 QnanewquestionForm extends Form
{
    protected $title;
    protected $description;

    /**
     * Construct a new question form
     *
     * @param HTMLOutputter $out output channel
     *
     * @return void
     */
    function __construct($out = null, $title = null, $description = null, $options = null)
    {
        parent::__construct($out);
        $this->title       = $title;
        $this->description = $description;
    }

    /**
     * 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('qnanewquestion');
    }

    /**
     * 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(
            'qna-question-title',
            // TRANS: Field label for a new question.
            _m('LABEL','Title'),
            $this->title,
            // TRANS: Field title for a new question.
            _m('The title of your question.'),
            'title',
            true    // HTML5 "required" attribute
        );
        $this->unli();
        $this->li();
        $this->out->textarea(
            'qna-question-description',
            // TRANS: Field label for question details.
            _m('LABEL','Description'),
            $this->description,
            // TRANS: Field title for question details.
            _m('Your question in detail.'),
            'description',
            true    // HTML5 "required" attribute
        );
        $this->unli();

        $this->out->elementEnd('ul');
        $toWidget = new ToSelector(
            $this->out,
            common_current_user(),
            null
        );
        $toWidget->show();
        $this->out->elementEnd('fieldset');
    }

    /**
     * Action elements
     *
     * @return void
     */
    function formActions()
    {
        // TRANS: Button text for saving a new question.
        $this->out->submit('qna-question-submit', _m('BUTTON', 'Save'), 'submit', 'submit');
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Form for revising 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 revise 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 QnareviseanswerForm extends Form
{
    protected $question;
    protected $answer;

    /**
     * Construct a new answer form
     *
     * @param QnA_Answer $answer
     * @param HTMLOutputter $out output channel
     *
     * @return void
     */
    function __construct(QnA_Answer $answer, HTMLOutputter $out)
    {
        parent::__construct($out);
        $this->question = $answer->getQuestion();
        $this->answer   = $answer;
    }

    /**
     * ID of the form
     *
     * @return int ID of the form
     */
    function id()
    {
        return 'answered-form';
    }

    /**
     * class of the form
     *
     * @return string class of the form
     */
    function formClass()
    {
        return 'form_settings ajax';
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */
    function action()
    {
        return common_local_url('qnareviseanswer');
    }

    /**
     * Data elements of the form
     *
     * @return void
     */
    function formData()
    {
        $out = $this->out;
        $out->element('p', 'revise-answer', 'Your answer');
        $id = "answer-" . $this->answer->id;
        $out->hidden('id', $id);
        $out->textarea('answer', 'answer', $this->answer->content);
    }

    /**
     * Action elements
     *
     * @return void
     */
    function formActions()
    {
        // TRANS: Button text for submitting a revised answer.
        $this->out->submit('submit', _m('BUTTON', 'Submit'));
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Form for showing / revising an answer
 *
 * PHP version 5
 *
 * LICENCE: 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  Form
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    exit(1);
}

require_once INSTALLDIR . '/lib/form.php';

/**
 * Form for showing / revising an answer
 *
 * @category Form
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 *
 */
class QnashowanswerForm extends Form
{
    /**
     * The answer to show
     */
    protected $answer   = null;

    /**
     * The question this is an answer to
     */
    protected $question = null;

    /**
     * Constructor
     *
     * @param HTMLOutputter $out    output channel
     * @param QnA_Answer    $answer answer to revise
     */
    function __construct($out = null, $answer = null)
    {
        parent::__construct($out);

        $this->answer   = $answer;
        $this->question = $answer->getQuestion();
    }

    /**
     * ID of the form
     *
     * @return int ID of the form
     */
    function id()
    {
        return 'show-' . $this->answer->id;
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */
    function action()
    {
        return common_local_url('qnareviseanswer');
    }

    /**
     * Include a session token for CSRF protection
     *
     * @return void
     */
    function sessionToken()
    {
        $this->out->hidden(
            'token',
            common_session_token()
        );
    }

    /**
     * Legend of the Form
     *
     * @return void
     */
    function formLegend()
    {
        // TRANS: Form legend for showing the answer.
        $this->out->element('legend', null, _m('Answer'));
    }

    /**
     * Data elements
     *
     * @return void
     */
    function formData()
    {
        $this->out->hidden(
            'qna-answer-id',
            'answer-' . $this->answer->id,
            'id'
        );

        $this->out->raw($this->answer->asHTML());
    }

    /**
     * Action elements
     *
     * @return void
     */
    function formActions()
    {
        $user = common_current_user();
        if (empty($user)) {
            return;
        }

        if (empty($this->question->closed)) {
            if ($user->id == $this->question->profile_id) {
                if (empty($this->answer->best)) {
                    $this->out->submit(
                        'qna-best-answer',
                        // TRANS: Button text for marking an answer as "best".
                        _m('BUTTON', 'Best'),
                        'submit',
                        'best',
                        // TRANS: Title for button text marking an answer as "best".
                        _m('Mark this answer as the best answer.')
                    );

                }
            }

            /*
             * @todo FIXME: Revise is disabled until we figure out the
             *         Ostatus bits This comment is just a reminder
             *         that the UI for this works.
             */
            /*
            if ($user->id == $this->answer->profile_id) {
                $this->out->submit(
                    'revise',
                    // TRANS: Button text for revising an answer.
                    _m('BUTTON', 'Revise'),
                    'submit',
                    null,
                    // TRANS: Title for button text for revising an answer.
                    _m('Revise your answer.')
                );
            }
             */
        }
    }

    /**
     * Class of the form.
     *
     * @return string the form's class
     */
    function formClass()
    {
        return 'form_answer_show ajax';
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Form for showing / revising an answer
 *
 * PHP version 5
 *
 * LICENCE: 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  Form
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    exit(1);
}

require_once INSTALLDIR . '/lib/form.php';

/**
 * Form for showing a question
 *
 * @category Form
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 *
 */
class QnashowquestionForm extends Form
{
    /**
     * The question to show
     */
    var $question = null;

    /**
     * Constructor
     *
     * @param HTMLOutputter $out      output channel
     * @param QnA_Question  $question the question to show
     */
    function __construct($out = null, $question = null)
    {
        parent::__construct($out);
        $this->question = $question;
    }

    /**
     * ID of the form
     *
     * @return int ID of the form
     */
    function id()
    {
        return 'question-' . $this->question->id;
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */
    function action()
    {
        return common_local_url('qnaclosequestion');
    }

    /**
     * Include a session token for CSRF protection
     *
     * @return void
     */
    function sessionToken()
    {
        $this->out->hidden(
            'token',
            common_session_token()
        );
    }

    /**
     * Legend of the Form
     *
     * @return void
     */
    function formLegend()
    {
        // TRANS: Form legend for revising the answer.
        $this->out->element('legend', null, _m('LEGEND','Question'));
    }

    /**
     * Data elements
     *
     * @return void
     */
    function formData()
    {
        $this->out->hidden(
            'qna-question-id',
            'question-' . $this->question->id,
            'id'
        );

        $this->out->hidden(
            'answer-action',
            common_local_url(
                'qnanewanswer',
                null,
                array('id' => 'question-' . $this->question->id)
            )
        );

        $this->out->raw($this->question->asHTML());
    }

    /**
     * Action elements
     *
     * @return void
     */
    function formActions()
    {
        $user = common_current_user();
        if (empty($user)) {
            return;
        }

        if (empty($this->question->closed)) {
            if ($user->id == $this->question->profile_id) {
             $this->out->submit(
                'qna-question-close',
                // TRANS: Button text for closing a question.
                _m('BUTTON', 'Close'),
                'submit',
                'submit',
                // TRANS: Title for button text for closing a question.
                _m('Close the question to no one can answer it anymore.')
             );
            }
        }
    }

    /**
     * Class of the form.
     *
     * @return string the form's class
     */
    function formClass()
    {
        return 'form_question_show ajax';
    }
}