2015-07-07 07:52:26 +09:00
< ? php
/**
* StatusNet , the distributed open - source microblogging tool
*
* Show the friends timeline , witch replies to non - friends hidden
*
* PHP version 5
*
* LICENCE : This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < http :// www . gnu . org / licenses />.
*
* @ category API
* @ package StatusNet
* @ author Craig Andrews < candrews @ integralblue . com >
* @ author Evan Prodromou < evan @ status . net >
* @ author Jeffery To < jeffery . to @ gmail . com >
* @ author mac65 < mac65 @ mac65 . com >
* @ author Mike Cochrane < mikec @ mikenz . geek . nz >
* @ author Robin Millette < robin @ millette . info >
* @ author Zach Copley < zach @ status . net >
* @ author Hannes Mannerheim < h @ nnesmannerhe . im >
* @ copyright 2009 - 2010 StatusNet , Inc .
* @ copyright 2009 Free Software Foundation , Inc http :// www . fsf . org
* @ license http :// www . fsf . org / licensing / licenses / agpl - 3.0 . html GNU Affero General Public License version 3.0
* @ link http :// status . net /
*/
/* External API usage documentation. Please update when you change how this method works. */
/*! @ page friendstimeline statuses / friends_timeline
@ section Description
Returns the 20 most recent statuses posted by the authenticating
user and that user ' s friends . This is the equivalent of " You and
friends " page in the web interface.
@ par URL patterns
@ li / api / statuses / friends_timeline .: format
@ li / api / statuses / friends_timeline /: id .: format
@ par Formats ( : format )
xml , json , rss , atom
@ par ID ( : id )
username , user id
@ par HTTP Method ( s )
GET
@ par Requires Authentication
Sometimes ( see : @ ref authentication )
@ param user_id ( Optional ) Specifies a user by ID
@ param screen_name ( Optional ) Specifies a user by screename ( nickname )
@ param since_id ( Optional ) Returns only statuses with an ID greater
than ( that is , more recent than ) the specified ID .
@ param max_id ( Optional ) Returns only statuses with an ID less than
( that is , older than ) or equal to the specified ID .
@ param count ( Optional ) Specifies the number of statuses to retrieve .
@ param page ( Optional ) Specifies the page of results to retrieve .
@ sa @ ref authentication
@ sa @ ref apiroot
@ subsection usagenotes Usage notes
@ li The URL pattern is relative to the @ ref apiroot .
@ li The XML response uses < a href = " http://georss.org/Main_Page " > GeoRSS </ a >
to encode the latitude and longitude ( see example response below < georss : point > ) .
@ subsection exampleusage Example usage
@ verbatim
curl http :// identi . ca / api / statuses / friends_timeline / evan . xml ? count = 1 & page = 2
@ endverbatim
@ subsection exampleresponse Example response
@ verbatim
< ? xml version = " 1.0 " ?>
< statuses type = " array " >
< status >
< text > back from the ! yul ! drupal meet with Evolving Web folk , @ anarcat , @ webchick and others , and an interesting refresher on SQL indexing </ text >
< truncated > false </ truncated >
< created_at > Wed Mar 31 01 : 33 : 02 + 0000 2010 </ created_at >
< in_reply_to_status_id />
< source >& lt ; a href = " http://code.google.com/p/microblog-purple/ " & gt ; mbpidgin & lt ; / a & gt ; </ source >
< id > 26674201 </ id >
< in_reply_to_user_id />
< in_reply_to_screen_name />
< geo />
< favorited > false </ favorited >
< user >
< id > 246 </ id >
< name > Mark </ name >
< screen_name > lambic </ screen_name >
< location > Montreal , Canada </ location >
< description > Geek </ description >
< profile_image_url > http :// avatar . identi . ca / 246 - 48 - 20080702141545. png </ profile_image_url >
< url > http :// lambic . co . uk </ url >
< protected > false </ protected >
< followers_count > 73 </ followers_count >
< profile_background_color > #F0F2F5</profile_background_color>
< profile_text_color />
< profile_link_color > #002E6E</profile_link_color>
< profile_sidebar_fill_color > #CEE1E9</profile_sidebar_fill_color>
< profile_sidebar_border_color />
< friends_count > 58 </ friends_count >
< created_at > Wed Jul 02 14 : 12 : 15 + 0000 2008 </ created_at >
< favourites_count > 2 </ favourites_count >
< utc_offset >- 14400 </ utc_offset >
< time_zone > US / Eastern </ time_zone >
< profile_background_image_url />
< profile_background_tile > false </ profile_background_tile >
< statuses_count > 933 </ statuses_count >
< following > false </ following >
< notifications > false </ notifications >
</ user >
</ status >
</ statuses >
@ endverbatim
*/
if ( ! defined ( 'STATUSNET' )) {
exit ( 1 );
}
/**
* Returns the most recent notices ( default 20 ) posted by the target user .
* This is the equivalent of 'You and friends' page accessed via Web .
*
* @ category API
* @ package StatusNet
* @ author Craig Andrews < candrews @ integralblue . com >
* @ author Evan Prodromou < evan @ status . net >
* @ author Jeffery To < jeffery . to @ gmail . com >
* @ author mac65 < mac65 @ mac65 . com >
* @ author Mike Cochrane < mikec @ mikenz . geek . nz >
* @ author Robin Millette < robin @ millette . info >
* @ author Zach Copley < zach @ status . net >
* @ license http :// www . fsf . org / licensing / licenses / agpl - 3.0 . html GNU Affero General Public License version 3.0
* @ link http :// status . net /
*/
class ApiTimelineFriendsHiddenRepliesAction extends ApiBareAuthAction
{
var $notices = null ;
/**
* Take arguments for running
*
* @ param array $args $_REQUEST args
*
* @ return boolean success flag
*
*/
protected function prepare ( array $args = array ())
{
parent :: prepare ( $args );
$this -> target = $this -> getTargetProfile ( $this -> arg ( 'id' ));
if ( ! ( $this -> target instanceof Profile )) {
// TRANS: Client error displayed when requesting dents of a user and friends for a user that does not exist.
$this -> clientError ( _ ( 'No such user.' ), 404 );
}
$this -> notices = $this -> getNotices ();
return true ;
}
/**
* Handle the request
*
* Just show the notices
*
* @ return void
*/
protected function handle ()
{
parent :: handle ();
$this -> showTimeline ();
}
/**
* Show the timeline of notices
*
* @ return void
*/
function showTimeline ()
{
$sitename = common_config ( 'site' , 'name' );
// TRANS: Title of API timeline for a user and friends.
// TRANS: %s is a username.
$title = sprintf ( _ ( " %s and friends " ), $this -> target -> nickname );
$taguribase = TagURI :: base ();
$id = " tag: $taguribase :FriendsTimelineHiddenReplies: " . $this -> target -> id ;
$subtitle = sprintf (
// TRANS: Message is used as a subtitle. %1$s is a user nickname, %2$s is a site name.
_ ( 'Updates from %1$s and friends on %2$s! (with replies to non-friends hidden)' ),
$this -> target -> nickname ,
$sitename
);
$logo = $this -> target -> avatarUrl ( AVATAR_PROFILE_SIZE );
$link = common_local_url ( 'all' ,
array ( 'nickname' => $this -> target -> nickname ));
$self = $this -> getSelfUri ();
switch ( $this -> format ) {
case 'xml' :
$this -> showXmlTimeline ( $this -> notices );
break ;
case 'rss' :
$this -> showRssTimeline (
$this -> notices ,
$title ,
$link ,
$subtitle ,
null ,
$logo ,
$self
);
break ;
case 'atom' :
header ( 'Content-Type: application/atom+xml; charset=utf-8' );
$atom = new AtomNoticeFeed ( $this -> auth_user );
$atom -> setId ( $id );
$atom -> setTitle ( $title );
$atom -> setSubtitle ( $subtitle );
$atom -> setLogo ( $logo );
$atom -> setUpdated ( 'now' );
$atom -> addLink ( $link );
$atom -> setSelfLink ( $self );
$atom -> addEntryFromNotices ( $this -> notices );
$this -> raw ( $atom -> getString ());
break ;
case 'json' :
$this -> showJsonTimeline ( $this -> notices );
break ;
case 'as' :
header ( 'Content-Type: ' . ActivityStreamJSONDocument :: CONTENT_TYPE );
$doc = new ActivityStreamJSONDocument ( $this -> auth_user , $title );
$doc -> addLink ( $link , 'alternate' , 'text/html' );
$doc -> addItemsFromNotices ( $this -> notices );
$this -> raw ( $doc -> asString ());
break ;
default :
// TRANS: Client error displayed when coming across a non-supported API method.
$this -> clientError ( _ ( 'API method not found.' ), 404 );
}
}
/**
* Get notices
*
* @ return array notices
*/
function getNotices ()
{
$notices = array ();
$stream = new InboxNoticeStreamHiddenReplies ( $this -> target , $this -> scoped );
$notice = $stream -> getNotices (( $this -> page - 1 ) * $this -> count ,
$this -> count ,
$this -> since_id ,
$this -> max_id );
while ( $notice -> fetch ()) {
$notices [] = clone ( $notice );
}
return $notices ;
}
/**
* Is this action read only ?
*
* @ param array $args other arguments
*
* @ return boolean true
*/
function isReadOnly ( $args )
{
return true ;
}
/**
* When was this feed last modified ?
*
* @ return string datestamp of the latest notice in the stream
*/
function lastModified ()
{
if ( ! empty ( $this -> notices ) && ( count ( $this -> notices ) > 0 )) {
return strtotime ( $this -> notices [ 0 ] -> created );
}
return null ;
}
/**
* An entity tag for this stream
*
* Returns an Etag based on the action name , language , user ID , and
* timestamps of the first and last notice in the timeline
*
* @ return string etag
*/
function etag ()
{
if ( ! empty ( $this -> notices ) && ( count ( $this -> notices ) > 0 )) {
$last = count ( $this -> notices ) - 1 ;
return '"' . implode (
':' ,
array ( $this -> arg ( 'action' ),
common_user_cache_hash ( $this -> auth_user ),
common_language (),
$this -> target -> id ,
strtotime ( $this -> notices [ 0 ] -> created ),
strtotime ( $this -> notices [ $last ] -> created ))
)
. '"' ;
}
return null ;
}
}
/**
* Stream of notices for a profile ' s " all " feed , with hidden replies to non - friends
*
* @ category General
* @ package StatusNet
* @ author Evan Prodromou < evan @ status . net >
* @ author Mikael Nordfeldth < mmn @ hethane . se >
* @ copyright 2011 StatusNet , Inc .
* @ copyright 2014 Free Software Foundation , Inc .
* @ license http :// www . fsf . org / licensing / licenses / agpl - 3.0 . html AGPL 3.0
* @ link http :// status . net /
*/
class InboxNoticeStreamHiddenReplies extends ScopingNoticeStream
{
/**
* Constructor
*
* @ param Profile $target Profile to get a stream for
* @ param Profile $scoped Currently scoped profile ( if null , it is fetched )
*/
function __construct ( Profile $target , Profile $scoped = null )
{
if ( $scoped === null ) {
$scoped = Profile :: current ();
}
// FIXME: we don't use CachingNoticeStream - but maybe we should?
parent :: __construct ( new CachingNoticeStream ( new RawInboxNoticeStreamHiddenReplies ( $target ), 'profileallhiddenreplies' ), $scoped );
}
}
/**
* Raw stream of notices for the target ' s inbox , with hidden replies to non - friends
*
* @ category General
* @ package StatusNet
* @ author Evan Prodromou < evan @ status . net >
* @ copyright 2011 StatusNet , Inc .
* @ license http :// www . fsf . org / licensing / licenses / agpl - 3.0 . html AGPL 3.0
* @ link http :// status . net /
*/
class RawInboxNoticeStreamHiddenReplies extends NoticeStream
{
protected $target = null ;
protected $inbox = null ;
2016-02-16 19:54:47 +09:00
protected $selectVerbs = array ();
2015-07-07 07:52:26 +09:00
/**
* Constructor
*
* @ param Profile $target Profile to get a stream for
*/
function __construct ( Profile $target )
{
2016-02-16 19:54:47 +09:00
parent :: __construct ();
2015-07-07 07:52:26 +09:00
$this -> target = $target ;
}
/**
* Get IDs in a range
*
* @ param int $offset Offset from start
* @ param int $limit Limit of number to get
* @ param int $since_id Since this notice
* @ param int $max_id Before this notice
*
* @ return Array IDs found
*/
function getNoticeIds ( $offset , $limit , $since_id , $max_id )
{
$notice = new Notice ();
$notice -> selectAdd ();
$notice -> selectAdd ( 'id' );
$notice -> whereAdd ( sprintf ( 'notice.created > "%s"' , $notice -> escape ( $this -> target -> created )));
// Reply:: is a table of mentions
// Subscription:: is a table of subscriptions (every user is subscribed to themselves)
$notice -> whereAdd (
// notices from profiles we subscribe to
sprintf ( '( notice.profile_id IN (SELECT subscribed FROM subscription WHERE subscriber=%1$d) ' .
// and in groups we're members of
'OR notice.id IN (SELECT notice_id FROM group_inbox WHERE group_id IN (SELECT group_id FROM group_member WHERE profile_id=%1$d))' .
// and from attention table (i, hannes, don't know whats in that though...)
'OR notice.id IN (SELECT notice_id FROM attention WHERE profile_id=%1$d) ) ' .
// all of the notices matching the above must also be either
// 1) a non-reply
'AND (notice.reply_to IS NULL ' .
// 2) OR a reply to myself
'OR notice.profile_id=%1$d ' .
// 3) OR a reply to someone i'm subscibing to
'OR notice.reply_to IN (SELECT id FROM notice as noticereplies WHERE noticereplies.profile_id IN (SELECT subscribed FROM subscription WHERE subscriber=%1$d))) ' .
// lastly: include all notices mentioning me
'OR (notice.id IN (SELECT notice_id FROM reply WHERE profile_id=%1$d) ' .
// but not if they are from someone i don't subscribe to
'AND notice.profile_id IN (SELECT subscribed FROM subscription WHERE subscriber=%1$d))' ,
$this -> target -> id )
);
if ( ! empty ( $since_id )) {
$notice -> whereAdd ( sprintf ( 'notice.id > %d' , $since_id ));
}
if ( ! empty ( $max_id )) {
$notice -> whereAdd ( sprintf ( 'notice.id <= %d' , $max_id ));
}
2016-02-16 19:54:47 +09:00
// We have changed how selectVerbs work in GNUsocial, so it's an associative array
// where each verb is in the key and then the value (true/false) is how to filter.
// $this->unselectVerbs is always unset in newer GNUsocials.
if ( ! isset ( $this -> unselectVerbs )) {
self :: filterVerbs ( $notice , $this -> selectVerbs );
} elseif ( ! empty ( $this -> selectVerbs )) {
// old behaviour was just if there were selectVerbs set
2015-07-07 07:52:26 +09:00
$notice -> whereAddIn ( 'verb' , $this -> selectVerbs , $notice -> columnType ( 'verb' ));
}
2016-02-16 19:54:47 +09:00
2015-07-07 07:52:26 +09:00
$notice -> limit ( $offset , $limit );
// notice.id will give us even really old posts, which were
// recently imported. For example if a remote instance had
// problems and just managed to post here. Another solution
// would be to have a 'notice.imported' field and order by it.
$notice -> orderBy ( 'notice.id DESC' );
if ( ! $notice -> find ()) {
return array ();
}
$ids = $notice -> fetchAll ( 'id' );
return $ids ;
}
}