3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2011, StatusNet, Inc.
6 * Microapp plugin for Questions and Answers
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 * @category QuestionAndAnswer
25 * @author Zach Copley <zach@status.net>
26 * @copyright 2011 StatusNet, Inc.
27 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
28 * @link http://status.net/
31 if (!defined('STATUSNET')) {
32 // This check helps protect against security problems;
33 // your code file can't be executed directly from the web.
38 * Question and Answer plugin
42 * @author Zach Copley <zach@status.net>
43 * @copyright 2011 StatusNet, Inc.
44 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
45 * @link http://status.net/
47 class QuestionAndAnswerPlugin extends MicroappPlugin
50 * Set up our tables (question and answer)
55 * @return boolean hook value; true means continue processing, false means stop.
57 function onCheckSchema()
59 $schema = Schema::get();
61 $schema->ensureTable('question', Question::schemaDef());
62 $schema->ensureTable('answer', Answer::schemaDef());
68 * Load related modules when needed
70 * @param string $cls Name of the class to be loaded
72 * @return boolean hook value; true means continue processing, false means stop.
74 function onAutoload($cls)
76 $dir = dirname(__FILE__);
80 case 'NewquestionAction':
81 case 'NewanswerAction':
82 case 'ShowquestionAction':
83 case 'ShowanswerAction':
84 include_once $dir . '/actions/'
85 . strtolower(mb_substr($cls, 0, -6)) . '.php';
89 include_once $dir . '/lib/' . strtolower($cls).'.php';
93 include_once $dir . '/classes/' . $cls.'.php';
102 * Map URLs to actions
104 * @param Net_URL_Mapper $m path-to-action mapper
106 * @return boolean hook value; true means continue processing, false means stop.
109 function onRouterInitialized($m)
111 $m->connect('main/question/new',
112 array('action' => 'newquestion'));
113 $m->connect('main/question/answer',
114 array('action' => 'newanswer'));
115 $m->connect('question/:id',
116 array('action' => 'showquestion'),
117 array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
118 $m->connect('answer/:id',
119 array('action' => 'showanswer'),
120 array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
124 function onPluginVersion(&$versions)
127 'name' => 'QuestionAndAnswer',
128 'version' => STATUSNET_VERSION,
129 'author' => 'Zach Copley',
130 'homepage' => 'http://status.net/wiki/Plugin:QuestionAndAnswer',
132 _m('Question and Answers micro-app.')
137 function appTitle() {
138 return _m('Question');
147 Question::OBJECT_TYPE,
153 * Given a parsed ActivityStreams activity, save it into a notice
154 * and other data structures.
156 * @param Activity $activity
157 * @param Profile $actor
158 * @param array $options=array()
160 * @return Notice the resulting notice
162 function saveNoticeFromActivity($activity, $actor, $options=array())
164 if (count($activity->objects) != 1) {
165 throw new Exception('Too many activity objects.');
168 $questionObj = $activity->objects[0];
170 if ($questinoObj->type != Question::OBJECT_TYPE) {
171 throw new Exception('Wrong type for object.');
176 switch ($activity->verb) {
177 case ActivityVerb::POST:
178 $notice = Question::saveNew(
182 // $questionObj->summary,
187 case Answer::ANONYMOUS:
188 $question = Question::staticGet('uri', $questionObj->id);
189 if (empty($question)) {
190 // FIXME: save the question
191 throw new Exception("Answer to unknown question.");
193 $notice = Answer::saveNew($actor, $question, $activity->verb, $options);
196 throw new Exception("Unknown verb for question");
203 * Turn a Notice into an activity object
205 * @param Notice $notice
207 * @return ActivityObject
210 function activityObjectFromNotice($notice)
214 switch ($notice->object_type) {
215 case Question::OBJECT_TYPE:
216 $question = Qeustion::fromNotice($notice);
219 case Answer::ANONYMOUS:
220 $answer = Answer::fromNotice($notice);
221 $question = $answer->getQuestion();
225 if (empty($question)) {
226 throw new Exception("Unknown object type.");
229 $notice = $question->getNotice();
231 if (empty($notice)) {
232 throw new Exception("Unknown question notice.");
235 $obj = new ActivityObject();
237 $obj->id = $question->uri;
238 $obj->type = Question::OBJECT_TYPE;
239 $obj->title = $question->title;
240 $obj->link = $notice->bestUrl();
242 // XXX: probably need other stuff here
248 * Change the verb on Answer notices
250 * @param Notice $notice
252 * @return ActivityObject
255 function onEndNoticeAsActivity($notice, &$act) {
256 switch ($notice->object_type) {
258 case Answer::ANONYMOUS:
259 $act->verb = $notice->object_type;
266 * Custom HTML output for our notices
268 * @param Notice $notice
269 * @param HTMLOutputter $out
272 function showNotice($notice, $out)
274 switch ($notice->object_type) {
275 case Question::OBJECT_TYPE:
276 $this->showQuestionNotice($notice, $out);
279 case Answer::ANONYMOUS:
281 $this->showAnswerNotice($notice, $out);
285 $out->elementStart('div', array('class' => 'question'));
287 $profile = $notice->getProfile();
288 $avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
291 array('src' => ($avatar) ?
292 $avatar->displayUrl() :
293 Avatar::defaultImage(AVATAR_MINI_SIZE),
294 'class' => 'avatar photo bookmark-avatar',
295 'width' => AVATAR_MINI_SIZE,
296 'height' => AVATAR_MINI_SIZE,
297 'alt' => $profile->getBestName()));
299 $out->raw(' '); // avoid for AJAX XML compatibility
301 $out->elementStart('span', 'vcard author'); // hack for belongsOnTimeline; JS needs to be able to find the author
303 array('class' => 'url',
304 'href' => $profile->profileurl,
305 'title' => $profile->getBestName()),
307 $out->elementEnd('span');
310 function showAnswerNotice($notice, $out)
312 $rsvp = Answer::fromNotice($notice);
314 $out->elementStart('div', 'answer');
315 $out->raw($answer->asHTML());
316 $out->elementEnd('div');
320 function showQuestionNotice($notice, $out)
322 $profile = $notice->getProfile();
323 $question = Question::fromNotice($notice);
325 assert(!empty($question));
326 assert(!empty($profile));
328 $out->elementStart('div', 'question-notice');
330 $out->elementStart('h3');
332 if (!empty($question->url)) {
334 array('href' => $question->url,
335 'class' => 'question-title'),
338 $out->text($question->title);
341 if (!empty($question->location)) {
342 $out->elementStart('div', 'question-location');
343 $out->element('strong', null, _('Location: '));
344 $out->element('span', 'location', $question->location);
345 $out->elementEnd('div');
348 if (!empty($question->description)) {
349 $out->elementStart('div', 'question-description');
350 $out->element('strong', null, _('Description: '));
351 $out->element('span', 'description', $question->description);
352 $out->elementEnd('div');
355 $answers = $question->getAnswers();
357 $out->elementStart('div', 'question-answers');
358 $out->element('strong', null, _('Answer: '));
359 $out->element('span', 'question-answer');
363 $out->elementEnd('div');
365 $user = common_current_user();
368 $question = $question->getAnswer($user->getProfile());
370 if (empty($answer)) {
371 $form = new AnswerForm($question, $out);
377 $out->elementEnd('div');
383 * @param HTMLOutputter $out
387 function entryForm($out)
389 return new QuestionForm($out);
393 * When a notice is deleted, clean up related tables.
395 * @param Notice $notice
398 function deleteRelated($notice)
400 switch ($notice->object_type) {
401 case Question::OBJECT_TYPE:
402 common_log(LOG_DEBUG, "Deleting question from notice...");
403 $question = Question::fromNotice($notice);
407 case Answer::ANONYMOUS:
408 common_log(LOG_DEBUG, "Deleting answer from notice...");
409 $answer = Answer::fromNotice($notice);
410 common_log(LOG_DEBUG, "to delete: $answer->id");
414 common_log(LOG_DEBUG, "Not deleting related, wtf...");
418 function onEndShowScripts($action)
420 // XXX maybe some cool shiz here
423 function onEndShowStyles($action)
425 $action->cssLink($this->path('css/questionandanswer.css'));