]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/threadednoticelist.php
Merge branch '1.0.x' into qna
[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         $notice = new Notice();
288         $notice->conversation = $id;
289         $n = $notice->count() - 1;
290         // TRANS: Link to show replies for a notice.
291         // TRANS: %d is the number of replies to a notice and used for plural.
292         $msg = sprintf(_m('Show reply', 'Show all %d replies', $n), $n);
293
294         $this->out->element('a', array('href' => $url), $msg);
295     }
296 }
297
298 /**
299  * Placeholder for reply form...
300  * Same as get added at runtime via SN.U.NoticeInlineReplyPlaceholder
301  */
302 class ThreadedNoticeListReplyItem extends NoticeListItem
303 {
304     /**
305      * recipe function for displaying a single notice.
306      *
307      * This uses all the other methods to correctly display a notice. Override
308      * it or one of the others to fine-tune the output.
309      *
310      * @return void
311      */
312     function show()
313     {
314         $this->showStart();
315         $this->showMiniForm();
316         $this->showEnd();
317     }
318
319     /**
320      * start a single notice.
321      *
322      * @return void
323      */
324     function showStart()
325     {
326         $this->out->elementStart('li', array('class' => 'notice-reply-placeholder'));
327     }
328
329     function showMiniForm()
330     {
331         $this->out->element('input', array('class' => 'placeholder',
332                                             // TRANS: Field label for reply mini form.
333                                            'value' => _('Write a reply...')));
334     }
335 }
336
337 /**
338  * Placeholder for showing faves...
339  */
340 abstract class NoticeListActorsItem extends NoticeListItem
341 {
342     /**
343      * @return array of profile IDs
344      */
345     abstract function getProfiles();
346
347     abstract function getListMessage($count, $you);
348
349     function show()
350     {
351         $links = array();
352         $you = false;
353         $cur = common_current_user();
354         foreach ($this->getProfiles() as $id) {
355             if ($cur && $cur->id == $id) {
356                 $you = true;
357                 // TRANS: Reference to the logged in user in favourite list.
358                 array_unshift($links, _m('FAVELIST', 'You'));
359             } else {
360                 $profile = Profile::staticGet('id', $id);
361                 if ($profile) {
362                     $links[] = sprintf('<a href="%s" title="%s">%s</a>',
363                                        htmlspecialchars($profile->profileurl),
364                                        htmlspecialchars($profile->getBestName()),
365                                        htmlspecialchars($profile->nickname));
366                 }
367             }
368         }
369
370         if ($links) {
371             $count = count($links);
372             $msg = $this->getListMessage($count, $you);
373             $out = sprintf($msg, $this->magicList($links));
374
375             $this->showStart();
376             $this->out->raw($out);
377             $this->showEnd();
378             return $count;
379         } else {
380             return 0;
381         }
382     }
383
384     function magicList($items)
385     {
386         if (count($items) == 0) {
387             return '';
388         } else if (count($items) == 1) {
389             return $items[0];
390         } else {
391             $first = array_slice($items, 0, -1);
392             $last = array_slice($items, -1, 1);
393             // TRANS: Separator in list of user names like "You, Bob, Mary".
394             $sepataror = _(', ');
395             // TRANS: For building a list such as "You, bob, mary and 5 others have favored this notice".
396             // TRANS: %1$s is a list of users, separated by a separator (default: ", "), %2$s is the last user in the list.
397             return sprintf(_m('FAVELIST', '%1$s and %2$s'), implode($separator, $first), implode($separator, $last));
398         }
399     }
400 }
401
402 /**
403  * Placeholder for showing faves...
404  */
405 class ThreadedNoticeListFavesItem extends NoticeListActorsItem
406 {
407     function getProfiles()
408     {
409         $fave = Fave::byNotice($this->notice->id);
410         $profiles = array();
411         while ($fave->fetch()) {
412             $profiles[] = $fave->user_id;
413         }
414         return $profiles;
415     }
416
417     function getListMessage($count, $you)
418     {
419         if ($count == 1 && $you) {
420             // darn first person being different from third person!
421             // TRANS: List message for notice favoured by logged in user.
422             return _m('FAVELIST', 'You have favored this notice.');
423         } else {
424             // TRANS: List message for favoured notices.
425             // TRANS: %d is the number of users that have favoured a notice.
426             return sprintf(_m(
427                               'FAVELIST',
428                               'One person has favored this notice.',
429                               '%d people have favored this notice.',
430                               $count),
431                            $count);
432         }
433     }
434
435     function showStart()
436     {
437         $this->out->elementStart('li', array('class' => 'notice-data notice-faves'));
438     }
439
440     function showEnd()
441     {
442         $this->out->elementEnd('li');
443     }
444
445 }
446
447 // @todo FIXME: needs documentation.
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         $rep = $this->notice->repeatStream();
469
470         $profiles = array();
471         while ($rep->fetch()) {
472             $profiles[] = $rep->profile_id;
473         }
474         return $profiles;
475     }
476
477     function getListMessage($count, $you)
478     {
479         if ($count == 1 && $you) {
480             // darn first person being different from third person!
481             // TRANS: List message for notice repeated by logged in user.
482             return _m('REPEATLIST', 'You have repeated this notice.');
483         } else {
484             // TRANS: List message for repeated notices.
485             // TRANS: %d is the number of users that have repeated a notice.
486             return sprintf(_m(
487                               'REPEATLIST',
488                               'One person has repeated this notice.',
489                               '%d people have repeated this notice.',
490                               $count),
491                            $count);
492         }
493     }
494
495     function showStart()
496     {
497         $this->out->elementStart('li', array('class' => 'notice-data notice-repeats'));
498     }
499
500     function showEnd()
501     {
502         $this->out->elementEnd('li');
503     }
504 }