]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/QnA/QnAPlugin.php
plugins onAutoload now only overloads if necessary (extlibs etc.)
[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      * Map URLs to actions
70      *
71      * @param Net_URL_Mapper $m path-to-action mapper
72      *
73      * @return boolean hook value; true means continue processing, false means stop.
74      */
75
76     function onRouterInitialized($m)
77     {
78         $UUIDregex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';
79
80         $m->connect(
81             'main/qna/newquestion',
82             array('action' => 'qnanewquestion')
83         );
84         $m->connect(
85             'answer/qna/closequestion',
86             array('action' => 'qnaclosequestion')
87         );
88         $m->connect(
89             'main/qna/newanswer',
90             array('action' => 'qnanewanswer')
91         );
92         $m->connect(
93             'main/qna/reviseanswer',
94             array('action' => 'qnareviseanswer')
95         );
96         $m->connect(
97             'question/vote/:id',
98             array('action' => 'qnavote', 'type' => 'question'),
99             array('id' => $UUIDregex)
100         );
101         $m->connect(
102             'question/:id',
103             array('action' => 'qnashowquestion'),
104             array('id' => $UUIDregex)
105         );
106         $m->connect(
107             'answer/vote/:id',
108             array('action' => 'qnavote', 'type' => 'answer'),
109             array('id' => $UUIDregex)
110         );
111         $m->connect(
112             'answer/:id',
113             array('action' => 'qnashowanswer'),
114             array('id' => $UUIDregex)
115         );
116
117         return true;
118     }
119
120     function onPluginVersion(&$versions)
121     {
122         $versions[] = array(
123             'name'        => 'QnA',
124             'version'     => STATUSNET_VERSION,
125             'author'      => 'Zach Copley',
126             'homepage'    => 'http://status.net/wiki/Plugin:QnA',
127             'description' =>
128              // TRANS: Plugin description.
129              _m('Question and Answers micro-app.')
130         );
131         return true;
132     }
133
134     function appTitle() {
135         // TRANS: Application title.
136         return _m('TITLE','Question');
137     }
138
139     function tag() {
140         return 'question';
141     }
142
143     function types() {
144         return array(
145             QnA_Question::OBJECT_TYPE,
146             QnA_Answer::OBJECT_TYPE
147         );
148     }
149
150     /**
151      * Given a parsed ActivityStreams activity, save it into a notice
152      * and other data structures.
153      *
154      * @param Activity $activity
155      * @param Profile $actor
156      * @param array $options=array()
157      *
158      * @return Notice the resulting notice
159      */
160     function saveNoticeFromActivity($activity, $actor, $options=array())
161     {
162         if (count($activity->objects) != 1) {
163             // TRANS: Exception thrown when there are too many activity objects.
164             throw new Exception(_m('Too many activity objects.'));
165         }
166
167         $questionObj = $activity->objects[0];
168
169         if ($questionObj->type != QnA_Question::OBJECT_TYPE) {
170             // TRANS: Exception thrown when an incorrect object type is encountered.
171             throw new Exception(_m('Wrong type for object.'));
172         }
173
174         $notice = null;
175
176         switch ($activity->verb) {
177         case ActivityVerb::POST:
178             $notice = QnA_Question::saveNew(
179                 $actor,
180                 $questionObj->title,
181                 $questionObj->summary,
182                 $options
183             );
184             break;
185         case Answer::ObjectType:
186             $question = QnA_Question::getKV('uri', $questionObj->id);
187             if (empty($question)) {
188                 // FIXME: save the question
189                 // TRANS: Exception thrown when answering a non-existing question.
190                 throw new Exception(_m('Answer to unknown question.'));
191             }
192             $notice = QnA_Answer::saveNew($actor, $question, $options);
193             break;
194         default:
195             // TRANS: Exception thrown when an object type is encountered that cannot be handled.
196             throw new Exception(_m('Unknown object type.'));
197         }
198
199         return $notice;
200     }
201
202     /**
203      * Turn a Notice into an activity object
204      *
205      * @param Notice $notice
206      *
207      * @return ActivityObject
208      */
209
210     function activityObjectFromNotice($notice)
211     {
212         $question = null;
213
214         switch ($notice->object_type) {
215         case QnA_Question::OBJECT_TYPE:
216             $question = QnA_Question::fromNotice($notice);
217             break;
218         case QnA_Answer::OBJECT_TYPE:
219             $answer   = QnA_Answer::fromNotice($notice);
220             $question = $answer->getQuestion();
221             break;
222         }
223
224         if (empty($question)) {
225             // TRANS: Exception thrown when an object type is encountered that cannot be handled.
226             throw new Exception(_m('Unknown object type.'));
227         }
228
229         $notice = $question->getNotice();
230
231         if (empty($notice)) {
232             // TRANS: Exception thrown when requesting a non-existing question notice.
233             throw new Exception(_m('Unknown question notice.'));
234         }
235
236         $obj = new ActivityObject();
237
238         $obj->id      = $question->uri;
239         $obj->type    = QnA_Question::OBJECT_TYPE;
240         $obj->title   = $question->title;
241         $obj->link    = $notice->bestUrl();
242
243         // XXX: probably need other stuff here
244
245         return $obj;
246     }
247
248     /**
249      * Output our CSS class for QnA notice list elements
250      *
251      * @param NoticeListItem $nli The item being shown
252      *
253      * @return boolean hook value
254      */
255
256     function onStartOpenNoticeListItemElement($nli)
257     {
258         $type = $nli->notice->object_type;
259
260         switch($type)
261         {
262         case QnA_Question::OBJECT_TYPE:
263             $id = (empty($nli->repeat)) ? $nli->notice->id : $nli->repeat->id;
264             $class = 'hentry notice question';
265             if ($nli->notice->scope != 0 && $nli->notice->scope != 1) {
266                 $class .= ' limited-scope';
267             }
268
269             $question = QnA_Question::getKV('uri', $nli->notice->uri);
270
271             if (!empty($question->closed)) {
272                 $class .= ' closed';
273             }
274
275             $nli->out->elementStart(
276                 'li', array(
277                     'class' => $class,
278                     'id'    => 'notice-' . $id
279                 )
280             );
281             Event::handle('EndOpenNoticeListItemElement', array($nli));
282             return false;
283             break;
284         case QnA_Answer::OBJECT_TYPE:
285             $id = (empty($nli->repeat)) ? $nli->notice->id : $nli->repeat->id;
286
287             $cls = array('hentry', 'notice', 'answer');
288
289             $answer = QnA_Answer::getKV('uri', $nli->notice->uri);
290
291             if (!empty($answer) && !empty($answer->best)) {
292                 $cls[] = 'best';
293             }
294
295             $nli->out->elementStart(
296                 'li',
297                 array(
298                     'class' => implode(' ', $cls),
299                     'id'    => 'notice-' . $id
300                 )
301             );
302             Event::handle('EndOpenNoticeListItemElement', array($nli));
303             return false;
304             break;
305         default:
306             return true;
307         }
308
309         return true;
310     }
311
312     /**
313      * Custom HTML output for our notices
314      *
315      * @param Notice $notice
316      * @param HTMLOutputter $out
317      */
318     function showNotice($notice, $out)
319     {
320         switch ($notice->object_type) {
321         case QnA_Question::OBJECT_TYPE:
322             return $this->showNoticeQuestion($notice, $out);
323         case QnA_Answer::OBJECT_TYPE:
324             return $this->showNoticeAnswer($notice, $out);
325         default:
326             throw new Exception(
327                 // TRANS: Exception thrown when performing an unexpected action on a question.
328                 // TRANS: %s is the unpexpected object type.
329                 sprintf(_m('Unexpected type for QnA plugin: %s.'),
330                         $notice->object_type
331                 )
332             );
333         }
334     }
335
336     function showNoticeQuestion($notice, $out)
337     {
338         $user = common_current_user();
339
340         // @hack we want regular rendering, then just add stuff after that
341         $nli = new NoticeListItem($notice, $out);
342         $nli->showNotice();
343
344         $out->elementStart('div', array('class' => 'entry-content question-description'));
345
346         $question = QnA_Question::getByNotice($notice);
347
348         if (!empty($question)) {
349
350             $form = new QnashowquestionForm($out, $question);
351             $form->show();
352
353         } else {
354             // TRANS: Error message displayed when question data is not present.
355             $out->text(_m('Question data is missing.'));
356         }
357         $out->elementEnd('div');
358
359         // @fixme
360         $out->elementStart('div', array('class' => 'entry-content'));
361     }
362
363     /**
364      * Output the HTML for this kind of object in a list
365      *
366      * @param NoticeListItem $nli The list item being shown.
367      *
368      * @return boolean hook value
369      *
370      * @todo FIXME: WARNING WARNING WARNING this closes a 'div' that is implicitly opened in BookmarkPlugin's showNotice implementation
371      */
372     function onStartShowNoticeItem($nli)
373     {
374         if (!$this->isMyNotice($nli->notice)) {
375             return true;
376         }
377
378         $out = $nli->out;
379         $notice = $nli->notice;
380
381         $this->showNotice($notice, $out);
382
383         $nli->showNoticeLink();
384         $nli->showNoticeSource();
385         $nli->showNoticeLocation();
386         $nli->showContext();
387         $nli->showRepeat();
388
389         $out->elementEnd('div');
390
391         $nli->showNoticeOptions();
392
393         if ($notice->object_type == QnA_Question::OBJECT_TYPE) {
394             $user = common_current_user();
395             $question = QnA_Question::getByNotice($notice);
396
397             if (!empty($user)) {
398                 $profile = $user->getProfile();
399                 $answer = $question->getAnswer($profile);
400
401                 // Output a placeholder input -- clicking on it will
402                 // bring up a real answer form
403
404                 // NOTE: this whole ul is just a placeholder
405                 if (empty($question->closed) && empty($answer)) {
406                     $out->elementStart('ul', 'notices qna-dummy');
407                     $out->elementStart('li', 'qna-dummy-placeholder');
408                     $out->element(
409                         'input',
410                         array(
411                             'class' => 'placeholder',
412                             // TRANS: Placeholder value for a possible answer to a question
413                             // TRANS: by the logged in user.
414                             'value' => _m('Your answer...')
415                         )
416                     );
417                     $out->elementEnd('li');
418                     $out->elementEnd('ul');
419                 }
420             }
421         }
422
423         return false;
424     }
425
426     function showNoticeAnswer($notice, $out)
427     {
428         $user = common_current_user();
429
430         $answer   = QnA_Answer::getByNotice($notice);
431         $question = $answer->getQuestion();
432
433         $nli = new NoticeListItem($notice, $out);
434         $nli->showNotice();
435
436         $out->elementStart('div', array('class' => 'entry-content answer-content'));
437
438         if (!empty($answer)) {
439             $form = new QnashowanswerForm($out, $answer);
440             $form->show();
441         } else {
442             // TRANS: Error message displayed when answer data is not present.
443             $out->text(_m('Answer data is missing.'));
444         }
445
446         $out->elementEnd('div');
447
448         // @todo FIXME
449         $out->elementStart('div', array('class' => 'entry-content'));
450     }
451
452     static function shorten($content, $notice)
453     {
454         $short = null;
455
456         if (Notice::contentTooLong($content)) {
457             common_debug("content too long");
458             $max = Notice::maxContent();
459             // TRANS: Link description for link to full notice text if it is longer than
460             // TRANS: what will be dispplayed.
461             $ellipsis = _m('…');
462             $short = mb_substr($content, 0, $max - 1);
463             $short .= sprintf('<a href="%1$s" rel="more" title="%2$s">%3$s</a>',
464                               $notice->uri,
465                               // TRANS: Title for link that is an ellipsis in English.
466                               _m('more...'),
467                               $ellipsis);
468         } else {
469             $short = $content;
470         }
471
472         return $short;
473     }
474
475     /**
476      * Form for our app
477      *
478      * @param HTMLOutputter $out
479      * @return Widget
480      */
481     function entryForm($out)
482     {
483         return new QnanewquestionForm($out);
484     }
485
486     /**
487      * When a notice is deleted, clean up related tables.
488      *
489      * @param Notice $notice
490      */
491     function deleteRelated($notice)
492     {
493         switch ($notice->object_type) {
494         case QnA_Question::OBJECT_TYPE:
495             common_log(LOG_DEBUG, "Deleting question from notice...");
496             $question = QnA_Question::fromNotice($notice);
497             $question->delete();
498             break;
499         case QnA_Answer::OBJECT_TYPE:
500             common_log(LOG_DEBUG, "Deleting answer from notice...");
501             $answer = QnA_Answer::fromNotice($notice);
502             common_log(LOG_DEBUG, "to delete: $answer->id");
503             $answer->delete();
504             break;
505         default:
506             common_log(LOG_DEBUG, "Not deleting related, wtf...");
507         }
508     }
509
510     function onEndShowScripts($action)
511     {
512         $action->script($this->path('js/qna.js'));
513         return true;
514     }
515
516     function onEndShowStyles($action)
517     {
518         $action->cssLink($this->path('css/qna.css'));
519         return true;
520     }
521 }