]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/threadednoticelist.php
Merge branch '1.0.x' of git://gitorious.org/statusnet/mainline into 1.0.x
[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             $root = $notice->conversationRoot();
95             if ($root) {
96                 $notice = $root;
97             }
98
99             try {
100                 $item = $this->newListItem($notice);
101                 $item->show();
102             } catch (Exception $e) {
103                 // we log exceptions and continue
104                 common_log(LOG_ERR, $e->getMessage());
105                 continue;
106             }
107         }
108
109         $this->out->elementEnd('ol');
110         $this->out->elementEnd('div');
111
112         return $cnt;
113     }
114
115     /**
116      * returns a new list item for the current notice
117      *
118      * Recipe (factory?) method; overridden by sub-classes to give
119      * a different list item class.
120      *
121      * @param Notice $notice the current notice
122      *
123      * @return NoticeListItem a list item for displaying the notice
124      */
125
126     function newListItem($notice)
127     {
128         return new ThreadedNoticeListItem($notice, $this->out);
129     }
130 }
131
132 /**
133  * widget for displaying a single notice
134  *
135  * This widget has the core smarts for showing a single notice: what to display,
136  * where, and under which circumstances. Its key method is show(); this is a recipe
137  * that calls all the other show*() methods to build up a single notice. The
138  * ProfileNoticeListItem subclass, for example, overrides showAuthor() to skip
139  * author info (since that's implicit by the data in the page).
140  *
141  * @category UI
142  * @package  StatusNet
143  * @author   Evan Prodromou <evan@status.net>
144  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
145  * @link     http://status.net/
146  * @see      NoticeList
147  * @see      ProfileNoticeListItem
148  */
149
150 class ThreadedNoticeListItem extends NoticeListItem
151 {
152     function initialItems()
153     {
154         return 3;
155     }
156
157     function showContext()
158     {
159         // Silence!
160     }
161
162     /**
163      * finish the notice
164      *
165      * Close the last elements in the notice list item
166      *
167      * @return void
168      */
169
170     function showEnd()
171     {
172         $max = $this->initialItems();
173         if (!$this->repeat) {
174             $notice = Notice::conversationStream($this->notice->conversation, 0, $max + 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 > $max) {
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('conversationreplies', array('id' => $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         $fave = Fave::byNotice($this->notice->id);
414         $profiles = array();
415         while ($fave->fetch()) {
416             $profiles[] = $fave->user_id;
417         }
418         return $profiles;
419     }
420
421     function getListMessage($count, $you)
422     {
423         if ($count == 1 && $you) {
424             // darn first person being different from third person!
425             return _m('FAVELIST', 'You have favored this notice.');
426         } else {
427             // if 'you' is the first item,
428             return _m('FAVELIST', '%1$s has favored this notice.', '%1$s have favored this notice.', $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 class ThreadedNoticeListInlineFavesItem extends ThreadedNoticeListFavesItem
445 {
446     function showStart()
447     {
448         $this->out->elementStart('div', array('class' => 'entry-content notice-faves'));
449     }
450
451     function showEnd()
452     {
453         $this->out->elementEnd('div');
454     }
455 }
456
457 /**
458  * Placeholder for showing faves...
459  */
460 class ThreadedNoticeListRepeatsItem extends NoticeListActorsItem
461 {
462     function getProfiles()
463     {
464         $rep = $this->notice->repeatStream();
465
466         $profiles = array();
467         while ($rep->fetch()) {
468             $profiles[] = $rep->profile_id;
469         }
470         return $profiles;
471     }
472
473     function getListMessage($count, $you)
474     {
475         if ($count == 1 && $you) {
476             // darn first person being different from third person!
477             return _m('REPEATLIST', 'You have repeated this notice.');
478         } else {
479             // if 'you' is the first item,
480             return _m('REPEATLIST', '%1$s has repeated this notice.', '%1$s have repeated this notice.', $count);
481         }
482     }
483
484     function showStart()
485     {
486         $this->out->elementStart('li', array('class' => 'notice-data notice-repeats'));
487     }
488
489     function showEnd()
490     {
491         $this->out->elementEnd('li');
492     }
493
494 }