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 * @copyright 2008-2009 Control Yourself, Inc.
26 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27 * @link http://laconi.ca/
30 if (!defined('LACONICA')) {
34 require_once INSTALLDIR.'/lib/personalgroupnav.php';
35 require_once INSTALLDIR.'/lib/noticelist.php';
36 require_once INSTALLDIR.'/lib/feedlist.php';
41 * When I created this page, "show stream" seemed like the best name for it.
42 * Now, it seems like a really bad name.
44 * It shows a stream of the user's posts, plus lots of profile info, links
45 * to subscriptions and stuff, etc.
49 * @author Evan Prodromou <evan@controlyourself.ca>
50 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
51 * @link http://laconi.ca/
54 class ShowstreamAction extends Action
62 if ($this->page == 1) {
63 return $this->user->nickname;
65 return sprintf(_("%s, page %d"),
66 $this->user->nickname,
71 function prepare($args)
73 parent::prepare($args);
75 $nickname_arg = $this->arg('nickname');
76 $nickname = common_canonical_nickname($nickname_arg);
78 // Permanent redirect on non-canonical nickname
80 if ($nickname_arg != $nickname) {
81 $args = array('nickname' => $nickname);
82 if ($this->arg('page') && $this->arg('page') != 1) {
83 $args['page'] = $this->arg['page'];
85 common_redirect(common_local_url('showstream', $args), 301);
89 $this->user = User::staticGet('nickname', $nickname);
92 $this->clientError(_('No such user.'), 404);
96 $this->profile = $this->user->getProfile();
98 if (!$this->profile) {
99 $this->serverError(_('User has no profile.'));
103 $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
108 function handle($args)
111 // Looks like we're good; start output
113 // For YADIS discovery, we also have a <meta> tag
115 header('X-XRDS-Location: '. common_local_url('xrds', array('nickname' =>
116 $this->user->nickname)));
121 function showContent()
123 $this->showProfile();
124 $this->showNotices();
127 function showLocalNav()
129 $nav = new PersonalGroupNav($this);
133 function showPageTitle()
135 // Don't show the H1; we have one in the profile block
138 function showExportData()
140 $fl = new FeedList($this);
141 $fl->show(array(0=>array('href'=>common_local_url('userrss',
142 array('nickname' => $this->user->nickname)),
144 'version' => 'RSS 1.0',
145 'item' => 'notices'),
146 1=>array('href'=>common_local_url('usertimeline',
147 array('nickname' => $this->user->nickname)),
149 'version' => 'Atom 1.0',
150 'item' => 'usertimeline'),
151 2=>array('href'=>common_local_url('foaf',
152 array('nickname' => $this->user->nickname)),
161 $this->element('link', array('rel' => 'alternate',
162 'href' => common_local_url('api',
163 array('apiaction' => 'statuses',
164 'method' => 'user_timeline.rss',
165 'argument' => $this->user->nickname)),
166 'type' => 'application/rss+xml',
167 'title' => sprintf(_('Notice feed for %s'), $this->user->nickname)));
168 $this->element('link', array('rel' => 'alternate feed',
169 'href' => common_local_url('api',
170 array('apiaction' => 'statuses',
171 'method' => 'user_timeline.atom',
172 'argument' => $this->user->nickname)),
173 'type' => 'application/atom+xml',
174 'title' => sprintf(_('Notice feed for %s'), $this->user->nickname)));
175 $this->element('link', array('rel' => 'alternate',
176 'href' => common_local_url('userrss', array('nickname' =>
177 $this->user->nickname)),
178 'type' => 'application/rdf+xml',
179 'title' => sprintf(_('Notice feed for %s'), $this->user->nickname)));
185 $this->element('link', array('rel' => 'meta',
186 'href' => common_local_url('foaf', array('nickname' =>
187 $this->user->nickname)),
188 'type' => 'application/rdf+xml',
190 // for remote subscriptions etc.
191 $this->element('meta', array('http-equiv' => 'X-XRDS-Location',
192 'content' => common_local_url('xrds', array('nickname' =>
193 $this->user->nickname))));
195 if ($this->profile->bio) {
196 $this->element('meta', array('name' => 'description',
197 'content' => $this->profile->bio));
200 if ($this->user->emailmicroid && $this->user->email && $this->profile->profileurl) {
201 $id = new Microid('mailto:'.$this->user->email,
203 $this->element('meta', array('name' => 'microid',
204 'content' => $id->toString()));
206 if ($this->user->jabbermicroid && $this->user->jabber && $this->profile->profileurl) {
207 $id = new Microid('xmpp:'.$this->user->jabber,
209 $this->element('meta', array('name' => 'microid',
210 'content' => $id->toString()));
213 // See https://wiki.mozilla.org/Microsummaries
215 $this->element('link', array('rel' => 'microsummary',
216 'href' => common_local_url('microsummary',
217 array('nickname' => $this->profile->nickname))));
220 function showProfile()
222 $this->elementStart('div', array('id' => 'profile', 'class' => 'vcard'));
224 $avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
225 $this->elementStart('div', array('id' => 'profile_avatar'));
226 $this->element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_PROFILE_SIZE),
227 'class' => 'avatar profile photo',
228 'width' => AVATAR_PROFILE_SIZE,
229 'height' => AVATAR_PROFILE_SIZE,
230 'alt' => $this->profile->nickname));
232 $this->elementStart('ul', array('id' => 'profile_actions'));
234 $this->elementStart('li', array('id' => 'profile_subscribe'));
235 $cur = common_current_user();
237 if ($cur->id != $this->profile->id) {
238 if ($cur->isSubscribed($this->profile)) {
239 $sf = new SubscribeForm($this, $this->profile);
242 $usf = new UnsubscribeForm($this, $this->profile);
247 $this->showRemoteSubscribeLink();
249 $this->elementEnd('li');
251 common_profile_new_message_nudge($cur, $this->user, $this->profile);
253 if ($cur && $cur->id != $this->profile->id) {
254 $blocked = $cur->hasBlocked($this->profile);
255 $this->elementStart('li', array('id' => 'profile_block'));
257 $bf = new BlockForm($this, $this->profile);
260 $ubf = new UnblockForm($this, $this->profile);
263 $this->elementEnd('li');
266 $this->elementEnd('ul');
268 $this->elementEnd('div');
270 $this->elementStart('div', array('id' => 'profile_information'));
272 if ($this->profile->fullname) {
273 $this->element('h1', array('class' => 'fn'), $this->profile->fullname . ' (' . $this->profile->nickname . ')');
275 $this->element('h1', array('class' => 'fn nickname'), $this->profile->nickname);
278 if ($this->profile->location) {
279 $this->element('p', 'location', $this->profile->location);
281 if ($this->profile->bio) {
282 $this->element('p', 'description note', $this->profile->bio);
284 if ($this->profile->homepage) {
285 $this->elementStart('p', 'website');
286 $this->element('a', array('href' => $this->profile->homepage,
287 'rel' => 'me', 'class' => 'url'),
288 $this->profile->homepage);
289 $this->elementEnd('p');
292 $this->elementEnd('div');
294 $this->elementEnd('div');
297 function showRemoteSubscribeLink()
299 $url = common_local_url('remotesubscribe',
300 array('nickname' => $this->profile->nickname));
301 $this->element('a', array('href' => $url,
302 'id' => 'remotesubscribe'),
306 function showNotices()
308 $notice = $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
310 $pnl = new ProfileNoticeList($notice, $this);
313 $this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
314 'showstream', array('nickname' => $this->user->nickname));
317 function showSections()
319 $this->showStatistics();
320 $this->showSubscriptions();
323 function showSubscriptions()
325 $subs = new Subscription();
326 $subs->subscriber = $this->profile->id;
327 $subs->whereAdd('subscribed != ' . $this->profile->id);
329 $subs->orderBy('created DESC');
331 // We ask for an extra one to know if we need to do another page
333 $subs->limit(0, SUBSCRIPTIONS + 1);
335 $subs_count = $subs->find();
337 $this->elementStart('div', array('id' => 'subscriptions',
338 'class' => 'section'));
340 $this->element('h2', null, _('Subscriptions'));
342 if ($subs_count > 0) {
344 $this->elementStart('ul', array('id' => 'subscriptions_avatars'));
346 for ($i = 0; $i < min($subs_count, SUBSCRIPTIONS); $i++) {
348 if (!$subs->fetch()) {
349 common_debug('Weirdly, broke out of subscriptions loop early', __FILE__);
353 $other = Profile::staticGet($subs->subscribed);
356 common_log_db_error($subs, 'SELECT', __FILE__);
360 $this->elementStart('li', 'vcard');
361 $this->elementStart('a', array('title' => ($other->fullname) ?
364 'href' => $other->profileurl,
366 'class' => 'subscription fn url'));
367 $avatar = $other->getAvatar(AVATAR_MINI_SIZE);
368 $this->element('img', array('src' => (($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_MINI_SIZE)),
369 'width' => AVATAR_MINI_SIZE,
370 'height' => AVATAR_MINI_SIZE,
371 'class' => 'avatar mini photo',
372 'alt' => ($other->fullname) ?
375 $this->elementEnd('a');
376 $this->elementEnd('li');
379 $this->elementEnd('ul');
382 if ($subs_count > SUBSCRIPTIONS) {
383 $this->elementStart('p', array('id' => 'subscriptions_viewall'));
385 $this->element('a', array('href' => common_local_url('subscriptions',
386 array('nickname' => $this->profile->nickname)),
387 'class' => 'moresubscriptions'),
388 _('All subscriptions'));
389 $this->elementEnd('p');
392 $this->elementEnd('div');
395 function showStatistics()
398 // XXX: WORM cache this
399 $subs = new Subscription();
400 $subs->subscriber = $this->profile->id;
401 $subs_count = (int) $subs->count() - 1;
403 $subbed = new Subscription();
404 $subbed->subscribed = $this->profile->id;
405 $subbed_count = (int) $subbed->count() - 1;
407 $notices = new Notice();
408 $notices->profile_id = $this->profile->id;
409 $notice_count = (int) $notices->count();
411 $this->elementStart('div', array('id' => 'statistics',
412 'class' => 'section'));
414 $this->element('h2', 'statistics', _('Statistics'));
417 $this->elementStart('dl', 'statistics');
418 $this->element('dt', 'membersince', _('Member since'));
419 $this->element('dd', 'membersince', date('j M Y',
420 strtotime($this->profile->created)));
422 $this->elementStart('dt', 'subscriptions');
423 $this->element('a', array('href' => common_local_url('subscriptions',
424 array('nickname' => $this->profile->nickname))),
426 $this->elementEnd('dt');
427 $this->element('dd', 'subscriptions', (is_int($subs_count)) ? $subs_count : '0');
428 $this->elementStart('dt', 'subscribers');
429 $this->element('a', array('href' => common_local_url('subscribers',
430 array('nickname' => $this->profile->nickname))),
432 $this->elementEnd('dt');
433 $this->element('dd', 'subscribers', (is_int($subbed_count)) ? $subbed_count : '0');
434 $this->element('dt', 'notices', _('Notices'));
435 $this->element('dd', 'notices', (is_int($notice_count)) ? $notice_count : '0');
436 // XXX: link these to something
437 $this->element('dt', 'tags', _('Tags'));
438 $this->elementStart('dd', 'tags');
439 $tags = Profile_tag::getTags($this->profile->id, $this->profile->id);
441 $this->elementStart('ul', 'tags xoxo');
442 foreach ($tags as $tag) {
443 $this->elementStart('li');
444 $this->element('a', array('rel' => 'bookmark tag',
445 'href' => common_local_url('peopletag',
446 array('tag' => $tag))),
448 $this->elementEnd('li');
450 $this->elementEnd('ul');
451 $this->elementEnd('dd');
453 $this->elementEnd('dl');
455 $this->elementEnd('div');
460 // We don't show the author for a profile, since we already know who it is!
462 class ProfileNoticeList extends NoticeList
464 function newListItem($notice)
466 return new ProfileNoticeListItem($notice, $this->out);
470 class ProfileNoticeListItem extends NoticeListItem
472 function showAuthor()