2009-11-20 04:55:38 +00:00
< ? php
/*
* StatusNet - the distributed open - source microblogging tool
2010-02-18 21:22:21 +00:00
* Copyright ( C ) 2009 - 2010 , StatusNet , Inc .
2009-11-20 04:55:38 +00:00
*
* 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 />.
*/
/**
2013-09-30 16:13:03 +01:00
* OStatusPlugin implementation for GNU Social
*
* Depends on : WebFinger plugin
*
2010-02-18 21:22:21 +00:00
* @ package OStatusPlugin
2009-11-20 04:55:38 +00:00
* @ maintainer Brion Vibber < brion @ status . net >
*/
2013-09-30 16:13:03 +01:00
if ( ! defined ( 'GNUSOCIAL' )) { exit ( 1 ); }
2009-11-20 04:55:38 +00:00
2010-02-08 19:06:03 +00:00
class OStatusPlugin extends Plugin
2009-11-20 04:55:38 +00:00
{
/**
* Hook for RouterInitialized event .
*
2014-11-07 14:24:05 +00:00
* @ param URLMapper $m path - to - action mapper
2009-11-20 04:55:38 +00:00
* @ return boolean hook return
*/
2014-11-07 14:24:05 +00:00
public function onRouterInitialized ( URLMapper $m )
2009-11-20 04:55:38 +00:00
{
2010-02-09 20:37:37 +00:00
// Discovery actions
2011-03-06 19:15:34 +00:00
$m -> connect ( 'main/ostatustag' ,
array ( 'action' => 'ostatustag' ));
$m -> connect ( 'main/ostatustag?nickname=:nickname' ,
array ( 'action' => 'ostatustag' ), array ( 'nickname' => '[A-Za-z0-9_-]+' ));
2011-09-18 22:32:59 +01:00
$m -> connect ( 'main/ostatus/nickname/:nickname' ,
2010-02-09 06:37:45 +00:00
array ( 'action' => 'ostatusinit' ), array ( 'nickname' => '[A-Za-z0-9_-]+' ));
2011-09-18 22:32:59 +01:00
$m -> connect ( 'main/ostatus/group/:group' ,
2010-03-03 22:06:05 +00:00
array ( 'action' => 'ostatusinit' ), array ( 'group' => '[A-Za-z0-9_-]+' ));
2011-09-18 22:32:59 +01:00
$m -> connect ( 'main/ostatus/peopletag/:peopletag/tagger/:tagger' ,
2011-03-06 19:15:34 +00:00
array ( 'action' => 'ostatusinit' ), array ( 'tagger' => '[A-Za-z0-9_-]+' ,
'peopletag' => '[A-Za-z0-9_-]+' ));
2011-09-18 22:32:59 +01:00
$m -> connect ( 'main/ostatus' ,
array ( 'action' => 'ostatusinit' ));
2011-03-06 19:15:34 +00:00
// Remote subscription actions
2010-02-09 06:37:45 +00:00
$m -> connect ( 'main/ostatussub' ,
2010-02-13 03:00:35 +00:00
array ( 'action' => 'ostatussub' ));
2010-03-03 21:40:26 +00:00
$m -> connect ( 'main/ostatusgroup' ,
array ( 'action' => 'ostatusgroup' ));
2011-03-06 19:15:34 +00:00
$m -> connect ( 'main/ostatuspeopletag' ,
array ( 'action' => 'ostatuspeopletag' ));
2010-02-09 20:37:37 +00:00
// PuSH actions
2010-02-08 22:06:36 +00:00
$m -> connect ( 'main/push/hub' , array ( 'action' => 'pushhub' ));
2010-02-08 19:06:03 +00:00
2010-02-08 22:06:36 +00:00
$m -> connect ( 'main/push/callback/:feed' ,
array ( 'action' => 'pushcallback' ),
2009-11-20 04:55:38 +00:00
array ( 'feed' => '[0-9]+' ));
2010-02-09 20:37:37 +00:00
// Salmon endpoint
2010-02-09 21:53:51 +00:00
$m -> connect ( 'main/salmon/user/:id' ,
2010-02-20 16:12:43 +00:00
array ( 'action' => 'usersalmon' ),
2010-02-09 20:37:37 +00:00
array ( 'id' => '[0-9]+' ));
2010-02-11 20:02:17 +00:00
$m -> connect ( 'main/salmon/group/:id' ,
2010-02-20 16:12:43 +00:00
array ( 'action' => 'groupsalmon' ),
2010-02-11 20:02:17 +00:00
array ( 'id' => '[0-9]+' ));
2011-03-06 19:15:34 +00:00
$m -> connect ( 'main/salmon/peopletag/:id' ,
array ( 'action' => 'peopletagsalmon' ),
array ( 'id' => '[0-9]+' ));
2009-11-20 04:55:38 +00:00
return true ;
}
2010-02-08 19:06:03 +00:00
/**
* Set up queue handlers for outgoing hub pushes
* @ param QueueManager $qm
* @ return boolean hook return
*/
function onEndInitializeQueueManager ( QueueManager $qm )
{
2010-02-24 02:19:13 +00:00
// Prepare outgoing distributions after notice save.
$qm -> connect ( 'ostatus' , 'OStatusQueueHandler' );
2010-02-21 21:40:59 +00:00
// Outgoing from our internal PuSH hub
2010-02-24 20:36:36 +00:00
$qm -> connect ( 'hubconf' , 'HubConfQueueHandler' );
2010-06-04 19:48:54 +01:00
$qm -> connect ( 'hubprep' , 'HubPrepQueueHandler' );
2010-02-08 19:06:03 +00:00
$qm -> connect ( 'hubout' , 'HubOutQueueHandler' );
2010-02-21 21:40:59 +00:00
2010-02-24 02:19:13 +00:00
// Outgoing Salmon replies (when we don't need a return value)
2010-02-24 20:36:36 +00:00
$qm -> connect ( 'salmon' , 'SalmonQueueHandler' );
2010-02-24 02:19:13 +00:00
2010-02-21 21:40:59 +00:00
// Incoming from a foreign PuSH hub
2010-02-24 20:36:36 +00:00
$qm -> connect ( 'pushin' , 'PushInQueueHandler' );
2015-10-21 02:50:03 +01:00
// Re-subscribe feeds that need renewal
$qm -> connect ( 'pushrenew' , 'PushRenewQueueHandler' );
2010-02-08 19:06:03 +00:00
return true ;
}
/**
* Put saved notices into the queue for pubsub distribution .
*/
function onStartEnqueueNotice ( $notice , & $transports )
{
2016-03-02 11:42:09 +00:00
if ( $notice -> inScope ( null ) && $notice -> getProfile () -> hasRight ( Right :: PUBLICNOTICE )) {
2012-07-29 23:17:16 +01:00
// put our transport first, in case there's any conflict (like OMB)
array_unshift ( $transports , 'ostatus' );
2016-03-23 16:52:02 +00:00
$this -> log ( LOG_INFO , " OSTATUS [ { $notice -> getID () } ]: queued for OStatus processing " );
2011-06-29 18:20:18 +01:00
} else {
2012-07-29 23:17:16 +01:00
// FIXME: we don't do privacy-controlled OStatus updates yet.
// once that happens, finer grain of control here.
2016-03-23 16:52:02 +00:00
$this -> log ( LOG_NOTICE , " OSTATUS [ { $notice -> getID () } ]: Not queueing because of privacy; scope = { $notice -> scope } " );
2010-06-04 00:58:45 +01:00
}
2010-02-08 19:06:03 +00:00
return true ;
}
/**
* Set up a PuSH hub link to our internal link for canonical timeline
2010-02-10 02:32:52 +00:00
* Atom feeds for users and groups .
2010-02-08 19:06:03 +00:00
*/
2010-02-18 18:20:48 +00:00
function onStartApiAtom ( $feed )
2010-02-08 19:06:03 +00:00
{
2010-02-13 03:00:35 +00:00
$id = null ;
if ( $feed instanceof AtomUserNoticeFeed ) {
2010-02-21 18:11:00 +00:00
$salmonAction = 'usersalmon' ;
$user = $feed -> getUser ();
$id = $user -> id ;
$profile = $user -> getProfile ();
2010-02-13 03:00:35 +00:00
} else if ( $feed instanceof AtomGroupNoticeFeed ) {
2010-02-21 18:11:00 +00:00
$salmonAction = 'groupsalmon' ;
$group = $feed -> getGroup ();
$id = $group -> id ;
2011-03-06 19:15:34 +00:00
} else if ( $feed instanceof AtomListNoticeFeed ) {
$salmonAction = 'peopletagsalmon' ;
$peopletag = $feed -> getList ();
$id = $peopletag -> id ;
2010-02-11 20:02:17 +00:00
} else {
2010-02-21 18:11:00 +00:00
return true ;
2010-02-11 20:02:17 +00:00
}
2010-02-10 22:58:39 +00:00
2010-02-21 18:11:00 +00:00
if ( ! empty ( $id )) {
2010-02-11 20:02:17 +00:00
$hub = common_config ( 'ostatus' , 'hub' );
if ( empty ( $hub )) {
// Updates will be handled through our internal PuSH hub.
$hub = common_local_url ( 'pushhub' );
2010-02-08 19:06:03 +00:00
}
2010-02-13 03:00:35 +00:00
$feed -> addLink ( $hub , array ( 'rel' => 'hub' ));
2010-02-11 20:02:17 +00:00
// Also, we'll add in the salmon link
$salmon = common_local_url ( $salmonAction , array ( 'id' => $id ));
2010-08-02 18:23:55 +01:00
$feed -> addLink ( $salmon , array ( 'rel' => Salmon :: REL_SALMON ));
2013-11-01 16:00:12 +00:00
// XXX: these are deprecated, but StatusNet only looks for NS_REPLIES
2010-02-26 18:17:24 +00:00
$feed -> addLink ( $salmon , array ( 'rel' => Salmon :: NS_REPLIES ));
$feed -> addLink ( $salmon , array ( 'rel' => Salmon :: NS_MENTIONS ));
2010-02-08 19:06:03 +00:00
}
2010-02-21 18:11:00 +00:00
return true ;
2010-02-08 19:06:03 +00:00
}
2010-02-13 03:00:35 +00:00
2010-02-09 06:37:45 +00:00
/**
* Add in an OStatus subscribe button
*/
2010-02-13 17:42:00 +00:00
function onStartProfileRemoteSubscribe ( $output , $profile )
2011-03-06 19:15:34 +00:00
{
$this -> onStartProfileListItemActionElements ( $output , $profile );
return false ;
}
2011-03-22 02:26:25 +00:00
function onStartGroupSubscribe ( $widget , $group )
2010-02-09 06:37:45 +00:00
{
$cur = common_current_user ();
if ( empty ( $cur )) {
2011-09-17 22:28:01 +01:00
$widget -> out -> elementStart ( 'li' , 'entity_subscribe' );
2011-09-18 17:44:16 +01:00
2010-02-09 06:37:45 +00:00
$url = common_local_url ( 'ostatusinit' ,
2011-03-06 19:15:34 +00:00
array ( 'group' => $group -> nickname ));
2011-03-22 02:26:25 +00:00
$widget -> out -> element ( 'a' , array ( 'href' => $url ,
2011-09-18 22:32:59 +01:00
'class' => 'entity_remote_subscribe' ),
2011-04-10 23:39:27 +01:00
// TRANS: Link to subscribe to a remote entity.
2010-02-13 17:42:00 +00:00
_m ( 'Subscribe' ));
2010-02-13 03:00:35 +00:00
2011-09-17 22:28:01 +01:00
$widget -> out -> elementEnd ( 'li' );
2011-03-22 02:26:25 +00:00
return false ;
2010-02-09 06:37:45 +00:00
}
2010-02-13 17:42:00 +00:00
2011-03-06 19:15:34 +00:00
return true ;
2010-02-09 06:37:45 +00:00
}
2011-03-06 19:15:34 +00:00
function onStartSubscribePeopletagForm ( $output , $peopletag )
2010-03-03 17:02:10 +00:00
{
$cur = common_current_user ();
if ( empty ( $cur )) {
2011-03-06 19:15:34 +00:00
$output -> elementStart ( 'li' , 'entity_subscribe' );
$profile = $peopletag -> getTagger ();
2010-03-03 17:02:10 +00:00
$url = common_local_url ( 'ostatusinit' ,
2011-03-06 19:15:34 +00:00
array ( 'tagger' => $profile -> nickname , 'peopletag' => $peopletag -> tag ));
2010-03-03 17:02:10 +00:00
$output -> element ( 'a' , array ( 'href' => $url ,
'class' => 'entity_remote_subscribe' ),
2011-04-10 23:39:27 +01:00
// TRANS: Link to subscribe to a remote entity.
2011-03-06 19:15:34 +00:00
_m ( 'Subscribe' ));
$output -> elementEnd ( 'li' );
return false ;
}
return true ;
}
/*
* If the field being looked for is URI look for the profile
*/
function onStartProfileCompletionSearch ( $action , $profile , $search_engine ) {
if ( $action -> field == 'uri' ) {
2011-08-27 17:06:34 +01:00
$profile -> joinAdd ( array ( 'id' , 'user:id' ));
2011-03-06 19:15:34 +00:00
$profile -> whereAdd ( 'uri LIKE "%' . $profile -> escape ( $q ) . '%"' );
$profile -> query ();
2015-11-08 09:33:41 +00:00
$validate = new Validate ();
2011-03-06 19:15:34 +00:00
if ( $profile -> N == 0 ) {
try {
2015-11-08 09:33:41 +00:00
if ( $validate -> email ( $q )) {
2011-03-06 19:15:34 +00:00
$oprofile = Ostatus_profile :: ensureWebfinger ( $q );
2015-11-08 09:33:41 +00:00
} else if ( $validate -> uri ( $q )) {
2011-03-06 19:15:34 +00:00
$oprofile = Ostatus_profile :: ensureProfileURL ( $q );
} else {
2011-04-10 23:39:27 +01:00
// TRANS: Exception in OStatus when invalid URI was entered.
throw new Exception ( _m ( 'Invalid URI.' ));
2011-03-06 19:15:34 +00:00
}
return $this -> filter ( array ( $oprofile -> localProfile ()));
} catch ( Exception $e ) {
2011-05-20 15:03:29 +01:00
// TRANS: Error message in OStatus plugin. Do not translate the domain names example.com
// TRANS: and example.net, as these are official standard domain names for use in examples.
2011-04-10 23:39:27 +01:00
$this -> msg = _m ( " Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname. " );
2011-03-06 19:15:34 +00:00
return array ();
}
}
return false ;
}
2010-03-03 21:26:02 +00:00
return true ;
2010-03-03 17:02:10 +00:00
}
2017-04-22 09:58:14 +01:00
/**
* Webfinger matches : @ user @ example . com or even @ user -- one . george_orwell @ 1984. biz
*
* @ return array The matching IDs ( without @ or acct : ) and each respective position in the given string .
*/
static function extractWebfingerIds ( $text )
{
$wmatches = array ();
2017-04-22 10:07:38 +01:00
$result = preg_match_all ( '/(?:^|\s+)@((?:\w+[\w\-\_\.]?)*(?:[\w\-\_\.]*\w+)@' . URL_REGEX_DOMAIN_NAME . ')/' ,
2017-04-22 09:58:14 +01:00
$text ,
$wmatches ,
PREG_OFFSET_CAPTURE );
if ( $result === false ) {
common_log ( LOG_ERR , __METHOD__ . ': Error parsing webfinger IDs from text (preg_last_error==' . preg_last_error () . ').' );
} else {
common_debug ( sprintf ( 'Found %i matches for WebFinger IDs: %s' , count ( $wmatches ), _ve ( $wmatches )));
}
return $wmatches [ 1 ];
}
/**
* Profile URL matches : @ example . com / mublog / user
*
* @ return array The matching URLs ( without @ or acct : ) and each respective position in the given string .
*/
static function extractUrlMentions ( $text )
{
$wmatches = array ();
2017-04-22 10:15:55 +01:00
$result = preg_match_all ( '/(?:^|\s+)@(' . URL_REGEX_DOMAIN_NAME . '(?:\/\w+)*)/' ,
2017-04-22 09:58:14 +01:00
$text ,
$wmatches ,
PREG_OFFSET_CAPTURE );
if ( $result === false ) {
common_log ( LOG_ERR , __METHOD__ . ': Error parsing profile URL mentions from text (preg_last_error==' . preg_last_error () . ').' );
} else {
common_debug ( sprintf ( 'Found %i matches for profile URL mentions: %s' , count ( $wmatches ), _ve ( $wmatches )));
}
return $wmatches [ 1 ];
}
2010-02-11 00:09:20 +00:00
/**
2010-03-02 00:36:33 +00:00
* Find any explicit remote mentions . Accepted forms :
* Webfinger : @ user @ example . com
* Profile link : @ example . com / mublog / user
2014-02-23 20:49:55 +00:00
* @ param Profile $sender
2010-03-02 00:36:33 +00:00
* @ param string $text input markup text
* @ param array & $mention in / out param : set of found mentions
* @ return boolean hook return value
2010-02-11 00:09:20 +00:00
*/
2013-10-29 10:27:26 +00:00
function onEndFindMentions ( Profile $sender , $text , & $mentions )
2010-02-11 00:09:20 +00:00
{
2010-03-03 17:32:25 +00:00
$matches = array ();
2017-04-22 09:58:14 +01:00
foreach ( self :: extractWebfingerIds ( $text ) as $wmatch ) {
list ( $target , $pos ) = $wmatch ;
$this -> log ( LOG_INFO , " Checking webfinger ' $target ' " );
$profile = null ;
try {
$oprofile = Ostatus_profile :: ensureWebfinger ( $target );
if ( ! $oprofile instanceof Ostatus_profile || ! $oprofile -> isPerson ()) {
2016-01-29 15:06:16 +00:00
continue ;
2010-03-02 00:36:33 +00:00
}
2017-04-22 09:58:14 +01:00
$profile = $oprofile -> localProfile ();
} catch ( OStatusShadowException $e ) {
// This means we got a local user in the webfinger lookup
$profile = $e -> profile ;
} catch ( Exception $e ) {
$this -> log ( LOG_ERR , " Webfinger check failed: " . $e -> getMessage ());
continue ;
}
2016-01-29 15:06:16 +00:00
2017-04-22 09:58:14 +01:00
assert ( $profile instanceof Profile );
2016-01-29 15:06:16 +00:00
2017-04-22 09:58:14 +01:00
$text = ! empty ( $profile -> nickname ) && mb_strlen ( $profile -> nickname ) < mb_strlen ( $target )
? $profile -> getNickname () // TODO: we could do getBestName() or getFullname() here
: $target ;
$url = $profile -> getUri ();
if ( ! common_valid_http_url ( $url )) {
$url = $profile -> getUrl ();
2010-03-03 17:32:25 +00:00
}
2017-04-22 09:58:14 +01:00
$matches [ $pos ] = array ( 'mentioned' => array ( $profile ),
'type' => 'mention' ,
'text' => $text ,
'position' => $pos ,
'length' => mb_strlen ( $target ),
'url' => $url );
2010-03-03 17:32:25 +00:00
}
2017-04-22 09:58:14 +01:00
foreach ( self :: extractUrlMentions ( $text ) as $wmatch ) {
list ( $target , $pos ) = $wmatch ;
$schemes = array ( 'http' , 'https' );
foreach ( $schemes as $scheme ) {
$url = " $scheme :// $target " ;
$this -> log ( LOG_INFO , " Checking profile address ' $url ' " );
try {
$oprofile = Ostatus_profile :: ensureProfileURL ( $url );
if ( $oprofile instanceof Ostatus_profile && ! $oprofile -> isGroup ()) {
$profile = $oprofile -> localProfile ();
$text = ! empty ( $profile -> nickname ) && mb_strlen ( $profile -> nickname ) < mb_strlen ( $target ) ?
$profile -> nickname : $target ;
$matches [ $pos ] = array ( 'mentioned' => array ( $profile ),
'type' => 'mention' ,
'text' => $text ,
'position' => $pos ,
'length' => mb_strlen ( $target ),
'url' => $profile -> getUrl ());
break ;
2010-03-02 00:36:33 +00:00
}
2017-04-22 09:58:14 +01:00
} catch ( Exception $e ) {
$this -> log ( LOG_ERR , " Profile check failed: " . $e -> getMessage ());
2010-03-02 00:36:33 +00:00
}
}
2010-03-03 17:32:25 +00:00
}
2010-02-21 15:53:32 +00:00
2010-03-03 17:32:25 +00:00
foreach ( $mentions as $i => $other ) {
// If we share a common prefix with a local user, override it!
$pos = $other [ 'position' ];
if ( isset ( $matches [ $pos ])) {
$mentions [ $i ] = $matches [ $pos ];
unset ( $matches [ $pos ]);
2010-02-09 20:37:37 +00:00
}
}
2010-03-03 17:32:25 +00:00
foreach ( $matches as $mention ) {
$mentions [] = $mention ;
}
2010-02-21 15:53:32 +00:00
2010-02-11 00:09:20 +00:00
return true ;
}
2010-03-08 22:01:43 +00:00
/**
* Allow remote profile references to be used in commands :
* sub update @ status . net
* whois evan @ identi . ca
* reply http :// identi . ca / evan hey what ' s up
*
* @ param Command $command
* @ param string $arg
* @ param Profile & $profile
* @ return hook return code
*/
function onStartCommandGetProfile ( $command , $arg , & $profile )
{
$oprofile = $this -> pullRemoteProfile ( $arg );
2014-05-06 13:36:52 +01:00
if ( $oprofile instanceof Ostatus_profile && ! $oprofile -> isGroup ()) {
2014-05-19 16:58:05 +01:00
try {
$profile = $oprofile -> localProfile ();
} catch ( NoProfileException $e ) {
// No locally stored profile found for remote profile
return true ;
}
2010-03-08 22:01:43 +00:00
return false ;
} else {
return true ;
}
}
/**
* Allow remote group references to be used in commands :
* join group + statusnet @ identi . ca
* join http :// identi . ca / group / statusnet
* drop identi . ca / group / statusnet
*
* @ param Command $command
* @ param string $arg
* @ param User_group & $group
* @ return hook return code
*/
function onStartCommandGetGroup ( $command , $arg , & $group )
{
$oprofile = $this -> pullRemoteProfile ( $arg );
2014-05-06 13:36:52 +01:00
if ( $oprofile instanceof Ostatus_profile && $oprofile -> isGroup ()) {
2010-03-08 22:01:43 +00:00
$group = $oprofile -> localGroup ();
return false ;
} else {
return true ;
}
}
protected function pullRemoteProfile ( $arg )
{
$oprofile = null ;
if ( preg_match ( '!^((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)$!' , $arg )) {
// webfinger lookup
try {
return Ostatus_profile :: ensureWebfinger ( $arg );
} catch ( Exception $e ) {
common_log ( LOG_ERR , 'Webfinger lookup failed for ' .
$arg . ': ' . $e -> getMessage ());
}
}
// Look for profile URLs, with or without scheme:
$urls = array ();
if ( preg_match ( '!^https?://((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)$!' , $arg )) {
$urls [] = $arg ;
}
if ( preg_match ( '!^((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)$!' , $arg )) {
$schemes = array ( 'http' , 'https' );
foreach ( $schemes as $scheme ) {
$urls [] = " $scheme :// $arg " ;
}
}
foreach ( $urls as $url ) {
try {
2010-03-18 20:21:26 +00:00
return Ostatus_profile :: ensureProfileURL ( $url );
2010-03-08 22:01:43 +00:00
} catch ( Exception $e ) {
common_log ( LOG_ERR , 'Profile lookup failed for ' .
$arg . ': ' . $e -> getMessage ());
}
}
return null ;
}
2015-10-28 01:10:28 +00:00
function onEndProfileSettingsActions ( $out ) {
$siteName = common_config ( 'site' , 'name' );
$js = 'navigator.registerContentHandler("application/vnd.mozilla.maybe.feed", "' . addslashes ( common_local_url ( 'ostatussub' , null , array ( 'profile' => '%s' ))) . '", "' . addslashes ( $siteName ) . '")' ;
$out -> elementStart ( 'li' );
$out -> element ( 'a' ,
array ( 'href' => 'javascript:' . $js ),
// TRANS: Option in profile settings to add this instance to Firefox as a feedreader
_ ( 'Add to Firefox as feedreader' ));
$out -> elementEnd ( 'li' );
}
2010-02-11 20:02:17 +00:00
/**
* Make sure necessary tables are filled out .
*/
2009-11-20 04:55:38 +00:00
function onCheckSchema () {
$schema = Schema :: get ();
2010-02-12 00:22:16 +00:00
$schema -> ensureTable ( 'ostatus_profile' , Ostatus_profile :: schemaDef ());
2010-02-18 21:22:21 +00:00
$schema -> ensureTable ( 'feedsub' , FeedSub :: schemaDef ());
2010-02-08 19:06:03 +00:00
$schema -> ensureTable ( 'hubsub' , HubSub :: schemaDef ());
2010-02-23 04:11:40 +00:00
$schema -> ensureTable ( 'magicsig' , Magicsig :: schemaDef ());
2009-11-20 04:55:38 +00:00
return true ;
2010-02-11 00:09:20 +00:00
}
2010-02-13 18:07:21 +00:00
2013-09-17 19:41:54 +01:00
public function onEndShowStylesheets ( Action $action ) {
2011-02-03 16:09:26 +00:00
$action -> cssLink ( $this -> path ( 'theme/base/css/ostatus.css' ));
2010-02-13 18:07:21 +00:00
return true ;
}
2010-02-13 19:28:05 +00:00
function onEndShowStatusNetScripts ( $action ) {
2011-02-03 16:09:26 +00:00
$action -> script ( $this -> path ( 'js/ostatus.js' ));
2010-02-13 19:28:05 +00:00
return true ;
}
2010-02-17 02:16:03 +00:00
2010-02-18 18:20:48 +00:00
/**
* Override the " from ostatus " bit in notice lists to link to the
* original post and show the domain it came from .
*
* @ param Notice in $notice
* @ param string out & $name
* @ param string out & $url
* @ param string out & $title
* @ return mixed hook return code
*/
2010-02-17 02:16:03 +00:00
function onStartNoticeSourceLink ( $notice , & $name , & $url , & $title )
{
2014-04-19 21:18:36 +01:00
// If we don't handle this, keep the event handler going
2015-10-14 00:30:29 +01:00
if ( ! in_array ( $notice -> source , array ( 'ostatus' , 'share' ))) {
2014-04-19 21:18:36 +01:00
return true ;
}
2010-02-17 02:16:03 +00:00
2014-04-19 21:18:36 +01:00
try {
$url = $notice -> getUrl ();
// If getUrl() throws exception, $url is never set
$bits = parse_url ( $url );
$domain = $bits [ 'host' ];
if ( substr ( $domain , 0 , 4 ) == 'www.' ) {
$name = substr ( $domain , 4 );
} else {
$name = $domain ;
2010-02-23 21:47:14 +00:00
}
2014-04-19 21:18:36 +01:00
// TRANS: Title. %s is a domain name.
$title = sprintf ( _m ( 'Sent from %s via OStatus' ), $domain );
// Abort event handler, we have a name and URL!
return false ;
} catch ( InvalidUrlException $e ) {
// This just means we don't have the notice source data
return true ;
2010-02-17 02:16:03 +00:00
}
}
2010-02-18 21:22:21 +00:00
/**
* Send incoming PuSH feeds for OStatus endpoints in for processing .
*
* @ param FeedSub $feedsub
* @ param DOMDocument $feed
* @ return mixed hook return code
*/
function onStartFeedSubReceive ( $feedsub , $feed )
{
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'feeduri' , $feedsub -> uri );
2013-11-01 12:20:23 +00:00
if ( $oprofile instanceof Ostatus_profile ) {
2010-02-24 01:09:52 +00:00
$oprofile -> processFeed ( $feed , 'push' );
2010-02-20 00:21:17 +00:00
} else {
common_log ( LOG_DEBUG , " No ostatus profile for incoming feed $feedsub->uri " );
2010-02-18 21:22:21 +00:00
}
}
2010-02-20 16:48:42 +00:00
2010-08-06 18:56:18 +01:00
/**
* Tell the FeedSub infrastructure whether we have any active OStatus
* usage for the feed ; if not it ' ll be able to garbage - collect the
* feed subscription .
2010-09-01 19:21:24 +01:00
*
2010-08-06 18:56:18 +01:00
* @ param FeedSub $feedsub
* @ param integer $count in / out
* @ return mixed hook return code
*/
function onFeedSubSubscriberCount ( $feedsub , & $count )
{
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'feeduri' , $feedsub -> uri );
2014-05-06 13:36:52 +01:00
if ( $oprofile instanceof Ostatus_profile ) {
2010-08-06 18:56:18 +01:00
$count += $oprofile -> subscriberCount ();
}
return true ;
}
2010-02-23 20:44:27 +00:00
/**
* When about to subscribe to a remote user , start a server - to - server
* PuSH subscription if needed . If we can ' t establish that , abort .
*
* @ fixme If something else aborts later , we could end up with a stray
* PuSH subscription . This is relatively harmless , though .
*
2013-09-09 20:35:16 +01:00
* @ param Profile $profile subscriber
* @ param Profile $other subscribee
2010-02-23 20:44:27 +00:00
*
* @ return hook return code
*
* @ throws Exception
*/
2013-09-09 20:35:16 +01:00
function onStartSubscribe ( Profile $profile , Profile $other )
2010-02-23 20:44:27 +00:00
{
2013-09-09 20:35:16 +01:00
if ( ! $profile -> isLocal ()) {
2010-02-23 20:44:27 +00:00
return true ;
}
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'profile_id' , $other -> id );
2014-05-06 13:36:52 +01:00
if ( ! $oprofile instanceof Ostatus_profile ) {
2010-02-23 20:44:27 +00:00
return true ;
}
2014-05-06 14:40:57 +01:00
$oprofile -> subscribe ();
2010-02-23 20:44:27 +00:00
}
/**
* Having established a remote subscription , send a notification to the
* remote OStatus profile ' s endpoint .
*
2013-09-09 20:35:16 +01:00
* @ param Profile $profile subscriber
* @ param Profile $other subscribee
2010-02-23 20:44:27 +00:00
*
* @ return hook return code
*
* @ throws Exception
*/
2013-09-09 20:35:16 +01:00
function onEndSubscribe ( Profile $profile , Profile $other )
2010-02-20 16:48:42 +00:00
{
2013-09-09 20:35:16 +01:00
if ( ! $profile -> isLocal ()) {
2010-02-20 16:48:42 +00:00
return true ;
}
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'profile_id' , $other -> id );
2014-05-06 13:36:52 +01:00
if ( ! $oprofile instanceof Ostatus_profile ) {
2010-02-20 16:48:42 +00:00
return true ;
}
2013-09-09 20:35:16 +01:00
$sub = Subscription :: pkeyGet ( array ( 'subscriber' => $profile -> id ,
2010-08-04 20:07:49 +01:00
'subscribed' => $other -> id ));
2010-02-21 15:53:32 +00:00
2010-08-04 20:07:49 +01:00
$act = $sub -> asActivity ();
2010-02-21 15:53:32 +00:00
2013-09-09 20:35:16 +01:00
$oprofile -> notifyActivity ( $act , $profile );
2010-02-20 16:48:42 +00:00
return true ;
}
2010-02-23 20:44:27 +00:00
/**
* Notify remote server and garbage collect unused feeds on unsubscribe .
2011-04-10 23:39:27 +01:00
* @ todo FIXME : Send these operations to background queues
2010-02-23 20:44:27 +00:00
*
* @ param User $user
* @ param Profile $other
* @ return hook return value
*/
2013-09-09 20:35:16 +01:00
function onEndUnsubscribe ( Profile $profile , Profile $other )
2010-02-23 20:44:27 +00:00
{
2013-09-09 20:35:16 +01:00
if ( ! $profile -> isLocal ()) {
2010-02-23 20:44:27 +00:00
return true ;
}
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'profile_id' , $other -> id );
2014-05-06 13:36:52 +01:00
if ( ! $oprofile instanceof Ostatus_profile ) {
2010-02-23 20:44:27 +00:00
return true ;
}
// Drop the PuSH subscription if there are no other subscribers.
$oprofile -> garbageCollect ();
$act = new Activity ();
$act -> verb = ActivityVerb :: UNFOLLOW ;
$act -> id = TagURI :: mint ( 'unfollow:%d:%d:%s' ,
$profile -> id ,
$other -> id ,
common_date_iso8601 ( time ()));
$act -> time = time ();
2011-04-10 23:39:27 +01:00
// TRANS: Title for unfollowing a remote profile.
$act -> title = _m ( 'TITLE' , 'Unfollow' );
2010-09-03 00:35:04 +01:00
// TRANS: Success message for unsubscribe from user attempt through OStatus.
// TRANS: %1$s is the unsubscriber's name, %2$s is the unsubscribed user's name.
2010-09-19 14:17:36 +01:00
$act -> content = sprintf ( _m ( '%1$s stopped following %2$s.' ),
2010-02-23 20:44:27 +00:00
$profile -> getBestName (),
$other -> getBestName ());
2014-07-02 17:50:28 +01:00
$act -> actor = $profile -> asActivityObject ();
2016-02-23 22:50:57 +00:00
$act -> objects [] = $other -> asActivityObject ();
2010-02-23 20:44:27 +00:00
2010-02-26 19:21:21 +00:00
$oprofile -> notifyActivity ( $act , $profile );
2010-02-23 20:44:27 +00:00
return true ;
}
2010-02-22 17:43:27 +00:00
/**
* When one of our local users tries to join a remote group ,
* notify the remote server . If the notification is rejected ,
* deny the join .
*
* @ param User_group $group
2011-09-18 19:06:35 +01:00
* @ param Profile $profile
2010-02-22 17:43:27 +00:00
*
* @ return mixed hook return value
2014-05-06 14:40:57 +01:00
* @ throws Exception of various kinds , some from $oprofile -> subscribe ();
2010-02-22 17:43:27 +00:00
*/
2011-09-18 19:06:35 +01:00
function onStartJoinGroup ( $group , $profile )
2010-02-22 17:43:27 +00:00
{
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'group_id' , $group -> id );
2014-05-06 13:36:52 +01:00
if ( ! $oprofile instanceof Ostatus_profile ) {
return true ;
}
2010-02-23 20:44:27 +00:00
2014-05-06 14:40:57 +01:00
$oprofile -> subscribe ();
2014-05-06 13:36:52 +01:00
// NOTE: we don't use Group_member::asActivity() since that record
// has not yet been created.
$act = new Activity ();
$act -> id = TagURI :: mint ( 'join:%d:%d:%s' ,
$profile -> id ,
$group -> id ,
common_date_iso8601 ( time ()));
2014-07-02 17:50:28 +01:00
$act -> actor = $profile -> asActivityObject ();
2014-05-06 13:36:52 +01:00
$act -> verb = ActivityVerb :: JOIN ;
2016-02-23 22:50:57 +00:00
$act -> objects [] = $oprofile -> asActivityObject ();
2014-05-06 13:36:52 +01:00
$act -> time = time ();
// TRANS: Title for joining a remote groep.
$act -> title = _m ( 'TITLE' , 'Join' );
// TRANS: Success message for subscribe to group attempt through OStatus.
// TRANS: %1$s is the member name, %2$s is the subscribed group's name.
$act -> content = sprintf ( _m ( '%1$s has joined group %2$s.' ),
$profile -> getBestName (),
$oprofile -> getBestName ());
if ( $oprofile -> notifyActivity ( $act , $profile )) {
return true ;
} else {
$oprofile -> garbageCollect ();
// TRANS: Exception thrown when joining a remote group fails.
throw new Exception ( _m ( 'Failed joining remote group.' ));
2010-02-22 17:43:27 +00:00
}
}
/**
* When one of our local users leaves a remote group , notify the remote
* server .
*
* @ fixme Might be good to schedule a resend of the leave notification
* if it failed due to a transitory error . We ' ve canceled the local
* membership already anyway , but if the remote server comes back up
* it ' ll be left with a stray membership record .
*
* @ param User_group $group
2011-09-18 19:06:35 +01:00
* @ param Profile $profile
2010-02-22 17:43:27 +00:00
*
* @ return mixed hook return value
*/
2011-09-19 00:28:17 +01:00
function onEndLeaveGroup ( $group , $profile )
2010-02-22 17:43:27 +00:00
{
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'group_id' , $group -> id );
2014-05-06 13:36:52 +01:00
if ( ! $oprofile instanceof Ostatus_profile ) {
return true ;
}
2010-02-23 20:59:10 +00:00
2014-05-06 13:36:52 +01:00
// Drop the PuSH subscription if there are no other subscribers.
$oprofile -> garbageCollect ();
2010-02-22 17:43:27 +00:00
2014-05-06 13:36:52 +01:00
$member = $profile ;
2010-02-22 17:43:27 +00:00
2014-05-06 13:36:52 +01:00
$act = new Activity ();
$act -> id = TagURI :: mint ( 'leave:%d:%d:%s' ,
$member -> id ,
$group -> id ,
common_date_iso8601 ( time ()));
2010-02-22 17:43:27 +00:00
2014-07-02 17:50:28 +01:00
$act -> actor = $member -> asActivityObject ();
2014-05-06 13:36:52 +01:00
$act -> verb = ActivityVerb :: LEAVE ;
2016-02-23 22:50:57 +00:00
$act -> objects [] = $oprofile -> asActivityObject ();
2010-02-22 17:43:27 +00:00
2014-05-06 13:36:52 +01:00
$act -> time = time ();
// TRANS: Title for leaving a remote group.
$act -> title = _m ( 'TITLE' , 'Leave' );
// TRANS: Success message for unsubscribe from group attempt through OStatus.
// TRANS: %1$s is the member name, %2$s is the unsubscribed group's name.
$act -> content = sprintf ( _m ( '%1$s has left group %2$s.' ),
$member -> getBestName (),
$oprofile -> getBestName ());
$oprofile -> notifyActivity ( $act , $member );
2010-02-22 17:43:27 +00:00
}
2011-03-06 19:15:34 +00:00
/**
* When one of our local users tries to subscribe to a remote peopletag ,
* notify the remote server . If the notification is rejected ,
* deny the subscription .
*
* @ param Profile_list $peopletag
* @ param User $user
*
* @ return mixed hook return value
2014-05-06 14:40:57 +01:00
* @ throws Exception of various kinds , some from $oprofile -> subscribe ();
2011-03-06 19:15:34 +00:00
*/
function onStartSubscribePeopletag ( $peopletag , $user )
{
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'peopletag_id' , $peopletag -> id );
2014-05-06 13:36:52 +01:00
if ( ! $oprofile instanceof Ostatus_profile ) {
return true ;
}
2011-03-06 19:15:34 +00:00
2014-05-06 14:40:57 +01:00
$oprofile -> subscribe ();
2014-05-06 13:36:52 +01:00
$sub = $user -> getProfile ();
$tagger = Profile :: getKV ( $peopletag -> tagger );
$act = new Activity ();
$act -> id = TagURI :: mint ( 'subscribe_peopletag:%d:%d:%s' ,
$sub -> id ,
$peopletag -> id ,
common_date_iso8601 ( time ()));
2014-07-02 17:50:28 +01:00
$act -> actor = $sub -> asActivityObject ();
2014-05-06 13:36:52 +01:00
$act -> verb = ActivityVerb :: FOLLOW ;
2016-02-23 22:50:57 +00:00
$act -> objects [] = $oprofile -> asActivityObject ();
2014-05-06 13:36:52 +01:00
$act -> time = time ();
// TRANS: Title for following a remote list.
$act -> title = _m ( 'TITLE' , 'Follow list' );
// TRANS: Success message for remote list follow through OStatus.
// TRANS: %1$s is the subscriber name, %2$s is the list, %3$s is the lister's name.
$act -> content = sprintf ( _m ( '%1$s is now following people listed in %2$s by %3$s.' ),
$sub -> getBestName (),
$oprofile -> getBestName (),
$tagger -> getBestName ());
if ( $oprofile -> notifyActivity ( $act , $sub )) {
return true ;
} else {
$oprofile -> garbageCollect ();
// TRANS: Exception thrown when subscription to remote list fails.
throw new Exception ( _m ( 'Failed subscribing to remote list.' ));
2011-03-06 19:15:34 +00:00
}
}
/**
* When one of our local users unsubscribes to a remote peopletag , notify the remote
* server .
*
* @ param Profile_list $peopletag
* @ param User $user
*
* @ return mixed hook return value
*/
function onEndUnsubscribePeopletag ( $peopletag , $user )
{
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'peopletag_id' , $peopletag -> id );
2014-05-06 13:36:52 +01:00
if ( ! $oprofile instanceof Ostatus_profile ) {
return true ;
2011-03-06 19:15:34 +00:00
}
2014-05-06 13:36:52 +01:00
// Drop the PuSH subscription if there are no other subscribers.
$oprofile -> garbageCollect ();
$sub = Profile :: getKV ( $user -> id );
$tagger = Profile :: getKV ( $peopletag -> tagger );
$act = new Activity ();
$act -> id = TagURI :: mint ( 'unsubscribe_peopletag:%d:%d:%s' ,
$sub -> id ,
$peopletag -> id ,
common_date_iso8601 ( time ()));
2014-07-02 17:50:28 +01:00
$act -> actor = $member -> asActivityObject ();
2014-05-06 13:36:52 +01:00
$act -> verb = ActivityVerb :: UNFOLLOW ;
2016-02-23 22:50:57 +00:00
$act -> objects [] = $oprofile -> asActivityObject ();
2014-05-06 13:36:52 +01:00
$act -> time = time ();
// TRANS: Title for unfollowing a remote list.
$act -> title = _m ( 'Unfollow list' );
// TRANS: Success message for remote list unfollow through OStatus.
// TRANS: %1$s is the subscriber name, %2$s is the list, %3$s is the lister's name.
$act -> content = sprintf ( _m ( '%1$s stopped following the list %2$s by %3$s.' ),
$sub -> getBestName (),
$oprofile -> getBestName (),
$tagger -> getBestName ());
$oprofile -> notifyActivity ( $act , $user );
2011-03-06 19:15:34 +00:00
}
2015-02-03 16:50:21 +00:00
/**
* Notify remote users when their notices get favorited .
*
* @ param Profile or User $profile of local user doing the faving
* @ param Notice $notice being favored
* @ return hook return value
*/
function onEndFavorNotice ( Profile $profile , Notice $notice )
{
// Only distribute local users' favor actions, remote users
// will have already distributed theirs.
if ( ! $profile -> isLocal ()) {
return true ;
}
$oprofile = Ostatus_profile :: getKV ( 'profile_id' , $notice -> profile_id );
if ( ! $oprofile instanceof Ostatus_profile ) {
return true ;
}
$fav = Fave :: pkeyGet ( array ( 'user_id' => $profile -> id ,
'notice_id' => $notice -> id ));
if ( ! $fav instanceof Fave ) {
// That's weird.
// TODO: Make pkeyGet throw exception, since this is a critical failure.
return true ;
}
$act = $fav -> asActivity ();
$oprofile -> notifyActivity ( $act , $profile );
return true ;
}
2011-04-09 12:47:32 +01:00
/**
* Notify remote user it has got a new people tag
* - tag verb is queued
* - the subscription is done immediately if not present
*
* @ param Profile_tag $ptag the people tag that was created
* @ return hook return value
2014-05-06 14:40:57 +01:00
* @ throws Exception of various kinds , some from $oprofile -> subscribe ();
2011-04-09 12:47:32 +01:00
*/
2011-03-06 19:15:34 +00:00
function onEndTagProfile ( $ptag )
{
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'profile_id' , $ptag -> tagged );
2014-05-06 13:36:52 +01:00
if ( ! $oprofile instanceof Ostatus_profile ) {
2011-03-06 19:15:34 +00:00
return true ;
}
$plist = $ptag -> getMeta ();
if ( $plist -> private ) {
return true ;
}
$act = new Activity ();
$tagger = $plist -> getTagger ();
2013-08-18 12:04:58 +01:00
$tagged = Profile :: getKV ( 'id' , $ptag -> tagged );
2011-03-06 19:15:34 +00:00
$act -> verb = ActivityVerb :: TAG ;
$act -> id = TagURI :: mint ( 'tag_profile:%d:%d:%s' ,
$plist -> tagger , $plist -> id ,
common_date_iso8601 ( time ()));
$act -> time = time ();
2011-04-29 17:59:47 +01:00
// TRANS: Title for listing a remote profile.
$act -> title = _m ( 'TITLE' , 'List' );
// TRANS: Success message for remote list addition through OStatus.
// TRANS: %1$s is the list creator's name, %2$s is the added list member, %3$s is the list name.
2011-04-13 09:15:25 +01:00
$act -> content = sprintf ( _m ( '%1$s listed %2$s in the list %3$s.' ),
2011-03-06 19:15:34 +00:00
$tagger -> getBestName (),
$tagged -> getBestName (),
$plist -> getBestName ());
2014-07-02 17:50:28 +01:00
$act -> actor = $tagger -> asActivityObject ();
$act -> objects = array ( $tagged -> asActivityObject ());
2011-03-06 19:15:34 +00:00
$act -> target = ActivityObject :: fromPeopletag ( $plist );
2011-04-09 12:47:32 +01:00
$oprofile -> notifyDeferred ( $act , $tagger );
2011-03-06 19:15:34 +00:00
// initiate a PuSH subscription for the person being tagged
2014-05-06 14:40:57 +01:00
$oprofile -> subscribe ();
2011-03-06 19:15:34 +00:00
return true ;
}
2011-04-09 12:47:32 +01:00
/**
* Notify remote user that a people tag has been removed
* - untag verb is queued
* - the subscription is undone immediately if not required
* i . e garbageCollect () ' d
*
* @ param Profile_tag $ptag the people tag that was deleted
* @ return hook return value
*/
2011-03-06 19:15:34 +00:00
function onEndUntagProfile ( $ptag )
{
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'profile_id' , $ptag -> tagged );
2014-05-06 13:36:52 +01:00
if ( ! $oprofile instanceof Ostatus_profile ) {
2011-03-06 19:15:34 +00:00
return true ;
}
$plist = $ptag -> getMeta ();
if ( $plist -> private ) {
return true ;
}
$act = new Activity ();
$tagger = $plist -> getTagger ();
2013-08-18 12:04:58 +01:00
$tagged = Profile :: getKV ( 'id' , $ptag -> tagged );
2011-03-06 19:15:34 +00:00
$act -> verb = ActivityVerb :: UNTAG ;
$act -> id = TagURI :: mint ( 'untag_profile:%d:%d:%s' ,
$plist -> tagger , $plist -> id ,
common_date_iso8601 ( time ()));
$act -> time = time ();
2011-04-29 17:59:47 +01:00
// TRANS: Title for unlisting a remote profile.
$act -> title = _m ( 'TITLE' , 'Unlist' );
// TRANS: Success message for remote list removal through OStatus.
// TRANS: %1$s is the list creator's name, %2$s is the removed list member, %3$s is the list name.
$act -> content = sprintf ( _m ( '%1$s removed %2$s from the list %3$s.' ),
2011-03-06 19:15:34 +00:00
$tagger -> getBestName (),
$tagged -> getBestName (),
$plist -> getBestName ());
2014-07-02 17:50:28 +01:00
$act -> actor = $tagger -> asActivityObject ();
$act -> objects = array ( $tagged -> asActivityObject ());
2011-03-06 19:15:34 +00:00
$act -> target = ActivityObject :: fromPeopletag ( $plist );
2011-04-09 12:47:32 +01:00
$oprofile -> notifyDeferred ( $act , $tagger );
2011-03-06 19:15:34 +00:00
// unsubscribe to PuSH feed if no more required
$oprofile -> garbageCollect ();
return true ;
}
2010-02-20 23:56:36 +00:00
/**
* Notify remote users when their notices get de - favorited .
*
2010-02-21 18:32:24 +00:00
* @ param Profile $profile Profile person doing the de - faving
* @ param Notice $notice Notice being favored
*
2010-02-20 23:56:36 +00:00
* @ return hook return value
*/
function onEndDisfavorNotice ( Profile $profile , Notice $notice )
2010-02-21 00:58:20 +00:00
{
2014-05-06 13:36:52 +01:00
// Only distribute local users' disfavor actions, remote users
// will have already distributed theirs.
if ( ! $profile -> isLocal ()) {
2010-02-21 01:34:29 +00:00
return true ;
}
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'profile_id' , $notice -> profile_id );
2014-05-06 13:36:52 +01:00
if ( ! $oprofile instanceof Ostatus_profile ) {
2010-02-21 15:53:32 +00:00
return true ;
2010-02-21 00:58:20 +00:00
}
2010-02-20 23:56:36 +00:00
2010-02-21 15:53:32 +00:00
$act = new Activity ();
$act -> verb = ActivityVerb :: UNFAVORITE ;
$act -> id = TagURI :: mint ( 'disfavor:%d:%d:%s' ,
$profile -> id ,
$notice -> id ,
common_date_iso8601 ( time ()));
$act -> time = time ();
2011-04-29 17:59:47 +01:00
// TRANS: Title for unliking a remote notice.
$act -> title = _m ( 'Unlike' );
2010-09-03 00:35:04 +01:00
// TRANS: Success message for remove a favorite notice through OStatus.
// TRANS: %1$s is the unfavoring user's name, %2$s is URI to the no longer favored notice.
2011-04-29 17:59:47 +01:00
$act -> content = sprintf ( _m ( '%1$s no longer likes %2$s.' ),
2010-02-21 15:53:32 +00:00
$profile -> getBestName (),
2014-04-30 19:44:23 +01:00
$notice -> getUrl ());
2010-02-21 15:53:32 +00:00
2014-07-02 17:50:28 +01:00
$act -> actor = $profile -> asActivityObject ();
2016-02-23 22:50:57 +00:00
$act -> objects [] = $notice -> asActivityObject ();
2010-02-21 15:53:32 +00:00
2010-02-26 19:21:21 +00:00
$oprofile -> notifyActivity ( $act , $profile );
2010-02-21 15:53:32 +00:00
2010-02-21 01:34:29 +00:00
return true ;
}
2010-02-22 03:52:52 +00:00
function onStartGetProfileUri ( $profile , & $uri )
{
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'profile_id' , $profile -> id );
2013-10-30 13:43:40 +00:00
if ( $oprofile instanceof Ostatus_profile ) {
2010-02-22 03:52:52 +00:00
$uri = $oprofile -> uri ;
return false ;
}
return true ;
}
2010-02-22 16:07:48 +00:00
2010-02-23 00:44:45 +00:00
function onStartUserGroupHomeUrl ( $group , & $url )
{
2010-02-25 19:59:15 +00:00
return $this -> onStartUserGroupPermalink ( $group , $url );
2010-02-23 00:44:45 +00:00
}
function onStartUserGroupPermalink ( $group , & $url )
{
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'group_id' , $group -> id );
2014-05-06 13:36:52 +01:00
if ( $oprofile instanceof Ostatus_profile ) {
2010-02-23 00:44:45 +00:00
// @fixme this should probably be in the user_group table
// @fixme this uri not guaranteed to be a profile page
$url = $oprofile -> uri ;
return false ;
}
}
2010-02-22 16:07:48 +00:00
function onStartShowSubscriptionsContent ( $action )
{
2010-03-02 17:41:18 +00:00
$this -> showEntityRemoteSubscribe ( $action );
2010-02-22 16:07:48 +00:00
return true ;
}
2010-02-24 20:36:36 +00:00
2010-03-03 20:30:43 +00:00
function onStartShowUserGroupsContent ( $action )
{
2010-03-03 21:40:26 +00:00
$this -> showEntityRemoteSubscribe ( $action , 'ostatusgroup' );
2010-03-03 20:30:43 +00:00
return true ;
}
2010-03-03 16:22:21 +00:00
function onEndShowSubscriptionsMiniList ( $action )
2010-03-02 17:41:18 +00:00
{
$this -> showEntityRemoteSubscribe ( $action );
return true ;
}
2010-03-03 17:56:19 +00:00
function onEndShowGroupsMiniList ( $action )
{
2010-03-03 21:40:26 +00:00
$this -> showEntityRemoteSubscribe ( $action , 'ostatusgroup' );
2010-03-03 17:56:19 +00:00
return true ;
}
2010-03-03 21:40:26 +00:00
function showEntityRemoteSubscribe ( $action , $target = 'ostatussub' )
2010-02-22 16:07:48 +00:00
{
2015-07-10 12:44:47 +01:00
if ( ! $action -> getScoped () instanceof Profile ) {
// early return if we're not logged in
return true ;
}
if ( $action -> getScoped () -> sameAs ( $action -> getTarget ())) {
2010-02-22 16:07:48 +00:00
$action -> elementStart ( 'div' , 'entity_actions' );
$action -> elementStart ( 'p' , array ( 'id' => 'entity_remote_subscribe' ,
'class' => 'entity_subscribe' ));
2015-07-10 21:49:38 +01:00
$action -> element ( 'a' , array ( 'href' => common_local_url ( $target ),
2010-09-19 14:17:36 +01:00
'class' => 'entity_remote_subscribe' ),
// TRANS: Link text for link to remote subscribe.
_m ( 'Remote' ));
2010-02-22 16:07:48 +00:00
$action -> elementEnd ( 'p' );
$action -> elementEnd ( 'div' );
}
}
2010-02-24 20:36:36 +00:00
/**
* Ping remote profiles with updates to this profile .
* Salmon pings are queued for background processing .
*/
function onEndBroadcastProfile ( Profile $profile )
{
2013-08-18 12:04:58 +01:00
$user = User :: getKV ( 'id' , $profile -> id );
2010-02-24 20:36:36 +00:00
// Find foreign accounts I'm subscribed to that support Salmon pings.
//
// @fixme we could run updates through the PuSH feed too,
// in which case we can skip Salmon pings to folks who
// are also subscribed to me.
$sql = " SELECT * FROM ostatus_profile " .
" WHERE profile_id IN " .
" (SELECT subscribed FROM subscription WHERE subscriber=%d) " .
" OR group_id IN " .
" (SELECT group_id FROM group_member WHERE profile_id=%d) " ;
$oprofile = new Ostatus_profile ();
$oprofile -> query ( sprintf ( $sql , $profile -> id , $profile -> id ));
if ( $oprofile -> N == 0 ) {
common_log ( LOG_DEBUG , " No OStatus remote subscribees for $profile->nickname " );
return true ;
}
$act = new Activity ();
$act -> verb = ActivityVerb :: UPDATE_PROFILE ;
$act -> id = TagURI :: mint ( 'update-profile:%d:%s' ,
$profile -> id ,
common_date_iso8601 ( time ()));
$act -> time = time ();
2010-09-19 14:17:36 +01:00
// TRANS: Title for activity.
2011-04-29 17:59:47 +01:00
$act -> title = _m ( 'Profile update' );
2010-09-03 00:35:04 +01:00
// TRANS: Ping text for remote profile update through OStatus.
// TRANS: %s is user that updated their profile.
2011-04-10 23:39:27 +01:00
$act -> content = sprintf ( _m ( '%s has updated their profile page.' ),
2010-02-24 20:36:36 +00:00
$profile -> getBestName ());
2014-07-02 17:50:28 +01:00
$act -> actor = $profile -> asActivityObject ();
2016-02-23 22:50:57 +00:00
$act -> objects [] = $act -> actor ;
2010-02-24 20:36:36 +00:00
while ( $oprofile -> fetch ()) {
2010-02-26 19:21:21 +00:00
$oprofile -> notifyDeferred ( $act , $profile );
2010-02-24 20:36:36 +00:00
}
return true ;
}
2010-03-03 19:32:03 +00:00
2015-07-10 12:44:47 +01:00
// FIXME: This one can accept both an Action and a Widget. Confusing! Refactor to (HTMLOutputter $out, Profile $target)!
function onStartProfileListItemActionElements ( $item )
2010-03-03 19:32:03 +00:00
{
2015-07-10 12:44:47 +01:00
if ( common_logged_in ()) {
// only non-logged in users get to see the "remote subscribe" form
return true ;
} elseif ( ! $item -> getTarget () -> isLocal ()) {
// we can (for now) only provide remote subscribe forms for local users
return true ;
}
2010-03-03 19:32:03 +00:00
2015-07-10 12:44:47 +01:00
if ( $item instanceof ProfileAction ) {
$output = $item ;
} elseif ( $item instanceof Widget ) {
$output = $item -> out ;
} else {
// Bad $item class, don't know how to use this for outputting!
throw new ServerException ( 'Bad item type for onStartProfileListItemActionElements' );
2010-03-03 19:32:03 +00:00
}
2015-07-10 12:44:47 +01:00
// Add an OStatus subscribe
$output -> elementStart ( 'li' , 'entity_subscribe' );
$url = common_local_url ( 'ostatusinit' ,
array ( 'nickname' => $item -> getTarget () -> getNickname ()));
$output -> element ( 'a' , array ( 'href' => $url ,
'class' => 'entity_remote_subscribe' ),
// TRANS: Link text for a user to subscribe to an OStatus user.
_m ( 'Subscribe' ));
$output -> elementEnd ( 'li' );
$output -> elementStart ( 'li' , 'entity_tag' );
$url = common_local_url ( 'ostatustag' ,
array ( 'nickname' => $item -> getTarget () -> getNickname ()));
$output -> element ( 'a' , array ( 'href' => $url ,
'class' => 'entity_remote_tag' ),
// TRANS: Link text for a user to list an OStatus user.
_m ( 'List' ));
$output -> elementEnd ( 'li' );
2010-03-03 19:32:03 +00:00
return true ;
}
2010-03-04 17:02:01 +00:00
2015-06-06 21:04:01 +01:00
function onPluginVersion ( array & $versions )
2010-03-04 17:02:01 +00:00
{
$versions [] = array ( 'name' => 'OStatus' ,
2013-11-01 12:51:41 +00:00
'version' => GNUSOCIAL_VERSION ,
2010-03-04 17:02:01 +00:00
'author' => 'Evan Prodromou, James Walker, Brion Vibber, Zach Copley' ,
2016-01-22 16:38:42 +00:00
'homepage' => 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/OStatus' ,
2010-09-19 14:17:36 +01:00
// TRANS: Plugin description.
'rawdescription' => _m ( 'Follow people across social networks that implement ' .
'<a href="http://ostatus.org/">OStatus</a>.' ));
2010-03-04 17:02:01 +00:00
return true ;
}
2010-03-11 01:00:05 +00:00
/**
2010-09-27 19:29:54 +01:00
* Utility function to check if the given URI is a canonical group profile
2010-03-11 01:00:05 +00:00
* page , and if so return the ID number .
*
* @ param string $url
* @ return mixed int or false
*/
public static function localGroupFromUrl ( $url )
{
2013-08-18 12:04:58 +01:00
$group = User_group :: getKV ( 'uri' , $url );
2014-05-19 13:46:54 +01:00
if ( $group instanceof User_group ) {
if ( $group -> isLocal ()) {
2010-09-27 19:29:54 +01:00
return $group -> id ;
}
} else {
// To find local groups which haven't had their uri fields filled out...
// If the domain has changed since a subscriber got the URI, it'll
// be broken.
$template = common_local_url ( 'groupbyid' , array ( 'id' => '31337' ));
$template = preg_quote ( $template , '/' );
$template = str_replace ( '31337' , '(\d+)' , $template );
if ( preg_match ( " / $template / " , $url , $matches )) {
return intval ( $matches [ 1 ]);
}
2010-03-11 01:00:05 +00:00
}
return false ;
}
2010-08-03 23:50:21 +01:00
public function onStartProfileGetAtomFeed ( $profile , & $feed )
{
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'profile_id' , $profile -> id );
2010-08-03 23:50:21 +01:00
2014-05-06 13:36:52 +01:00
if ( ! $oprofile instanceof Ostatus_profile ) {
2010-08-03 23:50:21 +01:00
return true ;
}
$feed = $oprofile -> feeduri ;
return false ;
}
2010-09-01 21:16:38 +01:00
2011-01-17 20:45:03 +00:00
function onStartGetProfileFromURI ( $uri , & $profile )
{
// Don't want to do Web-based discovery on our own server,
2016-01-09 13:36:47 +00:00
// so we check locally first. This duplicates the functionality
// in the Profile class, since the plugin always runs before
// that local lookup, but since we return false it won't run double.
2010-09-01 21:16:38 +01:00
2013-08-18 12:04:58 +01:00
$user = User :: getKV ( 'uri' , $uri );
2016-01-09 13:36:47 +00:00
if ( $user instanceof User ) {
2011-01-17 20:45:03 +00:00
$profile = $user -> getProfile ();
return false ;
2016-01-09 13:36:47 +00:00
} else {
$group = User_group :: getKV ( 'uri' , $uri );
if ( $group instanceof User_group ) {
$profile = $group -> getProfile ();
return false ;
}
2011-01-17 20:45:03 +00:00
}
2010-09-01 21:16:38 +01:00
2011-01-17 20:45:03 +00:00
// Now, check remotely
2013-10-30 13:43:40 +00:00
try {
$oprofile = Ostatus_profile :: ensureProfileURI ( $uri );
2010-09-01 21:16:38 +01:00
$profile = $oprofile -> localProfile ();
2013-10-30 13:43:40 +00:00
return ! ( $profile instanceof Profile ); // localProfile won't throw exception but can return null
} catch ( Exception $e ) {
return true ; // It's not an OStatus profile as far as we know, continue event handling
2010-09-01 21:16:38 +01:00
}
}
2010-09-05 22:43:29 +01:00
2013-11-01 15:54:59 +00:00
function onEndWebFingerNoticeLinks ( XML_XRD $xrd , Notice $target )
{
2016-03-27 13:56:27 +01:00
$salmon_url = null ;
$actor = $target -> getProfile ();
if ( $actor -> isLocal ()) {
$profiletype = $this -> profileTypeString ( $actor );
$salmon_url = common_local_url ( " { $profiletype } salmon " , array ( 'id' => $actor -> getID ()));
} else {
try {
$oprofile = Ostatus_profile :: fromProfile ( $actor );
$salmon_url = $oprofile -> salmonuri ;
} catch ( Exception $e ) {
// Even though it's not a local user, we couldn't get an Ostatus_profile?!
}
}
// Ostatus_profile salmon URL may be empty
if ( ! empty ( $salmon_url )) {
$xrd -> links [] = new XML_XRD_Element_Link ( Salmon :: REL_SALMON , $salmon_url );
}
2013-11-01 15:54:59 +00:00
return true ;
}
2013-10-20 14:32:56 +01:00
function onEndWebFingerProfileLinks ( XML_XRD $xrd , Profile $target )
2010-11-27 02:38:38 +00:00
{
2015-01-26 11:10:27 +00:00
if ( $target -> getObjectType () === ActivityObject :: PERSON ) {
$this -> addWebFingerPersonLinks ( $xrd , $target );
2016-06-25 19:13:19 +01:00
} elseif ( $target -> getObjectType () === ActivityObject :: GROUP ) {
$xrd -> links [] = new XML_XRD_Element_Link ( Discovery :: UPDATESFROM ,
common_local_url ( 'ApiTimelineGroup' ,
array ( 'id' => $target -> getGroup () -> getID (), 'format' => 'atom' )),
'application/atom+xml' );
2015-01-26 11:10:27 +00:00
}
2011-04-10 23:39:27 +01:00
2015-01-26 11:10:27 +00:00
// Salmon
$profiletype = $this -> profileTypeString ( $target );
$salmon_url = common_local_url ( " { $profiletype } salmon " , array ( 'id' => $target -> id ));
2010-11-27 02:38:38 +00:00
2013-09-30 16:13:03 +01:00
$xrd -> links [] = new XML_XRD_Element_Link ( Salmon :: REL_SALMON , $salmon_url );
2013-11-01 16:00:12 +00:00
// XXX: these are deprecated, but StatusNet only looks for NS_REPLIES
2013-09-30 16:13:03 +01:00
$xrd -> links [] = new XML_XRD_Element_Link ( Salmon :: NS_REPLIES , $salmon_url );
$xrd -> links [] = new XML_XRD_Element_Link ( Salmon :: NS_MENTIONS , $salmon_url );
2010-11-27 02:38:38 +00:00
2015-01-26 11:10:27 +00:00
// TODO - finalize where the redirect should go on the publisher
$xrd -> links [] = new XML_XRD_Element_Link ( 'http://ostatus.org/schema/1.0/subscribe' ,
common_local_url ( 'ostatussub' ) . '?profile={uri}' ,
null , // type not set
true ); // isTemplate
return true ;
}
protected function profileTypeString ( Profile $target )
{
// This is just used to have a definitive string response to "USERsalmon" or "GROUPsalmon"
switch ( $target -> getObjectType ()) {
case ActivityObject :: PERSON :
return 'user' ;
case ActivityObject :: GROUP :
return 'group' ;
default :
throw new ServerException ( 'Unknown profile type for WebFinger profile links' );
}
}
protected function addWebFingerPersonLinks ( XML_XRD $xrd , Profile $target )
{
$xrd -> links [] = new XML_XRD_Element_Link ( Discovery :: UPDATESFROM ,
common_local_url ( 'ApiTimelineUser' ,
array ( 'id' => $target -> id , 'format' => 'atom' )),
'application/atom+xml' );
2014-06-02 18:33:09 +01:00
// Get this profile's keypair
$magicsig = Magicsig :: getKV ( 'user_id' , $target -> id );
if ( ! $magicsig instanceof Magicsig && $target -> isLocal ()) {
2014-06-02 20:50:40 +01:00
$magicsig = Magicsig :: generate ( $target -> getUser ());
2010-11-27 02:38:38 +00:00
}
2015-06-06 12:49:27 +01:00
if ( ! $magicsig instanceof Magicsig ) {
return false ; // value doesn't mean anything, just figured I'd indicate this function didn't do anything
}
if ( Event :: handle ( 'StartAttachPubkeyToUserXRD' , array ( $magicsig , $xrd , $target ))) {
2014-06-02 18:33:09 +01:00
$xrd -> links [] = new XML_XRD_Element_Link ( Magicsig :: PUBLICKEYREL ,
2014-06-03 11:51:52 +01:00
'data:application/magic-public-key,' . $magicsig -> toString ());
2015-06-06 12:49:27 +01:00
// The following event handles plugins like Diaspora which add their own version of the Magicsig pubkey
Event :: handle ( 'EndAttachPubkeyToUserXRD' , array ( $magicsig , $xrd , $target ));
2014-06-02 18:33:09 +01:00
}
2010-09-05 22:43:29 +01:00
}
2014-07-01 10:42:08 +01:00
public function onGetLocalAttentions ( Profile $actor , array $attention_uris , array & $mentions , array & $groups )
{
2015-11-08 22:31:23 +00:00
list ( $groups , $mentions ) = Ostatus_profile :: filterAttention ( $actor , $attention_uris );
2014-07-01 10:42:08 +01:00
}
2014-07-01 14:48:34 +01:00
// FIXME: Maybe this shouldn't be so authoritative that it breaks other remote profile lookups?
static public function onCheckActivityAuthorship ( Activity $activity , Profile & $profile )
{
try {
2014-12-31 04:56:33 +00:00
$oprofile = Ostatus_profile :: ensureProfileURL ( $profile -> getUrl ());
2014-11-24 22:40:06 +00:00
$profile = $oprofile -> checkAuthorship ( $activity );
2014-07-01 14:48:34 +01:00
} catch ( Exception $e ) {
2014-10-25 13:23:15 +01:00
common_log ( LOG_ERR , 'Could not get a profile or check authorship (' . get_class ( $e ) . ': "' . $e -> getMessage () . '") for activity ID: ' . $activity -> id );
2014-07-01 14:48:34 +01:00
$profile = null ;
return false ;
}
return true ;
}
2014-07-04 10:45:42 +01:00
public function onProfileDeleteRelated ( $profile , & $related )
{
// Ostatus_profile has a 'profile_id' property, which will be used to find the object
$related [] = 'Ostatus_profile' ;
2015-01-24 11:47:39 +00:00
// Magicsig has a "user_id" column instead, so we have to delete it more manually:
$magicsig = Magicsig :: getKV ( 'user_id' , $profile -> id );
if ( $magicsig instanceof Magicsig ) {
$magicsig -> delete ();
}
2014-07-04 10:45:42 +01:00
return true ;
}
2015-10-03 23:17:07 +01:00
2015-10-04 11:06:48 +01:00
public function onSalmonSlap ( $endpoint_uri , MagicEnvelope $magic_env , Profile $target = null )
2015-10-03 23:17:07 +01:00
{
2016-06-23 22:27:18 +01:00
try {
$envxml = $magic_env -> toXML ( $target );
} catch ( Exception $e ) {
common_log ( LOG_ERR , sprintf ( 'Could not generate Magic Envelope XML for profile id==' . $target -> getID () . ': ' . $e -> getMessage ()));
return false ;
}
2015-10-03 23:17:07 +01:00
$headers = array ( 'Content-Type: application/magic-envelope+xml' );
try {
$client = new HTTPClient ();
$client -> setBody ( $envxml );
$response = $client -> post ( $endpoint_uri , $headers );
2016-01-13 13:17:49 +00:00
} catch ( Exception $e ) {
2015-10-03 23:17:07 +01:00
common_log ( LOG_ERR , " Salmon post to $endpoint_uri failed: " . $e -> getMessage ());
return false ;
}
if ( $response -> getStatus () === 422 ) {
2015-10-04 16:26:35 +01:00
common_debug ( sprintf ( 'Salmon (from profile %d) endpoint %s returned status %s. We assume it is a Diaspora seed; will adapt and try again if that plugin is enabled!' , $magic_env -> getActor () -> getID (), $endpoint_uri , $response -> getStatus ()));
2015-10-03 23:17:07 +01:00
return true ;
}
// 200 OK is the best response
// 202 Accepted is what we get from Diaspora for example
if ( ! in_array ( $response -> getStatus (), array ( 200 , 202 ))) {
common_log ( LOG_ERR , sprintf ( 'Salmon (from profile %d) endpoint %s returned status %s: %s' ,
2015-10-04 16:26:35 +01:00
$magic_env -> getActor () -> getID (), $endpoint_uri , $response -> getStatus (), $response -> getBody ()));
2015-10-03 23:17:07 +01:00
return true ;
}
// Since we completed the salmon slap, we discontinue the event
return false ;
}
2015-11-05 15:16:02 +00:00
2015-10-21 02:10:48 +01:00
public function onCronDaily ()
{
try {
$sub = FeedSub :: renewalCheck ();
} catch ( NoResultException $e ) {
common_log ( LOG_INFO , " There were no expiring feeds. " );
return ;
}
2015-10-21 02:50:03 +01:00
$qm = QueueManager :: get ();
2015-10-21 02:10:48 +01:00
while ( $sub -> fetch ()) {
2015-10-21 02:50:03 +01:00
$item = array ( 'feedsub_id' => $sub -> id );
$qm -> enqueue ( $item , 'pushrenew' );
2015-10-21 02:10:48 +01:00
}
}
2009-11-20 04:55:38 +00:00
}