3 * Laconica, the distributed open-source microblogging tool
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.
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.
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/>.
24 * @author Evan Prodromou <evan@controlyourself.ca>
25 * @author Sarven Capadisli <csarven@controlyourself.ca>
26 * @copyright 2008-2009 Control Yourself, Inc.
27 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
28 * @link http://laconi.ca/
31 if (!defined('LACONICA')) {
35 require_once INSTALLDIR.'/lib/personalgroupnav.php';
36 require_once INSTALLDIR.'/lib/noticelist.php';
37 require_once INSTALLDIR.'/lib/feedlist.php';
42 * When I created this page, "show stream" seemed like the best name for it.
43 * Now, it seems like a really bad name.
45 * It shows a stream of the user's posts, plus lots of profile info, links
46 * to subscriptions and stuff, etc.
50 * @author Evan Prodromou <evan@controlyourself.ca>
51 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
52 * @link http://laconi.ca/
55 class ShowstreamAction extends Action
63 if ($this->page == 1) {
64 return $this->user->nickname;
66 return sprintf(_("%s, page %d"),
67 $this->user->nickname,
72 function prepare($args)
74 parent::prepare($args);
76 $nickname_arg = $this->arg('nickname');
77 $nickname = common_canonical_nickname($nickname_arg);
79 // Permanent redirect on non-canonical nickname
81 if ($nickname_arg != $nickname) {
82 $args = array('nickname' => $nickname);
83 if ($this->arg('page') && $this->arg('page') != 1) {
84 $args['page'] = $this->arg['page'];
86 common_redirect(common_local_url('showstream', $args), 301);
90 $this->user = User::staticGet('nickname', $nickname);
93 $this->clientError(_('No such user.'), 404);
97 $this->profile = $this->user->getProfile();
99 if (!$this->profile) {
100 $this->serverError(_('User has no profile.'));
104 $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
109 function handle($args)
112 // Looks like we're good; start output
114 // For YADIS discovery, we also have a <meta> tag
116 header('X-XRDS-Location: '. common_local_url('xrds', array('nickname' =>
117 $this->user->nickname)));
122 function showContent()
124 $this->showProfile();
125 $this->showNotices();
128 function showLocalNav()
130 $nav = new PersonalGroupNav($this);
134 function showPageTitle()
136 $this->element('h1', NULL, $this->profile->nickname._("'s profile"));
139 function showPageNoticeBlock()
144 function showExportData()
146 $fl = new FeedList($this);
147 $fl->show(array(0=>array('href'=>common_local_url('userrss',
148 array('nickname' => $this->user->nickname)),
150 'version' => 'RSS 1.0',
151 'item' => 'notices'),
152 1=>array('href'=>common_local_url('usertimeline',
153 array('nickname' => $this->user->nickname)),
155 'version' => 'Atom 1.0',
156 'item' => 'usertimeline'),
157 2=>array('href'=>common_local_url('foaf',
158 array('nickname' => $this->user->nickname)),
167 $this->element('link', array('rel' => 'alternate',
168 'href' => common_local_url('api',
169 array('apiaction' => 'statuses',
170 'method' => 'user_timeline.rss',
171 'argument' => $this->user->nickname)),
172 'type' => 'application/rss+xml',
173 'title' => sprintf(_('Notice feed for %s'), $this->user->nickname)));
174 $this->element('link', array('rel' => 'alternate feed',
175 'href' => common_local_url('api',
176 array('apiaction' => 'statuses',
177 'method' => 'user_timeline.atom',
178 'argument' => $this->user->nickname)),
179 'type' => 'application/atom+xml',
180 'title' => sprintf(_('Notice feed for %s'), $this->user->nickname)));
181 $this->element('link', array('rel' => 'alternate',
182 'href' => common_local_url('userrss', array('nickname' =>
183 $this->user->nickname)),
184 'type' => 'application/rdf+xml',
185 'title' => sprintf(_('Notice feed for %s'), $this->user->nickname)));
191 $this->element('link', array('rel' => 'meta',
192 'href' => common_local_url('foaf', array('nickname' =>
193 $this->user->nickname)),
194 'type' => 'application/rdf+xml',
196 // for remote subscriptions etc.
197 $this->element('meta', array('http-equiv' => 'X-XRDS-Location',
198 'content' => common_local_url('xrds', array('nickname' =>
199 $this->user->nickname))));
201 if ($this->profile->bio) {
202 $this->element('meta', array('name' => 'description',
203 'content' => $this->profile->bio));
206 if ($this->user->emailmicroid && $this->user->email && $this->profile->profileurl) {
207 $id = new Microid('mailto:'.$this->user->email,
209 $this->element('meta', array('name' => 'microid',
210 'content' => $id->toString()));
212 if ($this->user->jabbermicroid && $this->user->jabber && $this->profile->profileurl) {
213 $id = new Microid('xmpp:'.$this->user->jabber,
215 $this->element('meta', array('name' => 'microid',
216 'content' => $id->toString()));
219 // See https://wiki.mozilla.org/Microsummaries
221 $this->element('link', array('rel' => 'microsummary',
222 'href' => common_local_url('microsummary',
223 array('nickname' => $this->profile->nickname))));
226 function showProfile()
228 $this->elementStart('div', array('id' => 'user_profile', 'class' => 'vcard author'));
229 $this->element('h2', null, _('User profile'));
231 $avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
232 $this->elementStart('dl', 'user_depiction');
233 $this->element('dt', null, _('Photo'));
234 $this->elementStart('dd');
235 $this->element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_PROFILE_SIZE),
236 'class' => 'photo avatar',
237 'width' => AVATAR_PROFILE_SIZE,
238 'height' => AVATAR_PROFILE_SIZE,
239 'alt' => $this->profile->nickname));
240 $this->elementEnd('dd');
241 $this->elementEnd('dl');
243 $this->elementStart('dl', 'user_nickname');
244 $this->element('dt', null, _('Nickname'));
245 $this->elementStart('dd');
246 $hasFN = ($this->profile->fullname) ? 'nickname url uid' : 'fn nickname url uid';
247 $this->element('a', array('href' => $this->profile->profileurl,
248 'rel' => 'me', 'class' => $hasFN),
249 $this->profile->nickname);
250 $this->elementEnd('dd');
251 $this->elementEnd('dl');
253 if ($this->profile->fullname) {
254 $this->elementStart('dl', 'user_fn');
255 $this->element('dt', null, _('Full name'));
256 $this->elementStart('dd');
257 $this->element('span', 'fn', $this->profile->fullname);
258 $this->elementEnd('dd');
259 $this->elementEnd('dl');
262 if ($this->profile->location) {
263 $this->elementStart('dl', 'user_location');
264 $this->element('dt', null, _('Location'));
265 $this->element('dd', 'location', $this->profile->location);
266 $this->elementEnd('dl');
269 if ($this->profile->homepage) {
270 $this->elementStart('dl', 'user_url');
271 $this->element('dt', null, _('URL'));
272 $this->elementStart('dd');
273 $this->element('a', array('href' => $this->profile->homepage,
274 'rel' => 'me', 'class' => 'url'),
275 $this->profile->homepage);
276 $this->elementEnd('dd');
277 $this->elementEnd('dl');
280 if ($this->profile->bio) {
281 $this->elementStart('dl', 'user_note');
282 $this->element('dt', null, _('Note'));
283 $this->element('dd', 'note', $this->profile->bio);
284 $this->elementEnd('dl');
287 $tags = Profile_tag::getTags($this->profile->id, $this->profile->id);
288 if (count($tags) > 0) {
289 $this->elementStart('dl', 'user_tags');
290 $this->element('dt', null, _('Tags'));
291 $this->elementStart('dd');
292 $this->elementStart('ul', 'tags xoxo');
293 foreach ($tags as $tag) {
294 $this->elementStart('li');
295 $this->element('span', 'mark_hash', '#');
296 $this->element('a', array('rel' => 'tag',
297 'href' => common_local_url('peopletag',
298 array('tag' => $tag))),
300 $this->elementEnd('li');
302 $this->elementEnd('ul');
303 $this->elementEnd('dd');
304 $this->elementEnd('dl');
306 $this->elementEnd('div');
309 $this->elementStart('div', array('id' => 'user_actions'));
310 $this->element('h2', null, _('User actions'));
311 $this->elementStart('ul');
312 $this->elementStart('li', array('id' => 'user_subscribe'));
313 $cur = common_current_user();
315 if ($cur->id != $this->profile->id) {
316 if ($cur->isSubscribed($this->profile)) {
317 $sf = new SubscribeForm($this, $this->profile);
320 $usf = new UnsubscribeForm($this, $this->profile);
325 $this->showRemoteSubscribeLink();
327 $this->elementEnd('li');
329 // common_profile_new_message_nudge($cur, $this->user, $this->profile);
331 $user = User::staticGet('id', $this->profile->id);
332 if ($cur && $cur->id != $user->id && $cur->mutuallySubscribed($user)) {
333 $this->elementStart('li', array('id' => 'user_send_a_new_message'));
334 $this->element('a', array('href' => common_local_url('newmessage', array('to' => $user->id))),
335 _('Send a message'));
336 $this->elementEnd('li');
338 if ($user->email && $user->emailnotifynudge) {
339 $this->elementStart('li', array('id' => 'user_nudge'));
340 $nf = new NudgeForm($this, $user);
342 $this->elementEnd('li');
348 if ($cur && $cur->id != $this->profile->id) {
349 $blocked = $cur->hasBlocked($this->profile);
350 $this->elementStart('li', array('id' => 'user_block'));
352 $bf = new BlockForm($this, $this->profile);
355 $ubf = new UnblockForm($this, $this->profile);
358 $this->elementEnd('li');
360 $this->elementEnd('ul');
361 $this->elementEnd('div');
364 function showRemoteSubscribeLink()
366 $url = common_local_url('remotesubscribe',
367 array('nickname' => $this->profile->nickname));
368 $this->element('a', array('href' => $url,
369 'id' => 'user_subscribe_remote'),
373 function showNotices()
375 $notice = $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
377 $pnl = new ProfileNoticeList($notice, $this);
380 $this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
381 'showstream', array('nickname' => $this->user->nickname));
384 function showSections()
386 $this->showStatistics();
387 $this->showSubscriptions();
390 function showSubscriptions()
392 $subs = new Subscription();
393 $subs->subscriber = $this->profile->id;
394 $subs->whereAdd('subscribed != ' . $this->profile->id);
396 $subs->orderBy('created DESC');
398 // We ask for an extra one to know if we need to do another page
400 $subs->limit(0, SUBSCRIPTIONS + 1);
402 $subs_count = $subs->find();
404 $this->elementStart('div', array('id' => 'user_subscriptions',
405 'class' => 'section'));
407 $this->element('h2', null, _('Subscriptions'));
409 if ($subs_count > 0) {
411 $this->elementStart('ul', 'users');
413 for ($i = 0; $i < min($subs_count, SUBSCRIPTIONS); $i++) {
415 if (!$subs->fetch()) {
416 common_debug('Weirdly, broke out of subscriptions loop early', __FILE__);
420 $other = Profile::staticGet($subs->subscribed);
423 common_log_db_error($subs, 'SELECT', __FILE__);
427 $this->elementStart('li', 'vcard');
428 $this->elementStart('a', array('title' => ($other->fullname) ?
431 'href' => $other->profileurl,
434 $avatar = $other->getAvatar(AVATAR_MINI_SIZE);
435 $this->element('img', array('src' => (($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_MINI_SIZE)),
436 'width' => AVATAR_MINI_SIZE,
437 'height' => AVATAR_MINI_SIZE,
438 'class' => 'avatar photo',
439 'alt' => ($other->fullname) ?
442 $this->element('span', 'fn nickname', $other->nickname);
443 $this->elementEnd('a');
444 $this->elementEnd('li');
447 $this->elementEnd('ul');
450 if ($subs_count > SUBSCRIPTIONS) {
451 $this->elementStart('p');
453 $this->element('a', array('href' => common_local_url('subscriptions',
454 array('nickname' => $this->profile->nickname)),
456 _('All subscriptions'));
457 $this->elementEnd('p');
460 $this->elementEnd('div');
463 function showStatistics()
465 // XXX: WORM cache this
466 $subs = new Subscription();
467 $subs->subscriber = $this->profile->id;
468 $subs_count = (int) $subs->count() - 1;
470 $subbed = new Subscription();
471 $subbed->subscribed = $this->profile->id;
472 $subbed_count = (int) $subbed->count() - 1;
474 $notices = new Notice();
475 $notices->profile_id = $this->profile->id;
476 $notice_count = (int) $notices->count();
478 $this->elementStart('div', array('id' => 'user_statistics',
479 'class' => 'section'));
481 $this->element('h2', null, _('Statistics'));
484 $this->elementStart('dl', 'user_member-since');
485 $this->element('dt', null, _('Member since'));
486 $this->element('dd', null, date('j M Y',
487 strtotime($this->profile->created)));
488 $this->elementEnd('dl');
490 $this->elementStart('dl', 'user_subscriptions');
491 $this->elementStart('dt');
492 $this->element('a', array('href' => common_local_url('subscriptions',
493 array('nickname' => $this->profile->nickname))),
495 $this->elementEnd('dt');
496 $this->element('dd', null, (is_int($subs_count)) ? $subs_count : '0');
497 $this->elementEnd('dl');
499 $this->elementStart('dl', 'user_subscribers');
500 $this->elementStart('dt');
501 $this->element('a', array('href' => common_local_url('subscribers',
502 array('nickname' => $this->profile->nickname))),
504 $this->elementEnd('dt');
505 $this->element('dd', 'subscribers', (is_int($subbed_count)) ? $subbed_count : '0');
506 $this->elementEnd('dl');
508 $this->elementStart('dl', 'user_notices');
509 $this->element('dt', null, _('Notices'));
510 $this->element('dd', null, (is_int($notice_count)) ? $notice_count : '0');
511 $this->elementEnd('dl');
513 $this->elementEnd('div');
518 // We don't show the author for a profile, since we already know who it is!
520 class ProfileNoticeList extends NoticeList
522 function newListItem($notice)
524 return new ProfileNoticeListItem($notice, $this->out);
528 class ProfileNoticeListItem extends NoticeListItem
530 function showAuthor()