]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Most objects and forms are in place, now I just have to make it work.
authorZach Copley <zach@status.net>
Fri, 18 Mar 2011 00:43:13 +0000 (17:43 -0700)
committerZach Copley <zach@status.net>
Fri, 18 Mar 2011 00:43:13 +0000 (17:43 -0700)
plugins/QuestionAndAnswer/QuestionAndAnswerPlugin.php
plugins/QuestionAndAnswer/actions/Newquestion.php [new file with mode: 0644]
plugins/QuestionAndAnswer/actions/answer.php [new file with mode: 0644]
plugins/QuestionAndAnswer/actions/showanswer.php [new file with mode: 0644]
plugins/QuestionAndAnswer/actions/showquestion.php [new file with mode: 0644]
plugins/QuestionAndAnswer/classes/Answer.php [new file with mode: 0644]
plugins/QuestionAndAnswer/classes/Question.php [new file with mode: 0644]
plugins/QuestionAndAnswer/css/questionandanswer.css [new file with mode: 0644]
plugins/QuestionAndAnswer/lib/answerform.php [new file with mode: 0644]
plugins/QuestionAndAnswer/lib/questionform.php [new file with mode: 0644]

index 0d7cb96c9100ed0cb3e071e2ddeae5d959b9651e..e519dac64fc3216550ed5ff4ede9d94f60178364 100644 (file)
@@ -81,16 +81,18 @@ class QuestionAndAnswerPlugin extends MicroappPlugin
         case 'NewanswerAction':
         case 'ShowquestionAction':
         case 'ShowanswerAction':
-            include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
+            include_once $dir . '/actions/'
+                . strtolower(mb_substr($cls, 0, -6)) . '.php';
             return false;
         case 'QuestionForm':
         case 'AnswerForm':
-            include_once $dir . '/'.strtolower($cls).'.php';
+            include_once $dir . '/lib/' . strtolower($cls).'.php';
             break;
         case 'Question':
         case 'Answer':
-            include_once $dir . '/'.$cls.'.php';
+            include_once $dir . '/classes/' . $cls.'.php';
             return false;
+            break;
         default:
             return true;
         }
@@ -141,13 +143,10 @@ class QuestionAndAnswerPlugin extends MicroappPlugin
     }
 
     function types() {
-        /*
-        return array(Happening::OBJECT_TYPE,
-                     RSVP::POSITIVE,
-                     RSVP::NEGATIVE,
-                     RSVP::POSSIBLE);
-
-         */
+        return array(
+            Question::OBJECT_TYPE,
+            Answer::NORMAL
+        );
     }
 
     /**
@@ -423,7 +422,7 @@ class QuestionAndAnswerPlugin extends MicroappPlugin
 
     function onEndShowStyles($action)
     {
-        $action->cssLink($this->path('questionandanswer.css'));
+        $action->cssLink($this->path('css/questionandanswer.css'));
         return true;
     }
 }
diff --git a/plugins/QuestionAndAnswer/actions/Newquestion.php b/plugins/QuestionAndAnswer/actions/Newquestion.php
new file mode 100644 (file)
index 0000000..cd1c2ff
--- /dev/null
@@ -0,0 +1,211 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Add a new Question
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  QuestionAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+if (!defined('STATUSNET')) {
+    // This check helps protect against security problems;
+    // your code file can't be executed directly from the web.
+    exit(1);
+}
+
+/**
+ * Add a new Question
+ *
+ * @category  Plugin
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+class NewquestionAction extends Action
+{
+    protected $user        = null;
+    protected $error       = null;
+    protected $complete    = null;
+
+    protected $question    = null;
+
+    /**
+     * Returns the title of the action
+     *
+     * @return string Action title
+     */
+    function title()
+    {
+        // TRANS: Title for Question page.
+        return _m('New question');
+    }
+
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+    function prepare($argarray)
+    {
+        parent::prepare($argarray);
+
+        $this->user = common_current_user();
+
+        if (empty($this->user)) {
+            // TRANS: Client exception thrown trying to create a Question while not logged in.
+            throw new ClientException(_m('You must be logged in to post a question.'),
+                                      403);
+        }
+
+        if ($this->isPost()) {
+            $this->checkSessionToken();
+        }
+
+        return true;
+    }
+
+    /**
+     * Handler method
+     *
+     * @param array $argarray is ignored since it's now passed in in prepare()
+     *
+     * @return void
+     */
+    function handle($argarray=null)
+    {
+        parent::handle($argarray);
+
+        if ($this->isPost()) {
+            $this->newQuestion();
+        } else {
+            $this->showPage();
+        }
+
+        return;
+    }
+
+    /**
+     * Add a new Question
+     *
+     * @return void
+     */
+    function newQuestion()
+    {
+        if ($this->boolean('ajax')) {
+            StatusNet::setApi(true);
+        }
+        try {
+            if (empty($this->question)) {
+            // TRANS: Client exception thrown trying to create a Question without a question.
+                throw new ClientException(_m('Question must have a question.'));
+            }
+
+            $saved = Question::saveNew(
+                $this->user->getProfile(),
+                $this->question
+            );
+        } catch (ClientException $ce) {
+            $this->error = $ce->getMessage();
+            $this->showPage();
+            return;
+        }
+
+        if ($this->boolean('ajax')) {
+            header('Content-Type: text/xml;charset=utf-8');
+            $this->xw->startDocument('1.0', 'UTF-8');
+            $this->elementStart('html');
+            $this->elementStart('head');
+            // TRANS: Page title after sending a notice.
+            $this->element('title', null, _m('Notice posted'));
+            $this->elementEnd('head');
+            $this->elementStart('body');
+            $this->showNotice($saved);
+            $this->elementEnd('body');
+            $this->elementEnd('html');
+        } else {
+            common_redirect($saved->bestUrl(), 303);
+        }
+    }
+
+    /**
+     * Output a notice
+     *
+     * Used to generate the notice code for Ajax results.
+     *
+     * @param Notice $notice Notice that was saved
+     *
+     * @return void
+     */
+    function showNotice($notice)
+    {
+        class_exists('NoticeList'); // @fixme hack for autoloader
+        $nli = new NoticeListItem($notice, $this);
+        $nli->show();
+    }
+
+    /**
+     * Show the Question form
+     *
+     * @return void
+     */
+    function showContent()
+    {
+        if (!empty($this->error)) {
+            $this->element('p', 'error', $this->error);
+        }
+
+        $form = new NewQuestionForm(
+            $this,
+            $this->question,
+            $this->options
+        );
+
+        $form->show();
+
+        return;
+    }
+
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+    function isReadOnly($args)
+    {
+        if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
+            $_SERVER['REQUEST_METHOD'] == 'HEAD') {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
+
diff --git a/plugins/QuestionAndAnswer/actions/answer.php b/plugins/QuestionAndAnswer/actions/answer.php
new file mode 100644 (file)
index 0000000..49bb73a
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Answer a question
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  QuestonAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+if (!defined('STATUSNET')) {
+    // This check helps protect against security problems;
+    // your code file can't be executed directly from the web.
+    exit(1);
+}
+
+/**
+ * Answer a question
+ *
+ * @category  QuestionAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+class AnswerAction extends Action
+{
+    protected $user        = null;
+    protected $error       = null;
+    protected $complete    = null;
+
+    protected $qustion     = null;
+    protected $answer      = null;
+
+    /**
+     * Returns the title of the action
+     *
+     * @return string Action title
+     */
+    function title()
+    {
+        // TRANS: Page title for and answer to a question.
+        return _m('Answer');
+    }
+
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+    function prepare($argarray)
+    {
+        parent::prepare($argarray);
+        if ($this->boolean('ajax')) {
+            StatusNet::setApi(true);
+        }
+
+        $this->user = common_current_user();
+
+        if (empty($this->user)) {
+            // TRANS: Client exception thrown trying to answer a question while not logged in.
+            throw new ClientException(_m("You must be logged in to answer to a question."),
+                                      403);
+        }
+
+        if ($this->isPost()) {
+            $this->checkSessionToken();
+        }
+
+        $id = $this->trimmed('id');
+        $this->question = Question::staticGet('id', $id);
+        if (empty($this->question)) {
+            // TRANS: Client exception thrown trying to respond to a non-existing question.
+            throw new ClientException(_m('Invalid or missing question.'), 404);
+        }
+
+        $answer = $this->trimmed('answer');
+
+
+        return true;
+    }
+
+    /**
+     * Handler method
+     *
+     * @param array $argarray is ignored since it's now passed in in prepare()
+     *
+     * @return void
+     */
+    function handle($argarray=null)
+    {
+        parent::handle($argarray);
+
+        if ($this->isPost()) {
+            $this->answer();
+        } else {
+            $this->showPage();
+        }
+
+        return;
+    }
+
+    /**
+     * Add a new answer
+     *
+     * @return void
+     */
+    function answer()
+    {
+        try {
+            $notice = Answer::saveNew(
+                $this->user->getProfile(),
+                $this->question,
+                $this->answer
+            );
+        } catch (ClientException $ce) {
+            $this->error = $ce->getMessage();
+            $this->showPage();
+            return;
+        }
+
+        if ($this->boolean('ajax')) {
+            header('Content-Type: text/xml;charset=utf-8');
+            $this->xw->startDocument('1.0', 'UTF-8');
+            $this->elementStart('html');
+            $this->elementStart('head');
+            // TRANS: Page title after sending an answer.
+            $this->element('title', null, _m('Answers'));
+            $this->elementEnd('head');
+            $this->elementStart('body');
+            $form = new Answer($this->question, $this);
+            $form->show();
+            $this->elementEnd('body');
+            $this->elementEnd('html');
+        } else {
+            common_redirect($this->question->bestUrl(), 303);
+        }
+    }
+
+    /**
+     * Show the Answer form
+     *
+     * @return void
+     */
+    function showContent()
+    {
+        if (!empty($this->error)) {
+            $this->element('p', 'error', $this->error);
+        }
+
+        $form = new AnswerForm($this->question, $this);
+
+        $form->show();
+
+        return;
+    }
+
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+    function isReadOnly($args)
+    {
+        if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
+            $_SERVER['REQUEST_METHOD'] == 'HEAD') {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/plugins/QuestionAndAnswer/actions/showanswer.php b/plugins/QuestionAndAnswer/actions/showanswer.php
new file mode 100644 (file)
index 0000000..d3202cd
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * Show an answer to a question
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  QuestionAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    // This check helps protect against security problems;
+    // your code file can't be executed directly from the web.
+    exit(1);
+}
+
+/**
+ * Show an answer to a question, and associated data
+ *
+ * @category  QuestionAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+class ShowAnswerAction extends ShownoticeAction
+{
+    protected $answer = null;
+
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+
+    function prepare($argarray)
+    {
+        OwnerDesignAction::prepare($argarray);
+
+        $this->id = $this->trimmed('id');
+
+        $this->answer = Answer::staticGet('id', $this->id);
+
+        if (empty($this->answer)) {
+            throw new ClientException(_('No such answer.'), 404);
+        }
+
+        $this->notice = Notice::staticGet('uri', $this->answer->uri);
+
+        if (empty($this->notice)) {
+            // Did we used to have it, and it got deleted?
+            throw new ClientException(_('No such answer.'), 404);
+        }
+
+        $this->user = User::staticGet('id', $this->answer->profile_id);
+
+        if (empty($this->user)) {
+            throw new ClientException(_('No such user.'), 404);
+        }
+
+        $this->profile = $this->user->getProfile();
+
+        if (empty($this->profile)) {
+            throw new ServerException(_('User without a profile.'));
+        }
+
+        $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
+
+        return true;
+    }
+
+    /**
+     * Title of the page
+     *
+     * Used by Action class for layout.
+     *
+     * @return string page tile
+     */
+
+    function title()
+    {
+        return sprintf(_('%s\'s answer to "%s"'),
+                       $this->user->nickname,
+                       $this->answer->title);
+    }
+
+    /**
+     * Overload page title display to show answer link
+     *
+     * @return void
+     */
+
+    function showPageTitle()
+    {
+        $this->elementStart('h1');
+        $this->element('a',
+                       array('href' => $this->answer->url),
+                       $this->asnwer->title);
+        $this->elementEnd('h1');
+    }
+}
diff --git a/plugins/QuestionAndAnswer/actions/showquestion.php b/plugins/QuestionAndAnswer/actions/showquestion.php
new file mode 100644 (file)
index 0000000..50f56fd
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Show a question
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  QuestionAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    // This check helps protect against security problems;
+    // your code file can't be executed directly from the web.
+    exit(1);
+}
+
+/**
+ * Show a question
+ *
+ * @category  QuestionAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+class ShowquestionAction extends ShownoticeAction
+{
+    protected $question = null;
+
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+    function prepare($argarray)
+    {
+        OwnerDesignAction::prepare($argarray);
+
+        $this->id = $this->trimmed('id');
+
+        $this->question = Question::staticGet('id', $this->id);
+
+        if (empty($this->question)) {
+            // TRANS: Client exception thrown trying to view a non-existing question.
+            throw new ClientException(_m('No such question.'), 404);
+        }
+
+        $this->notice = $this->question->getNotice();
+
+        if (empty($this->notice)) {
+            // Did we used to have it, and it got deleted?
+            // TRANS: Client exception thrown trying to view a non-existing question notice.
+            throw new ClientException(_m('No such question notice.'), 404);
+        }
+
+        $this->user = User::staticGet('id', $this->question->profile_id);
+
+        if (empty($this->user)) {
+            // TRANS: Client exception thrown trying to view a question of a non-existing user.
+            throw new ClientException(_m('No such user.'), 404);
+        }
+
+        $this->profile = $this->user->getProfile();
+
+        if (empty($this->profile)) {
+            // TRANS: Server exception thrown trying to view a question for a user for which the profile could not be loaded.
+            throw new ServerException(_m('User without a profile.'));
+        }
+
+        $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
+
+        return true;
+    }
+
+    /**
+     * Title of the page
+     *
+     * Used by Action class for layout.
+     *
+     * @return string page tile
+     */
+    function title()
+    {
+        // TRANS: Page title for a question.
+        // TRANS: %1$s is the nickname of the user who asked the question, %2$s is the question.
+        return sprintf(_m('%1$s\'s question: %2$s'),
+                       $this->user->nickname,
+                       $this->question->question);
+    }
+
+    /**
+     * @fixme combine the notice time with question update time
+     */
+    function lastModified()
+    {
+        return Action::lastModified();
+    }
+
+
+    /**
+     * @fixme combine the notice time with question update time
+     */
+    function etag()
+    {
+        return Action::etag();
+    }
+}
diff --git a/plugins/QuestionAndAnswer/classes/Answer.php b/plugins/QuestionAndAnswer/classes/Answer.php
new file mode 100644 (file)
index 0000000..45e52d0
--- /dev/null
@@ -0,0 +1,213 @@
+<?php
+/**
+ * Data class to save answers to questions
+ *
+ * PHP version 5
+ *
+ * @category QuestionAndAnswer
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * For storing answers
+ *
+ * @category QuestionAndAnswer
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * @see      DB_DataObject
+ */
+class Answer extends Managed_DataObject
+{
+    public $__table = 'answer'; // table name
+    public $id;          // char(36) primary key not null -> UUID
+    public $question_id; // char(36) -> question.id UUID
+    public $profile_id;  // int -> question.id
+    public $votes;       // int -> total number of votes (up & down)
+    public $best;        // (int) boolean -> whether the question asker has marked this as the best answer
+    public $created;     // datetime
+
+    /**
+     * Get an instance by key
+     *
+     * This is a utility method to get a single instance with a given key value.
+     *
+     * @param string $k Key to use to lookup (usually 'user_id' for this class)
+     * @param mixed  $v Value to lookup
+     *
+     * @return User_greeting_count object found, or null for no hits
+     *
+     */
+    function staticGet($k, $v=null)
+    {
+        return Memcached_DataObject::staticGet('Answer', $k, $v);
+    }
+
+    /**
+     * Get an instance by compound key
+     *
+     * This is a utility method to get a single instance with a given set of
+     * key-value pairs. Usually used for the primary key for a compound key; thus
+     * the name.
+     *
+     * @param array $kv array of key-value mappings
+     *
+     * @return Bookmark object found, or null for no hits
+     *
+     */
+    function pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('Answer', $kv);
+    }
+
+    /**
+     * The One True Thingy that must be defined and declared.
+     */
+    public static function schemaDef()
+    {
+        return array(
+            'description' => 'Record of answers to questions',
+            'fields' => array(
+                'id' => array('type' => 'char', 'length' => 36, 'not null' => true, 'description' => 'UUID of the response'),
+                'uri' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'UUID to the answer notice'),
+                'question_id' => array('type' => 'char', 'length' => 36, 'not null' => true, 'description' => 'UUID of question being responded to'),
+                'votes' => array('type' => 'int'),
+                'best'  => array('type' => 'int'),
+                'profile_id' => array('type' => 'int'),
+                'created' => array('type' => 'datetime', 'not null' => true),
+            ),
+            'primary key' => array('id'),
+            'unique keys' => array(
+                'question_uri_key' => array('uri'),
+                'question_id_profile_id_key' => array('question_id', 'profile_id'),
+            ),
+            'indexes' => array(
+                'profile_id_question_Id_index' => array('profile_id', 'question_id'),
+            )
+        );
+    }
+
+    /**
+     * Get an answer based on a notice
+     *
+     * @param Notice $notice Notice to check for
+     *
+     * @return Answer found response or null
+     */
+    function getByNotice($notice)
+    {
+        return self::staticGet('uri', $notice->uri);
+    }
+
+    /**
+     * Get the notice that belongs to this answer
+     *
+     * @return Notice
+     */
+    function getNotice()
+    {
+        return Notice::staticGet('uri', $this->uri);
+    }
+
+    function bestUrl()
+    {
+        return $this->getNotice()->bestUrl();
+    }
+
+    /**
+     * Get the Question this is an answer to
+     *
+     * @return Question
+     */
+    function getQuestion()
+    {
+        return Question::staticGet('id', $this->question_id);
+    }
+    /**
+     * Save a new answer notice
+     *
+     * @param Profile  $profile
+     * @param Question $Question the question being answered
+     * @param array
+     *
+     * @return Notice saved notice
+     */
+    static function saveNew($profile, $question, $options=null)
+    {
+        if (empty($options)) {
+            $options = array();
+        }
+
+        $a = new Answer();
+        $a->id          = UUID::gen();
+        $a->profile_id  = $profile->id;
+        $a->question_id = $question->id;
+        $a->created     = common_sql_now();
+        $a->uri         = common_local_url(
+            'showanswer',
+            array('id' => $pr->id)
+        );
+        
+        common_log(LOG_DEBUG, "Saving answer: $pr->id $pr->uri");
+        $a->insert();
+
+        // TRANS: Notice content answering a question.
+        // TRANS: %s is the answer
+        $content  = sprintf(
+            _m('answered "%s"'),
+            $answer
+        );
+        $link = '<a href="' . htmlspecialchars($question->uri) . '">' . htmlspecialchars($answer) . '</a>';
+        // TRANS: Rendered version of the notice content answering a question.
+        // TRANS: %s a link to the question with the chosen option as link description.
+        $rendered = sprintf(_m('answered "%s"'), $link);
+
+        $tags    = array();
+        $replies = array();
+
+        $options = array_merge(array('urls' => array(),
+                                     'rendered' => $rendered,
+                                     'tags' => $tags,
+                                     'replies' => $replies,
+                                     'reply_to' => $question->getNotice()->id,
+                                     'object_type' => QuestionAndAnswer::ANSWER_OBJECT),
+                               $options);
+
+        if (!array_key_exists('uri', $options)) {
+            $options['uri'] = $pr->uri;
+        }
+
+        $saved = Notice::saveNew($profile->id,
+                                 $content,
+                                 array_key_exists('source', $options) ?
+                                 $options['source'] : 'web',
+                                 $options);
+
+        return $saved;
+    }
+}
diff --git a/plugins/QuestionAndAnswer/classes/Question.php b/plugins/QuestionAndAnswer/classes/Question.php
new file mode 100644 (file)
index 0000000..95ceeb4
--- /dev/null
@@ -0,0 +1,240 @@
+<?php
+/**
+ * Data class to mark a notice as a question
+ *
+ * PHP version 5
+ *
+ * @category QuestionAndAnswer
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * For storing a question
+ *
+ * @category QuestionAndAnswer
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * @see      DB_DataObject
+ */
+
+class Question extends Managed_DataObject
+{
+    public $__table = 'question'; // table name
+    public $id;          // char(36) primary key not null -> UUID
+    public $uri;
+    public $profile_id;  // int -> profile.id
+    public $title;       // text
+    public $description; // text
+    public $created;     // datetime
+
+    /**
+     * Get an instance by key
+     *
+     * This is a utility method to get a single instance with a given key value.
+     *
+     * @param string $k Key to use to lookup (usually 'user_id' for this class)
+     * @param mixed  $v Value to lookup
+     *
+     * @return User_greeting_count object found, or null for no hits
+     *
+     */
+    function staticGet($k, $v=null)
+    {
+        return Memcached_DataObject::staticGet('Question', $k, $v);
+    }
+
+    /**
+     * Get an instance by compound key
+     *
+     * This is a utility method to get a single instance with a given set of
+     * key-value pairs. Usually used for the primary key for a compound key; thus
+     * the name.
+     *
+     * @param array $kv array of key-value mappings
+     *
+     * @return Bookmark object found, or null for no hits
+     *
+     */
+    function pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('Question', $kv);
+    }
+
+    /**
+     * The One True Thingy that must be defined and declared.
+     */
+    public static function schemaDef()
+    {
+        return array(
+            'description' => 'Per-notice question data for QuestionAndAnswer plugin',
+            'fields' => array(
+                'id' => array('type' => 'char', 'length' => 36, 'not null' => true, 'description' => 'UUID'),
+                'uri' => array('type' => 'varchar', 'length' => 255, 'not null' => true),
+                'profile_id' => array('type' => 'int'),
+                'title' => array('type' => 'text'),
+                'description' => array('type' => 'text'),
+                'created' => array('type' => 'datetime', 'not null' => true),
+            ),
+            'primary key' => array('id'),
+            'unique keys' => array(
+                'question_uri_key' => array('uri'),
+            ),
+        );
+    }
+
+    /**
+     * Get a question based on a notice
+     *
+     * @param Notice $notice Notice to check for
+     *
+     * @return Question found question or null
+     */
+    function getByNotice($notice)
+    {
+        return self::staticGet('uri', $notice->uri);
+    }
+
+    function getNotice()
+    {
+        return Notice::staticGet('uri', $this->uri);
+    }
+
+    function bestUrl()
+    {
+        return $this->getNotice()->bestUrl();
+    }
+
+    /**
+     * Get the answer from a particular user to this question, if any.
+     *
+     * @param Profile $profile
+     *
+     * @return Answer object or null
+     */
+    function getAnswer(Profile $profile)
+    {
+        $a = new Answer();
+        $a->question_id = $this->id;
+        $a->profile_id = $profile->id;
+        $a->find();
+        if ($a->fetch()) {
+            return $a;
+        } else {
+            return null;
+        }
+    }
+
+    function countAnswers()
+    {
+        $a = new Answer();
+        
+        $a->question_id = $this->id;
+        return $a-count();
+    }
+
+    /**
+     * Save a new question notice
+     *
+     * @param Profile $profile
+     * @param string  $question
+     * @param string  $title
+     * @param string  $description
+     * @param array   $option // and whatnot
+     *
+     * @return Notice saved notice
+     */
+    static function saveNew($profile, $question, $title, $description, $options = array())
+    {
+        $q = new Question();
+
+        $q->id          = UUID::gen();
+        $q->profile_id  = $profile->id;
+        $q->title       = $title;
+        $q->description = $description;
+
+        if (array_key_exists('created', $options)) {
+            $q->created = $options['created'];
+        } else {
+            $q->created = common_sql_now();
+        }
+
+        if (array_key_exists('uri', $options)) {
+            $q->uri = $options['uri'];
+        } else {
+            $q->uri = common_local_url(
+                'showquestion',
+                array('id' => $q->id)
+            );
+        }
+
+        common_log(LOG_DEBUG, "Saving question: $q->id $q->uri");
+        $q->insert();
+
+        // TRANS: Notice content creating a question.
+        // TRANS: %1$s is the title of the question, %2$s is a link to the question.
+        $content  = sprintf(
+            _m('question: %1$s %2$s'),
+            $title,
+            $q->uri
+        );
+        
+        $link = '<a href="' . htmlspecialchars($q->uri) . '">' . htmlspecialchars($title) . '</a>';
+        // TRANS: Rendered version of the notice content creating a question.
+        // TRANS: %s a link to the question as link description.
+        $rendered = sprintf(_m('Question: %s'), $link);
+
+        $tags    = array('question');
+        $replies = array();
+
+        $options = array_merge(
+            array(
+                'urls'        => array(),
+                'rendered'    => $rendered,
+                'tags'        => $tags,
+                'replies'     => $replies,
+                'object_type' => QuestionAndAnswerPlugin::QUESTION_OBJECT
+            ),
+            $options
+        );
+
+        if (!array_key_exists('uri', $options)) {
+            $options['uri'] = $p->uri;
+        }
+
+        $saved = Notice::saveNew(
+            $profile->id,
+            $content,
+            array_key_exists('source', $options) ?
+            $options['source'] : 'web',
+            $options
+        );
+
+        return $saved;
+    }
+}
diff --git a/plugins/QuestionAndAnswer/css/questionandanswer.css b/plugins/QuestionAndAnswer/css/questionandanswer.css
new file mode 100644 (file)
index 0000000..4701b5a
--- /dev/null
@@ -0,0 +1 @@
+/* stubb for q&a css */
diff --git a/plugins/QuestionAndAnswer/lib/answerform.php b/plugins/QuestionAndAnswer/lib/answerform.php
new file mode 100644 (file)
index 0000000..d093863
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Form for answering a question
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  QuestionAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    // This check helps protect against security problems;
+    // your code file can't be executed directly from the web.
+    exit(1);
+}
+
+/**
+ * Form to add a new answer to a question
+ *
+ * @category  QuestionAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+class AnswerForm extends Form
+{
+    protected $question;
+
+    /**
+     * Construct a new answer form
+     *
+     * @param Question $question
+     * @param HTMLOutputter $out output channel
+     *
+     * @return void
+     */
+    function __construct(Question $question, HTMLOutputter $out)
+    {
+        parent::__construct($out);
+        $this->question = $question;
+    }
+
+    /**
+     * ID of the form
+     *
+     * @return int ID of the form
+     */
+    function id()
+    {
+        return 'answer-form';
+    }
+
+    /**
+     * class of the form
+     *
+     * @return string class of the form
+     */
+    function formClass()
+    {
+        return 'form_settings ajax';
+    }
+
+    /**
+     * Action of the form
+     *
+     * @return string URL of the action
+     */
+    function action()
+    {
+        return common_local_url('answer', array('id' => $this->question->id));
+    }
+
+    /**
+     * Data elements of the form
+     *
+     * @return void
+     */
+    function formData()
+    {
+        $question = $this->question;
+        $out = $this->out;
+        $id = "question-" . $question->id;
+
+        $out->element('p', 'answer', $question->question);
+        $out->element('input', array('type' => 'text', 'name' => 'answer'));
+        
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+    function formActions()
+    {
+        // TRANS: Button text for submitting a poll response.
+        $this->out->submit('submit', _m('BUTTON', 'Submit'));
+    }
+}
+
diff --git a/plugins/QuestionAndAnswer/lib/questionform.php b/plugins/QuestionAndAnswer/lib/questionform.php
new file mode 100644 (file)
index 0000000..5892464
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Form for adding a new question
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  QuestonAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@copley.name>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    // This check helps protect against security problems;
+    // your code file can't be executed directly from the web.
+    exit(1);
+}
+
+/**
+ * Form to add a new question
+ *
+ * @category  QuestionAndAnswer
+ * @package   StatusNet
+ * @author    Zach Copley <zach@copley.name>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+class NewQuestionForm extends Form
+{
+    protected $question = null;
+
+    /**
+     * Construct a new question form
+     *
+     * @param HTMLOutputter $out output channel
+     *
+     * @return void
+     */
+    function __construct($out=null, $question=null, $options=null)
+    {
+        parent::__construct($out);
+    }
+
+    /**
+     * ID of the form
+     *
+     * @return int ID of the form
+     */
+    function id()
+    {
+        return 'newquestion-form';
+    }
+
+    /**
+     * class of the form
+     *
+     * @return string class of the form
+     */
+    function formClass()
+    {
+        return 'form_settings ajax-notice';
+    }
+
+    /**
+     * Action of the form
+     *
+     * @return string URL of the action
+     */
+    function action()
+    {
+        return common_local_url('newquestion');
+    }
+
+    /**
+     * Data elements of the form
+     *
+     * @return void
+     */
+    function formData()
+    {
+        $this->out->elementStart('fieldset', array('id' => 'newquestion-data'));
+        $this->out->elementStart('ul', 'form_data');
+
+        $this->li();
+        $this->out->input('question',
+                          // TRANS: Field label on the page to create a question.
+                          _m('Question'),
+                          $this->question,
+                          // TRANS: Field title on the page to create a question.
+                          _m('What is your question?'));
+        $this->unli();
+
+        $this->out->elementEnd('ul');
+        $this->out->elementEnd('fieldset');
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+    function formActions()
+    {
+        // TRANS: Button text for saving a new question.
+        $this->out->submit('submit', _m('BUTTON', 'Save'));
+    }
+}