]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/threadednoticelist.php
116167ccef975ff35a464434d4d6c379ca37895c
[quix0rs-gnu-social.git] / lib / threadednoticelist.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * widget for displaying a list of notices
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  UI
23  * @package   StatusNet
24  * @author    Brion Vibber <brion@status.net>
25  * @copyright 2011 StatusNet, Inc.
26  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27  * @link      http://status.net/
28  */
29
30 if (!defined('STATUSNET') && !defined('LACONICA')) {
31     exit(1);
32 }
33
34 /**
35  * widget for displaying a list of notices
36  *
37  * There are a number of actions that display a list of notices, in
38  * reverse chronological order. This widget abstracts out most of the
39  * code for UI for notice lists. It's overridden to hide some
40  * data for e.g. the profile page.
41  *
42  * @category UI
43  * @package  StatusNet
44  * @author   Evan Prodromou <evan@status.net>
45  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
46  * @link     http://status.net/
47  * @see      Notice
48  * @see      NoticeListItem
49  * @see      ProfileNoticeList
50  */
51
52 class ThreadedNoticeList extends NoticeList
53 {
54     /**
55      * show the list of notices
56      *
57      * "Uses up" the stream by looping through it. So, probably can't
58      * be called twice on the same list.
59      *
60      * @return int count of notices listed.
61      */
62
63     function show()
64     {
65         $this->out->elementStart('div', array('id' =>'notices_primary'));
66         $this->out->element('h2', null, _('Notices'));
67         $this->out->elementStart('ol', array('class' => 'notices threaded-notices xoxo'));
68
69         $cnt = 0;
70         $conversations = array();
71         while ($this->notice->fetch() && $cnt <= NOTICES_PER_PAGE) {
72             $cnt++;
73
74             if ($cnt > NOTICES_PER_PAGE) {
75                 break;
76             }
77
78             // Collapse repeats into their originals...
79             $notice = $this->notice;
80             if ($notice->repeat_of) {
81                 $orig = Notice::staticGet('id', $notice->repeat_of);
82                 if ($orig) {
83                     $notice = $orig;
84                 }
85             }
86             $convo = $notice->conversation;
87             if (!empty($conversations[$convo])) {
88                 // Seen this convo already -- skip!
89                 continue;
90             }
91             $conversations[$convo] = true;
92
93             // Get the convo's root notice
94             // @fixme stream goes in wrong direction, this needs sane caching
95             //$notice = Notice::conversationStream($convo, 0, 1);
96             //$notice->fetch();
97             $root = new Notice();
98             $root->conversation = $notice->conversation;
99             $root->orderBy('CREATED');
100             $root->limit(1);
101             $root->find(true);
102
103             try {
104                 $item = $this->newListItem($root);
105                 $item->show();
106             } catch (Exception $e) {
107                 // we log exceptions and continue
108                 common_log(LOG_ERR, $e->getMessage());
109                 continue;
110             }
111         }
112
113         $this->out->elementEnd('ol');
114         $this->out->elementEnd('div');
115
116         return $cnt;
117     }
118
119     /**
120      * returns a new list item for the current notice
121      *
122      * Recipe (factory?) method; overridden by sub-classes to give
123      * a different list item class.
124      *
125      * @param Notice $notice the current notice
126      *
127      * @return NoticeListItem a list item for displaying the notice
128      */
129
130     function newListItem($notice)
131     {
132         return new ThreadedNoticeListItem($notice, $this->out);
133     }
134 }
135
136 /**
137  * widget for displaying a single notice
138  *
139  * This widget has the core smarts for showing a single notice: what to display,
140  * where, and under which circumstances. Its key method is show(); this is a recipe
141  * that calls all the other show*() methods to build up a single notice. The
142  * ProfileNoticeListItem subclass, for example, overrides showAuthor() to skip
143  * author info (since that's implicit by the data in the page).
144  *
145  * @category UI
146  * @package  StatusNet
147  * @author   Evan Prodromou <evan@status.net>
148  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
149  * @link     http://status.net/
150  * @see      NoticeList
151  * @see      ProfileNoticeListItem
152  */
153
154 class ThreadedNoticeListItem extends NoticeListItem
155 {
156     const INITIAL_ITEMS = 3;
157
158     function showContext()
159     {
160         // Silence!
161     }
162
163     /**
164      * finish the notice
165      *
166      * Close the last elements in the notice list item
167      *
168      * @return void
169      */
170
171     function showEnd()
172     {
173         if (!$this->repeat) {
174             $notice = Notice::conversationStream($this->notice->conversation, 0, self::INITIAL_ITEMS + 2);
175             $notices = array();
176             $cnt = 0;
177             $moreCutoff = null;
178             while ($notice->fetch()) {
179                 if ($notice->id == $this->notice->id) {
180                     // Skip!
181                     continue;
182                 }
183                 $cnt++;
184                 if ($cnt > self::INITIAL_ITEMS) {
185                     // boo-yah
186                     $moreCutoff = clone($notice);
187                     break;
188                 }
189                 $notices[] = clone($notice); // *grumble* inefficient as hell
190             }
191
192             $this->out->elementStart('ul', 'notices threaded-replies xoxo');
193
194             $item = new ThreadedNoticeListFavesItem($this->notice, $this->out);
195             $hasFaves = $item->show();
196
197             $item = new ThreadedNoticeListRepeatsItem($this->notice, $this->out);
198             $hasRepeats = $item->show();
199
200             if ($notices) {
201                 if ($moreCutoff) {
202                     $item = new ThreadedNoticeListMoreItem($moreCutoff, $this->out);
203                     $item->show();
204                 }
205                 foreach (array_reverse($notices) as $notice) {
206                     $item = new ThreadedNoticeListSubItem($notice, $this->out);
207                     $item->show();
208                 }
209             }
210             if ($notices || $hasFaves || $hasRepeats) {
211                 // @fixme do a proper can-post check that's consistent
212                 // with the JS side
213                 if (common_current_user()) {
214                     $item = new ThreadedNoticeListReplyItem($this->notice, $this->out);
215                     $item->show();
216                 }
217             }
218             $this->out->elementEnd('ul');
219         }
220
221         parent::showEnd();
222     }
223 }
224
225 class ThreadedNoticeListSubItem extends NoticeListItem
226 {
227
228     function avatarSize()
229     {
230         return AVATAR_STREAM_SIZE; // @fixme would like something in between
231     }
232
233     function showNoticeLocation()
234     {
235         //
236     }
237
238     function showNoticeSource()
239     {
240         //
241     }
242
243     function showContext()
244     {
245         //
246     }
247
248     function showEnd()
249     {
250         $item = new ThreadedNoticeListInlineFavesItem($this->notice, $this->out);
251         $hasFaves = $item->show();
252         parent::showEnd();
253     }
254 }
255
256 /**
257  * Placeholder for loading more replies...
258  */
259 class ThreadedNoticeListMoreItem extends NoticeListItem
260 {
261
262     /**
263      * recipe function for displaying a single notice.
264      *
265      * This uses all the other methods to correctly display a notice. Override
266      * it or one of the others to fine-tune the output.
267      *
268      * @return void
269      */
270
271     function show()
272     {
273         $this->showStart();
274         $this->showMiniForm();
275         $this->showEnd();
276     }
277
278     /**
279      * start a single notice.
280      *
281      * @return void
282      */
283
284     function showStart()
285     {
286         $this->out->elementStart('li', array('class' => 'notice-reply-comments'));
287     }
288
289     function showMiniForm()
290     {
291         $id = $this->notice->conversation;
292         $url = common_local_url('conversation', array('id' => $id)) . '#notice-' . $this->notice->id;
293
294         $notice = new Notice();
295         $notice->conversation = $id;
296         $n = $notice->count() - 1;
297         $msg = sprintf(_m('Show %d reply', 'Show all %d replies', $n), $n);
298
299         $this->out->element('a', array('href' => $url), $msg);
300     }
301 }
302
303
304 /**
305  * Placeholder for reply form...
306  * Same as get added at runtime via SN.U.NoticeInlineReplyPlaceholder
307  */
308 class ThreadedNoticeListReplyItem extends NoticeListItem
309 {
310
311     /**
312      * recipe function for displaying a single notice.
313      *
314      * This uses all the other methods to correctly display a notice. Override
315      * it or one of the others to fine-tune the output.
316      *
317      * @return void
318      */
319
320     function show()
321     {
322         $this->showStart();
323         $this->showMiniForm();
324         $this->showEnd();
325     }
326
327     /**
328      * start a single notice.
329      *
330      * @return void
331      */
332
333     function showStart()
334     {
335         $this->out->elementStart('li', array('class' => 'notice-reply-placeholder'));
336     }
337
338     function showMiniForm()
339     {
340         $this->out->element('input', array('class' => 'placeholder',
341                                            'value' => _('Write a reply...')));
342     }
343 }
344
345 /**
346  * Placeholder for showing faves...
347  */
348 abstract class NoticeListActorsItem extends NoticeListItem
349 {
350     /**
351      * @return array of profile IDs
352      */
353     abstract function getProfiles();
354
355     abstract function getListMessage($count, $you);
356
357     function show()
358     {
359         $links = array();
360         $you = false;
361         $cur = common_current_user();
362         foreach ($this->getProfiles() as $id) {
363             if ($cur && $cur->id == $id) {
364                 $you = true;
365                 array_unshift($links, _m('FAVELIST', 'You'));
366             } else {
367                 $profile = Profile::staticGet('id', $id);
368                 if ($profile) {
369                     $links[] = sprintf('<a href="%s" title="%s">%s</a>',
370                                        htmlspecialchars($profile->profileurl),
371                                        htmlspecialchars($profile->getBestName()),
372                                        htmlspecialchars($profile->nickname));
373                 }
374             }
375         }
376
377         if ($links) {
378             $count = count($links);
379             $msg = $this->getListMessage($count, $you);
380             $out = sprintf($msg, $this->magicList($links));
381
382             $this->showStart();
383             $this->out->raw($out);
384             $this->showEnd();
385             return $count;
386         } else {
387             return 0;
388         }
389     }
390
391     function magicList($items)
392     {
393         if (count($items) == 0) {
394             return '';
395         } else if (count($items) == 1) {
396             return $items[0];
397         } else {
398             $first = array_slice($items, 0, -1);
399             $last = array_slice($items, -1, 1);
400             // TRANS For building a list such as "You, bob, mary and 5 others have favored this notice".
401             return sprintf(_m('FAVELIST', '%1$s and %2$s'), implode(', ', $first), implode(', ', $last));
402         }
403     }
404 }
405
406 /**
407  * Placeholder for showing faves...
408  */
409 class ThreadedNoticeListFavesItem extends NoticeListActorsItem
410 {
411     function getProfiles()
412     {
413         // @fixme caching & scalability!
414         $fave = new Fave();
415         $fave->notice_id = $this->notice->id;
416         $fave->find();
417
418         $profiles = array();
419         while ($fave->fetch()) {
420             $profiles[] = $fave->user_id;
421         }
422         return $profiles;
423     }
424
425     function getListMessage($count, $you)
426     {
427         if ($count == 1 && $you) {
428             // darn first person being different from third person!
429             return _m('FAVELIST', 'You have favored this notice.');
430         } else {
431             // if 'you' is the first item,
432             return _m('FAVELIST', '%1$s has favored this notice.', '%1$s have favored this notice.', $count);
433         }
434     }
435
436     function showStart()
437     {
438         $this->out->elementStart('li', array('class' => 'notice-data notice-faves'));
439     }
440
441     function showEnd()
442     {
443         $this->out->elementEnd('li');
444     }
445
446 }
447
448 class ThreadedNoticeListInlineFavesItem extends ThreadedNoticeListFavesItem
449 {
450     function showStart()
451     {
452         $this->out->elementStart('div', array('class' => 'entry-content notice-faves'));
453     }
454
455     function showEnd()
456     {
457         $this->out->elementEnd('div');
458     }
459 }
460
461 /**
462  * Placeholder for showing faves...
463  */
464 class ThreadedNoticeListRepeatsItem extends NoticeListActorsItem
465 {
466     function getProfiles()
467     {
468         // @fixme caching & scalability!
469         $rep = new Notice();
470         $rep->repeat_of = $this->notice->id;
471         $rep->find();
472
473         $profiles = array();
474         while ($rep->fetch()) {
475             $profiles[] = $rep->profile_id;
476         }
477         return $profiles;
478     }
479
480     function getListMessage($count, $you)
481     {
482         if ($count == 1 && $you) {
483             // darn first person being different from third person!
484             return _m('REPEATLIST', 'You have repeated this notice.');
485         } else {
486             // if 'you' is the first item,
487             return _m('REPEATLIST', '%1$s has repeated this notice.', '%1$s have repeated this notice.', $count);
488         }
489     }
490
491     function showStart()
492     {
493         $this->out->elementStart('li', array('class' => 'notice-data notice-repeats'));
494     }
495
496     function showEnd()
497     {
498         $this->out->elementEnd('li');
499     }
500
501 }