2017-11-08 01:37:53 +01:00
< ? php
/**
* @ file include / dfrn . php
* @ brief The implementation of the dfrn protocol
*
* @ see https :// github . com / friendica / friendica / wiki / Protocol and
* https :// github . com / friendica / friendica / blob / master / spec / dfrn2 . pdf
*/
2017-11-08 23:02:50 +01:00
namespace Friendica\Protocol ;
2017-11-08 01:37:53 +01:00
2018-01-13 15:36:21 +01:00
use Friendica\App ;
2018-01-01 02:58:09 +01:00
use Friendica\Content\OEmbed ;
2018-02-05 01:23:49 +01:00
use Friendica\Content\Text\BBCode ;
2018-03-08 20:58:35 +01:00
use Friendica\Content\Text\HTML ;
2018-01-17 19:42:40 +01:00
use Friendica\Core\Addon ;
2017-11-08 01:37:53 +01:00
use Friendica\Core\Config ;
2018-01-22 22:59:31 +01:00
use Friendica\Core\L10n ;
2017-11-08 01:37:53 +01:00
use Friendica\Core\System ;
use Friendica\Core\Worker ;
2017-11-08 04:57:46 +01:00
use Friendica\Database\DBM ;
2017-12-07 15:04:24 +01:00
use Friendica\Model\Contact ;
2018-03-17 02:45:02 +01:00
use Friendica\Model\Event ;
2017-12-07 15:09:28 +01:00
use Friendica\Model\GContact ;
2017-12-17 01:21:56 +01:00
use Friendica\Model\Group ;
2018-01-28 12:18:08 +01:00
use Friendica\Model\Item ;
2017-12-07 14:56:11 +01:00
use Friendica\Model\Profile ;
2017-12-19 18:15:56 +01:00
use Friendica\Model\User ;
2017-12-07 14:56:11 +01:00
use Friendica\Object\Image ;
2017-11-16 05:09:11 +01:00
use Friendica\Protocol\OStatus ;
2018-01-19 17:34:56 +01:00
use Friendica\Util\Crypto ;
2018-01-27 03:38:34 +01:00
use Friendica\Util\DateTimeFormat ;
2018-01-27 05:18:38 +01:00
use Friendica\Util\Network ;
2017-11-10 13:45:33 +01:00
use Friendica\Util\XML ;
2018-04-02 14:53:48 +02:00
use Friendica\Protocol\Diaspora ;
2017-11-08 01:37:53 +01:00
use dba ;
2017-11-08 06:10:47 +01:00
use DOMDocument ;
2017-12-17 21:24:57 +01:00
use DOMXPath ;
2018-01-19 02:15:26 +01:00
use HTMLPurifier ;
use HTMLPurifier_Config ;
2017-11-08 01:37:53 +01:00
2017-12-17 21:24:57 +01:00
require_once 'boot.php' ;
require_once 'include/dba.php' ;
2017-11-08 23:02:50 +01:00
require_once " include/enotify.php " ;
require_once " include/items.php " ;
require_once " include/text.php " ;
2017-11-08 01:37:53 +01:00
/**
* @ brief This class contain functions to create and send DFRN XML files
*/
2017-11-08 23:02:50 +01:00
class DFRN
{
2017-11-08 01:37:53 +01:00
2018-05-13 10:34:33 +02:00
const TOP_LEVEL = 0 ; // Top level posting
const REPLY = 1 ; // Regular reply that is stored locally
const REPLY_RC = 2 ; // Reply that will be relayed
2017-11-08 01:37:53 +01:00
/**
* @ brief Generates the atom entries for delivery . php
*
* This function is used whenever content is transmitted via DFRN .
*
* @ param array $items Item elements
* @ param array $owner Owner record
*
* @ return string DFRN entries
* @ todo Add type - hints
*/
2017-11-08 23:02:50 +01:00
public static function entries ( $items , $owner )
{
2017-11-08 01:37:53 +01:00
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2017-11-23 20:01:58 +01:00
$root = self :: addHeader ( $doc , $owner , " dfrn:owner " , " " , false );
2017-11-08 01:37:53 +01:00
if ( ! count ( $items )) {
return trim ( $doc -> saveXML ());
}
foreach ( $items as $item ) {
$entry = self :: entry ( $doc , " text " , $item , $owner , $item [ " entry:comment-allow " ], $item [ " entry:cid " ]);
$root -> appendChild ( $entry );
}
return ( trim ( $doc -> saveXML ()));
}
/**
* @ brief Generate an atom feed for the given user
*
* This function is called when another server is pulling data from the user feed .
*
2017-11-08 23:02:50 +01:00
* @ param string $dfrn_id DFRN ID from the requesting party
* @ param string $owner_nick Owner nick name
* @ param string $last_update Date of the last update
* @ param int $direction Can be - 1 , 0 or 1.
* @ param boolean $onlyheader Output only the header without content ? ( Default is " no " )
2017-11-08 01:37:53 +01:00
*
* @ return string DFRN feed entries
*/
2017-11-08 23:02:50 +01:00
public static function feed ( $dfrn_id , $owner_nick , $last_update , $direction = 0 , $onlyheader = false )
{
2017-11-08 01:37:53 +01:00
$a = get_app ();
$sitefeed = (( strlen ( $owner_nick )) ? false : true ); // not yet implemented, need to rewrite huge chunks of following logic
$public_feed = (( $dfrn_id ) ? false : true );
$starred = false ; // not yet implemented, possible security issues
$converse = false ;
if ( $public_feed && $a -> argc > 2 ) {
for ( $x = 2 ; $x < $a -> argc ; $x ++ ) {
if ( $a -> argv [ $x ] == 'converse' ) {
$converse = true ;
}
if ( $a -> argv [ $x ] == 'starred' ) {
$starred = true ;
}
if ( $a -> argv [ $x ] == 'category' && $a -> argc > ( $x + 1 ) && strlen ( $a -> argv [ $x + 1 ])) {
$category = $a -> argv [ $x + 1 ];
}
}
}
// default permissions - anonymous user
$sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' " ;
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`, `user`.`account-type`
2017-11-08 01:37:53 +01:00
FROM `contact` INNER JOIN `user` ON `user` . `uid` = `contact` . `uid`
WHERE `contact` . `self` AND `user` . `nickname` = '%s' LIMIT 1 " ,
dbesc ( $owner_nick )
);
2017-11-08 04:57:46 +01:00
if ( ! DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
killme ();
}
$owner = $r [ 0 ];
$owner_id = $owner [ 'uid' ];
$owner_nick = $owner [ 'nickname' ];
$sql_post_table = " " ;
if ( ! $public_feed ) {
$sql_extra = '' ;
2017-11-08 23:02:50 +01:00
switch ( $direction ) {
2017-11-08 01:37:53 +01:00
case ( - 1 ) :
$sql_extra = sprintf ( " AND `issued-id` = '%s' " , dbesc ( $dfrn_id ));
$my_id = $dfrn_id ;
break ;
case 0 :
$sql_extra = sprintf ( " AND `issued-id` = '%s' AND `duplex` = 1 " , dbesc ( $dfrn_id ));
$my_id = '1:' . $dfrn_id ;
break ;
case 1 :
$sql_extra = sprintf ( " AND `dfrn-id` = '%s' AND `duplex` = 1 " , dbesc ( $dfrn_id ));
$my_id = '0:' . $dfrn_id ;
break ;
default :
return false ;
break ; // NOTREACHED
}
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT * FROM `contact` WHERE NOT `blocked` AND `contact`.`uid` = %d $sql_extra LIMIT 1 " ,
2017-11-08 01:37:53 +01:00
intval ( $owner_id )
);
2017-11-08 04:57:46 +01:00
if ( ! DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
killme ();
}
$contact = $r [ 0 ];
2017-11-08 23:02:50 +01:00
include_once 'include/security.php' ;
2017-12-17 01:21:56 +01:00
$groups = Group :: getIdsByContactId ( $contact [ 'id' ]);
2017-11-08 01:37:53 +01:00
if ( count ( $groups )) {
for ( $x = 0 ; $x < count ( $groups ); $x ++ )
$groups [ $x ] = '<' . intval ( $groups [ $x ]) . '>' ;
$gs = implode ( '|' , $groups );
} else {
$gs = '<<>>' ; // Impossible to match
}
2017-11-08 23:02:50 +01:00
$sql_extra = sprintf (
"
2017-11-08 01:37:53 +01:00
AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s' )
" ,
intval ( $contact [ 'id' ]),
intval ( $contact [ 'id' ]),
dbesc ( $gs ),
dbesc ( $gs )
);
}
if ( $public_feed ) {
$sort = 'DESC' ;
} else {
$sort = 'ASC' ;
}
if ( ! strlen ( $last_update )) {
$last_update = 'now -30 days' ;
}
if ( isset ( $category )) {
2017-11-08 23:02:50 +01:00
$sql_post_table = sprintf (
" INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` " ,
dbesc ( protect_sprintf ( $category )),
intval ( TERM_OBJ_POST ),
intval ( TERM_CATEGORY ),
intval ( $owner_id )
);
2017-11-08 01:37:53 +01:00
//$sql_extra .= file_tag_file_query('item',$category,'category');
}
if ( $public_feed ) {
if ( ! $converse ) {
$sql_extra .= " AND `contact`.`self` = 1 " ;
}
}
2018-01-27 03:38:34 +01:00
$check_date = DateTimeFormat :: utc ( $last_update );
2017-11-08 01:37:53 +01:00
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `item`.*, `item`.`id` AS `item_id`,
2017-11-08 01:37:53 +01:00
`contact` . `name` , `contact` . `network` , `contact` . `photo` , `contact` . `url` ,
`contact` . `name-date` , `contact` . `uri-date` , `contact` . `avatar-date` ,
`contact` . `thumb` , `contact` . `dfrn-id` , `contact` . `self` ,
`sign` . `signed_text` , `sign` . `signature` , `sign` . `signer`
FROM `item` USE INDEX ( `uid_wall_changed` ) $sql_post_table
STRAIGHT_JOIN `contact` ON `contact` . `id` = `item` . `contact-id`
AND ( NOT `contact` . `blocked` OR `contact` . `pending` )
LEFT JOIN `sign` ON `sign` . `iid` = `item` . `id`
WHERE `item` . `uid` = % d AND `item` . `visible` AND NOT `item` . `moderated` AND `item` . `parent` != 0
AND `item` . `wall` AND `item` . `changed` > '%s'
$sql_extra
ORDER BY `item` . `parent` " . $sort . " , `item` . `created` ASC LIMIT 0 , 300 " ,
intval ( $owner_id ),
dbesc ( $check_date ),
dbesc ( $sort )
);
/*
* Will check further below if this actually returned results .
* We will provide an empty feed if that is the case .
*/
$items = $r ;
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
$alternatelink = $owner [ 'url' ];
if ( isset ( $category )) {
$alternatelink .= " /category/ " . $category ;
}
if ( $public_feed ) {
$author = " dfrn:owner " ;
} else {
$author = " author " ;
}
2017-11-23 20:01:58 +01:00
$root = self :: addHeader ( $doc , $owner , $author , $alternatelink , true );
2017-11-08 01:37:53 +01:00
/// @TODO This hook can't work anymore
2018-01-17 19:42:40 +01:00
// Addon::callHooks('atom_feed', $atom);
2017-11-08 01:37:53 +01:00
2017-11-08 04:57:46 +01:00
if ( ! DBM :: is_result ( $items ) || $onlyheader ) {
2017-11-08 01:37:53 +01:00
$atom = trim ( $doc -> saveXML ());
2018-01-17 19:42:40 +01:00
Addon :: callHooks ( 'atom_feed_end' , $atom );
2017-11-08 01:37:53 +01:00
return $atom ;
}
foreach ( $items as $item ) {
// prevent private email from leaking.
if ( $item [ 'network' ] == NETWORK_MAIL ) {
continue ;
}
// public feeds get html, our own nodes use bbcode
if ( $public_feed ) {
$type = 'html' ;
// catch any email that's in a public conversation and make sure it doesn't leak
if ( $item [ 'private' ]) {
continue ;
}
} else {
$type = 'text' ;
}
$entry = self :: entry ( $doc , $type , $item , $owner , true );
$root -> appendChild ( $entry );
}
$atom = trim ( $doc -> saveXML ());
2018-01-17 19:42:40 +01:00
Addon :: callHooks ( 'atom_feed_end' , $atom );
2017-11-08 01:37:53 +01:00
return $atom ;
}
/**
* @ brief Generate an atom entry for a given item id
*
2017-11-08 23:02:50 +01:00
* @ param int $item_id The item id
2017-11-08 01:37:53 +01:00
* @ param boolean $conversation Show the conversation . If false show the single post .
*
* @ return string DFRN feed entry
*/
2017-11-08 23:02:50 +01:00
public static function itemFeed ( $item_id , $conversation = false )
{
2017-11-08 01:37:53 +01:00
if ( $conversation ) {
$condition = '`item`.`parent`' ;
} else {
$condition = '`item`.`id`' ;
}
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `item`.*, `item`.`id` AS `item_id`,
2017-11-08 01:37:53 +01:00
`contact` . `name` , `contact` . `network` , `contact` . `photo` , `contact` . `url` ,
`contact` . `name-date` , `contact` . `uri-date` , `contact` . `avatar-date` ,
`contact` . `thumb` , `contact` . `dfrn-id` , `contact` . `self` ,
`sign` . `signed_text` , `sign` . `signature` , `sign` . `signer`
FROM `item`
STRAIGHT_JOIN `contact` ON `contact` . `id` = `item` . `contact-id`
AND ( NOT `contact` . `blocked` OR `contact` . `pending` )
LEFT JOIN `sign` ON `sign` . `iid` = `item` . `id`
WHERE % s = % d AND `item` . `visible` AND NOT `item` . `moderated` AND `item` . `parent` != 0
AND NOT `item` . `private` " ,
$condition ,
intval ( $item_id )
);
2017-11-08 04:57:46 +01:00
if ( ! DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
killme ();
}
$items = $r ;
$item = $r [ 0 ];
2017-12-19 18:15:56 +01:00
if ( $item [ 'uid' ] != 0 ) {
$owner = User :: getOwnerDataById ( $item [ 'uid' ]);
if ( ! $owner ) {
killme ();
}
2017-12-19 18:38:33 +01:00
} else {
2017-12-21 09:58:36 +01:00
$owner = [ 'uid' => 0 , 'nick' => 'feed-item' ];
2017-11-08 01:37:53 +01:00
}
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
$type = 'html' ;
if ( $conversation ) {
$root = $doc -> createElementNS ( NAMESPACE_ATOM1 , 'feed' );
$doc -> appendChild ( $root );
$root -> setAttribute ( " xmlns:thr " , NAMESPACE_THREAD );
$root -> setAttribute ( " xmlns:at " , NAMESPACE_TOMB );
$root -> setAttribute ( " xmlns:media " , NAMESPACE_MEDIA );
$root -> setAttribute ( " xmlns:dfrn " , NAMESPACE_DFRN );
$root -> setAttribute ( " xmlns:activity " , NAMESPACE_ACTIVITY );
$root -> setAttribute ( " xmlns:georss " , NAMESPACE_GEORSS );
$root -> setAttribute ( " xmlns:poco " , NAMESPACE_POCO );
$root -> setAttribute ( " xmlns:ostatus " , NAMESPACE_OSTATUS );
$root -> setAttribute ( " xmlns:statusnet " , NAMESPACE_STATUSNET );
2017-11-23 20:01:58 +01:00
//$root = self::addHeader($doc, $owner, "dfrn:owner", "", false);
2017-11-08 01:37:53 +01:00
foreach ( $items as $item ) {
$entry = self :: entry ( $doc , $type , $item , $owner , true , 0 );
$root -> appendChild ( $entry );
}
} else {
$root = self :: entry ( $doc , $type , $item , $owner , true , 0 , true );
}
$atom = trim ( $doc -> saveXML ());
return $atom ;
}
/**
* @ brief Create XML text for DFRN mails
*
2017-11-08 23:02:50 +01:00
* @ param array $item message elements
2017-11-08 01:37:53 +01:00
* @ param array $owner Owner record
*
* @ return string DFRN mail
* @ todo Add type - hints
*/
2017-11-08 23:02:50 +01:00
public static function mail ( $item , $owner )
{
2017-11-08 01:37:53 +01:00
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2017-11-23 20:01:58 +01:00
$root = self :: addHeader ( $doc , $owner , " dfrn:owner " , " " , false );
2017-11-08 01:37:53 +01:00
$mail = $doc -> createElement ( " dfrn:mail " );
$sender = $doc -> createElement ( " dfrn:sender " );
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $sender , " dfrn:name " , $owner [ 'name' ]);
XML :: addElement ( $doc , $sender , " dfrn:uri " , $owner [ 'url' ]);
XML :: addElement ( $doc , $sender , " dfrn:avatar " , $owner [ 'thumb' ]);
2017-11-08 01:37:53 +01:00
$mail -> appendChild ( $sender );
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $mail , " dfrn:id " , $item [ 'uri' ]);
XML :: addElement ( $doc , $mail , " dfrn:in-reply-to " , $item [ 'parent-uri' ]);
2018-01-27 03:38:34 +01:00
XML :: addElement ( $doc , $mail , " dfrn:sentdate " , DateTimeFormat :: utc ( $item [ 'created' ] . '+00:00' , DateTimeFormat :: ATOM ));
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $mail , " dfrn:subject " , $item [ 'title' ]);
XML :: addElement ( $doc , $mail , " dfrn:content " , $item [ 'body' ]);
2017-11-08 01:37:53 +01:00
$root -> appendChild ( $mail );
return ( trim ( $doc -> saveXML ()));
}
/**
* @ brief Create XML text for DFRN friend suggestions
*
2017-11-08 23:02:50 +01:00
* @ param array $item suggestion elements
2017-11-08 01:37:53 +01:00
* @ param array $owner Owner record
*
* @ return string DFRN suggestions
* @ todo Add type - hints
*/
2017-11-08 23:02:50 +01:00
public static function fsuggest ( $item , $owner )
{
2017-11-08 01:37:53 +01:00
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2017-11-23 20:01:58 +01:00
$root = self :: addHeader ( $doc , $owner , " dfrn:owner " , " " , false );
2017-11-08 01:37:53 +01:00
$suggest = $doc -> createElement ( " dfrn:suggest " );
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $suggest , " dfrn:url " , $item [ 'url' ]);
XML :: addElement ( $doc , $suggest , " dfrn:name " , $item [ 'name' ]);
XML :: addElement ( $doc , $suggest , " dfrn:photo " , $item [ 'photo' ]);
XML :: addElement ( $doc , $suggest , " dfrn:request " , $item [ 'request' ]);
XML :: addElement ( $doc , $suggest , " dfrn:note " , $item [ 'note' ]);
2017-11-08 01:37:53 +01:00
$root -> appendChild ( $suggest );
return ( trim ( $doc -> saveXML ()));
}
/**
* @ brief Create XML text for DFRN relocations
*
* @ param array $owner Owner record
2017-11-08 23:02:50 +01:00
* @ param int $uid User ID
2017-11-08 01:37:53 +01:00
*
* @ return string DFRN relocations
* @ todo Add type - hints
*/
2017-11-08 23:02:50 +01:00
public static function relocate ( $owner , $uid )
{
2017-11-08 01:37:53 +01:00
/* get site pubkey. this could be a new installation with no site keys*/
2017-11-08 23:02:50 +01:00
$pubkey = Config :: get ( 'system' , 'site_pubkey' );
2017-11-08 01:37:53 +01:00
if ( ! $pubkey ) {
2018-01-19 17:58:26 +01:00
$res = Crypto :: newKeypair ( 1024 );
2017-11-08 23:02:50 +01:00
Config :: set ( 'system' , 'site_prvkey' , $res [ 'prvkey' ]);
Config :: set ( 'system' , 'site_pubkey' , $res [ 'pubkey' ]);
2017-11-08 01:37:53 +01:00
}
2017-11-08 23:02:50 +01:00
$rp = q (
" SELECT `resource-id` , `scale`, type FROM `photo`
WHERE `profile` = 1 AND `uid` = % d ORDER BY scale ; " ,
$uid
);
2018-01-15 14:05:12 +01:00
$photos = [];
2017-12-07 14:56:11 +01:00
$ext = Image :: supportedTypes ();
2017-11-08 01:37:53 +01:00
foreach ( $rp as $p ) {
$photos [ $p [ 'scale' ]] = System :: baseUrl () . '/photo/' . $p [ 'resource-id' ] . '-' . $p [ 'scale' ] . '.' . $ext [ $p [ 'type' ]];
}
unset ( $rp , $ext );
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2017-11-23 20:01:58 +01:00
$root = self :: addHeader ( $doc , $owner , " dfrn:owner " , " " , false );
2017-11-08 01:37:53 +01:00
$relocate = $doc -> createElement ( " dfrn:relocate " );
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $relocate , " dfrn:url " , $owner [ 'url' ]);
XML :: addElement ( $doc , $relocate , " dfrn:name " , $owner [ 'name' ]);
XML :: addElement ( $doc , $relocate , " dfrn:addr " , $owner [ 'addr' ]);
XML :: addElement ( $doc , $relocate , " dfrn:avatar " , $owner [ 'avatar' ]);
XML :: addElement ( $doc , $relocate , " dfrn:photo " , $photos [ 4 ]);
XML :: addElement ( $doc , $relocate , " dfrn:thumb " , $photos [ 5 ]);
XML :: addElement ( $doc , $relocate , " dfrn:micro " , $photos [ 6 ]);
XML :: addElement ( $doc , $relocate , " dfrn:request " , $owner [ 'request' ]);
XML :: addElement ( $doc , $relocate , " dfrn:confirm " , $owner [ 'confirm' ]);
XML :: addElement ( $doc , $relocate , " dfrn:notify " , $owner [ 'notify' ]);
XML :: addElement ( $doc , $relocate , " dfrn:poll " , $owner [ 'poll' ]);
2017-11-23 20:01:58 +01:00
XML :: addElement ( $doc , $relocate , " dfrn:sitepubkey " , Config :: get ( 'system' , 'site_pubkey' ));
2017-11-08 01:37:53 +01:00
$root -> appendChild ( $relocate );
return ( trim ( $doc -> saveXML ()));
}
/**
* @ brief Adds the header elements for the DFRN protocol
*
2017-11-08 23:02:50 +01:00
* @ param object $doc XML document
* @ param array $owner Owner record
2017-11-08 01:37:53 +01:00
* @ param string $authorelement Element name for the author
* @ param string $alternatelink link to profile or category
2017-11-08 23:02:50 +01:00
* @ param bool $public Is it a header for public posts ?
2017-11-08 01:37:53 +01:00
*
* @ return object XML root object
* @ todo Add type - hints
*/
2017-11-23 20:01:58 +01:00
private static function addHeader ( $doc , $owner , $authorelement , $alternatelink = " " , $public = false )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
if ( $alternatelink == " " ) {
$alternatelink = $owner [ 'url' ];
}
$root = $doc -> createElementNS ( NAMESPACE_ATOM1 , 'feed' );
$doc -> appendChild ( $root );
$root -> setAttribute ( " xmlns:thr " , NAMESPACE_THREAD );
$root -> setAttribute ( " xmlns:at " , NAMESPACE_TOMB );
$root -> setAttribute ( " xmlns:media " , NAMESPACE_MEDIA );
$root -> setAttribute ( " xmlns:dfrn " , NAMESPACE_DFRN );
$root -> setAttribute ( " xmlns:activity " , NAMESPACE_ACTIVITY );
$root -> setAttribute ( " xmlns:georss " , NAMESPACE_GEORSS );
$root -> setAttribute ( " xmlns:poco " , NAMESPACE_POCO );
$root -> setAttribute ( " xmlns:ostatus " , NAMESPACE_OSTATUS );
$root -> setAttribute ( " xmlns:statusnet " , NAMESPACE_STATUSNET );
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $root , " id " , System :: baseUrl () . " /profile/ " . $owner [ " nick " ]);
XML :: addElement ( $doc , $root , " title " , $owner [ " name " ]);
2017-11-08 01:37:53 +01:00
2018-01-15 14:05:12 +01:00
$attributes = [ " uri " => " https://friendi.ca " , " version " => FRIENDICA_VERSION . " - " . DB_UPDATE_VERSION ];
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $root , " generator " , FRIENDICA_PLATFORM , $attributes );
2017-11-08 01:37:53 +01:00
2018-01-15 14:05:12 +01:00
$attributes = [ " rel " => " license " , " href " => " http://creativecommons.org/licenses/by/3.0/ " ];
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
2018-01-15 14:05:12 +01:00
$attributes = [ " rel " => " alternate " , " type " => " text/html " , " href " => $alternatelink ];
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
if ( $public ) {
// DFRN itself doesn't uses this. But maybe someone else wants to subscribe to the public feed.
2017-11-16 05:09:11 +01:00
OStatus :: hublinks ( $doc , $root , $owner [ " nick " ]);
2017-11-08 01:37:53 +01:00
2018-01-15 14:05:12 +01:00
$attributes = [ " rel " => " salmon " , " href " => System :: baseUrl () . " /salmon/ " . $owner [ " nick " ]];
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
2018-01-15 14:05:12 +01:00
$attributes = [ " rel " => " http://salmon-protocol.org/ns/salmon-replies " , " href " => System :: baseUrl () . " /salmon/ " . $owner [ " nick " ]];
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
2018-01-15 14:05:12 +01:00
$attributes = [ " rel " => " http://salmon-protocol.org/ns/salmon-mention " , " href " => System :: baseUrl () . " /salmon/ " . $owner [ " nick " ]];
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
}
// For backward compatibility we keep this element
if ( $owner [ 'page-flags' ] == PAGE_COMMUNITY ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $root , " dfrn:community " , 1 );
2017-11-08 01:37:53 +01:00
}
// The former element is replaced by this one
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $root , " dfrn:account_type " , $owner [ " account-type " ]);
2017-11-08 01:37:53 +01:00
/// @todo We need a way to transmit the different page flags like "PAGE_PRVGROUP"
2018-01-27 03:38:34 +01:00
XML :: addElement ( $doc , $root , " updated " , DateTimeFormat :: utcNow ( DateTimeFormat :: ATOM ));
2017-11-08 01:37:53 +01:00
2017-11-23 20:01:58 +01:00
$author = self :: addAuthor ( $doc , $owner , $authorelement , $public );
2017-11-08 01:37:53 +01:00
$root -> appendChild ( $author );
return $root ;
}
/**
* @ brief Adds the author element in the header for the DFRN protocol
*
2017-11-23 20:01:58 +01:00
* @ param object $doc XML document
* @ param array $owner Owner record
* @ param string $authorelement Element name for the author
* @ param boolean $public boolean
2017-11-08 01:37:53 +01:00
*
* @ return object XML author object
* @ todo Add type - hints
*/
2017-11-23 20:01:58 +01:00
private static function addAuthor ( $doc , $owner , $authorelement , $public )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
// Is the profile hidden or shouldn't be published in the net? Then add the "hide" element
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `id` FROM `profile` INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
2017-11-08 01:37:53 +01:00
WHERE ( `hidewall` OR NOT `net-publish` ) AND `user` . `uid` = % d " ,
2017-11-08 23:02:50 +01:00
intval ( $owner [ 'uid' ])
);
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
$hidewall = true ;
} else {
$hidewall = false ;
}
$author = $doc -> createElement ( $authorelement );
2018-01-27 03:38:34 +01:00
$namdate = DateTimeFormat :: utc ( $owner [ 'name-date' ] . '+00:00' , DateTimeFormat :: ATOM );
$uridate = DateTimeFormat :: utc ( $owner [ 'uri-date' ] . '+00:00' , DateTimeFormat :: ATOM );
$picdate = DateTimeFormat :: utc ( $owner [ 'avatar-date' ] . '+00:00' , DateTimeFormat :: ATOM );
2017-11-08 01:37:53 +01:00
2018-01-15 14:05:12 +01:00
$attributes = [];
2017-11-08 01:37:53 +01:00
if ( ! $public || ! $hidewall ) {
2018-01-15 14:05:12 +01:00
$attributes = [ " dfrn:updated " => $namdate ];
2017-11-08 01:37:53 +01:00
}
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " name " , $owner [ " name " ], $attributes );
XML :: addElement ( $doc , $author , " uri " , System :: baseUrl () . '/profile/' . $owner [ " nickname " ], $attributes );
XML :: addElement ( $doc , $author , " dfrn:handle " , $owner [ " addr " ], $attributes );
2017-11-08 01:37:53 +01:00
2018-01-15 14:05:12 +01:00
$attributes = [ " rel " => " photo " , " type " => " image/jpeg " ,
" media:width " => 175 , " media:height " => 175 , " href " => $owner [ 'photo' ]];
2017-11-08 01:37:53 +01:00
if ( ! $public || ! $hidewall ) {
$attributes [ " dfrn:updated " ] = $picdate ;
}
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
$attributes [ " rel " ] = " avatar " ;
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
if ( $hidewall ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " dfrn:hide " , " true " );
2017-11-08 01:37:53 +01:00
}
// The following fields will only be generated if the data isn't meant for a public feed
if ( $public ) {
return $author ;
}
$birthday = feed_birthday ( $owner [ 'uid' ], $owner [ 'timezone' ]);
2017-11-08 23:02:50 +01:00
if ( $birthday ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " dfrn:birthday " , $birthday );
2017-11-08 23:02:50 +01:00
}
2017-11-08 01:37:53 +01:00
// Only show contact details when we are allowed to
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `profile`.`about`, `profile`.`name`, `profile`.`homepage`, `user`.`nickname`,
2017-11-08 01:37:53 +01:00
`user` . `timezone` , `profile` . `locality` , `profile` . `region` , `profile` . `country-name` ,
`profile` . `pub_keywords` , `profile` . `xmpp` , `profile` . `dob`
FROM `profile`
INNER JOIN `user` ON `user` . `uid` = `profile` . `uid`
WHERE `profile` . `is-default` AND NOT `user` . `hidewall` AND `user` . `uid` = % d " ,
2017-11-08 23:02:50 +01:00
intval ( $owner [ 'uid' ])
);
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
$profile = $r [ 0 ];
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " poco:displayName " , $profile [ " name " ]);
XML :: addElement ( $doc , $author , " poco:updated " , $namdate );
2017-11-08 01:37:53 +01:00
if ( trim ( $profile [ " dob " ]) > '0001-01-01' ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " poco:birthday " , " 0000- " . date ( " m-d " , strtotime ( $profile [ " dob " ])));
2017-11-08 01:37:53 +01:00
}
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " poco:note " , $profile [ " about " ]);
XML :: addElement ( $doc , $author , " poco:preferredUsername " , $profile [ " nickname " ]);
2017-11-08 01:37:53 +01:00
$savetz = date_default_timezone_get ();
date_default_timezone_set ( $profile [ " timezone " ]);
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " poco:utcOffset " , date ( " P " ));
2017-11-08 01:37:53 +01:00
date_default_timezone_set ( $savetz );
if ( trim ( $profile [ " homepage " ]) != " " ) {
$urls = $doc -> createElement ( " poco:urls " );
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $urls , " poco:type " , " homepage " );
XML :: addElement ( $doc , $urls , " poco:value " , $profile [ " homepage " ]);
XML :: addElement ( $doc , $urls , " poco:primary " , " true " );
2017-11-08 01:37:53 +01:00
$author -> appendChild ( $urls );
}
if ( trim ( $profile [ " pub_keywords " ]) != " " ) {
$keywords = explode ( " , " , $profile [ " pub_keywords " ]);
2017-11-10 13:45:33 +01:00
foreach ( $keywords as $keyword ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " poco:tags " , trim ( $keyword ));
2017-11-08 01:37:53 +01:00
}
}
if ( trim ( $profile [ " xmpp " ]) != " " ) {
$ims = $doc -> createElement ( " poco:ims " );
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $ims , " poco:type " , " xmpp " );
XML :: addElement ( $doc , $ims , " poco:value " , $profile [ " xmpp " ]);
XML :: addElement ( $doc , $ims , " poco:primary " , " true " );
2017-11-08 01:37:53 +01:00
$author -> appendChild ( $ims );
}
if ( trim ( $profile [ " locality " ] . $profile [ " region " ] . $profile [ " country-name " ]) != " " ) {
$element = $doc -> createElement ( " poco:address " );
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $element , " poco:formatted " , Profile :: formatLocation ( $profile ));
2017-11-08 01:37:53 +01:00
if ( trim ( $profile [ " locality " ]) != " " ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $element , " poco:locality " , $profile [ " locality " ]);
2017-11-08 01:37:53 +01:00
}
if ( trim ( $profile [ " region " ]) != " " ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $element , " poco:region " , $profile [ " region " ]);
2017-11-08 01:37:53 +01:00
}
if ( trim ( $profile [ " country-name " ]) != " " ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $element , " poco:country " , $profile [ " country-name " ]);
2017-11-08 01:37:53 +01:00
}
$author -> appendChild ( $element );
}
}
return $author ;
}
/**
* @ brief Adds the author elements in the " entry " elements of the DFRN protocol
*
2017-11-08 23:02:50 +01:00
* @ param object $doc XML document
* @ param string $element Element name for the author
2017-11-08 01:37:53 +01:00
* @ param string $contact_url Link of the contact
2017-11-08 23:02:50 +01:00
* @ param array $item Item elements
2017-11-08 01:37:53 +01:00
*
* @ return object XML author object
* @ todo Add type - hints
*/
2017-11-23 20:01:58 +01:00
private static function addEntryAuthor ( $doc , $element , $contact_url , $item )
2017-11-08 23:02:50 +01:00
{
2017-11-19 23:03:39 +01:00
$contact = Contact :: getDetailsByURL ( $contact_url , $item [ " uid " ]);
2017-11-08 01:37:53 +01:00
$author = $doc -> createElement ( $element );
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " name " , $contact [ " name " ]);
XML :: addElement ( $doc , $author , " uri " , $contact [ " url " ]);
XML :: addElement ( $doc , $author , " dfrn:handle " , $contact [ " addr " ]);
2017-11-08 01:37:53 +01:00
/// @Todo
/// - Check real image type and image size
/// - Check which of these boths elements we should use
2018-01-15 14:05:12 +01:00
$attributes = [
2017-11-08 01:37:53 +01:00
" rel " => " photo " ,
" type " => " image/jpeg " ,
" media:width " => 80 ,
" media:height " => 80 ,
2018-01-15 14:05:12 +01:00
" href " => $contact [ " photo " ]];
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
2018-01-15 14:05:12 +01:00
$attributes = [
2017-11-08 01:37:53 +01:00
" rel " => " avatar " ,
" type " => " image/jpeg " ,
" media:width " => 80 ,
" media:height " => 80 ,
2018-01-15 14:05:12 +01:00
" href " => $contact [ " photo " ]];
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $author , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
return $author ;
}
/**
* @ brief Adds the activity elements
*
2017-11-08 23:02:50 +01:00
* @ param object $doc XML document
* @ param string $element Element name for the activity
2017-11-08 01:37:53 +01:00
* @ param string $activity activity value
*
* @ return object XML activity object
* @ todo Add type - hints
*/
2017-11-23 20:01:58 +01:00
private static function createActivity ( $doc , $element , $activity )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
if ( $activity ) {
$entry = $doc -> createElement ( $element );
2018-01-27 17:13:41 +01:00
$r = XML :: parseString ( $activity , false );
2017-11-08 01:37:53 +01:00
if ( ! $r ) {
return false ;
}
if ( $r -> type ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " activity:object-type " , $r -> type );
2017-11-08 01:37:53 +01:00
}
if ( $r -> id ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " id " , $r -> id );
2017-11-08 01:37:53 +01:00
}
if ( $r -> title ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " title " , $r -> title );
2017-11-08 01:37:53 +01:00
}
if ( $r -> link ) {
if ( substr ( $r -> link , 0 , 1 ) == '<' ) {
if ( strstr ( $r -> link , '&' ) && ( ! strstr ( $r -> link , '&' ))) {
$r -> link = str_replace ( '&' , '&' , $r -> link );
}
$r -> link = preg_replace ( '/\<link(.*?)\"\>/' , '<link$1"/>' , $r -> link );
// XML does need a single element as root element so we add a dummy element here
2018-01-27 17:13:41 +01:00
$data = XML :: parseString ( " <dummy> " . $r -> link . " </dummy> " , false );
2017-11-08 01:37:53 +01:00
if ( is_object ( $data )) {
2017-11-23 20:01:58 +01:00
foreach ( $data -> link as $link ) {
2018-01-15 14:05:12 +01:00
$attributes = [];
2017-11-10 13:45:33 +01:00
foreach ( $link -> attributes () as $parameter => $value ) {
2017-11-08 01:37:53 +01:00
$attributes [ $parameter ] = $value ;
}
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
}
}
} else {
2018-01-15 14:05:12 +01:00
$attributes = [ " rel " => " alternate " , " type " => " text/html " , " href " => $r -> link ];
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
}
}
if ( $r -> content ) {
2018-02-15 03:33:55 +01:00
XML :: addElement ( $doc , $entry , " content " , BBCode :: convert ( $r -> content ), [ " type " => " html " ]);
2017-11-08 01:37:53 +01:00
}
return $entry ;
}
return false ;
}
/**
* @ brief Adds the elements for attachments
*
2017-11-08 23:02:50 +01:00
* @ param object $doc XML document
2017-11-08 01:37:53 +01:00
* @ param object $root XML root
2017-11-08 23:02:50 +01:00
* @ param array $item Item element
2017-11-08 01:37:53 +01:00
*
* @ return object XML attachment object
* @ todo Add type - hints
*/
2017-11-23 20:01:58 +01:00
private static function getAttachment ( $doc , $root , $item )
2017-11-08 23:02:50 +01:00
{
$arr = explode ( '[/attach],' , $item [ 'attach' ]);
2017-11-08 01:37:53 +01:00
if ( count ( $arr )) {
foreach ( $arr as $r ) {
$matches = false ;
2017-11-08 23:02:50 +01:00
$cnt = preg_match ( '|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|' , $r , $matches );
2017-11-08 01:37:53 +01:00
if ( $cnt ) {
2018-01-15 14:05:12 +01:00
$attributes = [ " rel " => " enclosure " ,
2017-11-08 01:37:53 +01:00
" href " => $matches [ 1 ],
2018-01-15 14:05:12 +01:00
" type " => $matches [ 3 ]];
2017-11-08 01:37:53 +01:00
if ( intval ( $matches [ 2 ])) {
$attributes [ " length " ] = intval ( $matches [ 2 ]);
}
if ( trim ( $matches [ 4 ]) != " " ) {
$attributes [ " title " ] = trim ( $matches [ 4 ]);
}
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 01:37:53 +01:00
}
}
}
}
/**
* @ brief Adds the " entry " elements for the DFRN protocol
*
2017-11-08 23:02:50 +01:00
* @ param object $doc XML document
* @ param string $type " text " or " html "
* @ param array $item Item element
* @ param array $owner Owner record
* @ param bool $comment Trigger the sending of the " comment " element
* @ param int $cid Contact ID of the recipient
* @ param bool $single If set , the entry is created as an XML document with a single " entry " element
2017-11-08 01:37:53 +01:00
*
* @ return object XML entry object
* @ todo Add type - hints
*/
2017-11-08 23:02:50 +01:00
private static function entry ( $doc , $type , $item , $owner , $comment = false , $cid = 0 , $single = false )
{
2018-01-15 14:05:12 +01:00
$mentioned = [];
2017-11-08 01:37:53 +01:00
if ( ! $item [ 'parent' ]) {
return ;
}
if ( $item [ 'deleted' ]) {
2018-01-27 03:38:34 +01:00
$attributes = [ " ref " => $item [ 'uri' ], " when " => DateTimeFormat :: utc ( $item [ 'edited' ] . '+00:00' , DateTimeFormat :: ATOM )];
2017-11-20 18:56:31 +01:00
return XML :: createElement ( $doc , " at:deleted-entry " , " " , $attributes );
2017-11-08 01:37:53 +01:00
}
if ( ! $single ) {
$entry = $doc -> createElement ( " entry " );
} else {
$entry = $doc -> createElementNS ( NAMESPACE_ATOM1 , 'entry' );
$doc -> appendChild ( $entry );
$entry -> setAttribute ( " xmlns:thr " , NAMESPACE_THREAD );
$entry -> setAttribute ( " xmlns:at " , NAMESPACE_TOMB );
$entry -> setAttribute ( " xmlns:media " , NAMESPACE_MEDIA );
$entry -> setAttribute ( " xmlns:dfrn " , NAMESPACE_DFRN );
$entry -> setAttribute ( " xmlns:activity " , NAMESPACE_ACTIVITY );
$entry -> setAttribute ( " xmlns:georss " , NAMESPACE_GEORSS );
$entry -> setAttribute ( " xmlns:poco " , NAMESPACE_POCO );
$entry -> setAttribute ( " xmlns:ostatus " , NAMESPACE_OSTATUS );
$entry -> setAttribute ( " xmlns:statusnet " , NAMESPACE_STATUSNET );
}
if ( $item [ 'allow_cid' ] || $item [ 'allow_gid' ] || $item [ 'deny_cid' ] || $item [ 'deny_gid' ]) {
2018-01-28 12:18:08 +01:00
$body = Item :: fixPrivatePhotos ( $item [ 'body' ], $owner [ 'uid' ], $item , $cid );
2017-11-08 01:37:53 +01:00
} else {
$body = $item [ 'body' ];
}
// Remove the abstract element. It is only locally important.
2018-02-05 05:38:40 +01:00
$body = BBCode :: stripAbstract ( $body );
2017-11-08 01:37:53 +01:00
2018-02-14 05:58:46 +01:00
$htmlbody = '' ;
2017-11-08 01:37:53 +01:00
if ( $type == 'html' ) {
$htmlbody = $body ;
if ( $item [ 'title' ] != " " ) {
$htmlbody = " [b] " . $item [ 'title' ] . " [/b] \n \n " . $htmlbody ;
}
2018-02-15 03:33:55 +01:00
$htmlbody = BBCode :: convert ( $htmlbody , false , 7 );
2017-11-08 01:37:53 +01:00
}
2017-11-23 20:01:58 +01:00
$author = self :: addEntryAuthor ( $doc , " author " , $item [ " author-link " ], $item );
2017-11-08 01:37:53 +01:00
$entry -> appendChild ( $author );
2017-11-23 20:01:58 +01:00
$dfrnowner = self :: addEntryAuthor ( $doc , " dfrn:owner " , $item [ " owner-link " ], $item );
2017-11-08 01:37:53 +01:00
$entry -> appendChild ( $dfrnowner );
if (( $item [ 'parent' ] != $item [ 'id' ]) || ( $item [ 'parent-uri' ] !== $item [ 'uri' ]) || (( $item [ 'thr-parent' ] !== '' ) && ( $item [ 'thr-parent' ] !== $item [ 'uri' ]))) {
$parent_item = (( $item [ 'thr-parent' ]) ? $item [ 'thr-parent' ] : $item [ 'parent-uri' ]);
$parent = q ( " SELECT `guid`,`plink` FROM `item` WHERE `uri` = '%s' AND `uid` = %d " , dbesc ( $parent_item ), intval ( $item [ 'uid' ]));
2018-01-15 14:05:12 +01:00
$attributes = [ " ref " => $parent_item , " type " => " text/html " ,
2017-11-08 01:37:53 +01:00
" href " => $parent [ 0 ][ 'plink' ],
2018-01-15 14:05:12 +01:00
" dfrn:diaspora_guid " => $parent [ 0 ][ 'guid' ]];
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " thr:in-reply-to " , " " , $attributes );
2017-11-08 01:37:53 +01:00
}
// Add conversation data. This is used for OStatus
$conversation_href = System :: baseUrl () . " /display/ " . $owner [ " nick " ] . " / " . $item [ " parent " ];
$conversation_uri = $conversation_href ;
if ( isset ( $parent_item )) {
$r = dba :: fetch_first ( " SELECT `conversation-uri`, `conversation-href` FROM `conversation` WHERE `item-uri` = ? " , $item [ 'parent-uri' ]);
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
if ( $r [ 'conversation-uri' ] != '' ) {
$conversation_uri = $r [ 'conversation-uri' ];
}
if ( $r [ 'conversation-href' ] != '' ) {
$conversation_href = $r [ 'conversation-href' ];
}
}
}
2018-01-15 14:05:12 +01:00
$attributes = [
2017-11-08 01:37:53 +01:00
" href " => $conversation_href ,
2018-01-15 14:05:12 +01:00
" ref " => $conversation_uri ];
2017-11-08 01:37:53 +01:00
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " ostatus:conversation " , $conversation_uri , $attributes );
2017-11-08 01:37:53 +01:00
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " id " , $item [ " uri " ]);
XML :: addElement ( $doc , $entry , " title " , $item [ " title " ]);
2017-11-08 01:37:53 +01:00
2018-01-27 03:38:34 +01:00
XML :: addElement ( $doc , $entry , " published " , DateTimeFormat :: utc ( $item [ " created " ] . " +00:00 " , DateTimeFormat :: ATOM ));
XML :: addElement ( $doc , $entry , " updated " , DateTimeFormat :: utc ( $item [ " edited " ] . " +00:00 " , DateTimeFormat :: ATOM ));
2017-11-08 01:37:53 +01:00
// "dfrn:env" is used to read the content
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " dfrn:env " , base64url_encode ( $body , true ));
2017-11-08 01:37:53 +01:00
// The "content" field is not read by the receiver. We could remove it when the type is "text"
// We keep it at the moment, maybe there is some old version that doesn't read "dfrn:env"
2018-01-15 14:05:12 +01:00
XML :: addElement ( $doc , $entry , " content " , (( $type == 'html' ) ? $htmlbody : $body ), [ " type " => $type ]);
2017-11-08 01:37:53 +01:00
// We save this value in "plink". Maybe we should read it from there as well?
2017-11-20 18:56:31 +01:00
XML :: addElement (
2017-11-08 23:02:50 +01:00
$doc ,
$entry ,
" link " ,
" " ,
2018-01-15 14:05:12 +01:00
[ " rel " => " alternate " , " type " => " text/html " ,
" href " => System :: baseUrl () . " /display/ " . $item [ " guid " ]]
2017-11-08 23:02:50 +01:00
);
2017-11-08 01:37:53 +01:00
// "comment-allow" is some old fashioned stuff for old Friendica versions.
// It is included in the rewritten code for completeness
if ( $comment ) {
2018-01-18 07:54:44 +01:00
XML :: addElement ( $doc , $entry , " dfrn:comment-allow " , 1 );
2017-11-08 01:37:53 +01:00
}
if ( $item [ 'location' ]) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " dfrn:location " , $item [ 'location' ]);
2017-11-08 01:37:53 +01:00
}
if ( $item [ 'coord' ]) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " georss:point " , $item [ 'coord' ]);
2017-11-08 01:37:53 +01:00
}
if (( $item [ 'private' ]) || strlen ( $item [ 'allow_cid' ]) || strlen ( $item [ 'allow_gid' ]) || strlen ( $item [ 'deny_cid' ]) || strlen ( $item [ 'deny_gid' ])) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " dfrn:private " , (( $item [ 'private' ]) ? $item [ 'private' ] : 1 ));
2017-11-08 01:37:53 +01:00
}
if ( $item [ 'extid' ]) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " dfrn:extid " , $item [ 'extid' ]);
2017-11-08 01:37:53 +01:00
}
if ( $item [ 'bookmark' ]) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " dfrn:bookmark " , " true " );
2017-11-08 01:37:53 +01:00
}
if ( $item [ 'app' ]) {
2018-01-15 14:05:12 +01:00
XML :: addElement ( $doc , $entry , " statusnet:notice_info " , " " , [ " local_id " => $item [ 'id' ], " source " => $item [ 'app' ]]);
2017-11-08 01:37:53 +01:00
}
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " dfrn:diaspora_guid " , $item [ " guid " ]);
2017-11-08 01:37:53 +01:00
// The signed text contains the content in Markdown, the sender handle and the signatur for the content
// It is needed for relayed comments to Diaspora.
if ( $item [ 'signed_text' ]) {
2018-01-15 14:05:12 +01:00
$sign = base64_encode ( json_encode ([ 'signed_text' => $item [ 'signed_text' ], 'signature' => $item [ 'signature' ], 'signer' => $item [ 'signer' ]]));
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " dfrn:diaspora_signature " , $sign );
2017-11-08 01:37:53 +01:00
}
2018-01-21 00:52:54 +01:00
XML :: addElement ( $doc , $entry , " activity:verb " , self :: constructVerb ( $item ));
2017-11-08 01:37:53 +01:00
if ( $item [ 'object-type' ] != " " ) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " activity:object-type " , $item [ 'object-type' ]);
2017-11-08 01:37:53 +01:00
} elseif ( $item [ 'id' ] == $item [ 'parent' ]) {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " activity:object-type " , ACTIVITY_OBJ_NOTE );
2017-11-08 01:37:53 +01:00
} else {
2017-11-20 18:56:31 +01:00
XML :: addElement ( $doc , $entry , " activity:object-type " , ACTIVITY_OBJ_COMMENT );
2017-11-08 01:37:53 +01:00
}
2017-11-23 20:01:58 +01:00
$actobj = self :: createActivity ( $doc , " activity:object " , $item [ 'object' ]);
2017-11-08 01:37:53 +01:00
if ( $actobj ) {
$entry -> appendChild ( $actobj );
}
2017-11-23 20:01:58 +01:00
$actarg = self :: createActivity ( $doc , " activity:target " , $item [ 'target' ]);
2017-11-08 01:37:53 +01:00
if ( $actarg ) {
$entry -> appendChild ( $actarg );
}
2018-01-28 12:18:08 +01:00
$tags = Item :: getFeedTags ( $item );
2017-11-08 01:37:53 +01:00
if ( count ( $tags )) {
foreach ( $tags as $t ) {
if (( $type != 'html' ) || ( $t [ 0 ] != " @ " )) {
2018-01-15 14:05:12 +01:00
XML :: addElement ( $doc , $entry , " category " , " " , [ " scheme " => " X-DFRN: " . $t [ 0 ] . " : " . $t [ 1 ], " term " => $t [ 2 ]]);
2017-11-08 01:37:53 +01:00
}
}
}
if ( count ( $tags )) {
foreach ( $tags as $t ) {
if ( $t [ 0 ] == " @ " ) {
$mentioned [ $t [ 1 ]] = $t [ 1 ];
}
}
}
2017-11-10 13:45:33 +01:00
foreach ( $mentioned as $mention ) {
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' " ,
2017-11-08 01:37:53 +01:00
intval ( $owner [ " uid " ]),
2017-11-08 23:02:50 +01:00
dbesc ( normalise_link ( $mention ))
);
2017-11-08 01:37:53 +01:00
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r ) && ( $r [ 0 ][ " forum " ] || $r [ 0 ][ " prv " ])) {
2017-11-20 18:56:31 +01:00
XML :: addElement (
2017-11-08 23:02:50 +01:00
$doc ,
$entry ,
" link " ,
" " ,
2018-01-15 14:05:12 +01:00
[ " rel " => " mentioned " ,
2017-11-08 23:02:50 +01:00
" ostatus:object-type " => ACTIVITY_OBJ_GROUP ,
2018-01-15 14:05:12 +01:00
" href " => $mention ]
2017-11-08 23:02:50 +01:00
);
2017-11-08 01:37:53 +01:00
} else {
2017-11-20 18:56:31 +01:00
XML :: addElement (
2017-11-08 23:02:50 +01:00
$doc ,
$entry ,
" link " ,
" " ,
2018-01-15 14:05:12 +01:00
[ " rel " => " mentioned " ,
2017-11-08 23:02:50 +01:00
" ostatus:object-type " => ACTIVITY_OBJ_PERSON ,
2018-01-15 14:05:12 +01:00
" href " => $mention ]
2017-11-08 23:02:50 +01:00
);
2017-11-08 01:37:53 +01:00
}
}
2017-11-23 20:01:58 +01:00
self :: getAttachment ( $doc , $entry , $item );
2017-11-08 01:37:53 +01:00
return $entry ;
}
/**
* @ brief encrypts data via AES
*
* @ param string $data The data that is to be encrypted
2017-11-08 23:02:50 +01:00
* @ param string $key The AES key
2017-11-08 01:37:53 +01:00
*
* @ return string encrypted data
*/
2017-11-23 20:01:58 +01:00
private static function aesEncrypt ( $data , $key )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
return openssl_encrypt ( $data , 'aes-128-ecb' , $key , OPENSSL_RAW_DATA );
}
/**
* @ brief decrypts data via AES
*
* @ param string $encrypted The encrypted data
2017-11-08 23:02:50 +01:00
* @ param string $key The AES key
2017-11-08 01:37:53 +01:00
*
* @ return string decrypted data
*/
2017-11-23 20:01:58 +01:00
public static function aesDecrypt ( $encrypted , $key )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
return openssl_decrypt ( $encrypted , 'aes-128-ecb' , $key , OPENSSL_RAW_DATA );
}
/**
* @ brief Delivers the atom content to the contacts
*
2017-11-08 23:02:50 +01:00
* @ param array $owner Owner record
* @ param array $contact Contact record of the receiver
* @ param string $atom Content that will be transmitted
* @ param bool $dissolve ( to be documented )
2017-11-08 01:37:53 +01:00
*
2018-04-02 23:46:10 +02:00
* @ return int Deliver status . Negative values mean an error .
2017-11-08 01:37:53 +01:00
* @ todo Add array type - hint for $owner , $contact
*/
2017-11-08 23:02:50 +01:00
public static function deliver ( $owner , $contact , $atom , $dissolve = false )
{
2017-11-08 01:37:53 +01:00
$a = get_app ();
2018-04-02 23:46:10 +02:00
// At first try the Diaspora transport layer
$ret = self :: transmit ( $owner , $contact , $atom );
if ( $ret >= 200 ) {
2018-04-03 00:06:31 +02:00
logger ( 'Delivery via Diaspora transport layer was successful with status ' . $ret );
2018-04-02 23:46:10 +02:00
return $ret ;
}
2017-11-08 01:37:53 +01:00
$idtosend = $orig_id = (( $contact [ 'dfrn-id' ]) ? $contact [ 'dfrn-id' ] : $contact [ 'issued-id' ]);
if ( $contact [ 'duplex' ] && $contact [ 'dfrn-id' ]) {
$idtosend = '0:' . $orig_id ;
}
if ( $contact [ 'duplex' ] && $contact [ 'issued-id' ]) {
$idtosend = '1:' . $orig_id ;
}
$rino = Config :: get ( 'system' , 'rino_encrypt' );
$rino = intval ( $rino );
logger ( " Local rino version: " . $rino , LOGGER_DEBUG );
2017-11-08 23:02:50 +01:00
$ssl_val = intval ( Config :: get ( 'system' , 'ssl_policy' ));
2017-11-08 01:37:53 +01:00
$ssl_policy = '' ;
switch ( $ssl_val ) {
case SSL_POLICY_FULL :
$ssl_policy = 'full' ;
break ;
case SSL_POLICY_SELFSIGN :
$ssl_policy = 'self' ;
break ;
case SSL_POLICY_NONE :
default :
$ssl_policy = 'none' ;
break ;
}
$url = $contact [ 'notify' ] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (( $rino ) ? '&rino=' . $rino : '' );
logger ( 'dfrn_deliver: ' . $url );
2018-01-27 17:13:41 +01:00
$ret = Network :: curl ( $url );
2017-11-08 01:37:53 +01:00
if ( $ret [ 'errno' ] == CURLE_OPERATION_TIMEDOUT ) {
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2017-11-08 01:37:53 +01:00
return - 2 ; // timed out
}
$xml = $ret [ 'body' ];
$curl_stat = $a -> get_curl_code ();
2018-04-03 14:18:05 +02:00
if ( empty ( $curl_stat )) {
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2017-11-08 01:37:53 +01:00
return - 3 ; // timed out
}
logger ( 'dfrn_deliver: ' . $xml , LOGGER_DATA );
2018-04-03 14:18:05 +02:00
if ( empty ( $xml )) {
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2017-11-08 01:37:53 +01:00
return 3 ;
}
2017-11-08 23:02:50 +01:00
if ( strpos ( $xml , '<?xml' ) === false ) {
2017-11-08 01:37:53 +01:00
logger ( 'dfrn_deliver: no valid XML returned' );
logger ( 'dfrn_deliver: returned XML: ' . $xml , LOGGER_DATA );
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2017-11-08 01:37:53 +01:00
return 3 ;
}
2018-01-27 17:13:41 +01:00
$res = XML :: parseString ( $xml );
2017-11-08 01:37:53 +01:00
2018-04-23 08:03:55 +02:00
if (( intval ( $res -> status ) != 0 ) || ! strlen ( $res -> challenge ) || ! strlen ( $res -> dfrn_id )) {
Contact :: markForArchival ( $contact );
2018-04-03 14:18:05 +02:00
return ( $res -> status ? $res -> status : 3 );
2017-11-08 01:37:53 +01:00
}
2018-01-15 14:05:12 +01:00
$postvars = [];
2017-11-08 01:37:53 +01:00
$sent_dfrn_id = hex2bin (( string ) $res -> dfrn_id );
$challenge = hex2bin (( string ) $res -> challenge );
$perm = (( $res -> perm ) ? $res -> perm : null );
$dfrn_version = ( float ) (( $res -> dfrn_version ) ? $res -> dfrn_version : 2.0 );
$rino_remote_version = intval ( $res -> rino );
$page = (( $owner [ 'page-flags' ] == PAGE_COMMUNITY ) ? 1 : 0 );
logger ( " Remote rino version: " . $rino_remote_version . " for " . $contact [ " url " ], LOGGER_DEBUG );
if ( $owner [ 'page-flags' ] == PAGE_PRVGROUP ) {
$page = 2 ;
}
$final_dfrn_id = '' ;
if ( $perm ) {
if ((( $perm == 'rw' ) && ( ! intval ( $contact [ 'writable' ])))
2017-11-08 23:02:50 +01:00
|| (( $perm == 'r' ) && ( intval ( $contact [ 'writable' ])))
) {
q (
" update contact set writable = %d where id = %d " ,
2017-11-08 01:37:53 +01:00
intval (( $perm == 'rw' ) ? 1 : 0 ),
intval ( $contact [ 'id' ])
);
$contact [ 'writable' ] = ( string ) 1 - intval ( $contact [ 'writable' ]);
}
}
if (( $contact [ 'duplex' ] && strlen ( $contact [ 'pubkey' ]))
|| ( $owner [ 'page-flags' ] == PAGE_COMMUNITY && strlen ( $contact [ 'pubkey' ]))
2017-11-08 23:02:50 +01:00
|| ( $contact [ 'rel' ] == CONTACT_IS_SHARING && strlen ( $contact [ 'pubkey' ]))
) {
openssl_public_decrypt ( $sent_dfrn_id , $final_dfrn_id , $contact [ 'pubkey' ]);
openssl_public_decrypt ( $challenge , $postvars [ 'challenge' ], $contact [ 'pubkey' ]);
2017-11-08 01:37:53 +01:00
} else {
2017-11-08 23:02:50 +01:00
openssl_private_decrypt ( $sent_dfrn_id , $final_dfrn_id , $contact [ 'prvkey' ]);
openssl_private_decrypt ( $challenge , $postvars [ 'challenge' ], $contact [ 'prvkey' ]);
2017-11-08 01:37:53 +01:00
}
$final_dfrn_id = substr ( $final_dfrn_id , 0 , strpos ( $final_dfrn_id , '.' ));
2017-11-08 23:02:50 +01:00
if ( strpos ( $final_dfrn_id , ':' ) == 1 ) {
$final_dfrn_id = substr ( $final_dfrn_id , 2 );
2017-11-08 01:37:53 +01:00
}
if ( $final_dfrn_id != $orig_id ) {
logger ( 'dfrn_deliver: wrong dfrn_id.' );
// did not decode properly - cannot trust this site
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2017-11-08 01:37:53 +01:00
return 3 ;
}
$postvars [ 'dfrn_id' ] = $idtosend ;
$postvars [ 'dfrn_version' ] = DFRN_PROTOCOL_VERSION ;
if ( $dissolve ) {
$postvars [ 'dissolve' ] = '1' ;
}
if ((( $contact [ 'rel' ]) && ( $contact [ 'rel' ] != CONTACT_IS_SHARING ) && ( ! $contact [ 'blocked' ])) || ( $owner [ 'page-flags' ] == PAGE_COMMUNITY )) {
$postvars [ 'data' ] = $atom ;
$postvars [ 'perm' ] = 'rw' ;
} else {
2017-11-08 23:02:50 +01:00
$postvars [ 'data' ] = str_replace ( '<dfrn:comment-allow>1' , '<dfrn:comment-allow>0' , $atom );
2017-11-08 01:37:53 +01:00
$postvars [ 'perm' ] = 'r' ;
}
$postvars [ 'ssl_policy' ] = $ssl_policy ;
if ( $page ) {
$postvars [ 'page' ] = $page ;
}
if ( $rino > 0 && $rino_remote_version > 0 && ( ! $dissolve )) {
logger ( 'rino version: ' . $rino_remote_version );
switch ( $rino_remote_version ) {
case 1 :
$key = openssl_random_pseudo_bytes ( 16 );
2017-11-23 20:01:58 +01:00
$data = self :: aesEncrypt ( $postvars [ 'data' ], $key );
2017-11-08 01:37:53 +01:00
break ;
default :
2017-11-09 08:21:37 +01:00
logger ( " rino: invalid requested version ' $rino_remote_version ' " );
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2017-11-08 01:37:53 +01:00
return - 8 ;
}
$postvars [ 'rino' ] = $rino_remote_version ;
$postvars [ 'data' ] = bin2hex ( $data );
if ( $dfrn_version >= 2.1 ) {
if (( $contact [ 'duplex' ] && strlen ( $contact [ 'pubkey' ]))
2017-11-08 23:02:50 +01:00
|| ( $owner [ 'page-flags' ] == PAGE_COMMUNITY && strlen ( $contact [ 'pubkey' ]))
|| ( $contact [ 'rel' ] == CONTACT_IS_SHARING && strlen ( $contact [ 'pubkey' ]))
) {
openssl_public_encrypt ( $key , $postvars [ 'key' ], $contact [ 'pubkey' ]);
2017-11-08 01:37:53 +01:00
} else {
2017-11-08 23:02:50 +01:00
openssl_private_encrypt ( $key , $postvars [ 'key' ], $contact [ 'prvkey' ]);
2017-11-08 01:37:53 +01:00
}
} else {
if (( $contact [ 'duplex' ] && strlen ( $contact [ 'prvkey' ])) || ( $owner [ 'page-flags' ] == PAGE_COMMUNITY )) {
2017-11-08 23:02:50 +01:00
openssl_private_encrypt ( $key , $postvars [ 'key' ], $contact [ 'prvkey' ]);
2017-11-08 01:37:53 +01:00
} else {
2017-11-08 23:02:50 +01:00
openssl_public_encrypt ( $key , $postvars [ 'key' ], $contact [ 'pubkey' ]);
2017-11-08 01:37:53 +01:00
}
}
logger ( 'md5 rawkey ' . md5 ( $postvars [ 'key' ]));
$postvars [ 'key' ] = bin2hex ( $postvars [ 'key' ]);
}
2017-11-08 23:02:50 +01:00
logger ( 'dfrn_deliver: ' . " SENDING: " . print_r ( $postvars , true ), LOGGER_DATA );
2017-11-08 01:37:53 +01:00
2018-01-27 17:13:41 +01:00
$xml = Network :: post ( $contact [ 'notify' ], $postvars );
2017-11-08 01:37:53 +01:00
logger ( 'dfrn_deliver: ' . " RECEIVED: " . $xml , LOGGER_DATA );
$curl_stat = $a -> get_curl_code ();
2018-04-03 14:18:05 +02:00
if ( empty ( $curl_stat ) || empty ( $xml )) {
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2017-11-08 01:37:53 +01:00
return - 9 ; // timed out
}
2018-04-03 14:18:05 +02:00
if (( $curl_stat == 503 ) && stristr ( $a -> get_curl_headers (), 'retry-after' )) {
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2017-11-08 01:37:53 +01:00
return - 10 ;
}
2017-11-08 23:02:50 +01:00
if ( strpos ( $xml , '<?xml' ) === false ) {
2017-11-08 01:37:53 +01:00
logger ( 'dfrn_deliver: phase 2: no valid XML returned' );
logger ( 'dfrn_deliver: phase 2: returned XML: ' . $xml , LOGGER_DATA );
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2017-11-08 01:37:53 +01:00
return 3 ;
}
2018-01-27 17:13:41 +01:00
$res = XML :: parseString ( $xml );
2017-11-08 01:37:53 +01:00
2018-04-07 12:02:43 +02:00
if ( ! isset ( $res -> status )) {
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2017-11-08 01:37:53 +01:00
return - 11 ;
}
2018-04-07 12:02:43 +02:00
// Possibly old servers had returned an empty value when everything was okay
if ( empty ( $res -> status )) {
$res -> status = 200 ;
}
2017-11-08 01:37:53 +01:00
if ( ! empty ( $res -> message )) {
2018-04-03 00:02:43 +02:00
logger ( 'Delivery returned status ' . $res -> status . ' - ' . $res -> message , LOGGER_DEBUG );
2017-11-08 01:37:53 +01:00
}
2018-04-23 08:03:55 +02:00
if (( $res -> status >= 200 ) && ( $res -> status <= 299 )) {
2018-02-14 22:18:16 +01:00
Contact :: unmarkForArchival ( $contact );
}
2017-11-08 01:37:53 +01:00
return intval ( $res -> status );
2018-04-02 14:53:48 +02:00
}
/**
2018-04-02 23:46:10 +02:00
* @ brief Transmits atom content to the contacts via the Diaspora transport layer
2018-04-02 14:53:48 +02:00
*
2018-04-02 23:46:10 +02:00
* @ param array $owner Owner record
* @ param array $contact Contact record of the receiver
* @ param string $atom Content that will be transmitted
2018-04-02 14:53:48 +02:00
*
2018-04-02 23:46:10 +02:00
* @ return int Deliver status . Negative values mean an error .
2018-04-02 14:53:48 +02:00
*/
2018-04-03 14:18:05 +02:00
public static function transmit ( $owner , $contact , $atom , $public_batch = false )
2018-04-02 14:53:48 +02:00
{
$a = get_app ();
2018-04-27 07:11:52 +02:00
if ( ! $public_batch ) {
if ( empty ( $contact [ 'addr' ])) {
logger ( 'Empty contact handle for ' . $contact [ 'id' ] . ' - ' . $contact [ 'url' ] . ' - trying to update it.' );
if ( Contact :: updateFromProbe ( $contact [ 'id' ])) {
$new_contact = dba :: selectFirst ( 'contact' , [ 'addr' ], [ 'id' => $contact [ 'id' ]]);
$contact [ 'addr' ] = $new_contact [ 'addr' ];
}
if ( empty ( $contact [ 'addr' ])) {
logger ( 'Unable to find contact handle for ' . $contact [ 'id' ] . ' - ' . $contact [ 'url' ]);
Contact :: markForArchival ( $contact );
return - 21 ;
}
2018-04-03 14:18:05 +02:00
}
2018-04-27 07:11:52 +02:00
$fcontact = Diaspora :: personByHandle ( $contact [ 'addr' ]);
if ( empty ( $fcontact )) {
logger ( 'Unable to find contact details for ' . $contact [ 'id' ] . ' - ' . $contact [ 'addr' ]);
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2018-04-27 07:11:52 +02:00
return - 22 ;
2018-04-03 14:18:05 +02:00
}
2018-04-30 18:46:49 +02:00
$pubkey = $fcontact [ 'pubkey' ];
} else {
$pubkey = '' ;
2018-04-03 14:18:05 +02:00
}
2018-04-02 14:53:48 +02:00
2018-04-30 18:46:49 +02:00
$envelope = Diaspora :: buildMessage ( $atom , $owner , $contact , $owner [ 'uprvkey' ], $pubkey , $public_batch );
2018-04-02 14:53:48 +02:00
2018-04-22 12:04:30 +02:00
// Create the endpoint for public posts. This is some WIP and should later be added to the probing
if ( $public_batch && empty ( $contact [ " batch " ])) {
$parts = parse_url ( $contact [ " notify " ]);
$path_parts = explode ( '/' , $parts [ 'path' ]);
array_pop ( $path_parts );
$parts [ 'path' ] = implode ( '/' , $path_parts );
$contact [ " batch " ] = Network :: unparseURL ( $parts );
}
$dest_url = ( $public_batch ? $contact [ " batch " ] : $contact [ " notify " ]);
2018-04-02 14:53:48 +02:00
$content_type = ( $public_batch ? " application/magic-envelope+xml " : " application/json " );
2018-04-02 23:46:10 +02:00
$xml = Network :: post ( $dest_url , $envelope , [ " Content-Type: " . $content_type ]);
2018-04-02 14:53:48 +02:00
2018-04-02 23:46:10 +02:00
$curl_stat = $a -> get_curl_code ();
2018-04-03 14:18:05 +02:00
if ( empty ( $curl_stat ) || empty ( $xml )) {
logger ( 'Empty answer from ' . $contact [ 'id' ] . ' - ' . $dest_url );
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2018-04-02 23:46:10 +02:00
return - 9 ; // timed out
}
if (( $curl_stat == 503 ) && ( stristr ( $a -> get_curl_headers (), 'retry-after' ))) {
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2018-04-02 23:46:10 +02:00
return - 10 ;
}
if ( strpos ( $xml , '<?xml' ) === false ) {
2018-04-03 14:18:05 +02:00
logger ( 'No valid XML returned from ' . $contact [ 'id' ] . ' - ' . $dest_url );
logger ( 'Returned XML: ' . $xml , LOGGER_DATA );
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2018-04-02 23:46:10 +02:00
return 3 ;
}
$res = XML :: parseString ( $xml );
2018-04-03 14:18:05 +02:00
if ( empty ( $res -> status )) {
2018-04-23 08:03:55 +02:00
Contact :: markForArchival ( $contact );
2018-04-07 12:02:43 +02:00
return - 23 ;
2018-04-02 23:46:10 +02:00
}
if ( ! empty ( $res -> message )) {
2018-04-03 14:18:05 +02:00
logger ( 'Transmit to ' . $dest_url . ' returned status ' . $res -> status . ' - ' . $res -> message , LOGGER_DEBUG );
2018-04-02 23:46:10 +02:00
}
2018-04-23 08:03:55 +02:00
if (( $res -> status >= 200 ) && ( $res -> status <= 299 )) {
2018-04-02 23:46:10 +02:00
Contact :: unmarkForArchival ( $contact );
}
return intval ( $res -> status );
2017-11-08 01:37:53 +01:00
}
/**
* @ brief Add new birthday event for this person
*
2017-11-08 23:02:50 +01:00
* @ param array $contact Contact record
2017-11-08 01:37:53 +01:00
* @ param string $birthday Birthday of the contact
2017-11-23 20:01:58 +01:00
* @ return void
2017-11-08 01:37:53 +01:00
* @ todo Add array type - hint for $contact
*/
2017-11-23 20:01:58 +01:00
private static function birthdayEvent ( $contact , $birthday )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
// Check for duplicates
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `id` FROM `event` WHERE `uid` = %d AND `cid` = %d AND `start` = '%s' AND `type` = '%s' LIMIT 1 " ,
2018-04-22 03:03:12 +02:00
intval ( $contact [ 'uid' ]),
intval ( $contact [ 'id' ]),
2018-01-27 03:38:34 +01:00
dbesc ( DateTimeFormat :: utc ( $birthday )),
2018-04-22 03:03:12 +02:00
dbesc ( 'birthday' )
2017-11-08 23:02:50 +01:00
);
2017-11-08 01:37:53 +01:00
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
return ;
}
2018-04-22 03:03:12 +02:00
logger ( 'updating birthday: ' . $birthday . ' for contact ' . $contact [ 'id' ]);
2017-11-08 01:37:53 +01:00
2018-04-22 03:03:12 +02:00
$bdtext = L10n :: t ( '%s\'s birthday' , $contact [ 'name' ]);
$bdtext2 = L10n :: t ( 'Happy Birthday %s' , ' [url=' . $contact [ 'url' ] . ']' . $contact [ 'name' ] . '[/url]' );
2017-11-08 01:37:53 +01:00
2017-11-08 23:02:50 +01:00
$r = q (
" INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2017-11-08 01:37:53 +01:00
VALUES ( % d , % d , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' ) " ,
2018-04-22 03:03:12 +02:00
intval ( $contact [ 'uid' ]),
intval ( $contact [ 'id' ]),
2018-01-27 03:38:34 +01:00
dbesc ( DateTimeFormat :: utcNow ()),
dbesc ( DateTimeFormat :: utcNow ()),
dbesc ( DateTimeFormat :: utc ( $birthday )),
2018-04-22 03:03:12 +02:00
dbesc ( DateTimeFormat :: utc ( $birthday . ' + 1 day ' )),
2017-11-08 01:37:53 +01:00
dbesc ( $bdtext ),
dbesc ( $bdtext2 ),
2018-04-22 03:03:12 +02:00
dbesc ( 'birthday' )
2017-11-08 01:37:53 +01:00
);
}
/**
* @ brief Fetch the author data from head or entry items
*
2017-11-08 23:02:50 +01:00
* @ param object $xpath XPath object
* @ param object $context In which context should the data be searched
* @ param array $importer Record of the importer user mixed with contact of the content
* @ param string $element Element name from which the data is fetched
* @ param bool $onlyfetch Should the data only be fetched or should it update the contact record as well
2017-11-23 20:01:58 +01:00
* @ param string $xml optional , default empty
2017-11-08 01:37:53 +01:00
*
2017-12-17 21:27:50 +01:00
* @ return array Relevant data of the author
2017-11-08 01:37:53 +01:00
* @ todo Find good type - hints for all parameter
*/
2017-11-08 23:02:50 +01:00
private static function fetchauthor ( $xpath , $context , $importer , $element , $onlyfetch , $xml = " " )
{
2018-01-15 14:05:12 +01:00
$author = [];
2017-11-08 01:37:53 +01:00
$author [ " name " ] = $xpath -> evaluate ( $element . " /atom:name/text() " , $context ) -> item ( 0 ) -> nodeValue ;
$author [ " link " ] = $xpath -> evaluate ( $element . " /atom:uri/text() " , $context ) -> item ( 0 ) -> nodeValue ;
2018-02-26 22:53:42 +01:00
$contact_old = dba :: fetch_first ( " SELECT `id`, `uid`, `url`, `network`, `avatar-date`, `avatar`, `name-date`, `uri-date`, `addr`,
2017-11-08 01:37:53 +01:00
`name` , `nick` , `about` , `location` , `keywords` , `xmpp` , `bdyear` , `bd` , `hidden` , `contact-type`
2018-02-14 05:58:46 +01:00
FROM `contact` WHERE `uid` = ? AND `nurl` = ? AND `network` != ? " ,
2018-03-30 08:20:00 +02:00
$importer [ " importer_uid " ],
2018-02-14 05:58:46 +01:00
normalise_link ( $author [ " link " ]),
NETWORK_STATUSNET
2017-11-08 23:02:50 +01:00
);
2017-11-08 01:37:53 +01:00
2018-02-14 05:58:46 +01:00
if ( DBM :: is_result ( $contact_old )) {
$author [ " contact-id " ] = $contact_old [ " id " ];
$author [ " network " ] = $contact_old [ " network " ];
2017-11-08 01:37:53 +01:00
} else {
if ( ! $onlyfetch ) {
2018-03-30 08:20:00 +02:00
logger ( " Contact " . $author [ " link " ] . " wasn't found for user " . $importer [ " importer_uid " ] . " XML: " . $xml , LOGGER_DEBUG );
2017-11-08 01:37:53 +01:00
}
$author [ " contact-id " ] = $importer [ " id " ];
$author [ " network " ] = $importer [ " network " ];
$onlyfetch = true ;
}
// Until now we aren't serving different sizes - but maybe later
2018-01-15 14:05:12 +01:00
$avatarlist = [];
2017-11-08 01:37:53 +01:00
/// @todo check if "avatar" or "photo" would be the best field in the specification
$avatars = $xpath -> query ( $element . " /atom:link[@rel='avatar'] " , $context );
2017-11-23 20:01:58 +01:00
foreach ( $avatars as $avatar ) {
2017-11-08 01:37:53 +01:00
$href = " " ;
$width = 0 ;
2017-11-23 20:01:58 +01:00
foreach ( $avatar -> attributes as $attributes ) {
2017-11-08 01:37:53 +01:00
/// @TODO Rewrite these similar if () to one switch
if ( $attributes -> name == " href " ) {
$href = $attributes -> textContent ;
}
if ( $attributes -> name == " width " ) {
$width = $attributes -> textContent ;
}
if ( $attributes -> name == " updated " ) {
2018-02-26 22:53:42 +01:00
$author [ " avatar-date " ] = $attributes -> textContent ;
2017-11-08 01:37:53 +01:00
}
}
if (( $width > 0 ) && ( $href != " " )) {
$avatarlist [ $width ] = $href ;
}
}
if ( count ( $avatarlist ) > 0 ) {
krsort ( $avatarlist );
$author [ " avatar " ] = current ( $avatarlist );
}
2018-02-14 05:58:46 +01:00
if ( DBM :: is_result ( $contact_old ) && ! $onlyfetch ) {
logger ( " Check if contact details for contact " . $contact_old [ " id " ] . " ( " . $contact_old [ " nick " ] . " ) have to be updated. " , LOGGER_DEBUG );
2017-11-08 01:37:53 +01:00
2018-02-14 05:58:46 +01:00
$poco = [ " url " => $contact_old [ " url " ]];
2017-11-08 01:37:53 +01:00
// When was the last change to name or uri?
$name_element = $xpath -> query ( $element . " /atom:name " , $context ) -> item ( 0 );
2017-11-23 20:01:58 +01:00
foreach ( $name_element -> attributes as $attributes ) {
2017-11-08 01:37:53 +01:00
if ( $attributes -> name == " updated " ) {
$poco [ " name-date " ] = $attributes -> textContent ;
}
}
$link_element = $xpath -> query ( $element . " /atom:link " , $context ) -> item ( 0 );
2017-11-23 20:01:58 +01:00
foreach ( $link_element -> attributes as $attributes ) {
2017-11-08 01:37:53 +01:00
if ( $attributes -> name == " updated " ) {
$poco [ " uri-date " ] = $attributes -> textContent ;
}
}
// Update contact data
$value = $xpath -> evaluate ( $element . " /dfrn:handle/text() " , $context ) -> item ( 0 ) -> nodeValue ;
if ( $value != " " ) {
$poco [ " addr " ] = $value ;
}
$value = $xpath -> evaluate ( $element . " /poco:displayName/text() " , $context ) -> item ( 0 ) -> nodeValue ;
if ( $value != " " ) {
$poco [ " name " ] = $value ;
}
$value = $xpath -> evaluate ( $element . " /poco:preferredUsername/text() " , $context ) -> item ( 0 ) -> nodeValue ;
if ( $value != " " ) {
$poco [ " nick " ] = $value ;
}
$value = $xpath -> evaluate ( $element . " /poco:note/text() " , $context ) -> item ( 0 ) -> nodeValue ;
if ( $value != " " ) {
$poco [ " about " ] = $value ;
}
$value = $xpath -> evaluate ( $element . " /poco:address/poco:formatted/text() " , $context ) -> item ( 0 ) -> nodeValue ;
if ( $value != " " ) {
$poco [ " location " ] = $value ;
}
/// @todo Only search for elements with "poco:type" = "xmpp"
$value = $xpath -> evaluate ( $element . " /poco:ims/poco:value/text() " , $context ) -> item ( 0 ) -> nodeValue ;
if ( $value != " " ) {
$poco [ " xmpp " ] = $value ;
}
/// @todo Add support for the following fields that we don't support by now in the contact table:
/// - poco:utcOffset
/// - poco:urls
/// - poco:locality
/// - poco:region
/// - poco:country
// If the "hide" element is present then the profile isn't searchable.
$hide = intval ( $xpath -> evaluate ( $element . " /dfrn:hide/text() " , $context ) -> item ( 0 ) -> nodeValue == " true " );
2018-02-14 05:58:46 +01:00
logger ( " Hidden status for contact " . $contact_old [ " url " ] . " : " . $hide , LOGGER_DEBUG );
2017-11-08 01:37:53 +01:00
// If the contact isn't searchable then set the contact to "hidden".
// Problem: This can be manually overridden by the user.
if ( $hide ) {
2018-02-14 05:58:46 +01:00
$contact_old [ " hidden " ] = true ;
2017-11-08 01:37:53 +01:00
}
// Save the keywords into the contact table
2018-01-15 14:05:12 +01:00
$tags = [];
2017-11-08 01:37:53 +01:00
$tagelements = $xpath -> evaluate ( $element . " /poco:tags/text() " , $context );
2017-11-23 20:01:58 +01:00
foreach ( $tagelements as $tag ) {
2017-11-08 01:37:53 +01:00
$tags [ $tag -> nodeValue ] = $tag -> nodeValue ;
}
if ( count ( $tags )) {
$poco [ " keywords " ] = implode ( " , " , $tags );
}
// "dfrn:birthday" contains the birthday converted to UTC
$birthday = $xpath -> evaluate ( $element . " /dfrn:birthday/text() " , $context ) -> item ( 0 ) -> nodeValue ;
if ( strtotime ( $birthday ) > time ()) {
$bd_timestamp = strtotime ( $birthday );
$poco [ " bdyear " ] = date ( " Y " , $bd_timestamp );
}
// "poco:birthday" is the birthday in the format "yyyy-mm-dd"
$value = $xpath -> evaluate ( $element . " /poco:birthday/text() " , $context ) -> item ( 0 ) -> nodeValue ;
2018-01-15 14:05:12 +01:00
if ( ! in_array ( $value , [ " " , " 0000-00-00 " , " 0001-01-01 " ])) {
2017-11-08 01:37:53 +01:00
$bdyear = date ( " Y " );
$value = str_replace ( " 0000 " , $bdyear , $value );
if ( strtotime ( $value ) < time ()) {
$value = str_replace ( $bdyear , $bdyear + 1 , $value );
$bdyear = $bdyear + 1 ;
}
$poco [ " bd " ] = $value ;
}
2018-02-14 05:58:46 +01:00
$contact = array_merge ( $contact_old , $poco );
2017-11-08 01:37:53 +01:00
2018-02-14 05:58:46 +01:00
if ( $contact_old [ " bdyear " ] != $contact [ " bdyear " ]) {
2017-11-23 20:01:58 +01:00
self :: birthdayEvent ( $contact , $birthday );
2017-11-08 01:37:53 +01:00
}
// Get all field names
2018-01-15 14:05:12 +01:00
$fields = [];
2018-02-14 05:58:46 +01:00
foreach ( $contact_old as $field => $data ) {
2017-11-08 01:37:53 +01:00
$fields [ $field ] = $data ;
}
unset ( $fields [ " id " ]);
unset ( $fields [ " uid " ]);
unset ( $fields [ " url " ]);
unset ( $fields [ " avatar-date " ]);
2018-02-26 22:53:42 +01:00
unset ( $fields [ " avatar " ]);
2017-11-08 01:37:53 +01:00
unset ( $fields [ " name-date " ]);
unset ( $fields [ " uri-date " ]);
2018-02-14 05:58:46 +01:00
$update = false ;
2017-11-08 01:37:53 +01:00
// Update check for this field has to be done differently
2018-01-15 14:05:12 +01:00
$datefields = [ " name-date " , " uri-date " ];
2017-11-23 20:01:58 +01:00
foreach ( $datefields as $field ) {
2018-02-14 05:58:46 +01:00
if ( strtotime ( $contact [ $field ]) > strtotime ( $contact_old [ $field ])) {
logger ( " Difference for contact " . $contact [ " id " ] . " in field ' " . $field . " '. New value: ' " . $contact [ $field ] . " ', old value ' " . $contact_old [ $field ] . " ' " , LOGGER_DEBUG );
2017-11-08 01:37:53 +01:00
$update = true ;
}
}
2017-11-23 20:01:58 +01:00
foreach ( $fields as $field => $data ) {
2018-02-14 05:58:46 +01:00
if ( $contact [ $field ] != $contact_old [ $field ]) {
logger ( " Difference for contact " . $contact [ " id " ] . " in field ' " . $field . " '. New value: ' " . $contact [ $field ] . " ', old value ' " . $contact_old [ $field ] . " ' " , LOGGER_DEBUG );
2017-11-08 01:37:53 +01:00
$update = true ;
}
}
if ( $update ) {
logger ( " Update contact data for contact " . $contact [ " id " ] . " ( " . $contact [ " nick " ] . " ) " , LOGGER_DEBUG );
2017-11-08 23:02:50 +01:00
q (
" UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s',
2017-11-08 01:37:53 +01:00
`addr` = '%s' , `keywords` = '%s' , `bdyear` = '%s' , `bd` = '%s' , `hidden` = % d ,
`xmpp` = '%s' , `name-date` = '%s' , `uri-date` = '%s'
WHERE `id` = % d AND `network` = '%s' " ,
2017-11-09 02:08:42 +01:00
dbesc ( $contact [ " name " ]), dbesc ( $contact [ " nick " ]), dbesc ( $contact [ " about " ]), dbesc ( $contact [ " location " ]),
dbesc ( $contact [ " addr " ]), dbesc ( $contact [ " keywords " ]), dbesc ( $contact [ " bdyear " ]),
dbesc ( $contact [ " bd " ]), intval ( $contact [ " hidden " ]), dbesc ( $contact [ " xmpp " ]),
dbesc ( DBM :: date ( $contact [ " name-date " ])), dbesc ( DBM :: date ( $contact [ " uri-date " ])),
intval ( $contact [ " id " ]), dbesc ( $contact [ " network " ])
2017-11-08 23:02:50 +01:00
);
2017-11-08 01:37:53 +01:00
}
2017-11-29 23:29:11 +01:00
Contact :: updateAvatar (
2018-02-26 22:53:42 +01:00
$author [ 'avatar' ],
2018-03-30 08:20:00 +02:00
$importer [ 'importer_uid' ],
2018-02-26 22:53:42 +01:00
$contact [ 'id' ],
( strtotime ( $contact [ 'avatar-date' ]) > strtotime ( $contact_old [ 'avatar-date' ]) || ( $author [ 'avatar' ] != $contact_old [ 'avatar' ]))
2017-11-08 23:02:50 +01:00
);
2017-11-08 01:37:53 +01:00
/*
* The generation is a sign for the reliability of the provided data .
* It is used in the socgraph . php to prevent that old contact data
* that was relayed over several servers can overwrite contact
* data that we received directly .
*/
$poco [ " generation " ] = 2 ;
$poco [ " photo " ] = $author [ " avatar " ];
$poco [ " hide " ] = $hide ;
$poco [ " contact-type " ] = $contact [ " contact-type " ];
2017-12-07 15:09:28 +01:00
$gcid = GContact :: update ( $poco );
2017-11-08 01:37:53 +01:00
2018-03-30 08:20:00 +02:00
GContact :: link ( $gcid , $importer [ " importer_uid " ], $contact [ " id " ]);
2017-11-08 01:37:53 +01:00
}
2018-02-26 22:53:42 +01:00
return $author ;
2017-11-08 01:37:53 +01:00
}
/**
* @ brief Transforms activity objects into an XML string
*
2017-11-08 23:02:50 +01:00
* @ param object $xpath XPath object
2017-11-08 01:37:53 +01:00
* @ param object $activity Activity object
2017-12-17 21:27:50 +01:00
* @ param string $element element name
2017-11-08 01:37:53 +01:00
*
* @ return string XML string
* @ todo Find good type - hints for all parameter
*/
2017-11-23 20:01:58 +01:00
private static function transformActivity ( $xpath , $activity , $element )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
if ( ! is_object ( $activity )) {
return " " ;
}
$obj_doc = new DOMDocument ( " 1.0 " , " utf-8 " );
$obj_doc -> formatOutput = true ;
$obj_element = $obj_doc -> createElementNS ( NAMESPACE_ATOM1 , $element );
$activity_type = $xpath -> query ( " activity:object-type/text() " , $activity ) -> item ( 0 ) -> nodeValue ;
2017-11-20 18:56:31 +01:00
XML :: addElement ( $obj_doc , $obj_element , " type " , $activity_type );
2017-11-08 01:37:53 +01:00
$id = $xpath -> query ( " atom:id " , $activity ) -> item ( 0 );
if ( is_object ( $id )) {
$obj_element -> appendChild ( $obj_doc -> importNode ( $id , true ));
}
$title = $xpath -> query ( " atom:title " , $activity ) -> item ( 0 );
if ( is_object ( $title )) {
$obj_element -> appendChild ( $obj_doc -> importNode ( $title , true ));
}
$links = $xpath -> query ( " atom:link " , $activity );
if ( is_object ( $links )) {
2017-11-10 06:00:50 +01:00
foreach ( $links as $link ) {
2017-11-08 01:37:53 +01:00
$obj_element -> appendChild ( $obj_doc -> importNode ( $link , true ));
}
}
$content = $xpath -> query ( " atom:content " , $activity ) -> item ( 0 );
if ( is_object ( $content )) {
$obj_element -> appendChild ( $obj_doc -> importNode ( $content , true ));
}
$obj_doc -> appendChild ( $obj_element );
$objxml = $obj_doc -> saveXML ( $obj_element );
/// @todo This isn't totally clean. We should find a way to transform the namespaces
$objxml = str_replace ( " < " . $element . ' xmlns="http://www.w3.org/2005/Atom">' , " < " . $element . " > " , $objxml );
return ( $objxml );
}
/**
* @ brief Processes the mail elements
*
2017-11-08 23:02:50 +01:00
* @ param object $xpath XPath object
* @ param object $mail mail elements
* @ param array $importer Record of the importer user mixed with contact of the content
2017-11-23 20:01:58 +01:00
* @ return void
2017-11-08 01:37:53 +01:00
* @ todo Find good type - hints for all parameter
*/
2017-11-23 20:01:58 +01:00
private static function processMail ( $xpath , $mail , $importer )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
logger ( " Processing mails " );
/// @TODO Rewrite this to one statement
2018-01-15 14:05:12 +01:00
$msg = [];
2017-11-08 01:37:53 +01:00
$msg [ " uid " ] = $importer [ " importer_uid " ];
$msg [ " from-name " ] = $xpath -> query ( " dfrn:sender/dfrn:name/text() " , $mail ) -> item ( 0 ) -> nodeValue ;
$msg [ " from-url " ] = $xpath -> query ( " dfrn:sender/dfrn:uri/text() " , $mail ) -> item ( 0 ) -> nodeValue ;
$msg [ " from-photo " ] = $xpath -> query ( " dfrn:sender/dfrn:avatar/text() " , $mail ) -> item ( 0 ) -> nodeValue ;
$msg [ " contact-id " ] = $importer [ " id " ];
$msg [ " uri " ] = $xpath -> query ( " dfrn:id/text() " , $mail ) -> item ( 0 ) -> nodeValue ;
$msg [ " parent-uri " ] = $xpath -> query ( " dfrn:in-reply-to/text() " , $mail ) -> item ( 0 ) -> nodeValue ;
2018-02-14 23:08:16 +01:00
$msg [ " created " ] = DateTimeFormat :: utc ( $xpath -> query ( " dfrn:sentdate/text() " , $mail ) -> item ( 0 ) -> nodeValue );
2017-11-08 01:37:53 +01:00
$msg [ " title " ] = $xpath -> query ( " dfrn:subject/text() " , $mail ) -> item ( 0 ) -> nodeValue ;
$msg [ " body " ] = $xpath -> query ( " dfrn:content/text() " , $mail ) -> item ( 0 ) -> nodeValue ;
$msg [ " seen " ] = 0 ;
$msg [ " replied " ] = 0 ;
dba :: insert ( 'mail' , $msg );
// send notifications.
/// @TODO Arange this mess
2018-01-15 14:05:12 +01:00
$notif_params = [
2017-11-08 01:37:53 +01:00
" type " => NOTIFY_MAIL ,
" notify_flags " => $importer [ " notify-flags " ],
" language " => $importer [ " language " ],
" to_name " => $importer [ " username " ],
" to_email " => $importer [ " email " ],
" uid " => $importer [ " importer_uid " ],
" item " => $msg ,
" source_name " => $msg [ " from-name " ],
" source_link " => $importer [ " url " ],
" source_photo " => $importer [ " thumb " ],
" verb " => ACTIVITY_POST ,
" otype " => " mail "
2018-01-15 14:05:12 +01:00
];
2017-11-08 01:37:53 +01:00
notification ( $notif_params );
logger ( " Mail is processed, notification was sent. " );
}
/**
* @ brief Processes the suggestion elements
*
2017-11-08 23:02:50 +01:00
* @ param object $xpath XPath object
2017-11-08 01:37:53 +01:00
* @ param object $suggestion suggestion elements
2017-11-08 23:02:50 +01:00
* @ param array $importer Record of the importer user mixed with contact of the content
2017-11-23 20:01:58 +01:00
* @ return boolean
2017-11-08 01:37:53 +01:00
* @ todo Find good type - hints for all parameter
*/
2017-11-23 20:01:58 +01:00
private static function processSuggestion ( $xpath , $suggestion , $importer )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
$a = get_app ();
logger ( " Processing suggestions " );
/// @TODO Rewrite this to one statement
2018-01-15 14:05:12 +01:00
$suggest = [];
2017-11-08 01:37:53 +01:00
$suggest [ " uid " ] = $importer [ " importer_uid " ];
$suggest [ " cid " ] = $importer [ " id " ];
$suggest [ " url " ] = $xpath -> query ( " dfrn:url/text() " , $suggestion ) -> item ( 0 ) -> nodeValue ;
$suggest [ " name " ] = $xpath -> query ( " dfrn:name/text() " , $suggestion ) -> item ( 0 ) -> nodeValue ;
$suggest [ " photo " ] = $xpath -> query ( " dfrn:photo/text() " , $suggestion ) -> item ( 0 ) -> nodeValue ;
$suggest [ " request " ] = $xpath -> query ( " dfrn:request/text() " , $suggestion ) -> item ( 0 ) -> nodeValue ;
$suggest [ " body " ] = $xpath -> query ( " dfrn:note/text() " , $suggestion ) -> item ( 0 ) -> nodeValue ;
// Does our member already have a friend matching this description?
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `id` FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1 " ,
2017-11-08 01:37:53 +01:00
dbesc ( $suggest [ " name " ]),
dbesc ( normalise_link ( $suggest [ " url " ])),
intval ( $suggest [ " uid " ])
);
/*
* The valid result means the friend we ' re about to send a friend
* suggestion already has them in their contact , which means no further
* action is required .
*
* @ see https :// github . com / friendica / friendica / pull / 3254 #discussion_r107315246
*/
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
return false ;
}
// Do we already have an fcontact record for this person?
$fid = 0 ;
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1 " ,
2017-11-08 01:37:53 +01:00
dbesc ( $suggest [ " url " ]),
dbesc ( $suggest [ " name " ]),
dbesc ( $suggest [ " request " ])
);
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
$fid = $r [ 0 ][ " id " ];
// OK, we do. Do we already have an introduction for this person ?
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `id` FROM `intro` WHERE `uid` = %d AND `fid` = %d LIMIT 1 " ,
2017-11-08 01:37:53 +01:00
intval ( $suggest [ " uid " ]),
intval ( $fid )
);
/*
* The valid result means the friend we ' re about to send a friend
* suggestion already has them in their contact , which means no further
* action is required .
*
* @ see https :// github . com / friendica / friendica / pull / 3254 #discussion_r107315246
*/
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
return false ;
}
}
if ( ! $fid ) {
2017-11-08 23:02:50 +01:00
$r = q (
" INSERT INTO `fcontact` (`name`,`url`,`photo`,`request`) VALUES ('%s', '%s', '%s', '%s') " ,
2017-11-08 01:37:53 +01:00
dbesc ( $suggest [ " name " ]),
dbesc ( $suggest [ " url " ]),
dbesc ( $suggest [ " photo " ]),
dbesc ( $suggest [ " request " ])
);
}
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1 " ,
2017-11-08 01:37:53 +01:00
dbesc ( $suggest [ " url " ]),
dbesc ( $suggest [ " name " ]),
dbesc ( $suggest [ " request " ])
);
/*
* If no record in fcontact is found , below INSERT statement will not
* link an introduction to it .
*/
2017-11-08 04:57:46 +01:00
if ( ! DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
// database record did not get created. Quietly give up.
killme ();
}
$fid = $r [ 0 ][ " id " ];
$hash = random_string ();
2017-11-08 23:02:50 +01:00
$r = q (
" INSERT INTO `intro` (`uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked`)
2017-11-08 01:37:53 +01:00
VALUES ( % d , % d , % d , '%s' , '%s' , '%s' , % d ) " ,
intval ( $suggest [ " uid " ]),
intval ( $fid ),
intval ( $suggest [ " cid " ]),
dbesc ( $suggest [ " body " ]),
dbesc ( $hash ),
2018-01-27 03:38:34 +01:00
dbesc ( DateTimeFormat :: utcNow ()),
2017-11-08 01:37:53 +01:00
intval ( 0 )
);
2017-11-23 20:01:58 +01:00
notification (
2018-01-15 14:05:12 +01:00
[
2017-11-23 20:01:58 +01:00
" type " => NOTIFY_SUGGEST ,
" notify_flags " => $importer [ " notify-flags " ],
" language " => $importer [ " language " ],
" to_name " => $importer [ " username " ],
" to_email " => $importer [ " email " ],
" uid " => $importer [ " importer_uid " ],
" item " => $suggest ,
" link " => System :: baseUrl () . " /notifications/intros " ,
" source_name " => $importer [ " name " ],
" source_link " => $importer [ " url " ],
" source_photo " => $importer [ " photo " ],
" verb " => ACTIVITY_REQ_FRIEND ,
2018-01-15 14:05:12 +01:00
" otype " => " intro " ]
2017-11-08 23:02:50 +01:00
);
2017-11-08 01:37:53 +01:00
return true ;
}
/**
* @ brief Processes the relocation elements
*
2017-11-08 23:02:50 +01:00
* @ param object $xpath XPath object
2017-11-08 01:37:53 +01:00
* @ param object $relocation relocation elements
2017-11-08 23:02:50 +01:00
* @ param array $importer Record of the importer user mixed with contact of the content
2017-11-23 20:01:58 +01:00
* @ return boolean
2017-11-08 01:37:53 +01:00
* @ todo Find good type - hints for all parameter
*/
2017-11-23 20:01:58 +01:00
private static function processRelocation ( $xpath , $relocation , $importer )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
logger ( " Processing relocations " );
/// @TODO Rewrite this to one statement
2018-01-15 14:05:12 +01:00
$relocate = [];
2017-11-08 01:37:53 +01:00
$relocate [ " uid " ] = $importer [ " importer_uid " ];
$relocate [ " cid " ] = $importer [ " id " ];
$relocate [ " url " ] = $xpath -> query ( " dfrn:url/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
$relocate [ " addr " ] = $xpath -> query ( " dfrn:addr/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
$relocate [ " name " ] = $xpath -> query ( " dfrn:name/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
$relocate [ " avatar " ] = $xpath -> query ( " dfrn:avatar/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
$relocate [ " photo " ] = $xpath -> query ( " dfrn:photo/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
$relocate [ " thumb " ] = $xpath -> query ( " dfrn:thumb/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
$relocate [ " micro " ] = $xpath -> query ( " dfrn:micro/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
$relocate [ " request " ] = $xpath -> query ( " dfrn:request/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
$relocate [ " confirm " ] = $xpath -> query ( " dfrn:confirm/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
$relocate [ " notify " ] = $xpath -> query ( " dfrn:notify/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
$relocate [ " poll " ] = $xpath -> query ( " dfrn:poll/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
$relocate [ " sitepubkey " ] = $xpath -> query ( " dfrn:sitepubkey/text() " , $relocation ) -> item ( 0 ) -> nodeValue ;
if (( $relocate [ " avatar " ] == " " ) && ( $relocate [ " photo " ] != " " )) {
$relocate [ " avatar " ] = $relocate [ " photo " ];
}
if ( $relocate [ " addr " ] == " " ) {
$relocate [ " addr " ] = preg_replace ( " =(https?://)(.*)/profile/(.*)=ism " , " $ 3@ $ 2 " , $relocate [ " url " ]);
}
// update contact
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `photo`, `url` FROM `contact` WHERE `id` = %d AND `uid` = %d; " ,
2017-11-08 01:37:53 +01:00
intval ( $importer [ " id " ]),
2017-11-08 23:02:50 +01:00
intval ( $importer [ " importer_uid " ])
);
2017-11-08 01:37:53 +01:00
2017-11-08 04:57:46 +01:00
if ( ! DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
logger ( " Query failed to execute, no result returned in " . __FUNCTION__ );
return false ;
}
$old = $r [ 0 ];
// Update the gcontact entry
$relocate [ " server_url " ] = preg_replace ( " =(https?://)(.*)/profile/(.*)=ism " , " $ 1 $ 2 " , $relocate [ " url " ]);
2018-05-02 21:26:15 +02:00
$fields = [ 'name' => $relocate [ " name " ], 'photo' => $relocate [ " avatar " ],
'url' => $relocate [ " url " ], 'nurl' => normalise_link ( $relocate [ " url " ]),
'addr' => $relocate [ " addr " ], 'connect' => $relocate [ " addr " ],
'notify' => $relocate [ " notify " ], 'server_url' => $relocate [ " server_url " ]];
dba :: update ( 'gcontact' , $fields , [ 'nurl' => normalise_link ( $old [ " url " ])]);
2017-11-08 01:37:53 +01:00
// Update the contact table. We try to find every entry.
2018-05-02 21:26:15 +02:00
$fields = [ 'name' => $relocate [ " name " ], 'avatar' => $relocate [ " avatar " ],
'url' => $relocate [ " url " ], 'nurl' => normalise_link ( $relocate [ " url " ]),
'addr' => $relocate [ " addr " ], 'request' => $relocate [ " request " ],
'confirm' => $relocate [ " confirm " ], 'notify' => $relocate [ " notify " ],
'poll' => $relocate [ " poll " ], 'site-pubkey' => $relocate [ " sitepubkey " ]];
$condition = [ " (`id` = ?) OR (`nurl` = ?) " , $importer [ " id " ], normalise_link ( $old [ " url " ])];
2018-05-02 23:12:48 +02:00
dba :: update ( 'contact' , $fields , $condition );
2017-11-08 01:37:53 +01:00
2017-11-29 23:29:11 +01:00
Contact :: updateAvatar ( $relocate [ " avatar " ], $importer [ " importer_uid " ], $importer [ " id " ], true );
2017-11-08 01:37:53 +01:00
2018-05-02 21:26:15 +02:00
logger ( 'Contacts are updated.' );
2017-11-08 01:37:53 +01:00
// update items
2018-05-02 21:26:15 +02:00
// This is an extreme performance killer
Item :: update ([ 'owner-link' => $relocate [ " url " ]], [ 'owner-link' => $old [ " url " ], 'uid' => $importer [ " importer_uid " ]]);
Item :: update ([ 'author-link' => $relocate [ " url " ]], [ 'author-link' => $old [ " url " ], 'uid' => $importer [ " importer_uid " ]]);
2017-11-08 01:37:53 +01:00
2018-05-02 21:26:15 +02:00
logger ( 'Items are updated.' );
2017-11-08 01:37:53 +01:00
/// @TODO
/// merge with current record, current contents have priority
/// update record, set url-updated
/// update profile photos
/// schedule a scan?
return true ;
}
/**
* @ brief Updates an item
*
2017-11-08 23:02:50 +01:00
* @ param array $current the current item record
* @ param array $item the new item record
* @ param array $importer Record of the importer user mixed with contact of the content
* @ param int $entrytype Is it a toplevel entry , a comment or a relayed comment ?
2017-11-23 20:01:58 +01:00
* @ return mixed
2017-11-08 01:37:53 +01:00
* @ todo set proper type - hints ( array ? )
*/
2017-11-23 20:01:58 +01:00
private static function updateContent ( $current , $item , $importer , $entrytype )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
$changed = false ;
2018-01-24 23:22:31 +01:00
if ( self :: isEditedTimestampNewer ( $current , $item )) {
2017-11-08 01:37:53 +01:00
// do not accept (ignore) an earlier edit than one we currently have.
2018-01-27 03:38:34 +01:00
if ( DateTimeFormat :: utc ( $item [ " edited " ]) < $current [ " edited " ]) {
2017-11-08 01:37:53 +01:00
return false ;
}
2018-03-17 21:56:56 +01:00
$fields = [ 'title' => defaults ( $item , 'title' , '' ), 'body' => defaults ( $item , 'body' , '' ),
'tag' => defaults ( $item , 'tag' , '' ), 'changed' => DateTimeFormat :: utcNow (),
2018-01-27 03:38:34 +01:00
'edited' => DateTimeFormat :: utc ( $item [ " edited " ])];
2017-11-08 01:37:53 +01:00
2018-01-15 14:05:12 +01:00
$condition = [ " `uri` = ? AND `uid` IN (0, ?) " , $item [ " uri " ], $importer [ " importer_uid " ]];
2018-02-06 13:40:22 +01:00
Item :: update ( $fields , $condition );
2017-11-08 01:37:53 +01:00
$changed = true ;
}
return $changed ;
}
/**
* @ brief Detects the entry type of the item
*
* @ param array $importer Record of the importer user mixed with contact of the content
2017-11-08 23:02:50 +01:00
* @ param array $item the new item record
2017-11-08 01:37:53 +01:00
*
* @ return int Is it a toplevel entry , a comment or a relayed comment ?
* @ todo set proper type - hints ( array ? )
*/
2017-11-23 20:01:58 +01:00
private static function getEntryType ( $importer , $item )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
if ( $item [ " parent-uri " ] != $item [ " uri " ]) {
$community = false ;
if ( $importer [ " page-flags " ] == PAGE_COMMUNITY || $importer [ " page-flags " ] == PAGE_PRVGROUP ) {
$sql_extra = " " ;
$community = true ;
logger ( " possible community action " );
} else {
$sql_extra = " AND `contact`.`self` AND `item`.`wall` " ;
}
// was the top-level post for this action written by somebody on this site?
// Specifically, the recipient?
$is_a_remote_action = false ;
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `item`.`parent-uri` FROM `item`
2017-11-08 01:37:53 +01:00
WHERE `item` . `uri` = '%s'
LIMIT 1 " ,
dbesc ( $item [ " parent-uri " ])
);
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `item`.`forum_mode`, `item`.`wall` FROM `item`
2017-11-08 01:37:53 +01:00
INNER JOIN `contact` ON `contact` . `id` = `item` . `contact-id`
WHERE `item` . `uri` = '%s' AND ( `item` . `parent-uri` = '%s' OR `item` . `thr-parent` = '%s' )
AND `item` . `uid` = % d
$sql_extra
LIMIT 1 " ,
dbesc ( $r [ 0 ][ " parent-uri " ]),
dbesc ( $r [ 0 ][ " parent-uri " ]),
dbesc ( $r [ 0 ][ " parent-uri " ]),
intval ( $importer [ " importer_uid " ])
);
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
$is_a_remote_action = true ;
}
}
/*
* Does this have the characteristics of a community or private group action ?
* If it 's an action to a wall post on a community/prvgroup page it' s a
* valid community action . Also forum_mode makes it valid for sure .
* If neither , it ' s not .
*/
if ( $is_a_remote_action && $community && ( ! $r [ 0 ][ " forum_mode " ]) && ( ! $r [ 0 ][ " wall " ])) {
$is_a_remote_action = false ;
logger ( " not a community action " );
}
if ( $is_a_remote_action ) {
2018-05-13 10:34:33 +02:00
return DFRN :: REPLY_RC ;
2017-11-08 01:37:53 +01:00
} else {
2018-05-13 10:34:33 +02:00
return DFRN :: REPLY ;
2017-11-08 01:37:53 +01:00
}
} else {
2018-05-13 10:34:33 +02:00
return DFRN :: TOP_LEVEL ;
2017-11-08 01:37:53 +01:00
}
}
/**
* @ brief Send a " poke "
*
2017-11-08 23:02:50 +01:00
* @ param array $item the new item record
* @ param array $importer Record of the importer user mixed with contact of the content
* @ param int $posted_id The record number of item record that was just posted
2017-11-23 20:01:58 +01:00
* @ return void
2017-11-08 01:37:53 +01:00
* @ todo set proper type - hints ( array ? )
*/
2017-11-23 20:01:58 +01:00
private static function doPoke ( $item , $importer , $posted_id )
2017-11-08 23:02:50 +01:00
{
$verb = urldecode ( substr ( $item [ " verb " ], strpos ( $item [ " verb " ], " # " ) + 1 ));
2017-11-08 01:37:53 +01:00
if ( ! $verb ) {
return ;
}
2018-01-27 17:13:41 +01:00
$xo = XML :: parseString ( $item [ " object " ], false );
2017-11-08 01:37:53 +01:00
if (( $xo -> type == ACTIVITY_OBJ_PERSON ) && ( $xo -> id )) {
// somebody was poked/prodded. Was it me?
foreach ( $xo -> link as $l ) {
$atts = $l -> attributes ();
switch ( $atts [ " rel " ]) {
case " alternate " :
$Blink = $atts [ " href " ];
break ;
default :
break ;
}
}
if ( $Blink && link_compare ( $Blink , System :: baseUrl () . " /profile/ " . $importer [ " nickname " ])) {
// send a notification
2017-11-08 23:02:50 +01:00
notification (
2018-01-15 14:05:12 +01:00
[
2017-11-08 01:37:53 +01:00
" type " => NOTIFY_POKE ,
" notify_flags " => $importer [ " notify-flags " ],
" language " => $importer [ " language " ],
" to_name " => $importer [ " username " ],
" to_email " => $importer [ " email " ],
" uid " => $importer [ " importer_uid " ],
" item " => $item ,
2018-01-28 12:18:08 +01:00
" link " => System :: baseUrl () . " /display/ " . urlencode ( Item :: getGuidById ( $posted_id )),
2017-11-08 01:37:53 +01:00
" source_name " => stripslashes ( $item [ " author-name " ]),
" source_link " => $item [ " author-link " ],
2017-11-23 20:01:58 +01:00
" source_photo " => (( link_compare ( $item [ " author-link " ], $importer [ " url " ]))
2017-11-08 01:37:53 +01:00
? $importer [ " thumb " ] : $item [ " author-avatar " ]),
" verb " => $item [ " verb " ],
" otype " => " person " ,
" activity " => $verb ,
2018-01-15 14:05:12 +01:00
" parent " => $item [ " parent " ]]
2017-11-08 23:02:50 +01:00
);
2017-11-08 01:37:53 +01:00
}
}
}
/**
* @ brief Processes several actions , depending on the verb
*
2017-11-08 23:02:50 +01:00
* @ param int $entrytype Is it a toplevel entry , a comment or a relayed comment ?
* @ param array $importer Record of the importer user mixed with contact of the content
* @ param array $item the new item record
* @ param bool $is_like Is the verb a " like " ?
2017-11-08 01:37:53 +01:00
*
* @ return bool Should the processing of the entries be continued ?
* @ todo set proper type - hints ( array ? )
*/
2017-11-23 20:01:58 +01:00
private static function processVerbs ( $entrytype , $importer , & $item , & $is_like )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
logger ( " Process verb " . $item [ " verb " ] . " and object-type " . $item [ " object-type " ] . " for entrytype " . $entrytype , LOGGER_DEBUG );
2018-05-13 10:34:33 +02:00
if (( $entrytype == DFRN :: TOP_LEVEL )) {
2017-11-08 01:37:53 +01:00
// The filling of the the "contact" variable is done for legcy reasons
// The functions below are partly used by ostatus.php as well - where we have this variable
$r = q ( " SELECT * FROM `contact` WHERE `id` = %d " , intval ( $importer [ " id " ]));
$contact = $r [ 0 ];
$nickname = $contact [ " nick " ];
// Big question: Do we need these functions? They were part of the "consume_feed" function.
// This function once was responsible for DFRN and OStatus.
if ( activity_match ( $item [ " verb " ], ACTIVITY_FOLLOW )) {
logger ( " New follower " );
2018-01-28 18:26:39 +01:00
Contact :: addRelationship ( $importer , $contact , $item , $nickname );
2017-11-08 01:37:53 +01:00
return false ;
}
2017-11-08 23:02:50 +01:00
if ( activity_match ( $item [ " verb " ], ACTIVITY_UNFOLLOW )) {
2017-11-08 01:37:53 +01:00
logger ( " Lost follower " );
2018-01-28 18:26:39 +01:00
Contact :: removeFollower ( $importer , $contact , $item );
2017-11-08 01:37:53 +01:00
return false ;
}
if ( activity_match ( $item [ " verb " ], ACTIVITY_REQ_FRIEND )) {
logger ( " New friend request " );
2018-01-28 18:26:39 +01:00
Contact :: addRelationship ( $importer , $contact , $item , $nickname , true );
2017-11-08 01:37:53 +01:00
return false ;
}
2017-11-08 23:02:50 +01:00
if ( activity_match ( $item [ " verb " ], ACTIVITY_UNFRIEND )) {
2017-11-08 01:37:53 +01:00
logger ( " Lost sharer " );
2018-01-28 18:26:39 +01:00
Contact :: removeSharer ( $importer , $contact , $item );
2017-11-08 01:37:53 +01:00
return false ;
}
} else {
if (( $item [ " verb " ] == ACTIVITY_LIKE )
|| ( $item [ " verb " ] == ACTIVITY_DISLIKE )
|| ( $item [ " verb " ] == ACTIVITY_ATTEND )
|| ( $item [ " verb " ] == ACTIVITY_ATTENDNO )
2017-11-08 23:02:50 +01:00
|| ( $item [ " verb " ] == ACTIVITY_ATTENDMAYBE )
) {
2017-11-08 01:37:53 +01:00
$is_like = true ;
$item [ " type " ] = " activity " ;
$item [ " gravity " ] = GRAVITY_LIKE ;
// only one like or dislike per person
// splitted into two queries for performance issues
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1 " ,
2017-11-08 01:37:53 +01:00
intval ( $item [ " uid " ]),
dbesc ( $item [ " author-link " ]),
dbesc ( $item [ " verb " ]),
dbesc ( $item [ " parent-uri " ])
);
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
return false ;
}
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1 " ,
2017-11-08 01:37:53 +01:00
intval ( $item [ " uid " ]),
dbesc ( $item [ " author-link " ]),
dbesc ( $item [ " verb " ]),
dbesc ( $item [ " parent-uri " ])
);
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
return false ;
}
} else {
$is_like = false ;
}
if (( $item [ " verb " ] == ACTIVITY_TAG ) && ( $item [ " object-type " ] == ACTIVITY_OBJ_TAGTERM )) {
2018-01-27 17:13:41 +01:00
$xo = XML :: parseString ( $item [ " object " ], false );
$xt = XML :: parseString ( $item [ " target " ], false );
2017-11-08 01:37:53 +01:00
if ( $xt -> type == ACTIVITY_OBJ_NOTE ) {
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1 " ,
2017-11-08 01:37:53 +01:00
dbesc ( $xt -> id ),
intval ( $importer [ " importer_uid " ])
);
2017-11-08 04:57:46 +01:00
if ( ! DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
logger ( " Query failed to execute, no result returned in " . __FUNCTION__ );
return false ;
}
// extract tag, if not duplicate, add to parent item
if ( $xo -> content ) {
2018-02-06 13:40:22 +01:00
if ( ! stristr ( $r [ 0 ][ " tag " ], trim ( $xo -> content ))) {
$tag = $r [ 0 ][ " tag " ] . ( strlen ( $r [ 0 ][ " tag " ]) ? ',' : '' ) . '#[url=' . $xo -> id . ']' . $xo -> content . '[/url]' ;
Item :: update ([ 'tag' => $tag ], [ 'id' => $r [ 0 ][ " id " ]]);
2017-11-08 01:37:53 +01:00
}
}
}
}
}
return true ;
}
/**
* @ brief Processes the link elements
*
* @ param object $links link elements
2017-11-08 23:02:50 +01:00
* @ param array $item the item record
2017-11-23 20:01:58 +01:00
* @ return void
2017-11-08 01:37:53 +01:00
* @ todo set proper type - hints
*/
2017-11-23 20:01:58 +01:00
private static function parseLinks ( $links , & $item )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
$rel = " " ;
$href = " " ;
$type = " " ;
$length = " 0 " ;
$title = " " ;
2017-11-23 20:01:58 +01:00
foreach ( $links as $link ) {
foreach ( $link -> attributes as $attributes ) {
2018-01-19 17:50:43 +01:00
switch ( $attributes -> name ) {
case " href " : $href = $attributes -> textContent ; break ;
case " rel " : $rel = $attributes -> textContent ; break ;
case " type " : $type = $attributes -> textContent ; break ;
case " length " : $length = $attributes -> textContent ; break ;
case " title " : $title = $attributes -> textContent ; break ;
2017-11-08 01:37:53 +01:00
}
}
if (( $rel != " " ) && ( $href != " " )) {
switch ( $rel ) {
case " alternate " :
$item [ " plink " ] = $href ;
break ;
case " enclosure " :
$enclosure = $href ;
if ( strlen ( $item [ " attach " ])) {
$item [ " attach " ] .= " , " ;
}
$item [ " attach " ] .= '[attach]href="' . $href . '" length="' . $length . '" type="' . $type . '" title="' . $title . '"[/attach]' ;
break ;
}
}
}
}
/**
* @ brief Processes the entry elements which contain the items and comments
*
2017-11-23 20:01:58 +01:00
* @ param array $header Array of the header elements that always stay the same
* @ param object $xpath XPath object
* @ param object $entry entry elements
* @ param array $importer Record of the importer user mixed with contact of the content
* @ param object $xml xml
* @ return void
2017-11-08 01:37:53 +01:00
* @ todo Add type - hints
*/
2017-11-23 20:01:58 +01:00
private static function processEntry ( $header , $xpath , $entry , $importer , $xml )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
logger ( " Processing entries " );
$item = $header ;
$item [ " protocol " ] = PROTOCOL_DFRN ;
$item [ " source " ] = $xml ;
// Get the uri
$item [ " uri " ] = $xpath -> query ( " atom:id/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
$item [ " edited " ] = $xpath -> query ( " atom:updated/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
2018-02-14 05:58:46 +01:00
$current = dba :: selectFirst ( 'item' ,
[ 'id' , 'uid' , 'edited' , 'body' ],
[ 'uri' => $item [ " uri " ], 'uid' => $importer [ " importer_uid " ]]
2017-11-08 01:37:53 +01:00
);
// Is there an existing item?
2018-02-14 05:58:46 +01:00
if ( DBM :: is_result ( $current ) && ! self :: isEditedTimestampNewer ( $current , $item )) {
2018-02-10 14:33:15 +01:00
logger ( " Item " . $item [ " uri " ] . " ( " . $item [ 'edited' ] . " ) already existed. " , LOGGER_DEBUG );
2017-11-08 01:37:53 +01:00
return ;
}
// Fetch the owner
$owner = self :: fetchauthor ( $xpath , $entry , $importer , " dfrn:owner " , true );
$item [ " owner-name " ] = $owner [ " name " ];
$item [ " owner-link " ] = $owner [ " link " ];
$item [ " owner-avatar " ] = $owner [ " avatar " ];
// fetch the author
$author = self :: fetchauthor ( $xpath , $entry , $importer , " atom:author " , true );
$item [ " author-name " ] = $author [ " name " ];
$item [ " author-link " ] = $author [ " link " ];
$item [ " author-avatar " ] = $author [ " avatar " ];
$item [ " title " ] = $xpath -> query ( " atom:title/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
$item [ " created " ] = $xpath -> query ( " atom:published/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
$item [ " body " ] = $xpath -> query ( " dfrn:env/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
2018-01-15 14:05:12 +01:00
$item [ " body " ] = str_replace ([ ' ' , " \t " , " \r " , " \n " ], [ '' , '' , '' , '' ], $item [ " body " ]);
2017-11-08 01:37:53 +01:00
// make sure nobody is trying to sneak some html tags by us
$item [ " body " ] = notags ( base64url_decode ( $item [ " body " ]));
2018-01-28 18:36:37 +01:00
$item [ " body " ] = BBCode :: limitBodySize ( $item [ " body " ]);
2017-11-08 01:37:53 +01:00
/// @todo Do we really need this check for HTML elements? (It was copied from the old function)
2017-11-08 23:02:50 +01:00
if (( strpos ( $item [ 'body' ], '<' ) !== false ) && ( strpos ( $item [ 'body' ], '>' ) !== false )) {
2017-12-17 21:31:37 +01:00
$base_url = get_app () -> get_baseurl ();
2017-11-08 23:02:50 +01:00
$item [ 'body' ] = reltoabs ( $item [ 'body' ], $base_url );
2017-11-08 01:37:53 +01:00
$item [ 'body' ] = html2bb_video ( $item [ 'body' ]);
2018-01-01 02:58:09 +01:00
$item [ 'body' ] = OEmbed :: HTML2BBCode ( $item [ 'body' ]);
2017-11-08 01:37:53 +01:00
2018-01-19 02:15:26 +01:00
$config = HTMLPurifier_Config :: createDefault ();
2017-11-08 01:37:53 +01:00
$config -> set ( 'Cache.DefinitionImpl' , null );
// we shouldn't need a whitelist, because the bbcode converter
// will strip out any unsupported tags.
2018-01-19 02:15:26 +01:00
$purifier = new HTMLPurifier ( $config );
2017-11-08 01:37:53 +01:00
$item [ 'body' ] = $purifier -> purify ( $item [ 'body' ]);
2018-03-08 20:58:35 +01:00
$item [ 'body' ] = @ HTML :: toBBCode ( $item [ 'body' ]);
2017-11-08 01:37:53 +01:00
}
/// @todo We should check for a repeated post and if we know the repeated author.
// We don't need the content element since "dfrn:env" is always present
//$item["body"] = $xpath->query("atom:content/text()", $entry)->item(0)->nodeValue;
$item [ " location " ] = $xpath -> query ( " dfrn:location/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
$georsspoint = $xpath -> query ( " georss:point " , $entry );
if ( $georsspoint ) {
$item [ " coord " ] = $georsspoint -> item ( 0 ) -> nodeValue ;
}
$item [ " private " ] = $xpath -> query ( " dfrn:private/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
$item [ " extid " ] = $xpath -> query ( " dfrn:extid/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
if ( $xpath -> query ( " dfrn:bookmark/text() " , $entry ) -> item ( 0 ) -> nodeValue == " true " ) {
$item [ " bookmark " ] = true ;
}
$notice_info = $xpath -> query ( " statusnet:notice_info " , $entry );
if ( $notice_info && ( $notice_info -> length > 0 )) {
2017-11-23 20:01:58 +01:00
foreach ( $notice_info -> item ( 0 ) -> attributes as $attributes ) {
2017-11-08 01:37:53 +01:00
if ( $attributes -> name == " source " ) {
$item [ " app " ] = strip_tags ( $attributes -> textContent );
}
}
}
$item [ " guid " ] = $xpath -> query ( " dfrn:diaspora_guid/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
2018-01-28 12:18:08 +01:00
// We store the data from "dfrn:diaspora_signature" in a different table, this is done in "Item::insert"
2017-11-08 01:37:53 +01:00
$dsprsig = unxmlify ( $xpath -> query ( " dfrn:diaspora_signature/text() " , $entry ) -> item ( 0 ) -> nodeValue );
if ( $dsprsig != " " ) {
$item [ " dsprsig " ] = $dsprsig ;
}
$item [ " verb " ] = $xpath -> query ( " activity:verb/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
if ( $xpath -> query ( " activity:object-type/text() " , $entry ) -> item ( 0 ) -> nodeValue != " " ) {
$item [ " object-type " ] = $xpath -> query ( " activity:object-type/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
}
$object = $xpath -> query ( " activity:object " , $entry ) -> item ( 0 );
2017-11-23 20:01:58 +01:00
$item [ " object " ] = self :: transformActivity ( $xpath , $object , " object " );
2017-11-08 01:37:53 +01:00
if ( trim ( $item [ " object " ]) != " " ) {
2018-01-27 17:13:41 +01:00
$r = XML :: parseString ( $item [ " object " ], false );
2017-11-08 01:37:53 +01:00
if ( isset ( $r -> type )) {
$item [ " object-type " ] = $r -> type ;
}
}
$target = $xpath -> query ( " activity:target " , $entry ) -> item ( 0 );
2017-11-23 20:01:58 +01:00
$item [ " target " ] = self :: transformActivity ( $xpath , $target , " target " );
2017-11-08 01:37:53 +01:00
$categories = $xpath -> query ( " atom:category " , $entry );
if ( $categories ) {
2017-11-23 20:01:58 +01:00
foreach ( $categories as $category ) {
2017-11-08 01:37:53 +01:00
$term = " " ;
$scheme = " " ;
2017-11-23 20:01:58 +01:00
foreach ( $category -> attributes as $attributes ) {
2017-11-08 01:37:53 +01:00
if ( $attributes -> name == " term " ) {
$term = $attributes -> textContent ;
}
if ( $attributes -> name == " scheme " ) {
$scheme = $attributes -> textContent ;
}
}
if (( $term != " " ) && ( $scheme != " " )) {
$parts = explode ( " : " , $scheme );
if (( count ( $parts ) >= 4 ) && ( array_shift ( $parts ) == " X-DFRN " )) {
$termhash = array_shift ( $parts );
$termurl = implode ( " : " , $parts );
if ( strlen ( $item [ " tag " ])) {
$item [ " tag " ] .= " , " ;
}
$item [ " tag " ] .= $termhash . " [url= " . $termurl . " ] " . $term . " [/url] " ;
}
}
}
}
$enclosure = " " ;
$links = $xpath -> query ( " atom:link " , $entry );
if ( $links ) {
2017-11-23 20:01:58 +01:00
self :: parseLinks ( $links , $item );
2017-11-08 01:37:53 +01:00
}
$item [ 'conversation-uri' ] = $xpath -> query ( 'ostatus:conversation/text()' , $entry ) -> item ( 0 ) -> nodeValue ;
$conv = $xpath -> query ( 'ostatus:conversation' , $entry );
if ( is_object ( $conv -> item ( 0 ))) {
2017-11-23 20:01:58 +01:00
foreach ( $conv -> item ( 0 ) -> attributes as $attributes ) {
2017-11-08 01:37:53 +01:00
if ( $attributes -> name == " ref " ) {
$item [ 'conversation-uri' ] = $attributes -> textContent ;
}
if ( $attributes -> name == " href " ) {
$item [ 'conversation-href' ] = $attributes -> textContent ;
}
}
}
// Is it a reply or a top level posting?
$item [ " parent-uri " ] = $item [ " uri " ];
$inreplyto = $xpath -> query ( " thr:in-reply-to " , $entry );
if ( is_object ( $inreplyto -> item ( 0 ))) {
2017-11-23 20:01:58 +01:00
foreach ( $inreplyto -> item ( 0 ) -> attributes as $attributes ) {
2017-11-08 01:37:53 +01:00
if ( $attributes -> name == " ref " ) {
$item [ " parent-uri " ] = $attributes -> textContent ;
}
}
}
// Get the type of the item (Top level post, reply or remote reply)
2017-11-23 20:01:58 +01:00
$entrytype = self :: getEntryType ( $importer , $item );
2017-11-08 01:37:53 +01:00
// Now assign the rest of the values that depend on the type of the message
2018-05-13 10:34:33 +02:00
if ( in_array ( $entrytype , [ DFRN :: REPLY , DFRN :: REPLY_RC ])) {
2017-11-08 01:37:53 +01:00
if ( ! isset ( $item [ " object-type " ])) {
$item [ " object-type " ] = ACTIVITY_OBJ_COMMENT ;
}
if ( $item [ " contact-id " ] != $owner [ " contact-id " ]) {
$item [ " contact-id " ] = $owner [ " contact-id " ];
}
if (( $item [ " network " ] != $owner [ " network " ]) && ( $owner [ " network " ] != " " )) {
$item [ " network " ] = $owner [ " network " ];
}
if ( $item [ " contact-id " ] != $author [ " contact-id " ]) {
$item [ " contact-id " ] = $author [ " contact-id " ];
}
if (( $item [ " network " ] != $author [ " network " ]) && ( $author [ " network " ] != " " )) {
$item [ " network " ] = $author [ " network " ];
}
}
2018-05-13 10:34:33 +02:00
if ( $entrytype == DFRN :: REPLY_RC ) {
2017-11-08 01:37:53 +01:00
$item [ " type " ] = " remote-comment " ;
$item [ " wall " ] = 1 ;
2018-05-13 10:34:33 +02:00
} elseif ( $entrytype == DFRN :: TOP_LEVEL ) {
2017-11-08 01:37:53 +01:00
if ( ! isset ( $item [ " object-type " ])) {
$item [ " object-type " ] = ACTIVITY_OBJ_NOTE ;
}
// Is it an event?
if ( $item [ " object-type " ] == ACTIVITY_OBJ_EVENT ) {
logger ( " Item " . $item [ " uri " ] . " seems to contain an event. " , LOGGER_DEBUG );
2018-03-17 02:45:02 +01:00
$ev = Event :: fromBBCode ( $item [ " body " ]);
2017-11-08 01:37:53 +01:00
if (( x ( $ev , " desc " ) || x ( $ev , " summary " )) && x ( $ev , " start " )) {
logger ( " Event in item " . $item [ " uri " ] . " was found. " , LOGGER_DEBUG );
2018-01-19 17:50:43 +01:00
$ev [ " cid " ] = $importer [ " id " ];
2018-03-30 08:20:00 +02:00
$ev [ " uid " ] = $importer [ " importer_uid " ];
2018-01-19 17:50:43 +01:00
$ev [ " uri " ] = $item [ " uri " ];
$ev [ " edited " ] = $item [ " edited " ];
$ev [ " private " ] = $item [ " private " ];
$ev [ " guid " ] = $item [ " guid " ];
2017-11-08 01:37:53 +01:00
2017-11-08 23:02:50 +01:00
$r = q (
" SELECT `id` FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1 " ,
2017-11-08 01:37:53 +01:00
dbesc ( $item [ " uri " ]),
2018-03-30 08:20:00 +02:00
intval ( $importer [ " importer_uid " ])
2017-11-08 01:37:53 +01:00
);
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-08 01:37:53 +01:00
$ev [ " id " ] = $r [ 0 ][ " id " ];
}
2018-03-17 02:45:02 +01:00
$event_id = Event :: store ( $ev );
2017-11-08 01:37:53 +01:00
logger ( " Event " . $event_id . " was stored " , LOGGER_DEBUG );
return ;
}
}
}
2017-11-23 20:01:58 +01:00
if ( ! self :: processVerbs ( $entrytype , $importer , $item , $is_like )) {
logger ( " Exiting because 'processVerbs' told us so " , LOGGER_DEBUG );
2017-11-08 01:37:53 +01:00
return ;
}
// Update content if 'updated' changes
2017-11-08 04:57:46 +01:00
if ( DBM :: is_result ( $current )) {
2018-02-14 05:58:46 +01:00
if ( self :: updateContent ( $current , $item , $importer , $entrytype )) {
2017-11-08 01:37:53 +01:00
logger ( " Item " . $item [ " uri " ] . " was updated. " , LOGGER_DEBUG );
} else {
logger ( " Item " . $item [ " uri " ] . " already existed. " , LOGGER_DEBUG );
}
return ;
}
2018-05-13 10:34:33 +02:00
if ( in_array ( $entrytype , [ DFRN :: REPLY , DFRN :: REPLY_RC ])) {
2018-01-28 12:18:08 +01:00
$posted_id = Item :: insert ( $item );
2017-11-08 01:37:53 +01:00
$parent = 0 ;
if ( $posted_id ) {
logger ( " Reply from contact " . $item [ " contact-id " ] . " was stored with id " . $posted_id , LOGGER_DEBUG );
2018-04-24 16:58:39 +02:00
if ( $item [ 'uid' ] == 0 ) {
2018-04-24 15:21:25 +02:00
Item :: distribute ( $posted_id );
}
2017-11-08 01:37:53 +01:00
return true ;
}
2018-05-13 10:34:33 +02:00
} else { // $entrytype == DFRN::TOP_LEVEL
2018-04-22 12:58:03 +02:00
if (( $importer [ " uid " ] == 0 ) && ( $importer [ " importer_uid " ] != 0 )) {
2018-03-30 08:20:00 +02:00
logger ( " Contact " . $importer [ " id " ] . " isn't known to user " . $importer [ " importer_uid " ] . " . The post will be ignored. " , LOGGER_DEBUG );
return ;
}
2017-11-08 23:02:50 +01:00
if ( ! link_compare ( $item [ " owner-link " ], $importer [ " url " ])) {
2017-11-08 01:37:53 +01:00
/*
* The item owner info is not our contact . It ' s OK and is to be expected if this is a tgroup delivery ,
* but otherwise there 's a possible data mixup on the sender' s system .
2018-01-28 12:18:08 +01:00
* the tgroup delivery code called from Item :: insert will correct it if it ' s a forum ,
2017-11-08 01:37:53 +01:00
* but we ' re going to unconditionally correct it here so that the post will always be owned by our contact .
*/
logger ( 'Correcting item owner.' , LOGGER_DEBUG );
$item [ " owner-name " ] = $importer [ " senderName " ];
$item [ " owner-link " ] = $importer [ " url " ];
$item [ " owner-avatar " ] = $importer [ " thumb " ];
}
2018-01-24 21:27:32 +01:00
if (( $importer [ " rel " ] == CONTACT_IS_FOLLOWER ) && ( ! self :: tgroupCheck ( $importer [ " importer_uid " ], $item ))) {
2017-11-08 01:37:53 +01:00
logger ( " Contact " . $importer [ " id " ] . " is only follower and tgroup check was negative. " , LOGGER_DEBUG );
return ;
}
// This is my contact on another system, but it's really me.
// Turn this into a wall post.
2018-01-28 12:18:08 +01:00
$notify = Item :: isRemoteSelf ( $importer , $item );
2017-11-08 01:37:53 +01:00
2018-01-28 12:18:08 +01:00
$posted_id = Item :: insert ( $item , false , $notify );
2017-11-08 01:37:53 +01:00
2018-05-04 23:12:13 +02:00
if ( $notify ) {
2018-05-04 23:33:15 +02:00
$posted_id = $notify ;
2018-05-04 23:12:13 +02:00
}
2017-11-08 01:37:53 +01:00
logger ( " Item was stored with id " . $posted_id , LOGGER_DEBUG );
2018-04-24 15:21:25 +02:00
if ( $item [ 'uid' ] == 0 ) {
Item :: distribute ( $posted_id );
}
2017-11-23 20:01:58 +01:00
if ( stristr ( $item [ " verb " ], ACTIVITY_POKE )) {
self :: doPoke ( $item , $importer , $posted_id );
}
2017-11-08 01:37:53 +01:00
}
}
/**
* @ brief Deletes items
*
2017-11-08 23:02:50 +01:00
* @ param object $xpath XPath object
2017-11-08 01:37:53 +01:00
* @ param object $deletion deletion elements
2017-11-08 23:02:50 +01:00
* @ param array $importer Record of the importer user mixed with contact of the content
2017-11-23 20:01:58 +01:00
* @ return void
2017-11-08 01:37:53 +01:00
* @ todo set proper type - hints
*/
2017-11-23 20:01:58 +01:00
private static function processDeletion ( $xpath , $deletion , $importer )
2017-11-08 23:02:50 +01:00
{
2017-11-08 01:37:53 +01:00
logger ( " Processing deletions " );
2018-02-14 05:58:46 +01:00
$uri = null ;
2017-11-23 20:01:58 +01:00
foreach ( $deletion -> attributes as $attributes ) {
2017-11-08 01:37:53 +01:00
if ( $attributes -> name == " ref " ) {
$uri = $attributes -> textContent ;
}
}
if ( ! $uri || ! $importer [ " id " ]) {
return false ;
}
2018-03-30 08:20:00 +02:00
$condition = [ " `uri` = ? AND `uid` = ? AND NOT `file` LIKE '%[%' " , $uri , $importer [ " importer_uid " ]];
2018-03-01 22:52:36 +01:00
$item = dba :: selectFirst ( 'item' , [ 'id' , 'parent' , 'contact-id' ], $condition );
2018-02-06 13:40:22 +01:00
if ( ! DBM :: is_result ( $item )) {
2018-03-30 08:20:00 +02:00
logger ( " Item with uri " . $uri . " for user " . $importer [ " importer_uid " ] . " wasn't found. " , LOGGER_DEBUG );
2017-11-08 01:37:53 +01:00
return ;
2018-02-06 13:40:22 +01:00
}
2017-11-08 01:37:53 +01:00
2018-03-01 22:52:36 +01:00
// When it is a starting post it has to belong to the person that wants to delete it
if (( $item [ 'id' ] == $item [ 'parent' ]) && ( $item [ 'contact-id' ] != $importer [ " id " ])) {
logger ( " Item with uri " . $uri . " don't belong to contact " . $importer [ " id " ] . " - ignoring deletion. " , LOGGER_DEBUG );
return ;
}
// Comments can be deleted by the thread owner or comment owner
if (( $item [ 'id' ] != $item [ 'parent' ]) && ( $item [ 'contact-id' ] != $importer [ " id " ])) {
$condition = [ 'id' => $item [ 'parent' ], 'contact-id' => $importer [ " id " ]];
if ( ! dba :: exists ( 'item' , $condition )) {
logger ( " Item with uri " . $uri . " wasn't found or mustn't be deleted by contact " . $importer [ " id " ] . " - ignoring deletion. " , LOGGER_DEBUG );
return ;
}
}
2018-05-29 07:22:57 +02:00
if ( $item [ " deleted " ]) {
2018-02-06 13:40:22 +01:00
return ;
}
2018-01-18 07:54:44 +01:00
2018-05-29 21:00:26 +02:00
logger ( 'deleting item ' . $item [ 'id' ] . ' uri=' . $uri , LOGGER_DEBUG );
2018-05-29 07:22:57 +02:00
2018-05-29 21:00:26 +02:00
Item :: delete ([ 'id' => $item [ 'id' ]]);
2017-11-08 01:37:53 +01:00
}
/**
* @ brief Imports a DFRN message
*
2017-12-17 21:27:50 +01:00
* @ param string $xml The DFRN message
* @ param array $importer Record of the importer user mixed with contact of the content
* @ param bool $sort_by_date Is used when feeds are polled
2017-11-08 01:37:53 +01:00
* @ return integer Import status
* @ todo set proper type - hints
*/
2017-11-08 23:02:50 +01:00
public static function import ( $xml , $importer , $sort_by_date = false )
{
2017-11-08 01:37:53 +01:00
if ( $xml == " " ) {
return 400 ;
}
$doc = new DOMDocument ();
@ $doc -> loadXML ( $xml );
2017-12-17 21:24:57 +01:00
$xpath = new DOMXPath ( $doc );
2017-11-08 01:37:53 +01:00
$xpath -> registerNamespace ( " atom " , NAMESPACE_ATOM1 );
$xpath -> registerNamespace ( " thr " , NAMESPACE_THREAD );
$xpath -> registerNamespace ( " at " , NAMESPACE_TOMB );
$xpath -> registerNamespace ( " media " , NAMESPACE_MEDIA );
$xpath -> registerNamespace ( " dfrn " , NAMESPACE_DFRN );
$xpath -> registerNamespace ( " activity " , NAMESPACE_ACTIVITY );
$xpath -> registerNamespace ( " georss " , NAMESPACE_GEORSS );
$xpath -> registerNamespace ( " poco " , NAMESPACE_POCO );
$xpath -> registerNamespace ( " ostatus " , NAMESPACE_OSTATUS );
$xpath -> registerNamespace ( " statusnet " , NAMESPACE_STATUSNET );
2018-01-15 14:05:12 +01:00
$header = [];
2018-03-30 08:20:00 +02:00
$header [ " uid " ] = $importer [ " importer_uid " ];
2017-11-08 01:37:53 +01:00
$header [ " network " ] = NETWORK_DFRN ;
$header [ " type " ] = " remote " ;
$header [ " wall " ] = 0 ;
$header [ " origin " ] = 0 ;
$header [ " contact-id " ] = $importer [ " id " ];
// Update the contact table if the data has changed
// The "atom:author" is only present in feeds
if ( $xpath -> query ( " /atom:feed/atom:author " ) -> length > 0 ) {
self :: fetchauthor ( $xpath , $doc -> firstChild , $importer , " atom:author " , false , $xml );
}
// Only the "dfrn:owner" in the head section contains all data
if ( $xpath -> query ( " /atom:feed/dfrn:owner " ) -> length > 0 ) {
self :: fetchauthor ( $xpath , $doc -> firstChild , $importer , " dfrn:owner " , false , $xml );
}
2018-03-30 08:20:00 +02:00
logger ( " Import DFRN message for user " . $importer [ " importer_uid " ] . " from contact " . $importer [ " id " ], LOGGER_DEBUG );
2017-11-08 01:37:53 +01:00
2018-04-21 23:59:02 +02:00
// is it a public forum? Private forums aren't exposed with this method
$forum = intval ( $xpath -> evaluate ( " /atom:feed/dfrn:community/text() " ) -> item ( 0 ) -> nodeValue );
2017-11-08 01:37:53 +01:00
// The account type is new since 3.5.1
if ( $xpath -> query ( " /atom:feed/dfrn:account_type " ) -> length > 0 ) {
2017-12-17 21:31:37 +01:00
$accounttype = intval ( $xpath -> evaluate ( " /atom:feed/dfrn:account_type/text() " ) -> item ( 0 ) -> nodeValue );
2017-11-08 01:37:53 +01:00
if ( $accounttype != $importer [ " contact-type " ]) {
2018-01-15 14:05:12 +01:00
dba :: update ( 'contact' , [ 'contact-type' => $accounttype ], [ 'id' => $importer [ " id " ]]);
2017-11-08 01:37:53 +01:00
}
2018-04-21 23:59:02 +02:00
// A forum contact can either have set "forum" or "prv" - but not both
if (( $accounttype == ACCOUNT_TYPE_COMMUNITY ) && (( $forum != $importer [ " forum " ]) || ( $forum == $importer [ " prv " ]))) {
$condition = [ '(`forum` != ? OR `prv` != ?) AND `id` = ?' , $forum , ! $forum , $importer [ " id " ]];
dba :: update ( 'contact' , [ 'forum' => $forum , 'prv' => ! $forum ], $condition );
}
} elseif ( $forum != $importer [ " forum " ]) { // Deprecated since 3.5.1
2018-01-15 14:05:12 +01:00
$condition = [ '`forum` != ? AND `id` = ?' , $forum , $importer [ " id " ]];
dba :: update ( 'contact' , [ 'forum' => $forum ], $condition );
2017-11-08 01:37:53 +01:00
}
2018-04-21 23:59:02 +02:00
2017-11-08 01:37:53 +01:00
// We are processing relocations even if we are ignoring a contact
$relocations = $xpath -> query ( " /atom:feed/dfrn:relocate " );
2017-11-23 20:01:58 +01:00
foreach ( $relocations as $relocation ) {
self :: processRelocation ( $xpath , $relocation , $importer );
2017-11-08 01:37:53 +01:00
}
2018-04-01 07:07:35 +02:00
if (( $importer [ " uid " ] != 0 ) && ! $importer [ " readonly " ]) {
$mails = $xpath -> query ( " /atom:feed/dfrn:mail " );
foreach ( $mails as $mail ) {
self :: processMail ( $xpath , $mail , $importer );
}
2017-11-08 01:37:53 +01:00
2018-04-01 07:07:35 +02:00
$suggestions = $xpath -> query ( " /atom:feed/dfrn:suggest " );
foreach ( $suggestions as $suggestion ) {
self :: processSuggestion ( $xpath , $suggestion , $importer );
}
2017-11-08 01:37:53 +01:00
}
$deletions = $xpath -> query ( " /atom:feed/at:deleted-entry " );
2017-11-23 20:01:58 +01:00
foreach ( $deletions as $deletion ) {
self :: processDeletion ( $xpath , $deletion , $importer );
2017-11-08 01:37:53 +01:00
}
if ( ! $sort_by_date ) {
$entries = $xpath -> query ( " /atom:feed/atom:entry " );
2017-11-23 20:01:58 +01:00
foreach ( $entries as $entry ) {
self :: processEntry ( $header , $xpath , $entry , $importer , $xml );
2017-11-08 01:37:53 +01:00
}
} else {
2018-01-15 14:05:12 +01:00
$newentries = [];
2017-11-08 01:37:53 +01:00
$entries = $xpath -> query ( " /atom:feed/atom:entry " );
2017-11-23 20:01:58 +01:00
foreach ( $entries as $entry ) {
2017-11-08 01:37:53 +01:00
$created = $xpath -> query ( " atom:published/text() " , $entry ) -> item ( 0 ) -> nodeValue ;
$newentries [ strtotime ( $created )] = $entry ;
}
// Now sort after the publishing date
ksort ( $newentries );
2017-11-23 20:01:58 +01:00
foreach ( $newentries as $entry ) {
self :: processEntry ( $header , $xpath , $entry , $importer , $xml );
2017-11-08 01:37:53 +01:00
}
}
2018-03-30 08:20:00 +02:00
logger ( " Import done for user " . $importer [ " importer_uid " ] . " from contact " . $importer [ " id " ], LOGGER_DEBUG );
2017-11-08 01:37:53 +01:00
return 200 ;
}
2018-01-13 15:36:21 +01:00
/**
* @ param App $a App
* @ param string $contact_nick contact nickname
*/
public static function autoRedir ( App $a , $contact_nick )
{
// prevent looping
if ( x ( $_REQUEST , 'redir' ) && intval ( $_REQUEST [ 'redir' ])) {
return ;
}
if (( ! $contact_nick ) || ( $contact_nick === $a -> user [ 'nickname' ])) {
return ;
}
if ( local_user ()) {
// We need to find out if $contact_nick is a user on this hub, and if so, if I
// am a contact of that user. However, that user may have other contacts with the
// same nickname as me on other hubs or other networks. Exclude these by requiring
// that the contact have a local URL. I will be the only person with my nickname at
// this URL, so if a result is found, then I am a contact of the $contact_nick user.
//
// We also have to make sure that I'm a legitimate contact--I'm not blocked or pending.
$baseurl = System :: baseUrl ();
$domain_st = strpos ( $baseurl , " :// " );
if ( $domain_st === false ) {
return ;
}
$baseurl = substr ( $baseurl , $domain_st + 3 );
$nurl = normalise_link ( $baseurl );
/// @todo Why is there a query for "url" *and* "nurl"? Especially this normalising is strange.
$r = q ( " SELECT `id` FROM `contact` WHERE `uid` = (SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1)
AND `nick` = '%s' AND NOT `self` AND ( `url` LIKE '%%%s%%' OR `nurl` LIKE '%%%s%%' ) AND NOT `blocked` AND NOT `pending` LIMIT 1 " ,
dbesc ( $contact_nick ),
dbesc ( $a -> user [ 'nickname' ]),
dbesc ( $baseurl ),
dbesc ( $nurl )
);
if (( ! DBM :: is_result ( $r )) || $r [ 0 ][ 'id' ] == remote_user ()) {
return ;
}
$r = q ( " SELECT * FROM contact WHERE nick = '%s'
AND network = '%s' AND uid = % d AND url LIKE '%%%s%%' LIMIT 1 " ,
dbesc ( $contact_nick ),
dbesc ( NETWORK_DFRN ),
intval ( local_user ()),
dbesc ( $baseurl )
);
if ( ! DBM :: is_result ( $r )) {
return ;
}
$cid = $r [ 0 ][ 'id' ];
$dfrn_id = (( $r [ 0 ][ 'issued-id' ]) ? $r [ 0 ][ 'issued-id' ] : $r [ 0 ][ 'dfrn-id' ]);
if ( $r [ 0 ][ 'duplex' ] && $r [ 0 ][ 'issued-id' ]) {
$orig_id = $r [ 0 ][ 'issued-id' ];
$dfrn_id = '1:' . $orig_id ;
}
if ( $r [ 0 ][ 'duplex' ] && $r [ 0 ][ 'dfrn-id' ]) {
$orig_id = $r [ 0 ][ 'dfrn-id' ];
$dfrn_id = '0:' . $orig_id ;
}
// ensure that we've got a valid ID. There may be some edge cases with forums and non-duplex mode
// that may have triggered some of the "went to {profile/intro} and got an RSS feed" issues
if ( strlen ( $dfrn_id ) < 3 ) {
return ;
}
$sec = random_string ();
dba :: insert ( 'profile_check' , [ 'uid' => local_user (), 'cid' => $cid , 'dfrn_id' => $dfrn_id , 'sec' => $sec , 'expire' => time () + 45 ]);
$url = curPageURL ();
logger ( 'auto_redir: ' . $r [ 0 ][ 'name' ] . ' ' . $sec , LOGGER_DEBUG );
$dest = (( $url ) ? '&destination_url=' . $url : '' );
goaway ( $r [ 0 ][ 'poll' ] . '?dfrn_id=' . $dfrn_id
. '&dfrn_version=' . DFRN_PROTOCOL_VERSION . '&type=profile&sec=' . $sec . $dest );
}
return ;
}
2018-01-21 00:52:54 +01:00
/**
* @ brief Returns the activity verb
*
* @ param array $item Item array
*
* @ return string activity verb
*/
private static function constructVerb ( array $item )
{
if ( $item [ 'verb' ]) {
return $item [ 'verb' ];
}
return ACTIVITY_POST ;
}
2018-01-24 21:27:32 +01:00
private static function tgroupCheck ( $uid , $item )
{
$mention = false ;
// check that the message originated elsewhere and is a top-level post
if ( $item [ 'wall' ] || $item [ 'origin' ] || ( $item [ 'uri' ] != $item [ 'parent-uri' ])) {
return false ;
}
$u = q ( " SELECT * FROM `user` WHERE `uid` = %d LIMIT 1 " ,
intval ( $uid )
);
if ( ! DBM :: is_result ( $u )) {
return false ;
}
$community_page = ( $u [ 0 ][ 'page-flags' ] == PAGE_COMMUNITY );
$prvgroup = ( $u [ 0 ][ 'page-flags' ] == PAGE_PRVGROUP );
$link = normalise_link ( System :: baseUrl () . '/profile/' . $u [ 0 ][ 'nickname' ]);
/*
* Diaspora uses their own hardwired link URL in @- tags
* instead of the one we supply with webfinger
*/
$dlink = normalise_link ( System :: baseUrl () . '/u/' . $u [ 0 ][ 'nickname' ]);
$cnt = preg_match_all ( '/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism' , $item [ 'body' ], $matches , PREG_SET_ORDER );
if ( $cnt ) {
foreach ( $matches as $mtch ) {
if ( link_compare ( $link , $mtch [ 1 ]) || link_compare ( $dlink , $mtch [ 1 ])) {
$mention = true ;
logger ( 'mention found: ' . $mtch [ 2 ]);
}
}
}
if ( ! $mention ) {
return false ;
}
return $community_page || $prvgroup ;
}
/**
* This function returns true if $update has an edited timestamp newer
* than $existing , i . e . $update contains new data which should override
* what ' s already there . If there is no timestamp yet , the update is
* assumed to be newer . If the update has no timestamp , the existing
* item is assumed to be up - to - date . If the timestamps are equal it
* assumes the update has been seen before and should be ignored .
*
*/
2018-01-24 23:22:31 +01:00
private static function isEditedTimestampNewer ( $existing , $update )
2018-01-24 21:27:32 +01:00
{
if ( ! x ( $existing , 'edited' ) || ! $existing [ 'edited' ]) {
return true ;
}
if ( ! x ( $update , 'edited' ) || ! $update [ 'edited' ]) {
return false ;
}
2018-01-27 03:38:34 +01:00
$existing_edited = DateTimeFormat :: utc ( $existing [ 'edited' ]);
$update_edited = DateTimeFormat :: utc ( $update [ 'edited' ]);
2018-01-24 21:27:32 +01:00
return ( strcmp ( $existing_edited , $update_edited ) < 0 );
}
2017-11-08 01:37:53 +01:00
}