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