]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/QnA/QnAPlugin.php
a586d89b4094c274cab322978327268a3164279e
[quix0rs-gnu-social.git] / plugins / QnA / QnAPlugin.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2011, StatusNet, Inc.
5  *
6  * Microapp plugin for Questions and Answers
7  *
8  * PHP version 5
9  *
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.
14  *
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.
19  *
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/>.
22  *
23  * @category  QnA
24  * @package   StatusNet
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/
29  */
30
31 if (!defined('STATUSNET')) {
32     // This check helps protect against security problems;
33     // your code file can't be executed directly from the web.
34     exit(1);
35 }
36
37 /**
38  * Question and Answer plugin
39  *
40  * @category  Plugin
41  * @package   StatusNet
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/
46  */
47 class QnAPlugin extends MicroAppPlugin
48 {
49     /**
50      * Set up our tables (question and answer)
51      *
52      * @see Schema
53      * @see ColumnDef
54      *
55      * @return boolean hook value; true means continue processing, false means stop.
56      */
57     function onCheckSchema()
58     {
59         $schema = Schema::get();
60
61         $schema->ensureTable('qna_question', QnA_Question::schemaDef());
62         $schema->ensureTable('qna_answer', QnA_Answer::schemaDef());
63         $schema->ensureTable('qna_vote', QnA_Vote::schemaDef());
64
65         return true;
66     }
67
68     /**
69      * Load related modules when needed
70      *
71      * @param string $cls Name of the class to be loaded
72      *
73      * @return boolean hook value; true means continue processing, false means stop.
74      */
75     function onAutoload($cls)
76     {
77         $dir = dirname(__FILE__);
78
79         switch ($cls)
80         {
81         case 'QnanewquestionAction':
82         case 'QnanewanswerAction':
83         case 'QnashowquestionAction':
84         case 'QnashowanswerAction':
85         case 'QnareviseanswerAction':
86         case 'QnavoteAction':
87             include_once $dir . '/actions/'
88                 . strtolower(mb_substr($cls, 0, -6)) . '.php';
89             return false;
90         case 'QnaquestionForm':
91         case 'QnashowanswerForm':
92         case 'QnanewanswerForm':
93         case 'QnareviseanswerForm':
94         case 'QnavoteForm':
95         case 'AnswerNoticeListItem':
96             include_once $dir . '/lib/' . strtolower($cls).'.php';
97             break;
98         case 'QnA_Question':
99         case 'QnA_Answer':
100         case 'QnA_Vote':
101             include_once $dir . '/classes/' . $cls.'.php';
102             return false;
103             break;
104         default:
105             return true;
106         }
107     }
108
109     /**
110      * Map URLs to actions
111      *
112      * @param Net_URL_Mapper $m path-to-action mapper
113      *
114      * @return boolean hook value; true means continue processing, false means stop.
115      */
116
117     function onRouterInitialized($m)
118     {
119         $UUIDregex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';
120
121         $m->connect(
122             'main/qna/newquestion',
123             array('action' => 'qnanewquestion')
124         );
125         $m->connect(
126             'main/qna/newanswer',
127             array('action' => 'qnanewanswer')
128         );
129         $m->connect(
130             'main/qna/reviseanswer',
131             array('action' => 'qnareviseanswer')
132         );
133         $m->connect(
134             'question/vote/:id',
135             array('action' => 'qnavote', 'type' => 'question'),
136             array('id' => $UUIDregex)
137         );
138         $m->connect(
139             'question/:id',
140             array('action' => 'qnashowquestion'),
141             array('id' => $UUIDregex)
142         );
143         $m->connect(
144             'answer/vote/:id',
145             array('action' => 'qnavote', 'type' => 'answer'),
146             array('id' => $UUIDregex)
147         );
148         $m->connect(
149             'answer/:id',
150             array('action' => 'qnashowanswer'),
151             array('id' => $UUIDregex)
152         );
153
154         return true;
155     }
156
157     function onPluginVersion(&$versions)
158     {
159         $versions[] = array(
160             'name'        => 'QnA',
161             'version'     => STATUSNET_VERSION,
162             'author'      => 'Zach Copley',
163             'homepage'    => 'http://status.net/wiki/Plugin:QnA',
164             'description' =>
165              _m('Question and Answers micro-app.')
166         );
167         return true;
168     }
169
170     function appTitle() {
171         return _m('Question');
172     }
173
174     function tag() {
175         return 'question';
176     }
177
178     function types() {
179         return array(
180             QnA_Question::OBJECT_TYPE,
181             QnA_Answer::OBJECT_TYPE
182         );
183     }
184
185     /**
186      * Given a parsed ActivityStreams activity, save it into a notice
187      * and other data structures.
188      *
189      * @param Activity $activity
190      * @param Profile $actor
191      * @param array $options=array()
192      *
193      * @return Notice the resulting notice
194      */
195     function saveNoticeFromActivity($activity, $actor, $options=array())
196     {
197         if (count($activity->objects) != 1) {
198             throw new Exception('Too many activity objects.');
199         }
200
201         $questionObj = $activity->objects[0];
202
203         if ($questinoObj->type != QnA_Question::OBJECT_TYPE) {
204             throw new Exception('Wrong type for object.');
205         }
206
207         $notice = null;
208
209         switch ($activity->verb) {
210         case ActivityVerb::POST:
211             $notice = QnA_Question::saveNew(
212                 $actor,
213                 $questionObj->title,
214                 $questionObj->summary,
215                 $options
216             );
217             break;
218         case Answer::ObjectType:
219             $question = QnA_Question::staticGet('uri', $questionObj->id);
220             if (empty($question)) {
221                 // FIXME: save the question
222                 throw new Exception("Answer to unknown question.");
223             }
224             $notice = QnA_Answer::saveNew($actor, $question, $options);
225             break;
226         default:
227             throw new Exception("Unknown object type received by QnA Plugin");
228         }
229
230         return $notice;
231     }
232
233     /**
234      * Turn a Notice into an activity object
235      *
236      * @param Notice $notice
237      *
238      * @return ActivityObject
239      */
240
241     function activityObjectFromNotice($notice)
242     {
243         $question = null;
244
245         switch ($notice->object_type) {
246         case QnA_Question::OBJECT_TYPE:
247             $question = QnA_Question::fromNotice($notice);
248             break;
249         case QnA_Answer::OBJECT_TYPE:
250             $answer   = QnA_Answer::fromNotice($notice);
251             $question = $answer->getQuestion();
252             break;
253         }
254
255         if (empty($question)) {
256             throw new Exception("Unknown object type.");
257         }
258
259         $notice = $question->getNotice();
260
261         if (empty($notice)) {
262             throw new Exception("Unknown question notice.");
263         }
264
265         $obj = new ActivityObject();
266
267         $obj->id      = $question->uri;
268         $obj->type    = QnA_Question::OBJECT_TYPE;
269         $obj->title   = $question->title;
270         $obj->link    = $notice->bestUrl();
271
272         // XXX: probably need other stuff here
273
274         return $obj;
275     }
276
277     /**
278      * Change the verb on Answer notices
279      *
280      * @param Notice $notice
281      *
282      * @return ActivityObject
283      */
284
285     function onEndNoticeAsActivity($notice, &$act) {
286         switch ($notice->object_type) {
287         case Answer::NORMAL:
288         case Answer::ANONYMOUS:
289             $act->verb = $notice->object_type;
290             break;
291         }
292         return true;
293     }
294
295     /**
296      * Output our CSS class for QnA notice list elements
297      *
298      * @param NoticeListItem $nli The item being shown
299      *
300      * @return boolean hook value
301      */
302
303     function onStartOpenNoticeListItemElement($nli)
304     {
305         $type = $nli->notice->object_type;
306
307         switch($type)
308         {
309         case QnA_Question::OBJECT_TYPE:
310             $id = (empty($nli->repeat)) ? $nli->notice->id : $nli->repeat->id;
311             $nli->out->elementStart(
312                 'li', array(
313                     'class' => 'hentry notice question',
314                     'id'    => 'notice-' . $id
315                 )
316             );
317             Event::handle('EndOpenNoticeListItemElement', array($nli));
318             return false;
319             break;
320         case QnA_Answer::OBJECT_TYPE:
321             $id = (empty($nli->repeat)) ? $nli->notice->id : $nli->repeat->id;
322
323             $cls = array('hentry', 'notice', 'answer');
324
325             $answer = QnA_Answer::staticGet('uri', $notice->uri);
326
327             if (!empty($answer) && !empty($answer->best)) {
328                 $cls[] = 'best';
329             }
330
331             $nli->out->elementStart(
332                 'li',
333                 array(
334                     'class' => implode(' ', $cls),
335                     'id'    => 'notice-' . $id
336                 )
337             );
338             Event::handle('EndOpenNoticeListItemElement', array($nli));
339             return false;
340             break;
341         default:
342             return true;
343         }
344
345         return true;
346     }
347
348     /**
349      * Custom HTML output for our notices
350      *
351      * @param Notice $notice
352      * @param HTMLOutputter $out
353      */
354     
355     function showNotice($notice, $out)
356     {
357         switch ($notice->object_type) {
358         case QnA_Question::OBJECT_TYPE:
359             return $this->showNoticeQuestion($notice, $out);
360         case QnA_Answer::OBJECT_TYPE:
361             return $this->showNoticeAnswer($notice, $out);
362         default:
363             // TRANS: Exception thrown when performing an unexpected action on a question.
364             // TRANS: %s is the unpexpected object type.
365             throw new Exception(
366                 sprintf(
367                     _m('Unexpected type for QnA plugin: %s.'),
368                     $notice->object_type
369                 )
370             );
371         }
372     }
373
374     function showNoticeQuestion($notice, $out)
375     {
376         $user = common_current_user();
377
378         // @hack we want regular rendering, then just add stuff after that
379         $nli = new NoticeListItem($notice, $out);
380         $nli->showNotice();
381
382         $out->elementStart('div', array('class' => 'entry-content question-desciption'));
383
384         $question = QnA_Question::getByNotice($notice);
385
386         if (!empty($question)) {
387
388             $short = $this->shorten($question->description, $notice);
389             $out->raw($short);
390
391             // Don't prompt user for an answer if the question is closed or
392             // the current user posed the question in the first place
393             if (empty($question->closed)) {
394                 if (!empty($user)) {
395                     $profile = $user->getProfile();
396                     $answer = $question->getAnswer($profile);
397                     if (!$answer) {
398                         $form = new QnanewanswerForm($question, $out);
399                         $form->show();
400                     }
401                 }
402             } else {
403                 $out->element('span', 'closed', _m('This question is closed.'));
404             }
405         } else {
406             $out->text(_m('Question data is missing.'));
407         }
408         $out->elementEnd('div');
409
410         // @fixme
411         $out->elementStart('div', array('class' => 'entry-content'));
412     }
413
414     function showNoticeAnswer($notice, $out)
415     {
416         $user = common_current_user();
417         
418         $answer   = QnA_Answer::getByNotice($notice);
419         $question = $answer->getQuestion();
420
421         $nli = new NoticeListItem($notice, $out);
422         $nli->showNotice();
423
424         $out->elementStart('div', array('class' => 'entry-content answer-content'));
425
426         if (!empty($answer)) {
427             $form = new QnashowanswerForm($out, $answer);
428             $form->show();
429         } else {
430             $out->text(_m('Answer data is missing.'));
431         }
432
433         $out->elementEnd('div');
434
435         // @fixme
436         $out->elementStart('div', array('class' => 'entry-content'));
437     }
438
439     static function shorten($content, $notice)
440     {
441         $short = null;
442
443         if (Notice::contentTooLong($content)) {
444             common_debug("content too long");
445             $max = Notice::maxContent();
446             $short = mb_substr($content, 0, $max - 1);
447             $short .= sprintf(
448                 '<a href="%s" rel="more" title="%s">…</a>',
449                 $notice->uri,
450                 _m('more')
451             );
452         } else {
453             $short = $content;
454         }
455
456         return $short;
457     }
458
459     /**
460      * Form for our app
461      *
462      * @param HTMLOutputter $out
463      * @return Widget
464      */
465
466     function entryForm($out)
467     {
468         return new QnaquestionForm($out);
469     }
470
471     /**
472      * When a notice is deleted, clean up related tables.
473      *
474      * @param Notice $notice
475      */
476
477     function deleteRelated($notice)
478     {
479         switch ($notice->object_type) {
480         case QnA_Question::OBJECT_TYPE:
481             common_log(LOG_DEBUG, "Deleting question from notice...");
482             $question = QnA_Question::fromNotice($notice);
483             $question->delete();
484             break;
485         case QnA_Answer::OBJECT_TYPE:
486             common_log(LOG_DEBUG, "Deleting answer from notice...");
487             $answer = QnA_Answer::fromNotice($notice);
488             common_log(LOG_DEBUG, "to delete: $answer->id");
489             $answer->delete();
490             break;
491         default:
492             common_log(LOG_DEBUG, "Not deleting related, wtf...");
493         }
494     }
495
496     function onEndShowScripts($action)
497     {
498         // XXX maybe some cool shiz here
499     }
500
501     function onEndShowStyles($action)
502     {
503         $action->cssLink($this->path('css/qna.css'));
504         return true;
505     }
506 }