2009-11-20 17:42:19 +00:00
< ? php
/*
2010-02-08 19:06:03 +00:00
* StatusNet - the distributed open - source microblogging tool
* Copyright ( C ) 2009 - 2010 , StatusNet , Inc .
*
* 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 />.
*/
2015-06-06 14:58:08 +01:00
if ( ! defined ( 'GNUSOCIAL' )) { exit ( 1 ); }
2010-10-08 18:42:59 +01:00
2010-02-08 19:06:03 +00:00
/**
2010-02-18 21:22:21 +00:00
* @ package OStatusPlugin
2015-06-06 14:58:08 +01:00
* @ author Brion Vibber < brion @ status . net >
* @ maintainer Mikael Nordfeldth < mmn @ hethane . se >
2010-02-08 19:06:03 +00:00
*/
2010-08-16 22:02:31 +01:00
class Ostatus_profile extends Managed_DataObject
2009-11-20 17:42:19 +00:00
{
2010-02-12 00:22:16 +00:00
public $__table = 'ostatus_profile' ;
2009-11-20 17:42:19 +00:00
2010-02-18 21:22:21 +00:00
public $uri ;
2009-11-20 17:42:19 +00:00
public $profile_id ;
2010-02-12 00:22:16 +00:00
public $group_id ;
2011-03-06 19:15:34 +00:00
public $peopletag_id ;
2009-11-20 17:42:19 +00:00
public $feeduri ;
2010-02-12 00:22:16 +00:00
public $salmonuri ;
2010-02-24 19:06:10 +00:00
public $avatar ; // remote URL of the last avatar we saved
2010-02-12 00:22:16 +00:00
2009-11-20 17:42:19 +00:00
public $created ;
2010-02-18 21:22:21 +00:00
public $modified ;
2009-11-20 17:42:19 +00:00
2010-01-04 18:30:19 +00:00
/**
2010-08-16 22:02:31 +01:00
* Return table definition for Schema setup and DB_DataObject usage .
2010-01-04 18:30:19 +00:00
*
* @ return array array of column definitions
*/
static function schemaDef ()
{
2010-08-16 22:02:31 +01:00
return array (
'fields' => array (
2015-02-12 17:18:55 +00:00
'uri' => array ( 'type' => 'varchar' , 'length' => 191 , 'not null' => true ),
2010-08-16 22:02:31 +01:00
'profile_id' => array ( 'type' => 'integer' ),
'group_id' => array ( 'type' => 'integer' ),
2011-03-06 19:15:34 +00:00
'peopletag_id' => array ( 'type' => 'integer' ),
2015-02-12 17:18:55 +00:00
'feeduri' => array ( 'type' => 'varchar' , 'length' => 191 ),
'salmonuri' => array ( 'type' => 'varchar' , 'length' => 191 ),
2010-08-16 22:02:31 +01:00
'avatar' => array ( 'type' => 'text' ),
'created' => array ( 'type' => 'datetime' , 'not null' => true ),
'modified' => array ( 'type' => 'datetime' , 'not null' => true ),
),
'primary key' => array ( 'uri' ),
'unique keys' => array (
2014-05-19 16:58:05 +01:00
'ostatus_profile_profile_id_key' => array ( 'profile_id' ),
'ostatus_profile_group_id_key' => array ( 'group_id' ),
'ostatus_profile_peopletag_id_key' => array ( 'peopletag_id' ),
'ostatus_profile_feeduri_key' => array ( 'feeduri' ),
2010-08-16 22:02:31 +01:00
),
'foreign keys' => array (
2010-10-20 01:25:56 +01:00
'ostatus_profile_profile_id_fkey' => array ( 'profile' , array ( 'profile_id' => 'id' )),
'ostatus_profile_group_id_fkey' => array ( 'user_group' , array ( 'group_id' => 'id' )),
2011-03-06 19:15:34 +00:00
'ostatus_profile_peopletag_id_fkey' => array ( 'profile_list' , array ( 'peopletag_id' => 'id' )),
2010-08-16 22:02:31 +01:00
),
);
2009-11-20 17:42:19 +00:00
}
2014-04-28 13:08:42 +01:00
public function getUri ()
{
return $this -> uri ;
}
2016-03-27 13:54:14 +01:00
static function fromProfile ( Profile $profile )
2015-01-10 01:07:39 +00:00
{
2016-03-27 13:54:14 +01:00
$oprofile = Ostatus_profile :: getKV ( 'profile_id' , $profile -> getID ());
2015-01-10 01:07:39 +00:00
if ( ! $oprofile instanceof Ostatus_profile ) {
2016-03-27 13:54:14 +01:00
throw new Exception ( 'No Ostatus_profile for Profile ID: ' . $profile -> getID ());
2015-01-10 01:07:39 +00:00
}
2016-03-27 13:54:14 +01:00
return $oprofile ;
2015-01-10 01:07:39 +00:00
}
2010-01-04 18:30:19 +00:00
/**
2014-05-19 16:58:05 +01:00
* Fetch the locally stored profile for this feed
2010-01-04 18:30:19 +00:00
* @ return Profile
2014-05-19 16:58:05 +01:00
* @ throws NoProfileException if it was not found
2010-01-04 18:30:19 +00:00
*/
2010-02-12 01:11:46 +00:00
public function localProfile ()
2009-11-20 17:42:19 +00:00
{
2014-11-23 22:42:12 +00:00
if ( $this -> isGroup ()) {
return $this -> localGroup () -> getProfile ();
}
2014-05-19 16:58:05 +01:00
$profile = Profile :: getKV ( 'id' , $this -> profile_id );
2015-01-13 12:18:57 +00:00
if ( ! $profile instanceof Profile ) {
throw new NoProfileException ( $this -> profile_id );
2010-02-12 01:11:46 +00:00
}
2015-01-13 12:18:57 +00:00
return $profile ;
2010-02-12 01:11:46 +00:00
}
/**
* Fetch the StatusNet - side profile for this feed
* @ return Profile
*/
public function localGroup ()
{
2015-01-27 12:38:11 +00:00
$group = User_group :: getKV ( 'id' , $this -> group_id );
if ( ! $group instanceof User_group ) {
throw new NoSuchGroupException ( array ( 'id' => $this -> group_id ));
2010-02-12 01:11:46 +00:00
}
2015-01-27 12:38:11 +00:00
return $group ;
2009-11-20 17:42:19 +00:00
}
2011-03-06 19:15:34 +00:00
/**
* Fetch the StatusNet - side peopletag for this feed
* @ return Profile
*/
public function localPeopletag ()
{
if ( $this -> peopletag_id ) {
2013-08-18 12:04:58 +01:00
return Profile_list :: getKV ( 'id' , $this -> peopletag_id );
2011-03-06 19:15:34 +00:00
}
return null ;
}
2010-02-22 17:43:27 +00:00
/**
* Returns an ActivityObject describing this remote user or group profile .
* Can then be used to generate Atom chunks .
*
* @ return ActivityObject
*/
function asActivityObject ()
{
if ( $this -> isGroup ()) {
2010-02-26 02:55:11 +00:00
return ActivityObject :: fromGroup ( $this -> localGroup ());
2011-03-06 19:15:34 +00:00
} else if ( $this -> isPeopletag ()) {
return ActivityObject :: fromPeopletag ( $this -> localPeopletag ());
2010-02-22 17:43:27 +00:00
} else {
2014-07-02 17:50:28 +01:00
return $this -> localProfile () -> asActivityObject ();
2010-02-22 17:43:27 +00:00
}
}
2010-02-12 18:54:48 +00:00
/**
* Returns an XML string fragment with profile information as an
* Activity Streams noun object with the given element type .
*
* Assumes that 'activity' namespace has been previously defined .
*
2011-08-19 16:13:15 +01:00
* @ todo FIXME : Replace with wrappers on asActivityObject when it ' s got everything .
2010-02-22 17:43:27 +00:00
*
2010-02-12 18:54:48 +00:00
* @ param string $element one of 'actor' , 'subject' , 'object' , 'target'
* @ return string
*/
function asActivityNoun ( $element )
{
if ( $this -> isGroup ()) {
2010-02-26 02:55:11 +00:00
$noun = ActivityObject :: fromGroup ( $this -> localGroup ());
return $noun -> asString ( 'activity:' . $element );
2011-03-06 19:15:34 +00:00
} else if ( $this -> isPeopletag ()) {
$noun = ActivityObject :: fromPeopletag ( $this -> localPeopletag ());
return $noun -> asString ( 'activity:' . $element );
2010-02-12 18:54:48 +00:00
} else {
2014-07-02 17:50:28 +01:00
$noun = $this -> localProfile () -> asActivityObject ();
2010-02-26 02:55:11 +00:00
return $noun -> asString ( 'activity:' . $element );
2010-02-12 18:54:48 +00:00
}
}
2010-02-10 02:32:52 +00:00
/**
2010-02-22 17:43:27 +00:00
* @ return boolean true if this is a remote group
2010-02-10 02:32:52 +00:00
*/
function isGroup ()
{
2011-03-06 19:15:34 +00:00
if ( $this -> profile_id || $this -> peopletag_id && ! $this -> group_id ) {
2010-02-22 17:43:27 +00:00
return false ;
2011-03-06 19:15:34 +00:00
} else if ( $this -> group_id && ! $this -> profile_id && ! $this -> peopletag_id ) {
2010-02-22 17:43:27 +00:00
return true ;
2011-03-06 19:15:34 +00:00
} else if ( $this -> group_id && ( $this -> profile_id || $this -> peopletag_id )) {
// TRANS: Server exception. %s is a URI
2014-04-28 13:08:42 +01:00
throw new ServerException ( sprintf ( _m ( 'Invalid ostatus_profile state: Two or more IDs set for %s.' ), $this -> getUri ()));
2010-02-22 17:43:27 +00:00
} else {
2011-03-06 19:15:34 +00:00
// TRANS: Server exception. %s is a URI
2014-04-28 13:08:42 +01:00
throw new ServerException ( sprintf ( _m ( 'Invalid ostatus_profile state: All IDs empty for %s.' ), $this -> getUri ()));
2011-03-06 19:15:34 +00:00
}
}
2016-01-29 15:15:06 +00:00
public function isPerson ()
{
return $this -> localProfile () -> isPerson ();
}
2011-03-06 19:15:34 +00:00
/**
* @ return boolean true if this is a remote peopletag
*/
function isPeopletag ()
{
if ( $this -> profile_id || $this -> group_id && ! $this -> peopletag_id ) {
return false ;
} else if ( $this -> peopletag_id && ! $this -> profile_id && ! $this -> group_id ) {
return true ;
} else if ( $this -> peopletag_id && ( $this -> profile_id || $this -> group_id )) {
// TRANS: Server exception. %s is a URI
2014-04-28 13:08:42 +01:00
throw new ServerException ( sprintf ( _m ( 'Invalid ostatus_profile state: Two or more IDs set for %s.' ), $this -> getUri ()));
2011-03-06 19:15:34 +00:00
} else {
// TRANS: Server exception. %s is a URI
2014-04-28 13:08:42 +01:00
throw new ServerException ( sprintf ( _m ( 'Invalid ostatus_profile state: All IDs empty for %s.' ), $this -> getUri ()));
2010-02-22 17:43:27 +00:00
}
2010-02-10 02:32:52 +00:00
}
2010-02-12 01:11:46 +00:00
/**
2010-02-18 21:22:21 +00:00
* Send a subscription request to the hub for this feed .
* The hub will later send us a confirmation POST to / main / push / callback .
2010-02-12 01:11:46 +00:00
*
2014-05-06 14:40:57 +01:00
* @ return void
* @ throws ServerException if feed state is not valid or subscription fails .
2010-02-12 01:11:46 +00:00
*/
2010-02-20 00:21:17 +00:00
public function subscribe ()
2010-02-12 01:11:46 +00:00
{
2010-02-18 21:22:21 +00:00
$feedsub = FeedSub :: ensureFeed ( $this -> feeduri );
2010-03-19 22:47:43 +00:00
if ( $feedsub -> sub_state == 'active' ) {
// Active subscription, we don't need to do anything.
2014-05-06 14:40:57 +01:00
return ;
2010-02-12 01:11:46 +00:00
}
2014-05-06 14:40:57 +01:00
// Inactive or we got left in an inconsistent state.
// Run a subscription request to make sure we're current!
return $feedsub -> subscribe ();
2010-02-12 01:11:46 +00:00
}
/**
2010-08-06 18:56:18 +01:00
* Check if this remote profile has any active local subscriptions , and
2017-05-01 10:04:27 +01:00
* if not drop the WebSub subscription feed .
2010-02-18 21:22:21 +00:00
*
2014-05-06 14:40:57 +01:00
* @ return boolean true if subscription is removed , false if there are still subscribers to the feed
* @ throws Exception of various kinds on failure .
2010-02-12 01:11:46 +00:00
*/
2010-02-18 21:22:21 +00:00
public function unsubscribe () {
2014-05-06 14:40:57 +01:00
return $this -> garbageCollect ();
2010-02-12 01:11:46 +00:00
}
2010-02-23 20:44:27 +00:00
/**
* Check if this remote profile has any active local subscriptions , and
2017-05-01 10:04:27 +01:00
* if not drop the WebSub subscription feed .
2010-02-23 20:44:27 +00:00
*
2014-05-06 14:40:57 +01:00
* @ return boolean true if subscription is removed , false if there are still subscribers to the feed
* @ throws Exception of various kinds on failure .
2010-02-23 20:44:27 +00:00
*/
public function garbageCollect ()
2010-08-06 18:56:18 +01:00
{
2013-08-18 12:04:58 +01:00
$feedsub = FeedSub :: getKV ( 'uri' , $this -> feeduri );
2014-06-01 15:07:08 +01:00
if ( $feedsub instanceof FeedSub ) {
return $feedsub -> garbageCollect ();
}
// Since there's no FeedSub we can assume it's already garbage collected
return true ;
2010-08-06 18:56:18 +01:00
}
/**
* Check if this remote profile has any active local subscriptions , so the
2017-05-01 10:04:27 +01:00
* WebSub subscription layer can decide if it can drop the feed .
2010-08-06 18:56:18 +01:00
*
* This gets called via the FeedSubSubscriberCount event when running
* FeedSub :: garbageCollect () .
*
* @ return int
2014-05-19 16:58:05 +01:00
* @ throws NoProfileException if there is no local profile for the object
2010-08-06 18:56:18 +01:00
*/
public function subscriberCount ()
2010-02-23 20:44:27 +00:00
{
if ( $this -> isGroup ()) {
$members = $this -> localGroup () -> getMembers ( 0 , 1 );
$count = $members -> N ;
2011-03-06 19:15:34 +00:00
} else if ( $this -> isPeopletag ()) {
$subscribers = $this -> localPeopletag () -> getSubscribers ( 0 , 1 );
$count = $subscribers -> N ;
2010-02-23 20:44:27 +00:00
} else {
2011-03-06 19:15:34 +00:00
$profile = $this -> localProfile ();
if ( $profile -> hasLocalTags ()) {
$count = 1 ;
2014-05-19 16:58:05 +01:00
} else {
$count = $profile -> subscriberCount ();
2011-03-06 19:15:34 +00:00
}
2010-02-23 20:44:27 +00:00
}
2010-08-06 18:56:18 +01:00
common_log ( LOG_INFO , __METHOD__ . " SUB COUNT BEFORE: $count " );
// Other plugins may be piggybacking on OStatus without having
// an active group or user-to-user subscription we know about.
Event :: handle ( 'Ostatus_profileSubscriberCount' , array ( $this , & $count ));
common_log ( LOG_INFO , __METHOD__ . " SUB COUNT AFTER: $count " );
return $count ;
2010-02-23 20:44:27 +00:00
}
2010-02-12 18:54:48 +00:00
/**
* Send an Activity Streams notification to the remote Salmon endpoint ,
* if so configured .
*
2010-02-21 15:53:11 +00:00
* @ param Profile $actor Actor who did the activity
* @ param string $verb Activity :: SUBSCRIBE or Activity :: JOIN
* @ param Object $object object of the action ; must define asActivityNoun ( $tag )
2010-02-12 18:54:48 +00:00
*/
2014-06-02 18:44:57 +01:00
public function notify ( Profile $actor , $verb , $object = null , $target = null )
2010-02-12 18:54:48 +00:00
{
if ( $object == null ) {
2010-02-21 02:25:40 +00:00
$object = $this ;
2010-02-12 18:54:48 +00:00
}
2014-06-02 18:37:06 +01:00
if ( empty ( $this -> salmonuri )) {
return false ;
}
$text = 'update' ;
$id = TagURI :: mint ( '%s:%s:%s' ,
$verb ,
$actor -> getURI (),
common_date_iso8601 ( time ()));
2010-02-12 18:54:48 +00:00
2014-06-02 18:37:06 +01:00
// @todo FIXME: Consolidate all these NS settings somewhere.
$attributes = array ( 'xmlns' => Activity :: ATOM ,
'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/' ,
'xmlns:thr' => 'http://purl.org/syndication/thread/1.0' ,
'xmlns:georss' => 'http://www.georss.org/georss' ,
'xmlns:ostatus' => 'http://ostatus.org/schema/1.0' ,
'xmlns:poco' => 'http://portablecontacts.net/spec/1.0' ,
'xmlns:media' => 'http://purl.org/syndication/atommedia' );
2010-02-20 01:01:38 +00:00
2014-06-02 18:37:06 +01:00
$entry = new XMLStringer ();
$entry -> elementStart ( 'entry' , $attributes );
$entry -> element ( 'id' , null , $id );
$entry -> element ( 'title' , null , $text );
$entry -> element ( 'summary' , null , $text );
$entry -> element ( 'published' , null , common_date_w3dtf ( common_sql_now ()));
2010-02-12 18:54:48 +00:00
2014-06-02 18:37:06 +01:00
$entry -> element ( 'activity:verb' , null , $verb );
$entry -> raw ( $actor -> asAtomAuthor ());
$entry -> raw ( $actor -> asActivityActor ());
$entry -> raw ( $object -> asActivityNoun ( 'object' ));
if ( $target != null ) {
$entry -> raw ( $target -> asActivityNoun ( 'target' ));
}
$entry -> elementEnd ( 'entry' );
2010-02-12 18:54:48 +00:00
2014-06-02 18:37:06 +01:00
$xml = $entry -> getString ();
common_log ( LOG_INFO , " Posting to Salmon endpoint $this->salmonuri : $xml " );
2010-02-12 18:54:48 +00:00
2015-10-04 16:23:11 +01:00
Salmon :: post ( $this -> salmonuri , $xml , $actor );
2010-02-12 18:54:48 +00:00
}
2010-02-24 20:36:36 +00:00
/**
* Send a Salmon notification ping immediately , and confirm that we got
* an acceptable response from the remote site .
*
* @ param mixed $entry XML string , Notice , or Activity
2011-01-05 22:05:59 +00:00
* @ param Profile $actor
2010-02-24 20:36:36 +00:00
* @ return boolean success
*/
2014-05-05 18:06:22 +01:00
public function notifyActivity ( $entry , Profile $actor )
2010-02-21 15:53:11 +00:00
{
if ( $this -> salmonuri ) {
2015-10-04 16:23:11 +01:00
return Salmon :: post ( $this -> salmonuri , $this -> notifyPrepXml ( $entry ), $actor , $this -> localProfile ());
2010-02-24 20:36:36 +00:00
}
2017-04-27 08:23:45 +01:00
common_debug ( __CLASS__ . ' error: No salmonuri for Ostatus_profile uri: ' . _ve ( $this -> getUri ()));
2010-02-21 15:53:11 +00:00
2010-02-24 20:36:36 +00:00
return false ;
}
2010-02-21 15:53:11 +00:00
2010-02-24 20:36:36 +00:00
/**
* Queue a Salmon notification for later . If queues are disabled we ' ll
* send immediately but won ' t get the return value .
*
* @ param mixed $entry XML string , Notice , or Activity
* @ return boolean success
*/
2010-02-26 19:21:21 +00:00
public function notifyDeferred ( $entry , $actor )
2010-02-24 20:36:36 +00:00
{
if ( $this -> salmonuri ) {
2016-01-20 13:55:41 +00:00
try {
common_debug ( " OSTATUS: user { $actor -> getNickname () } ( { $actor -> getID () } ) wants to ping { $this -> localProfile () -> getNickname () } on { $this -> salmonuri } " );
$data = array ( 'salmonuri' => $this -> salmonuri ,
'entry' => $this -> notifyPrepXml ( $entry ),
'actor' => $actor -> getID (),
'target' => $this -> localProfile () -> getID ());
$qm = QueueManager :: get ();
return $qm -> enqueue ( $data , 'salmon' );
} catch ( Exception $e ) {
common_log ( LOG_ERR , 'OSTATUS: Something went wrong when creating a Salmon slap: ' . _ve ( $e -> getMessage ()));
return false ;
}
2010-02-21 15:53:11 +00:00
}
2010-02-22 17:43:27 +00:00
return false ;
2010-02-21 15:53:11 +00:00
}
2010-02-24 20:36:36 +00:00
protected function notifyPrepXml ( $entry )
{
$preamble = '<?xml version="1.0" encoding="UTF-8" ?' . '>' ;
if ( is_string ( $entry )) {
return $entry ;
} else if ( $entry instanceof Activity ) {
return $preamble . $entry -> asString ( true );
} else if ( $entry instanceof Notice ) {
return $preamble . $entry -> asAtomEntry ( true , true );
} else {
2010-09-19 14:17:36 +01:00
// TRANS: Server exception.
throw new ServerException ( _m ( 'Invalid type passed to Ostatus_profile::notify. It must be XML string or Activity entry.' ));
2010-02-24 20:36:36 +00:00
}
}
2010-02-12 18:54:48 +00:00
function getBestName ()
{
if ( $this -> isGroup ()) {
return $this -> localGroup () -> getBestName ();
2011-03-06 19:15:34 +00:00
} else if ( $this -> isPeopletag ()) {
return $this -> localPeopletag () -> getBestName ();
2010-02-12 18:54:48 +00:00
} else {
return $this -> localProfile () -> getBestName ();
}
}
2009-11-20 17:42:19 +00:00
/**
* Read and post notices for updates from the feed .
* Currently assumes that all items in the feed are new ,
2017-05-01 10:04:27 +01:00
* coming from a WebSub hub .
2009-11-20 17:42:19 +00:00
*
2010-03-05 18:55:07 +00:00
* @ param DOMDocument $doc
* @ param string $source identifier ( " push " )
2009-11-20 17:42:19 +00:00
*/
2010-03-05 18:55:07 +00:00
public function processFeed ( DOMDocument $doc , $source )
2009-11-20 17:42:19 +00:00
{
2010-03-05 18:55:07 +00:00
$feed = $doc -> documentElement ;
2010-03-20 12:23:13 +00:00
if ( $feed -> localName == 'feed' && $feed -> namespaceURI == Activity :: ATOM ) {
$this -> processAtomFeed ( $feed , $source );
2011-08-19 16:13:15 +01:00
} else if ( $feed -> localName == 'rss' ) { // @todo FIXME: Check namespace.
2010-03-20 12:23:13 +00:00
$this -> processRssFeed ( $feed , $source );
} else {
2010-11-02 22:48:36 +00:00
// TRANS: Exception.
2010-09-19 14:17:36 +01:00
throw new Exception ( _m ( 'Unknown feed format.' ));
2010-03-05 18:55:07 +00:00
}
2010-03-20 12:23:13 +00:00
}
2010-03-05 18:55:07 +00:00
2010-03-20 12:23:13 +00:00
public function processAtomFeed ( DOMElement $feed , $source )
{
2010-02-16 23:31:11 +00:00
$entries = $feed -> getElementsByTagNameNS ( Activity :: ATOM , 'entry' );
if ( $entries -> length == 0 ) {
common_log ( LOG_ERR , __METHOD__ . " : no entries in feed update, ignoring " );
return ;
}
2015-12-31 14:05:35 +00:00
$this -> processEntries ( $entries , $feed , $source );
2010-02-16 23:31:11 +00:00
}
2010-03-20 12:23:13 +00:00
public function processRssFeed ( DOMElement $rss , $source )
{
$channels = $rss -> getElementsByTagName ( 'channel' );
if ( $channels -> length == 0 ) {
2010-11-02 22:48:36 +00:00
// TRANS: Exception.
2010-09-19 14:17:36 +01:00
throw new Exception ( _m ( 'RSS feed without a channel.' ));
2010-03-20 12:23:13 +00:00
} else if ( $channels -> length > 1 ) {
common_log ( LOG_WARNING , __METHOD__ . " : more than one channel in an RSS feed " );
}
$channel = $channels -> item ( 0 );
$items = $channel -> getElementsByTagName ( 'item' );
2015-12-31 14:05:35 +00:00
$this -> processEntries ( $items , $channel , $source );
}
public function processEntries ( DOMNodeList $entries , DOMElement $feed , $source )
{
for ( $i = 0 ; $i < $entries -> length ; $i ++ ) {
$entry = $entries -> item ( $i );
try {
$this -> processEntry ( $entry , $feed , $source );
} catch ( AlreadyFulfilledException $e ) {
common_debug ( 'We already had this entry: ' . $e -> getMessage ());
2016-01-05 14:00:07 +00:00
} catch ( ServerException $e ) {
// FIXME: This should be UnknownUriException and the ActivityUtils:: findLocalObject should only test one URI
common_log ( LOG_ERR , 'Entry threw exception while processing a feed from ' . $source . ': ' . $e -> getMessage ());
2015-12-31 14:05:35 +00:00
}
2010-03-20 12:23:13 +00:00
}
}
2010-02-16 23:31:11 +00:00
/**
* Process a posted entry from this feed source .
*
* @ param DOMElement $entry
* @ param DOMElement $feed for context
2010-03-05 18:55:07 +00:00
* @ param string $source identifier ( " push " or " salmon " )
2011-07-20 16:33:28 +01:00
*
* @ return Notice Notice representing the new ( or existing ) activity
2010-02-16 23:31:11 +00:00
*/
2015-12-31 14:05:35 +00:00
public function processEntry ( DOMElement $entry , DOMElement $feed , $source )
2010-02-16 23:31:11 +00:00
{
$activity = new Activity ( $entry , $feed );
2011-07-20 16:33:28 +01:00
return $this -> processActivity ( $activity , $source );
2011-07-19 20:20:06 +01:00
}
2013-10-21 21:28:17 +01:00
// TODO: Make this throw an exception
2015-12-31 14:05:35 +00:00
public function processActivity ( Activity $activity , $source )
2011-07-19 20:20:06 +01:00
{
2011-07-20 16:33:28 +01:00
$notice = null ;
2011-07-19 20:20:06 +01:00
// The "WithProfile" events were added later.
2010-02-16 23:31:11 +00:00
2014-07-01 14:48:34 +01:00
if ( Event :: handle ( 'StartHandleFeedEntryWithProfile' , array ( $activity , $this -> localProfile (), & $notice )) &&
2010-12-23 17:42:42 +00:00
Event :: handle ( 'StartHandleFeedEntry' , array ( $activity ))) {
2011-07-19 20:20:06 +01:00
2015-09-29 14:17:38 +01:00
common_log ( LOG_INFO , " Ignoring activity with unrecognized verb $activity->verb " );
2010-03-22 12:17:14 +00:00
2010-08-13 21:14:47 +01:00
Event :: handle ( 'EndHandleFeedEntry' , array ( $activity ));
2011-07-20 16:33:28 +01:00
Event :: handle ( 'EndHandleFeedEntryWithProfile' , array ( $activity , $this , $notice ));
2010-02-16 23:31:11 +00:00
}
2011-08-19 16:13:15 +01:00
2011-07-20 16:33:28 +01:00
return $notice ;
2010-02-16 23:31:11 +00:00
}
/**
* Process an incoming post activity from this remote feed .
* @ param Activity $activity
2010-02-24 01:09:52 +00:00
* @ param string $method 'push' or 'salmon'
* @ return mixed saved Notice or false
2010-02-16 23:31:11 +00:00
*/
2010-02-24 01:09:52 +00:00
public function processPost ( $activity , $method )
2010-02-16 23:31:11 +00:00
{
2015-09-29 14:17:38 +01:00
$actor = ActivityUtils :: checkAuthorship ( $activity , $this -> localProfile ());
2010-03-20 14:30:57 +00:00
2015-09-29 14:17:38 +01:00
$options = array ( 'is_local' => Notice :: REMOTE );
2010-03-03 00:30:09 +00:00
2010-02-23 00:44:45 +00:00
try {
2015-09-29 14:17:38 +01:00
$stored = Notice :: saveActivity ( $activity , $actor , $options );
2010-02-23 00:44:45 +00:00
} catch ( Exception $e ) {
2010-02-24 01:09:52 +00:00
common_log ( LOG_ERR , " OStatus save of remote message $sourceUri failed: " . $e -> getMessage ());
throw $e ;
2010-02-23 00:44:45 +00:00
}
2015-09-29 14:17:38 +01:00
return $stored ;
2010-02-24 01:09:52 +00:00
}
2010-02-22 03:51:11 +00:00
2010-02-24 01:09:52 +00:00
/**
* Filters a list of recipient ID URIs to just those for local delivery .
2014-07-01 10:42:08 +01:00
* @ param Profile local profile of sender
2010-02-24 01:09:52 +00:00
* @ param array in / out & $attention_uris set of URIs , will be pruned on output
* @ return array of group IDs
*/
2014-07-01 10:42:08 +01:00
static public function filterAttention ( Profile $sender , array $attention )
2010-02-24 01:09:52 +00:00
{
2013-11-15 12:43:57 +00:00
common_log ( LOG_DEBUG , " Original reply recipients: " . implode ( ', ' , array_keys ( $attention )));
2010-02-24 01:09:52 +00:00
$groups = array ();
$replies = array ();
2013-10-28 21:21:14 +00:00
foreach ( $attention as $recipient => $type ) {
2010-02-24 01:09:52 +00:00
// Is the recipient a local user?
2013-08-18 12:04:58 +01:00
$user = User :: getKV ( 'uri' , $recipient );
2013-10-28 21:21:14 +00:00
if ( $user instanceof User ) {
2011-08-19 16:13:15 +01:00
// @todo FIXME: Sender verification, spam etc?
2010-02-24 01:09:52 +00:00
$replies [] = $recipient ;
continue ;
}
// Is the recipient a local group?
2013-10-28 21:21:14 +00:00
// TODO: $group = User_group::getKV('uri', $recipient);
2010-03-11 01:00:05 +00:00
$id = OStatusPlugin :: localGroupFromUrl ( $recipient );
if ( $id ) {
2013-08-18 12:04:58 +01:00
$group = User_group :: getKV ( 'id' , $id );
2013-10-28 21:21:14 +00:00
if ( $group instanceof User_group ) {
2014-07-01 10:42:08 +01:00
// Deliver to all members of this local group if allowed.
if ( $sender -> isMember ( $group )) {
$groups [] = $group -> id ;
} else {
common_log ( LOG_DEBUG , sprintf ( 'Skipping reply to local group %s as sender %d is not a member' , $group -> getNickname (), $sender -> id ));
2010-02-24 01:09:52 +00:00
}
continue ;
2010-02-24 02:19:13 +00:00
} else {
common_log ( LOG_DEBUG , " Skipping reply to bogus group $recipient " );
2010-02-24 01:09:52 +00:00
}
}
2010-02-24 02:19:13 +00:00
2010-09-16 22:43:27 +01:00
// Is the recipient a remote user or group?
try {
2013-10-30 13:14:02 +00:00
$oprofile = self :: ensureProfileURI ( $recipient );
2010-09-16 22:43:27 +01:00
if ( $oprofile -> isGroup ()) {
// Deliver to local members of this remote group.
2011-08-19 16:13:15 +01:00
// @todo FIXME: Sender verification?
2010-09-16 22:43:27 +01:00
$groups [] = $oprofile -> group_id ;
} else {
// may be canonicalized or something
2014-04-28 13:08:42 +01:00
$replies [] = $oprofile -> getUri ();
2010-09-16 22:43:27 +01:00
}
continue ;
} catch ( Exception $e ) {
// Neither a recognizable local nor remote user!
common_log ( LOG_DEBUG , " Skipping reply to unrecognized profile $recipient : " . $e -> getMessage ());
}
2010-02-24 02:19:13 +00:00
2010-02-23 00:44:45 +00:00
}
2010-02-24 02:19:13 +00:00
common_log ( LOG_DEBUG , " Local reply recipients: " . implode ( ', ' , $replies ));
common_log ( LOG_DEBUG , " Local group recipients: " . implode ( ', ' , $groups ));
2013-10-28 21:21:14 +00:00
return array ( $groups , $replies );
2010-02-16 23:31:11 +00:00
}
2010-02-18 21:22:21 +00:00
/**
2010-03-21 22:18:37 +00:00
* Look up and if necessary create an Ostatus_profile for the remote entity
* with the given profile page URL . This should never return null -- you
* will either get an object or an exception will be thrown .
*
2010-02-18 21:22:21 +00:00
* @ param string $profile_url
* @ return Ostatus_profile
2010-04-07 00:32:04 +01:00
* @ throws Exception on various error conditions
* @ throws OStatusShadowException if this reference would obscure a local user / group
2010-02-18 21:22:21 +00:00
*/
2014-12-08 18:52:00 +00:00
public static function ensureProfileURL ( $profile_url , array $hints = array ())
2010-02-18 21:22:21 +00:00
{
2010-03-16 16:25:18 +00:00
$oprofile = self :: getFromProfileURL ( $profile_url );
2013-10-30 13:43:40 +00:00
if ( $oprofile instanceof Ostatus_profile ) {
2010-03-16 16:25:18 +00:00
return $oprofile ;
}
$hints [ 'profileurl' ] = $profile_url ;
// Fetch the URL
// XXX: HTTP caching
$client = new HTTPClient ();
$client -> setHeader ( 'Accept' , 'text/html,application/xhtml+xml' );
$response = $client -> get ( $profile_url );
if ( ! $response -> isOk ()) {
2010-09-19 14:17:36 +01:00
// TRANS: Exception. %s is a profile URL.
throw new Exception ( sprintf ( _m ( 'Could not reach profile page %s.' ), $profile_url ));
2010-03-16 16:25:18 +00:00
}
// Check if we have a non-canonical URL
2016-01-13 13:00:05 +00:00
$finalUrl = $response -> getEffectiveUrl ();
2010-03-16 16:25:18 +00:00
if ( $finalUrl != $profile_url ) {
$hints [ 'profileurl' ] = $finalUrl ;
$oprofile = self :: getFromProfileURL ( $finalUrl );
2013-10-30 13:43:40 +00:00
if ( $oprofile instanceof Ostatus_profile ) {
2010-03-16 16:25:18 +00:00
return $oprofile ;
}
}
2015-10-28 00:54:20 +00:00
if ( in_array (
preg_replace ( '/\s*;.*$/' , '' , $response -> getHeader ( 'Content-Type' )),
array ( 'application/rss+xml' , 'application/atom+xml' , 'application/xml' , 'text/xml' ))
) {
2016-01-13 13:00:05 +00:00
$hints [ 'feedurl' ] = $response -> getEffectiveUrl ();
2015-10-28 00:54:20 +00:00
} else {
// Try to get some hCard data
2010-03-16 16:25:18 +00:00
2015-10-28 00:54:20 +00:00
$body = $response -> getBody ();
2010-03-16 16:25:18 +00:00
2015-10-28 00:54:20 +00:00
$hcardHints = DiscoveryHints :: hcardHints ( $body , $finalUrl );
2010-03-16 16:25:18 +00:00
2015-10-28 00:54:20 +00:00
if ( ! empty ( $hcardHints )) {
$hints = array_merge ( $hints , $hcardHints );
}
2010-03-16 16:25:18 +00:00
}
// Check if they've got an LRDD header
2013-09-30 16:13:03 +01:00
$lrdd = LinkHeader :: getLink ( $response , 'lrdd' );
try {
$xrd = new XML_XRD ();
$xrd -> loadFile ( $lrdd );
2010-03-16 16:25:18 +00:00
$xrdHints = DiscoveryHints :: fromXRD ( $xrd );
$hints = array_merge ( $hints , $xrdHints );
2013-09-30 16:13:03 +01:00
} catch ( Exception $e ) {
// No hints available from XRD
2010-03-16 16:25:18 +00:00
}
// If discovery found a feedurl (probably from LRDD), use it.
if ( array_key_exists ( 'feedurl' , $hints )) {
return self :: ensureFeedURL ( $hints [ 'feedurl' ], $hints );
}
// Get the feed URL from HTML
2010-02-18 21:22:21 +00:00
$discover = new FeedDiscovery ();
2010-03-16 16:25:18 +00:00
$feedurl = $discover -> discoverFromHTML ( $finalUrl , $body );
if ( ! empty ( $feedurl )) {
$hints [ 'feedurl' ] = $feedurl ;
return self :: ensureFeedURL ( $feedurl , $hints );
}
2010-03-21 22:18:37 +00:00
2010-11-02 22:48:36 +00:00
// TRANS: Exception. %s is a URL.
2010-09-19 14:17:36 +01:00
throw new Exception ( sprintf ( _m ( 'Could not find a feed URL for profile page %s.' ), $finalUrl ));
2010-03-16 16:25:18 +00:00
}
2010-03-21 22:18:37 +00:00
/**
* Look up the Ostatus_profile , if present , for a remote entity with the
* given profile page URL . Will return null for both unknown and invalid
* remote profiles .
*
* @ return mixed Ostatus_profile or null
2010-04-07 00:32:04 +01:00
* @ throws OStatusShadowException for local profiles
2010-03-21 22:18:37 +00:00
*/
2010-03-16 16:25:18 +00:00
static function getFromProfileURL ( $profile_url )
{
2013-08-18 12:04:58 +01:00
$profile = Profile :: getKV ( 'profileurl' , $profile_url );
2013-10-30 13:43:40 +00:00
if ( ! $profile instanceof Profile ) {
2010-03-16 16:25:18 +00:00
return null ;
}
2014-07-01 14:48:34 +01:00
try {
$oprofile = self :: getFromProfile ( $profile );
// We found the profile, return it!
2010-03-16 16:25:18 +00:00
return $oprofile ;
2014-07-01 14:48:34 +01:00
} catch ( NoResultException $e ) {
// Could not find an OStatus profile, is it instead a local user?
$user = User :: getKV ( 'id' , $profile -> id );
if ( $user instanceof User ) {
// @todo i18n FIXME: use sprintf and add i18n (?)
throw new OStatusShadowException ( $profile , " ' $profile_url ' is the profile for local user ' { $user -> nickname } '. " );
}
2010-03-16 16:25:18 +00:00
}
// Continue discovery; it's a remote profile
// for OMB or some other protocol, may also
// support OStatus
return null ;
}
2014-07-01 14:48:34 +01:00
static function getFromProfile ( Profile $profile )
{
$oprofile = new Ostatus_profile ();
$oprofile -> profile_id = $profile -> id ;
if ( ! $oprofile -> find ( true )) {
throw new NoResultException ( $oprofile );
}
return $oprofile ;
}
2010-03-21 22:18:37 +00:00
/**
* Look up and if necessary create an Ostatus_profile for remote entity
* with the given update feed . This should never return null -- you will
* either get an object or an exception will be thrown .
*
* @ return Ostatus_profile
* @ throws Exception
*/
2014-12-08 18:52:00 +00:00
public static function ensureFeedURL ( $feed_url , array $hints = array ())
2010-03-16 16:25:18 +00:00
{
2015-02-20 13:47:12 +00:00
$oprofile = Ostatus_profile :: getKV ( 'feeduri' , $feed_url );
if ( $oprofile instanceof Ostatus_profile ) {
return $oprofile ;
}
2010-03-16 16:25:18 +00:00
$discover = new FeedDiscovery ();
$feeduri = $discover -> discoverFromFeedURL ( $feed_url );
$hints [ 'feedurl' ] = $feeduri ;
2010-08-03 00:08:54 +01:00
$huburi = $discover -> getHubLink ();
2010-02-26 03:48:28 +00:00
$hints [ 'hub' ] = $huburi ;
2013-11-01 16:00:12 +00:00
// XXX: NS_REPLIES is deprecated anyway, so let's remove it in the future.
$salmonuri = $discover -> getAtomLink ( Salmon :: REL_SALMON )
? : $discover -> getAtomLink ( Salmon :: NS_REPLIES );
2010-02-26 03:48:28 +00:00
$hints [ 'salmon' ] = $salmonuri ;
2010-02-18 21:22:21 +00:00
2015-01-16 00:10:55 +00:00
if ( ! $huburi && ! common_config ( 'feedsub' , 'fallback_hub' ) && ! common_config ( 'feedsub' , 'nohub' )) {
2017-05-01 10:04:27 +01:00
// We can only deal with folks with a WebSub hub
2015-01-16 00:10:55 +00:00
// unless we have something similar available locally.
2010-02-18 21:22:21 +00:00
throw new FeedSubNoHubException ();
}
2010-03-19 20:50:06 +00:00
$feedEl = $discover -> root ;
if ( $feedEl -> tagName == 'feed' ) {
return self :: ensureAtomFeed ( $feedEl , $hints );
} else if ( $feedEl -> tagName == 'channel' ) {
return self :: ensureRssChannel ( $feedEl , $hints );
} else {
throw new FeedSubBadXmlException ( $feeduri );
}
}
2010-02-21 17:56:46 +00:00
2010-03-21 22:18:37 +00:00
/**
* Look up and , if necessary , create an Ostatus_profile for the remote
* profile with the given Atom feed - actually loaded from the feed .
* This should never return null -- you will either get an object or
* an exception will be thrown .
*
* @ param DOMElement $feedEl root element of a loaded Atom feed
* @ param array $hints additional discovery information passed from higher levels
2011-08-19 16:13:15 +01:00
* @ todo FIXME : Should this be marked public ?
2010-03-21 22:18:37 +00:00
* @ return Ostatus_profile
* @ throws Exception
*/
2014-12-08 18:52:00 +00:00
public static function ensureAtomFeed ( DOMElement $feedEl , array $hints )
2010-03-19 20:50:06 +00:00
{
2010-12-17 18:09:37 +00:00
$author = ActivityUtils :: getFeedAuthor ( $feedEl );
2010-02-21 17:56:46 +00:00
2010-12-17 18:09:37 +00:00
if ( empty ( $author )) {
// XXX: make some educated guesses here
// TRANS: Feed sub exception.
2011-04-10 23:39:27 +01:00
throw new FeedSubException ( _m ( 'Cannot find enough profile ' .
2010-12-17 18:09:37 +00:00
'information to make a feed.' ));
2010-02-18 21:22:21 +00:00
}
2010-02-21 17:56:46 +00:00
2010-12-17 18:09:37 +00:00
return self :: ensureActivityObjectProfile ( $author , $hints );
2010-02-18 21:22:21 +00:00
}
2010-03-21 22:18:37 +00:00
/**
* Look up and , if necessary , create an Ostatus_profile for the remote
* profile with the given RSS feed - actually loaded from the feed .
* This should never return null -- you will either get an object or
* an exception will be thrown .
*
* @ param DOMElement $feedEl root element of a loaded RSS feed
* @ param array $hints additional discovery information passed from higher levels
2011-08-19 16:13:15 +01:00
* @ todo FIXME : Should this be marked public ?
2010-03-21 22:18:37 +00:00
* @ return Ostatus_profile
* @ throws Exception
*/
2014-12-08 18:52:00 +00:00
public static function ensureRssChannel ( DOMElement $feedEl , array $hints )
2010-03-19 20:50:06 +00:00
{
2010-03-20 22:18:55 +00:00
// Special-case for Posterous. They have some nice metadata in their
// posterous:author elements. We should use them instead of the channel.
$items = $feedEl -> getElementsByTagName ( 'item' );
if ( $items -> length > 0 ) {
$item = $items -> item ( 0 );
$authorEl = ActivityUtils :: child ( $item , ActivityObject :: AUTHOR , ActivityObject :: POSTEROUS );
if ( ! empty ( $authorEl )) {
$obj = ActivityObject :: fromPosterousAuthor ( $authorEl );
2010-03-21 12:37:58 +00:00
// Posterous has multiple authors per feed, and multiple feeds
// per author. We check if this is the "main" feed for this author.
if ( array_key_exists ( 'profileurl' , $hints ) &&
! empty ( $obj -> poco ) &&
common_url_to_nickname ( $hints [ 'profileurl' ]) == $obj -> poco -> preferredUsername ) {
return self :: ensureActivityObjectProfile ( $obj , $hints );
}
2010-03-20 22:18:55 +00:00
}
}
2015-10-27 18:43:57 +00:00
$obj = ActivityUtils :: getFeedAuthor ( $feedEl );
2011-08-19 16:13:15 +01:00
// @todo FIXME: We should check whether this feed has elements
2010-03-19 20:50:06 +00:00
// with different <author> or <dc:creator> elements, and... I dunno.
// Do something about that.
2015-10-27 18:43:57 +00:00
if ( empty ( $obj )) { $obj = ActivityObject :: fromRssChannel ( $feedEl ); }
2010-03-19 20:50:06 +00:00
return self :: ensureActivityObjectProfile ( $obj , $hints );
}
2010-02-18 21:22:21 +00:00
/**
* Download and update given avatar image
2010-03-19 17:15:00 +00:00
*
2010-02-18 21:22:21 +00:00
* @ param string $url
2015-01-26 16:43:09 +00:00
* @ return Avatar The Avatar we have on disk . ( seldom used )
2010-02-18 21:22:21 +00:00
* @ throws Exception in various failure cases
*/
2015-01-27 13:14:24 +00:00
public function updateAvatar ( $url , $force = false )
2010-02-18 21:22:21 +00:00
{
2015-01-26 16:43:09 +00:00
try {
// If avatar URL differs: update. If URLs were identical but we're forced: update.
if ( $url == $this -> avatar && ! $force ) {
// If there's no locally stored avatar, throw an exception and continue fetching below.
$avatar = Avatar :: getUploaded ( $this -> localProfile ()) instanceof Avatar ;
return $avatar ;
}
} catch ( NoAvatarException $e ) {
// No avatar available, let's fetch it.
2010-02-24 19:06:10 +00:00
}
2015-01-26 16:43:09 +00:00
2010-03-19 17:15:00 +00:00
if ( ! common_valid_http_url ( $url )) {
2010-11-02 22:48:36 +00:00
// TRANS: Server exception. %s is a URL.
2011-04-10 23:39:27 +01:00
throw new ServerException ( sprintf ( _m ( 'Invalid avatar URL %s.' ), $url ));
2010-03-19 17:15:00 +00:00
}
2010-02-24 19:06:10 +00:00
2015-01-27 12:38:11 +00:00
$self = $this -> localProfile ();
2010-02-22 17:43:27 +00:00
2011-08-19 16:13:15 +01:00
// @todo FIXME: This should be better encapsulated
2010-02-18 21:22:21 +00:00
// ripped from oauthstore.php (for old OMB client)
$temp_filename = tempnam ( sys_get_temp_dir (), 'listener_avatar' );
2010-10-13 19:04:41 +01:00
try {
2015-01-27 12:49:26 +00:00
$imgData = HTTPClient :: quickGet ( $url );
// Make sure it's at least an image file. ImageFile can do the rest.
if ( false === getimagesizefromstring ( $imgData )) {
throw new UnsupportedMediaException ( _ ( 'Downloaded group avatar was not an image.' ));
2010-10-13 19:04:41 +01:00
}
2015-01-27 12:49:26 +00:00
file_put_contents ( $temp_filename , $imgData );
unset ( $imgData ); // No need to carry this in memory.
2010-02-19 03:18:14 +00:00
2010-10-13 19:04:41 +01:00
if ( $this -> isGroup ()) {
$id = $this -> group_id ;
} else {
$id = $this -> profile_id ;
}
2015-03-10 23:20:48 +00:00
$imagefile = new ImageFile ( null , $temp_filename );
2010-10-13 19:04:41 +01:00
$filename = Avatar :: filename ( $id ,
image_type_to_extension ( $imagefile -> type ),
null ,
common_timestamp ());
rename ( $temp_filename , Avatar :: path ( $filename ));
} catch ( Exception $e ) {
unlink ( $temp_filename );
throw $e ;
2010-02-19 20:08:07 +00:00
}
2011-08-19 16:13:15 +01:00
// @todo FIXME: Hardcoded chmod is lame, but seems to be necessary to
2010-09-08 00:49:05 +01:00
// keep from accidentally saving images from command-line (queues)
// that can't be read from web server, which causes hard-to-notice
// problems later on:
//
// http://status.net/open-source/issues/2663
chmod ( Avatar :: path ( $filename ), 0644 );
2013-10-06 00:30:44 +01:00
$self -> setOriginal ( $filename );
2010-02-24 19:06:10 +00:00
$orig = clone ( $this );
$this -> avatar = $url ;
$this -> update ( $orig );
2015-01-26 16:43:09 +00:00
return Avatar :: getUploaded ( $self );
2010-02-18 21:22:21 +00:00
}
2010-02-24 19:06:10 +00:00
/**
* Pull avatar URL from ActivityObject or profile hints
*
* @ param ActivityObject $object
* @ param array $hints
* @ return mixed URL string or false
*/
2014-12-08 18:52:00 +00:00
public static function getActivityObjectAvatar ( ActivityObject $object , array $hints = array ())
2010-02-21 17:56:46 +00:00
{
2010-02-26 02:51:44 +00:00
if ( $object -> avatarLinks ) {
$best = false ;
// Take the exact-size avatar, or the largest avatar, or the first avatar if all sizeless
foreach ( $object -> avatarLinks as $avatar ) {
if ( $avatar -> width == AVATAR_PROFILE_SIZE && $avatar -> height = AVATAR_PROFILE_SIZE ) {
// Exact match!
$best = $avatar ;
break ;
}
if ( ! $best || $avatar -> width > $best -> width ) {
$best = $avatar ;
}
}
return $best -> url ;
2010-02-24 19:06:10 +00:00
} else if ( array_key_exists ( 'avatar' , $hints )) {
return $hints [ 'avatar' ];
}
return false ;
2010-02-21 17:56:46 +00:00
}
2010-02-17 01:49:49 +00:00
/**
* Get an appropriate avatar image source URL , if available .
*
* @ param ActivityObject $actor
* @ param DOMElement $feed
* @ return string
*/
2014-12-08 18:52:00 +00:00
protected static function getAvatar ( ActivityObject $actor , DOMElement $feed )
2010-02-17 01:49:49 +00:00
{
$url = '' ;
$icon = '' ;
if ( $actor -> avatar ) {
$url = trim ( $actor -> avatar );
}
if ( ! $url ) {
// Check <atom:logo> and <atom:icon> on the feed
$els = $feed -> childNodes ();
if ( $els && $els -> length ) {
for ( $i = 0 ; $i < $els -> length ; $i ++ ) {
$el = $els -> item ( $i );
if ( $el -> namespaceURI == Activity :: ATOM ) {
if ( empty ( $url ) && $el -> localName == 'logo' ) {
$url = trim ( $el -> textContent );
break ;
}
if ( empty ( $icon ) && $el -> localName == 'icon' ) {
// Use as a fallback
$icon = trim ( $el -> textContent );
}
}
}
}
if ( $icon && ! $url ) {
$url = $icon ;
}
}
if ( $url ) {
$opts = array ( 'allowed_schemes' => array ( 'http' , 'https' ));
2013-10-07 13:46:09 +01:00
if ( common_valid_http_url ( $url )) {
2010-02-17 01:49:49 +00:00
return $url ;
}
}
2011-02-03 16:09:26 +00:00
return Plugin :: staticPath ( 'OStatus' , 'images/96px-Feed-icon.svg.png' );
2010-02-17 01:49:49 +00:00
}
/**
2010-02-18 21:22:21 +00:00
* Fetch , or build if necessary , an Ostatus_profile for the actor
* in a given Activity Streams activity .
2010-03-21 22:18:37 +00:00
* This should never return null -- you will either get an object or
* an exception will be thrown .
2010-02-18 21:22:21 +00:00
*
* @ param Activity $activity
* @ param string $feeduri if we already know the canonical feed URI !
2010-02-19 18:29:06 +00:00
* @ param string $salmonuri if we already know the salmon return channel URI
2010-02-18 21:22:21 +00:00
* @ return Ostatus_profile
2010-03-21 22:18:37 +00:00
* @ throws Exception
2010-02-17 01:49:49 +00:00
*/
2014-12-08 18:52:00 +00:00
public static function ensureActorProfile ( Activity $activity , array $hints = array ())
2010-02-17 01:49:49 +00:00
{
2010-02-26 03:48:28 +00:00
return self :: ensureActivityObjectProfile ( $activity -> actor , $hints );
2010-02-21 17:56:46 +00:00
}
2010-03-21 22:18:37 +00:00
/**
* Fetch , or build if necessary , an Ostatus_profile for the profile
* in a given Activity Streams object ( can be subject , actor , or object ) .
* This should never return null -- you will either get an object or
* an exception will be thrown .
*
* @ param ActivityObject $object
* @ param array $hints additional discovery information passed from higher levels
* @ return Ostatus_profile
* @ throws Exception
*/
2014-12-08 18:52:00 +00:00
public static function ensureActivityObjectProfile ( ActivityObject $object , array $hints = array ())
2010-02-21 17:56:46 +00:00
{
$profile = self :: getActivityObjectProfile ( $object );
2014-06-28 19:33:09 +01:00
if ( $profile instanceof Ostatus_profile ) {
2010-02-24 19:06:10 +00:00
$profile -> updateFromActivityObject ( $object , $hints );
} else {
2010-02-26 03:48:28 +00:00
$profile = self :: createActivityObjectProfile ( $object , $hints );
2010-02-17 01:49:49 +00:00
}
return $profile ;
}
/**
* @ param Activity $activity
* @ return mixed matching Ostatus_profile or false if none known
2010-03-21 22:46:28 +00:00
* @ throws ServerException if feed info invalid
2010-02-17 01:49:49 +00:00
*/
2014-12-08 18:52:00 +00:00
public static function getActorProfile ( Activity $activity )
2010-02-17 01:49:49 +00:00
{
2010-02-21 17:56:46 +00:00
return self :: getActivityObjectProfile ( $activity -> actor );
}
2010-03-21 22:46:28 +00:00
/**
* @ param ActivityObject $activity
* @ return mixed matching Ostatus_profile or false if none known
* @ throws ServerException if feed info invalid
*/
2014-12-08 18:52:00 +00:00
protected static function getActivityObjectProfile ( ActivityObject $object )
2010-02-21 17:56:46 +00:00
{
$uri = self :: getActivityObjectProfileURI ( $object );
2013-08-18 12:04:58 +01:00
return Ostatus_profile :: getKV ( 'uri' , $uri );
2010-02-21 17:56:46 +00:00
}
2010-02-17 01:49:49 +00:00
/**
2010-03-21 22:46:28 +00:00
* Get the identifier URI for the remote entity described
* by this ActivityObject . This URI is * not * guaranteed to be
* a resolvable HTTP / HTTPS URL .
*
* @ param ActivityObject $object
2010-02-17 01:49:49 +00:00
* @ return string
2010-03-21 22:46:28 +00:00
* @ throws ServerException if feed info invalid
2010-02-17 01:49:49 +00:00
*/
2014-12-08 18:52:00 +00:00
protected static function getActivityObjectProfileURI ( ActivityObject $object )
2010-02-17 01:49:49 +00:00
{
2010-03-21 22:46:28 +00:00
if ( $object -> id ) {
2010-03-21 23:25:12 +00:00
if ( ActivityUtils :: validateUri ( $object -> id )) {
2010-03-21 22:46:28 +00:00
return $object -> id ;
}
2010-02-17 01:49:49 +00:00
}
2010-03-21 22:46:28 +00:00
// If the id is missing or invalid (we've seen feeds mistakenly listing
// things like local usernames in that field) then we'll use the profile
// page link, if valid.
if ( $object -> link && common_valid_http_url ( $object -> link )) {
2010-02-21 17:56:46 +00:00
return $object -> link ;
2010-02-17 01:49:49 +00:00
}
2011-04-29 17:59:47 +01:00
// TRANS: Server exception.
2011-04-17 19:08:03 +01:00
throw new ServerException ( _m ( 'No author ID URI found.' ));
2010-02-17 01:49:49 +00:00
}
/**
2011-08-19 16:13:15 +01:00
* @ todo FIXME : Validate stuff somewhere .
2010-02-17 01:49:49 +00:00
*/
2010-02-21 17:56:46 +00:00
2010-02-22 17:43:27 +00:00
/**
* Create local ostatus_profile and profile / user_group entries for
* the provided remote user or group .
2010-03-21 22:18:37 +00:00
* This should never return null -- you will either get an object or
* an exception will be thrown .
2010-02-22 17:43:27 +00:00
*
* @ param ActivityObject $object
* @ param array $hints
*
* @ return Ostatus_profile
*/
2014-12-08 18:52:00 +00:00
protected static function createActivityObjectProfile ( ActivityObject $object , array $hints = array ())
2010-02-21 17:56:46 +00:00
{
2016-10-22 22:08:44 +01:00
common_debug ( 'Attempting to create an Ostatus_profile from an ActivityObject with ID: ' . _ve ( $object -> id ));
2010-02-26 03:48:28 +00:00
$homeuri = $object -> id ;
$discover = false ;
2010-02-18 21:22:21 +00:00
if ( ! $homeuri ) {
common_log ( LOG_DEBUG , __METHOD__ . " empty actor profile URI: " . var_export ( $activity , true ));
2011-04-29 17:59:47 +01:00
// TRANS: Exception.
2011-04-17 19:08:03 +01:00
throw new Exception ( _m ( 'No profile URI.' ));
2010-03-11 01:00:05 +00:00
}
2013-08-18 12:04:58 +01:00
$user = User :: getKV ( 'uri' , $homeuri );
2014-04-28 13:08:42 +01:00
if ( $user instanceof User ) {
2010-09-19 14:17:36 +01:00
// TRANS: Exception.
2011-04-10 23:39:27 +01:00
throw new Exception ( _m ( 'Local user cannot be referenced as remote.' ));
2010-03-11 01:00:05 +00:00
}
if ( OStatusPlugin :: localGroupFromUrl ( $homeuri )) {
2010-09-19 14:17:36 +01:00
// TRANS: Exception.
2011-04-10 23:39:27 +01:00
throw new Exception ( _m ( 'Local group cannot be referenced as remote.' ));
2010-02-18 21:22:21 +00:00
}
2010-02-17 01:49:49 +00:00
2013-08-18 12:04:58 +01:00
$ptag = Profile_list :: getKV ( 'uri' , $homeuri );
2013-11-01 12:20:23 +00:00
if ( $ptag instanceof Profile_list ) {
2013-08-18 12:04:58 +01:00
$local_user = User :: getKV ( 'id' , $ptag -> tagger );
2013-11-01 12:20:23 +00:00
if ( $local_user instanceof User ) {
2011-04-29 17:59:47 +01:00
// TRANS: Exception.
2011-04-17 19:08:03 +01:00
throw new Exception ( _m ( 'Local list cannot be referenced as remote.' ));
2011-03-06 19:15:34 +00:00
}
}
2016-10-22 22:08:44 +01:00
if ( ! array_key_exists ( 'feedurl' , $hints )) {
2010-02-26 03:48:28 +00:00
$discover = new FeedDiscovery ();
2016-10-22 22:08:44 +01:00
$hints [ 'feedurl' ] = $discover -> discoverFromURL ( $homeuri );
common_debug ( __METHOD__ . ' did not have a "feedurl" hint, FeedDiscovery found ' . _ve ( $hints [ 'feedurl' ]));
2010-02-22 02:57:09 +00:00
}
2016-10-22 22:08:44 +01:00
$feeduri = $hints [ 'feedurl' ];
2010-02-22 02:57:09 +00:00
2010-02-26 03:48:28 +00:00
if ( array_key_exists ( 'salmon' , $hints )) {
$salmonuri = $hints [ 'salmon' ];
} else {
if ( ! $discover ) {
$discover = new FeedDiscovery ();
$discover -> discoverFromFeedURL ( $hints [ 'feedurl' ]);
2010-02-22 02:57:09 +00:00
}
2013-11-01 16:00:12 +00:00
// XXX: NS_REPLIES is deprecated anyway, so let's remove it in the future.
$salmonuri = $discover -> getAtomLink ( Salmon :: REL_SALMON )
? : $discover -> getAtomLink ( Salmon :: NS_REPLIES );
2010-02-22 02:57:09 +00:00
}
2010-02-26 03:48:28 +00:00
if ( array_key_exists ( 'hub' , $hints )) {
$huburi = $hints [ 'hub' ];
} else {
if ( ! $discover ) {
$discover = new FeedDiscovery ();
$discover -> discoverFromFeedURL ( $hints [ 'feedurl' ]);
}
2010-08-03 00:08:54 +01:00
$huburi = $discover -> getHubLink ();
2010-02-26 03:48:28 +00:00
}
2010-02-20 16:17:54 +00:00
2015-01-16 00:10:55 +00:00
if ( ! $huburi && ! common_config ( 'feedsub' , 'fallback_hub' ) && ! common_config ( 'feedsub' , 'nohub' )) {
2017-05-01 10:04:27 +01:00
// We can only deal with folks with a WebSub hub
2010-02-26 03:48:28 +00:00
throw new FeedSubNoHubException ();
2010-02-20 00:21:17 +00:00
}
2010-02-17 01:49:49 +00:00
$oprofile = new Ostatus_profile ();
2010-02-21 17:56:46 +00:00
$oprofile -> uri = $homeuri ;
$oprofile -> feeduri = $feeduri ;
$oprofile -> salmonuri = $salmonuri ;
2010-02-17 01:49:49 +00:00
2010-02-21 17:56:46 +00:00
$oprofile -> created = common_sql_now ();
$oprofile -> modified = common_sql_now ();
2010-02-19 18:29:06 +00:00
2010-02-22 17:43:27 +00:00
if ( $object -> type == ActivityObject :: PERSON ) {
$profile = new Profile ();
2010-02-25 21:02:08 +00:00
$profile -> created = common_sql_now ();
2010-02-24 19:06:10 +00:00
self :: updateProfile ( $profile , $object , $hints );
2010-02-22 17:43:27 +00:00
$oprofile -> profile_id = $profile -> insert ();
2013-10-20 16:22:20 +01:00
if ( $oprofile -> profile_id === false ) {
2011-08-19 16:13:15 +01:00
// TRANS: Server exception.
2011-04-10 23:39:27 +01:00
throw new ServerException ( _m ( 'Cannot save local profile.' ));
2010-02-22 17:43:27 +00:00
}
2011-03-06 19:15:34 +00:00
} else if ( $object -> type == ActivityObject :: GROUP ) {
2013-10-20 16:22:20 +01:00
$profile = new Profile ();
$profile -> query ( 'BEGIN' );
2010-02-22 17:43:27 +00:00
$group = new User_group ();
2010-02-25 21:02:08 +00:00
$group -> uri = $homeuri ;
2010-02-22 17:43:27 +00:00
$group -> created = common_sql_now ();
2010-02-24 19:06:10 +00:00
self :: updateGroup ( $group , $object , $hints );
2010-02-22 17:43:27 +00:00
2013-10-20 16:22:20 +01:00
// TODO: We should do this directly in User_group->insert()!
// currently it's duplicated in User_group->update()
// AND User_group->register()!!!
$fields = array ( /*group field => profile field*/
'nickname' => 'nickname' ,
'fullname' => 'fullname' ,
'mainpage' => 'profileurl' ,
'homepage' => 'homepage' ,
'description' => 'bio' ,
'location' => 'location' ,
'created' => 'created' ,
'modified' => 'modified' ,
);
foreach ( $fields as $gf => $pf ) {
$profile -> $pf = $group -> $gf ;
}
$profile_id = $profile -> insert ();
if ( $profile_id === false ) {
$profile -> query ( 'ROLLBACK' );
throw new ServerException ( _ ( 'Profile insertion failed.' ));
}
$group -> profile_id = $profile_id ;
2010-02-22 17:43:27 +00:00
$oprofile -> group_id = $group -> insert ();
2013-10-20 16:22:20 +01:00
if ( $oprofile -> group_id === false ) {
$profile -> query ( 'ROLLBACK' );
2010-11-02 22:48:36 +00:00
// TRANS: Server exception.
2011-04-10 23:39:27 +01:00
throw new ServerException ( _m ( 'Cannot save local profile.' ));
2010-02-22 17:43:27 +00:00
}
2013-10-20 16:22:20 +01:00
$profile -> query ( 'COMMIT' );
2011-03-06 19:15:34 +00:00
} else if ( $object -> type == ActivityObject :: _LIST ) {
$ptag = new Profile_list ();
$ptag -> uri = $homeuri ;
$ptag -> created = common_sql_now ();
self :: updatePeopletag ( $ptag , $object , $hints );
$oprofile -> peopletag_id = $ptag -> insert ();
2013-10-20 16:22:20 +01:00
if ( $oprofile -> peopletag_id === false ) {
// TRANS: Server exception.
2011-04-17 19:08:03 +01:00
throw new ServerException ( _m ( 'Cannot save local list.' ));
2011-03-06 19:15:34 +00:00
}
2010-02-22 17:43:27 +00:00
}
2010-02-17 01:49:49 +00:00
$ok = $oprofile -> insert ();
2010-02-21 17:56:46 +00:00
2013-10-20 16:22:20 +01:00
if ( $ok === false ) {
2010-11-02 22:48:36 +00:00
// TRANS: Server exception.
2011-04-10 23:39:27 +01:00
throw new ServerException ( _m ( 'Cannot save OStatus profile.' ));
2010-04-22 02:19:16 +01:00
}
$avatar = self :: getActivityObjectAvatar ( $object , $hints );
if ( $avatar ) {
try {
2010-02-22 17:43:27 +00:00
$oprofile -> updateAvatar ( $avatar );
2010-04-22 02:19:16 +01:00
} catch ( Exception $ex ) {
// Profile is saved, but Avatar is messed up. We're
// just going to continue.
common_log ( LOG_WARNING , " Exception saving OStatus profile avatar: " . $ex -> getMessage ());
2010-02-22 17:43:27 +00:00
}
2010-02-17 01:49:49 +00:00
}
2010-04-22 02:19:16 +01:00
return $oprofile ;
2010-02-17 01:49:49 +00:00
}
2010-02-24 19:06:10 +00:00
/**
* Save any updated profile information to our local copy .
* @ param ActivityObject $object
* @ param array $hints
*/
2014-12-08 18:52:00 +00:00
public function updateFromActivityObject ( ActivityObject $object , array $hints = array ())
2010-02-24 19:06:10 +00:00
{
2016-03-28 15:25:29 +01:00
if ( self :: getActivityObjectProfileURI ( $object ) !== $this -> getUri ()) {
common_log ( LOG_ERR , 'Trying to update profile with URI ' . _ve ( $this -> getUri ()) . ' from ActivityObject with URI: ' . _ve ( self :: getActivityObjectProfileURI ( $object )));
2016-03-28 15:19:47 +01:00
// FIXME: Maybe not AuthorizationException?
throw new AuthorizationException ( 'Trying to update profile from ActivityObject with different URI.' );
}
2016-10-22 22:08:44 +01:00
common_debug ( 'Updating Ostatus_profile with URI ' . _ve ( $this -> getUri ()) . ' from ActivityObject' );
2010-02-24 19:06:10 +00:00
if ( $this -> isGroup ()) {
$group = $this -> localGroup ();
self :: updateGroup ( $group , $object , $hints );
2011-03-06 19:15:34 +00:00
} else if ( $this -> isPeopletag ()) {
$ptag = $this -> localPeopletag ();
self :: updatePeopletag ( $ptag , $object , $hints );
2010-02-24 19:06:10 +00:00
} else {
$profile = $this -> localProfile ();
self :: updateProfile ( $profile , $object , $hints );
}
2011-03-06 19:15:34 +00:00
2010-02-24 19:06:10 +00:00
$avatar = self :: getActivityObjectAvatar ( $object , $hints );
2011-03-06 19:15:34 +00:00
if ( $avatar && ! isset ( $ptag )) {
2010-04-22 02:19:16 +01:00
try {
$this -> updateAvatar ( $avatar );
2016-03-28 15:41:29 +01:00
} catch ( Exception $e ) {
common_log ( LOG_WARNING , 'Exception (' . get_class ( $e ) . ') updating OStatus profile avatar: ' . $e -> getMessage ());
2010-04-22 02:19:16 +01:00
}
2010-02-24 19:06:10 +00:00
}
}
2014-12-08 18:52:00 +00:00
public static function updateProfile ( Profile $profile , ActivityObject $object , array $hints = array ())
2010-02-24 19:06:10 +00:00
{
$orig = clone ( $profile );
2011-02-10 14:39:40 +00:00
// Existing nickname is better than nothing.
if ( ! array_key_exists ( 'nickname' , $hints )) {
$hints [ 'nickname' ] = $profile -> nickname ;
}
$nickname = self :: getActivityObjectNickname ( $object , $hints );
if ( ! empty ( $nickname )) {
$profile -> nickname = $nickname ;
}
2010-02-26 00:58:51 +00:00
if ( ! empty ( $object -> title )) {
$profile -> fullname = $object -> title ;
} else if ( array_key_exists ( 'fullname' , $hints )) {
$profile -> fullname = $hints [ 'fullname' ];
}
2010-02-24 19:06:10 +00:00
if ( ! empty ( $object -> link )) {
$profile -> profileurl = $object -> link ;
} else if ( array_key_exists ( 'profileurl' , $hints )) {
$profile -> profileurl = $hints [ 'profileurl' ];
2013-10-07 13:46:09 +01:00
} else if ( common_valid_http_url ( $object -> id )) {
2010-02-26 00:01:22 +00:00
$profile -> profileurl = $object -> id ;
2010-02-24 19:06:10 +00:00
}
2011-02-10 14:39:40 +00:00
$bio = self :: getActivityObjectBio ( $object , $hints );
if ( ! empty ( $bio )) {
$profile -> bio = $bio ;
}
$location = self :: getActivityObjectLocation ( $object , $hints );
if ( ! empty ( $location )) {
$profile -> location = $location ;
}
$homepage = self :: getActivityObjectHomepage ( $object , $hints );
if ( ! empty ( $homepage )) {
$profile -> homepage = $homepage ;
}
2010-02-25 00:51:24 +00:00
if ( ! empty ( $object -> geopoint )) {
$location = ActivityContext :: locationFromPoint ( $object -> geopoint );
if ( ! empty ( $location )) {
$profile -> lat = $location -> lat ;
$profile -> lon = $location -> lon ;
}
}
2011-08-19 16:13:15 +01:00
// @todo FIXME: tags/categories
2010-02-24 19:06:10 +00:00
// @todo tags from categories
if ( $profile -> id ) {
2016-10-22 22:24:13 +01:00
//common_debug('Updating OStatus profile '._ve($profile->getID().' from remote info '._ve($object->id).': ' . _ve($object) . _ve($hints));
common_debug ( 'Updating OStatus profile ' . _ve ( $profile -> getID ()) . ' from remote info ' . _ve ( $object -> id ) . ' gathered from hints: ' . _ve ( $hints ));
2010-02-24 19:06:10 +00:00
$profile -> update ( $orig );
}
}
2014-12-08 18:52:00 +00:00
protected static function updateGroup ( User_group $group , ActivityObject $object , array $hints = array ())
2010-02-24 19:06:10 +00:00
{
$orig = clone ( $group );
$group -> nickname = self :: getActivityObjectNickname ( $object , $hints );
$group -> fullname = $object -> title ;
2010-02-25 21:02:08 +00:00
if ( ! empty ( $object -> link )) {
$group -> mainpage = $object -> link ;
} else if ( array_key_exists ( 'profileurl' , $hints )) {
$group -> mainpage = $hints [ 'profileurl' ];
}
2010-02-24 19:06:10 +00:00
// @todo tags from categories
2010-02-25 21:02:08 +00:00
$group -> description = self :: getActivityObjectBio ( $object , $hints );
$group -> location = self :: getActivityObjectLocation ( $object , $hints );
$group -> homepage = self :: getActivityObjectHomepage ( $object , $hints );
2010-02-24 19:06:10 +00:00
2013-10-20 16:22:20 +01:00
if ( $group -> id ) { // If no id, we haven't called insert() yet, so don't run update()
2016-10-22 22:24:13 +01:00
//common_debug('Updating OStatus group '._ve($group->getID().' from remote info '._ve($object->id).': ' . _ve($object) . _ve($hints));
common_debug ( 'Updating OStatus group ' . _ve ( $group -> getID ()) . ' from remote info ' . _ve ( $object -> id ) . ' gathered from hints: ' . _ve ( $hints ));
2010-02-24 19:06:10 +00:00
$group -> update ( $orig );
}
}
2014-12-08 18:52:00 +00:00
protected static function updatePeopletag ( $tag , ActivityObject $object , array $hints = array ()) {
2011-03-06 19:15:34 +00:00
$orig = clone ( $tag );
$tag -> tag = $object -> title ;
if ( ! empty ( $object -> link )) {
$tag -> mainpage = $object -> link ;
} else if ( array_key_exists ( 'profileurl' , $hints )) {
$tag -> mainpage = $hints [ 'profileurl' ];
}
$tag -> description = $object -> summary ;
$tagger = self :: ensureActivityObjectProfile ( $object -> owner );
$tag -> tagger = $tagger -> profile_id ;
if ( $tag -> id ) {
2016-10-22 22:24:13 +01:00
//common_debug('Updating OStatus peopletag '._ve($tag->getID().' from remote info '._ve($object->id).': ' . _ve($object) . _ve($hints));
common_debug ( 'Updating OStatus peopletag ' . _ve ( $tag -> getID ()) . ' from remote info ' . _ve ( $object -> id ) . ' gathered from hints: ' . _ve ( $hints ));
2011-03-06 19:15:34 +00:00
$tag -> update ( $orig );
}
}
2014-12-08 18:52:00 +00:00
protected static function getActivityObjectHomepage ( ActivityObject $object , array $hints = array ())
2010-02-25 00:51:24 +00:00
{
$homepage = null ;
$poco = $object -> poco ;
if ( ! empty ( $poco )) {
$url = $poco -> getPrimaryURL ();
2010-03-02 00:35:36 +00:00
if ( $url && $url -> type == 'homepage' ) {
2010-02-25 00:51:24 +00:00
$homepage = $url -> value ;
}
}
// @todo Try for a another PoCo URL?
return $homepage ;
}
2014-12-08 18:52:00 +00:00
protected static function getActivityObjectLocation ( ActivityObject $object , array $hints = array ())
2010-02-25 00:51:24 +00:00
{
$location = null ;
2010-02-26 00:58:51 +00:00
if ( ! empty ( $object -> poco ) &&
isset ( $object -> poco -> address -> formatted )) {
$location = $object -> poco -> address -> formatted ;
} else if ( array_key_exists ( 'location' , $hints )) {
$location = $hints [ 'location' ];
}
if ( ! empty ( $location )) {
2015-02-12 17:18:55 +00:00
if ( mb_strlen ( $location ) > 191 ) { // not 255 because utf8mb4 takes more space
$location = mb_substr ( $note , 0 , 191 - 3 ) . ' … ' ;
2010-02-25 00:51:24 +00:00
}
}
// @todo Try to find location some othe way? Via goerss point?
return $location ;
}
2014-12-08 18:52:00 +00:00
protected static function getActivityObjectBio ( ActivityObject $object , array $hints = array ())
2010-02-25 00:51:24 +00:00
{
$bio = null ;
if ( ! empty ( $object -> poco )) {
$note = $object -> poco -> note ;
2010-02-26 00:58:51 +00:00
} else if ( array_key_exists ( 'bio' , $hints )) {
$note = $hints [ 'bio' ];
}
if ( ! empty ( $note )) {
if ( Profile :: bioTooLong ( $note )) {
// XXX: truncate ok?
$bio = mb_substr ( $note , 0 , Profile :: maxBio () - 3 ) . ' … ' ;
} else {
$bio = $note ;
2010-02-25 00:51:24 +00:00
}
}
// @todo Try to get bio info some other way?
return $bio ;
}
2014-12-08 18:52:00 +00:00
public static function getActivityObjectNickname ( ActivityObject $object , array $hints = array ())
2010-02-21 17:56:46 +00:00
{
2010-02-24 19:06:10 +00:00
if ( $object -> poco ) {
if ( ! empty ( $object -> poco -> preferredUsername )) {
return common_nicknamize ( $object -> poco -> preferredUsername );
}
}
2010-02-26 00:58:51 +00:00
2010-02-22 12:57:44 +00:00
if ( ! empty ( $object -> nickname )) {
return common_nicknamize ( $object -> nickname );
}
2010-02-21 17:56:46 +00:00
2010-02-26 00:58:51 +00:00
if ( array_key_exists ( 'nickname' , $hints )) {
return $hints [ 'nickname' ];
}
2010-03-20 11:44:38 +00:00
// Try the profile url (like foo.example.com or example.com/user/foo)
2010-12-15 20:14:25 +00:00
if ( ! empty ( $object -> link )) {
$profileUrl = $object -> link ;
} else if ( ! empty ( $hints [ 'profileurl' ])) {
$profileUrl = $hints [ 'profileurl' ];
}
2010-03-20 11:44:38 +00:00
if ( ! empty ( $profileUrl )) {
$nickname = self :: nicknameFromURI ( $profileUrl );
}
// Try the URI (may be a tag:, http:, acct:, ...
if ( empty ( $nickname )) {
$nickname = self :: nicknameFromURI ( $object -> id );
}
2010-02-21 17:56:46 +00:00
2010-02-22 02:57:09 +00:00
// Try a Webfinger if one was passed (way) down
if ( empty ( $nickname )) {
if ( array_key_exists ( 'webfinger' , $hints )) {
$nickname = self :: nicknameFromURI ( $hints [ 'webfinger' ]);
}
}
// Try the name
2010-02-21 17:56:46 +00:00
if ( empty ( $nickname )) {
$nickname = common_nicknamize ( $object -> title );
2010-02-17 01:49:49 +00:00
}
2010-02-21 17:56:46 +00:00
return $nickname ;
2010-02-17 01:49:49 +00:00
}
2010-02-21 17:56:46 +00:00
protected static function nicknameFromURI ( $uri )
{
2010-12-15 20:14:25 +00:00
if ( preg_match ( '/(\w+):/' , $uri , $matches )) {
$protocol = $matches [ 1 ];
} else {
return null ;
}
2010-02-21 17:56:46 +00:00
switch ( $protocol ) {
case 'acct' :
case 'mailto' :
if ( preg_match ( " /^ $protocol :(.*)?@.* \$ / " , $uri , $matches )) {
return common_canonical_nickname ( $matches [ 1 ]);
}
return null ;
case 'http' :
return common_url_to_nickname ( $uri );
break ;
default :
return null ;
}
}
2010-02-22 02:37:12 +00:00
2010-03-04 02:23:28 +00:00
/**
2010-03-21 22:18:37 +00:00
* Look up , and if necessary create , an Ostatus_profile for the remote
* entity with the given webfinger address .
* This should never return null -- you will either get an object or
* an exception will be thrown .
*
2010-03-04 02:23:28 +00:00
* @ param string $addr webfinger address
* @ return Ostatus_profile
* @ throws Exception on error conditions
2010-04-07 00:32:04 +01:00
* @ throws OStatusShadowException if this reference would obscure a local user / group
2010-03-04 02:23:28 +00:00
*/
2010-02-22 02:37:12 +00:00
public static function ensureWebfinger ( $addr )
{
2017-04-16 10:01:16 +01:00
// Normalize $addr, i.e. add 'acct:' if missing
$addr = Discovery :: normalize ( $addr );
2010-02-26 01:29:52 +00:00
2017-04-16 10:01:16 +01:00
// Try the cache
2010-02-26 01:29:52 +00:00
$uri = self :: cacheGet ( sprintf ( 'ostatus_profile:webfinger:%s' , $addr ));
if ( $uri !== false ) {
if ( is_null ( $uri )) {
2010-03-04 02:23:28 +00:00
// Negative cache entry
2010-09-19 14:17:36 +01:00
// TRANS: Exception.
2017-04-22 09:55:24 +01:00
throw new Exception ( _m ( 'Not a valid webfinger address (via cache).' ));
2010-02-26 01:29:52 +00:00
}
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'uri' , $uri );
2013-10-30 13:43:40 +00:00
if ( $oprofile instanceof Ostatus_profile ) {
2010-02-26 01:29:52 +00:00
return $oprofile ;
}
2017-04-22 09:55:24 +01:00
common_log ( LOG_ERR , sprintf ( __METHOD__ . ': Webfinger address cache inconsistent with database, did not find Ostatus_profile uri==%s' , $uri ));
self :: cacheSet ( sprintf ( 'ostatus_profile:webfinger:%s' , $addr ), false );
2010-02-26 01:29:52 +00:00
}
2010-03-16 16:25:18 +00:00
// Try looking it up
2017-04-16 10:01:16 +01:00
$oprofile = Ostatus_profile :: getKV ( 'uri' , $addr );
2010-02-22 02:37:12 +00:00
2013-10-30 13:43:40 +00:00
if ( $oprofile instanceof Ostatus_profile ) {
2014-04-28 13:08:42 +01:00
self :: cacheSet ( sprintf ( 'ostatus_profile:webfinger:%s' , $addr ), $oprofile -> getUri ());
2010-02-22 02:37:12 +00:00
return $oprofile ;
}
// Now, try some discovery
2010-02-25 22:34:56 +00:00
$disco = new Discovery ();
2010-02-22 02:37:12 +00:00
2010-02-26 20:38:48 +00:00
try {
2010-03-16 16:25:18 +00:00
$xrd = $disco -> lookup ( $addr );
2010-02-26 20:38:48 +00:00
} catch ( Exception $e ) {
2010-03-04 02:23:28 +00:00
// Save negative cache entry so we don't waste time looking it up again.
2011-08-19 16:13:15 +01:00
// @todo FIXME: Distinguish temporary failures?
2010-02-26 01:29:52 +00:00
self :: cacheSet ( sprintf ( 'ostatus_profile:webfinger:%s' , $addr ), null );
2011-04-29 17:59:47 +01:00
// TRANS: Exception.
2010-09-19 14:17:36 +01:00
throw new Exception ( _m ( 'Not a valid webfinger address.' ));
2010-02-22 02:37:12 +00:00
}
2015-06-06 14:58:08 +01:00
$hints = array_merge ( array ( 'webfinger' => $addr ),
DiscoveryHints :: fromXRD ( $xrd ));
2010-03-16 16:25:18 +00:00
// If there's an Hcard, let's grab its info
if ( array_key_exists ( 'hcard' , $hints )) {
if ( ! array_key_exists ( 'profileurl' , $hints ) ||
$hints [ 'hcard' ] != $hints [ 'profileurl' ]) {
$hcardHints = DiscoveryHints :: fromHcardUrl ( $hints [ 'hcard' ]);
$hints = array_merge ( $hcardHints , $hints );
}
2010-02-26 00:58:51 +00:00
}
2010-02-22 02:37:12 +00:00
// If we got a feed URL, try that
2014-07-27 22:06:08 +01:00
$feedUrl = null ;
2010-03-16 16:25:18 +00:00
if ( array_key_exists ( 'feedurl' , $hints )) {
2014-07-27 22:06:08 +01:00
$feedUrl = $hints [ 'feedurl' ];
2010-02-22 02:37:12 +00:00
try {
2010-03-18 22:11:25 +00:00
common_log ( LOG_INFO , " Discovery on acct: $addr with feed URL " . $hints [ 'feedurl' ]);
2010-03-16 16:25:18 +00:00
$oprofile = self :: ensureFeedURL ( $hints [ 'feedurl' ], $hints );
2014-04-28 13:08:42 +01:00
self :: cacheSet ( sprintf ( 'ostatus_profile:webfinger:%s' , $addr ), $oprofile -> getUri ());
2010-02-22 02:37:12 +00:00
return $oprofile ;
} catch ( Exception $e ) {
common_log ( LOG_WARNING , " Failed creating profile from feed URL ' $feedUrl ': " . $e -> getMessage ());
// keep looking
}
}
// If we got a profile page, try that!
2014-07-27 22:06:08 +01:00
$profileUrl = null ;
2010-03-16 16:25:18 +00:00
if ( array_key_exists ( 'profileurl' , $hints )) {
2014-07-27 22:06:08 +01:00
$profileUrl = $hints [ 'profileurl' ];
2010-02-22 02:37:12 +00:00
try {
2010-02-26 02:07:52 +00:00
common_log ( LOG_INFO , " Discovery on acct: $addr with profile URL $profileUrl " );
2010-03-18 20:13:57 +00:00
$oprofile = self :: ensureProfileURL ( $hints [ 'profileurl' ], $hints );
2014-04-28 13:08:42 +01:00
self :: cacheSet ( sprintf ( 'ostatus_profile:webfinger:%s' , $addr ), $oprofile -> getUri ());
2010-02-22 02:37:12 +00:00
return $oprofile ;
2010-04-07 00:32:04 +01:00
} catch ( OStatusShadowException $e ) {
// We've ended up with a remote reference to a local user or group.
2011-08-19 16:13:15 +01:00
// @todo FIXME: Ideally we should be able to say who it was so we can
2010-04-07 00:32:04 +01:00
// go back and refer to it the regular way
throw $e ;
2010-02-22 02:37:12 +00:00
} catch ( Exception $e ) {
common_log ( LOG_WARNING , " Failed creating profile from profile URL ' $profileUrl ': " . $e -> getMessage ());
// keep looking
2010-04-07 00:32:04 +01:00
//
2011-08-19 16:13:15 +01:00
// @todo FIXME: This means an error discovering from profile page
2010-04-07 00:32:04 +01:00
// may give us a corrupt entry using the webfinger URI, which
// will obscure the correct page-keyed profile later on.
2010-02-22 02:37:12 +00:00
}
}
// XXX: try hcard
// XXX: try FOAF
2010-03-16 16:25:18 +00:00
if ( array_key_exists ( 'salmon' , $hints )) {
$salmonEndpoint = $hints [ 'salmon' ];
2010-02-22 02:37:12 +00:00
// An account URL, a salmon endpoint, and a dream? Not much to go
// on, but let's give it a try
$uri = 'acct:' . $addr ;
$profile = new Profile ();
$profile -> nickname = self :: nicknameFromUri ( $uri );
$profile -> created = common_sql_now ();
2014-07-27 22:06:08 +01:00
if ( ! is_null ( $profileUrl )) {
2010-02-22 02:57:09 +00:00
$profile -> profileurl = $profileUrl ;
}
2010-02-22 02:37:12 +00:00
$profile_id = $profile -> insert ();
2013-10-30 13:43:40 +00:00
if ( $profile_id === false ) {
2010-02-22 02:37:12 +00:00
common_log_db_error ( $profile , 'INSERT' , __FILE__ );
2010-09-19 14:17:36 +01:00
// TRANS: Exception. %s is a webfinger address.
2011-04-10 23:39:27 +01:00
throw new Exception ( sprintf ( _m ( 'Could not save profile for "%s".' ), $addr ));
2010-02-22 02:37:12 +00:00
}
$oprofile = new Ostatus_profile ();
$oprofile -> uri = $uri ;
$oprofile -> salmonuri = $salmonEndpoint ;
$oprofile -> profile_id = $profile_id ;
$oprofile -> created = common_sql_now ();
2014-07-27 22:06:08 +01:00
if ( ! is_null ( $feedUrl )) {
$oprofile -> feeduri = $feedUrl ;
2010-02-22 02:57:09 +00:00
}
2010-02-22 02:37:12 +00:00
$result = $oprofile -> insert ();
2013-10-30 13:43:40 +00:00
if ( $result === false ) {
2014-07-27 22:06:08 +01:00
$profile -> delete ();
2010-02-22 02:37:12 +00:00
common_log_db_error ( $oprofile , 'INSERT' , __FILE__ );
2010-09-19 14:17:36 +01:00
// TRANS: Exception. %s is a webfinger address.
2011-04-17 19:08:03 +01:00
throw new Exception ( sprintf ( _m ( 'Could not save OStatus profile for "%s".' ), $addr ));
2010-02-22 02:37:12 +00:00
}
2014-04-28 13:08:42 +01:00
self :: cacheSet ( sprintf ( 'ostatus_profile:webfinger:%s' , $addr ), $oprofile -> getUri ());
2010-02-22 02:37:12 +00:00
return $oprofile ;
}
2010-09-19 14:17:36 +01:00
// TRANS: Exception. %s is a webfinger address.
2011-04-10 23:39:27 +01:00
throw new Exception ( sprintf ( _m ( 'Could not find a valid profile for "%s".' ), $addr ));
2010-02-22 02:37:12 +00:00
}
2010-02-25 03:02:43 +00:00
2010-03-17 22:49:10 +00:00
/**
* Store the full - length scrubbed HTML of a remote notice to an attachment
* file on our server . We ' ll link to this at the end of the cropped version .
*
* @ param string $title plaintext for HTML page ' s title
* @ param string $rendered HTML fragment for HTML page ' s body
* @ return File
*/
2010-02-25 03:02:43 +00:00
function saveHTMLFile ( $title , $rendered )
{
2010-03-30 00:27:50 +01:00
$final = sprintf ( " <!DOCTYPE html> \n " .
'<html><head>' .
'<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">' .
'<title>%s</title>' .
'</head>' .
2010-03-17 22:49:10 +00:00
'<body>%s</body></html>' ,
2010-02-25 03:02:43 +00:00
htmlspecialchars ( $title ),
$rendered );
$filename = File :: filename ( $this -> localProfile (),
'ostatus' , // ignored?
'text/html' );
$filepath = File :: path ( $filename );
2015-02-19 17:59:28 +00:00
$fileurl = File :: url ( $filename );
2010-02-25 03:02:43 +00:00
file_put_contents ( $filepath , $final );
$file = new File ;
$file -> filename = $filename ;
2015-02-19 17:59:28 +00:00
$file -> urlhash = File :: hashurl ( $fileurl );
$file -> url = $fileurl ;
2010-02-25 03:02:43 +00:00
$file -> size = filesize ( $filepath );
$file -> date = time ();
$file -> mimetype = 'text/html' ;
$file_id = $file -> insert ();
if ( $file_id === false ) {
common_log_db_error ( $file , " INSERT " , __FILE__ );
2010-11-02 22:48:36 +00:00
// TRANS: Server exception.
2010-09-19 14:17:36 +01:00
throw new ServerException ( _m ( 'Could not store HTML content of long post as file.' ));
2010-02-25 03:02:43 +00:00
}
2010-02-25 03:26:40 +00:00
return $file ;
2010-02-25 03:02:43 +00:00
}
2010-09-01 21:17:18 +01:00
static function ensureProfileURI ( $uri )
{
$oprofile = null ;
2010-09-01 22:05:11 +01:00
// First, try to query it
2013-08-18 12:04:58 +01:00
$oprofile = Ostatus_profile :: getKV ( 'uri' , $uri );
2010-09-01 22:05:11 +01:00
2013-10-30 13:43:40 +00:00
if ( $oprofile instanceof Ostatus_profile ) {
return $oprofile ;
}
2010-09-01 22:05:11 +01:00
2013-10-30 13:43:40 +00:00
// If unfound, do discovery stuff
if ( preg_match ( " /^( \ w+) \ :(.*)/ " , $uri , $match )) {
$protocol = $match [ 1 ];
switch ( $protocol ) {
case 'http' :
case 'https' :
2013-11-01 12:20:23 +00:00
$oprofile = self :: ensureProfileURL ( $uri );
2013-10-30 13:43:40 +00:00
break ;
case 'acct' :
case 'mailto' :
$rest = $match [ 2 ];
2013-11-01 12:20:23 +00:00
$oprofile = self :: ensureWebfinger ( $rest );
2013-10-30 13:43:40 +00:00
break ;
default :
// TRANS: Server exception.
// TRANS: %1$s is a protocol, %2$s is a URI.
throw new ServerException ( sprintf ( _m ( 'Unrecognized URI protocol for profile: %1$s (%2$s).' ),
$protocol ,
$uri ));
2010-09-01 21:17:18 +01:00
}
2013-10-30 13:43:40 +00:00
} else {
// TRANS: Server exception. %s is a URI.
throw new ServerException ( sprintf ( _m ( 'No URI protocol for profile: %s.' ), $uri ));
2010-09-01 21:17:18 +01:00
}
2011-02-09 08:08:52 +00:00
2010-09-01 22:05:11 +01:00
return $oprofile ;
2010-09-01 21:17:18 +01:00
}
2010-12-23 17:42:42 +00:00
2014-07-01 14:48:34 +01:00
public function checkAuthorship ( Activity $activity )
2010-12-23 17:42:42 +00:00
{
2011-03-06 19:15:34 +00:00
if ( $this -> isGroup () || $this -> isPeopletag ()) {
// A group or propletag feed will contain posts from multiple authors.
2010-12-23 17:42:42 +00:00
$oprofile = self :: ensureActorProfile ( $activity );
2011-03-06 19:15:34 +00:00
if ( $oprofile -> isGroup () || $oprofile -> isPeopletag ()) {
2010-12-23 17:42:42 +00:00
// Groups can't post notices in StatusNet.
2011-03-06 19:15:34 +00:00
common_log ( LOG_WARNING ,
" OStatus: skipping post with group listed " .
2014-04-28 13:08:42 +01:00
" as author: " . $oprofile -> getUri () . " in feed from " . $this -> getUri ());
2014-11-23 23:02:20 +00:00
throw new ServerException ( 'Activity author is a non-actor' );
2010-12-23 17:42:42 +00:00
}
} else {
$actor = $activity -> actor ;
if ( empty ( $actor )) {
// OK here! assume the default
2014-04-28 13:08:42 +01:00
} else if ( $actor -> id == $this -> getUri () || $actor -> link == $this -> getUri ()) {
2010-12-23 17:42:42 +00:00
$this -> updateFromActivityObject ( $actor );
} else if ( $actor -> id ) {
// We have an ActivityStreams actor with an explicit ID that doesn't match the feed owner.
// This isn't what we expect from mainline OStatus person feeds!
// Group feeds go down another path, with different validation...
// Most likely this is a plain ol' blog feed of some kind which
// doesn't match our expectations. We'll take the entry, but ignore
// the <author> info.
2014-04-28 13:08:42 +01:00
common_log ( LOG_WARNING , " Got an actor ' { $actor -> title } ' ( { $actor -> id } ) on single-user feed for " . $this -> getUri ());
2010-12-23 17:42:42 +00:00
} else {
// Plain <author> without ActivityStreams actor info.
// We'll just ignore this info for now and save the update under the feed's identity.
}
$oprofile = $this ;
}
2014-07-01 14:48:34 +01:00
return $oprofile -> localProfile ();
2010-12-23 17:42:42 +00:00
}
2015-07-11 18:46:01 +01:00
public function updateUriKeys ( $profile_uri , array $hints = array ())
{
$orig = clone ( $this );
2016-06-25 10:59:31 +01:00
common_debug ( 'URIFIX These identities both say they are each other: ' . _ve ( $orig -> uri ) . ' and ' . _ve ( $profile_uri ));
2015-07-11 18:46:01 +01:00
$this -> uri = $profile_uri ;
2016-06-25 10:59:31 +01:00
if ( array_key_exists ( 'feedurl' , $hints ) && common_valid_http_url ( $hints [ 'feedurl' ])) {
try {
$feedsub = FeedSub :: getByUri ( $this -> feeduri );
common_debug ( 'URIFIX Changing FeedSub id==[' . _ve ( $feedsub -> id ) . '] feeduri ' . _ve ( $feedsub -> uri ) . ' to ' . _ve ( $hints [ 'feedurl' ]));
2015-07-11 18:46:01 +01:00
$feedorig = clone ( $feedsub );
$feedsub -> uri = $hints [ 'feedurl' ];
$feedsub -> updateWithKeys ( $feedorig );
2016-06-25 10:59:31 +01:00
} catch ( EmptyPkeyValueException $e ) {
common_debug ( 'URIFIX Old Ostatus_profile did not have feedurl set, ensuring new feedurl: ' . _ve ( $hints [ 'feedurl' ]));
FeedSub :: ensureFeed ( $hints [ 'feedurl' ]);
} catch ( NoResultException $e ) {
common_debug ( 'URIFIX Missing FeedSub entry for the Ostatus_profile, ensuring new feedurl: ' . _ve ( $hints [ 'feedurl' ]));
2015-07-11 18:46:01 +01:00
FeedSub :: ensureFeed ( $hints [ 'feedurl' ]);
}
$this -> feeduri = $hints [ 'feedurl' ];
2016-06-25 10:59:31 +01:00
} elseif ( array_key_exists ( 'feedurl' )) {
common_log ( LOG_WARN , 'The feedurl hint we got was not a valid HTTP URL: ' . _ve ( $hints [ 'feedurl' ]));
2015-07-11 18:46:01 +01:00
}
2016-06-25 10:59:31 +01:00
2015-07-11 18:46:01 +01:00
if ( array_key_exists ( 'salmon' , $hints )) {
common_debug ( 'URIFIX Changing Ostatus_profile salmonuri from "' . $this -> salmonuri . '" to "' . $hints [ 'salmon' ] . '"' );
$this -> salmonuri = $hints [ 'salmon' ];
}
common_debug ( 'URIFIX Updating Ostatus_profile URI for ' . $orig -> uri . ' to ' . $this -> uri );
2016-01-28 15:42:59 +00:00
$this -> updateWithKeys ( $orig ); // Will use the PID column(s) in the 'UPDATE ... WHERE [unique selector]'
2015-07-11 18:46:01 +01:00
common_debug ( 'URIFIX Subscribing/renewing feedsub for Ostatus_profile ' . $this -> uri );
$this -> subscribe ();
}
2009-11-20 17:42:19 +00:00
}