2017-11-08 00:37:53 +00:00
< ? php
/**
2020-02-09 15:18:46 +00:00
* @ copyright Copyright ( C ) 2020 , Friendica
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
2017-11-08 00:37:53 +00:00
*
*/
2020-02-09 15:18:46 +00:00
2017-11-08 22:02:50 +00:00
namespace Friendica\Protocol ;
2017-11-08 00:37:53 +00:00
2018-07-20 02:15:21 +00:00
use DOMDocument ;
use DOMXPath ;
2019-08-15 15:23:00 +00:00
use Friendica\App\BaseURL ;
2018-02-05 00:23:49 +00:00
use Friendica\Content\Text\BBCode ;
2018-12-26 06:06:24 +00:00
use Friendica\Core\Hook ;
2018-10-29 21:20:46 +00:00
use Friendica\Core\Logger ;
2018-08-11 20:40:44 +00:00
use Friendica\Core\Protocol ;
2018-07-20 12:19:26 +00:00
use Friendica\Database\DBA ;
2019-12-15 22:28:01 +00:00
use Friendica\DI ;
2017-12-07 14:04:24 +00:00
use Friendica\Model\Contact ;
2018-08-05 10:23:57 +00:00
use Friendica\Model\Conversation ;
2018-03-17 01:45:02 +00:00
use Friendica\Model\Event ;
2020-08-06 10:27:06 +00:00
use Friendica\Model\FContact ;
2018-01-28 11:18:08 +00:00
use Friendica\Model\Item ;
2020-04-14 16:52:53 +00:00
use Friendica\Model\ItemURI ;
2019-05-08 05:44:22 +00:00
use Friendica\Model\Mail ;
2020-02-04 21:11:11 +00:00
use Friendica\Model\Notify\Type ;
2018-07-25 23:14:55 +00:00
use Friendica\Model\PermissionSet ;
2020-05-03 15:13:40 +00:00
use Friendica\Model\Post\Category ;
2018-08-05 10:23:57 +00:00
use Friendica\Model\Profile ;
2020-04-15 16:37:09 +00:00
use Friendica\Model\Tag ;
2017-12-19 17:15:56 +00:00
use Friendica\Model\User ;
2020-08-30 10:09:54 +00:00
use Friendica\Model\Verb ;
2019-07-27 11:09:12 +00:00
use Friendica\Network\Probe ;
2018-01-19 16:34:56 +00:00
use Friendica\Util\Crypto ;
2018-01-27 02:38:34 +00:00
use Friendica\Util\DateTimeFormat ;
2019-10-18 01:26:15 +00:00
use Friendica\Util\Images ;
2018-01-27 04:18:38 +00:00
use Friendica\Util\Network ;
2018-11-08 13:45:46 +00:00
use Friendica\Util\Strings ;
2017-11-10 12:45:33 +00:00
use Friendica\Util\XML ;
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* This class contain functions to create and send DFRN XML files
2017-11-08 00:37:53 +00:00
*/
2017-11-08 22:02:50 +00:00
class DFRN
{
2017-11-08 00:37:53 +00:00
2018-05-13 08:34:33 +00: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 00:37:53 +00:00
2018-08-19 13:37:56 +00:00
/**
2020-01-19 06:05:23 +00:00
* Generates an array of contact and user for DFRN imports
2018-08-19 13:37:56 +00:00
*
* This array contains not only the receiver but also the sender of the message .
*
* @ param integer $cid Contact id
* @ param integer $uid User id
*
* @ return array importer
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-08-19 13:37:56 +00:00
*/
public static function getImporter ( $cid , $uid = 0 )
{
$condition = [ 'id' => $cid , 'blocked' => false , 'pending' => false ];
$contact = DBA :: selectFirst ( 'contact' , [], $condition );
if ( ! DBA :: isResult ( $contact )) {
return [];
}
$contact [ 'cpubkey' ] = $contact [ 'pubkey' ];
$contact [ 'cprvkey' ] = $contact [ 'prvkey' ];
$contact [ 'senderName' ] = $contact [ 'name' ];
if ( $uid != 0 ) {
$condition = [ 'uid' => $uid , 'account_expired' => false , 'account_removed' => false ];
$user = DBA :: selectFirst ( 'user' , [], $condition );
if ( ! DBA :: isResult ( $user )) {
return [];
}
2018-09-05 05:02:06 +00:00
$user [ 'importer_uid' ] = $user [ 'uid' ];
$user [ 'uprvkey' ] = $user [ 'prvkey' ];
2018-08-19 13:37:56 +00:00
} else {
$user = [ 'importer_uid' => 0 , 'uprvkey' => '' , 'timezone' => 'UTC' ,
'nickname' => '' , 'sprvkey' => '' , 'spubkey' => '' ,
'page-flags' => 0 , 'account-type' => 0 , 'prvnets' => 0 ];
}
return array_merge ( $contact , $user );
}
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* Generates the atom entries for delivery . php
2017-11-08 00:37:53 +00:00
*
* This function is used whenever content is transmitted via DFRN .
*
* @ param array $items Item elements
* @ param array $owner Owner record
*
* @ return string DFRN entries
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
* @ todo Find proper type - hints
2017-11-08 00:37:53 +00:00
*/
2017-11-08 22:02:50 +00:00
public static function entries ( $items , $owner )
{
2017-11-08 00:37:53 +00:00
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2017-11-23 19:01:58 +00:00
$root = self :: addHeader ( $doc , $owner , " dfrn:owner " , " " , false );
2017-11-08 00:37:53 +00:00
if ( ! count ( $items )) {
return trim ( $doc -> saveXML ());
}
foreach ( $items as $item ) {
2018-07-10 12:27:56 +00:00
// These values aren't sent when sending from the queue.
/// @todo Check if we can set these values from the queue or if they are needed at all.
2019-10-16 12:35:14 +00:00
$item [ " entry:comment-allow " ] = ( $item [ " entry:comment-allow " ] ? ? '' ) ? : true ;
$item [ " entry:cid " ] = $item [ " entry:cid " ] ? ? 0 ;
2018-07-10 12:27:56 +00:00
2017-11-08 00:37:53 +00:00
$entry = self :: entry ( $doc , " text " , $item , $owner , $item [ " entry:comment-allow " ], $item [ " entry:cid " ]);
2019-02-24 15:30:09 +00:00
if ( isset ( $entry )) {
$root -> appendChild ( $entry );
}
2017-11-08 00:37:53 +00:00
}
2017-07-20 18:04:32 +00:00
return trim ( $doc -> saveXML ());
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Generate an atom feed for the given user
2017-11-08 00:37:53 +00:00
*
* This function is called when another server is pulling data from the user feed .
*
2017-11-08 22:02:50 +00: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 00:37:53 +00:00
*
* @ return string DFRN feed entries
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-11-08 00:37:53 +00:00
*/
2017-11-08 22:02:50 +00:00
public static function feed ( $dfrn_id , $owner_nick , $last_update , $direction = 0 , $onlyheader = false )
{
2020-01-04 22:42:01 +00:00
$a = DI :: app ();
2017-11-08 00:37:53 +00:00
$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
2020-03-02 07:57:23 +00:00
$sql_extra = sprintf ( " AND `item`.`private` != %s " , Item :: PRIVATE );
2017-11-08 00:37:53 +00:00
2020-04-24 13:41:11 +00:00
$owner = DBA :: selectFirst ( 'owner-view' , [], [ 'nickname' => $owner_nick ]);
if ( ! DBA :: isResult ( $owner )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( sprintf ( 'No contact found for nickname=%d' , $owner_nick ), Logger :: WARNING );
2018-12-26 05:40:12 +00:00
exit ();
2017-11-08 00:37:53 +00:00
}
$owner_id = $owner [ 'uid' ];
$sql_post_table = " " ;
if ( ! $public_feed ) {
2017-11-08 22:02:50 +00:00
switch ( $direction ) {
2017-11-08 00:37:53 +00:00
case ( - 1 ) :
2018-07-21 13:10:13 +00:00
$sql_extra = sprintf ( " AND `issued-id` = '%s' " , DBA :: escape ( $dfrn_id ));
2017-11-08 00:37:53 +00:00
break ;
case 0 :
2018-07-21 13:10:13 +00:00
$sql_extra = sprintf ( " AND `issued-id` = '%s' AND `duplex` = 1 " , DBA :: escape ( $dfrn_id ));
2017-11-08 00:37:53 +00:00
break ;
case 1 :
2018-07-21 13:10:13 +00:00
$sql_extra = sprintf ( " AND `dfrn-id` = '%s' AND `duplex` = 1 " , DBA :: escape ( $dfrn_id ));
2017-11-08 00:37:53 +00:00
break ;
default :
return false ;
break ; // NOTREACHED
}
2017-11-08 22:02:50 +00:00
$r = q (
" SELECT * FROM `contact` WHERE NOT `blocked` AND `contact`.`uid` = %d $sql_extra LIMIT 1 " ,
2017-11-08 00:37:53 +00:00
intval ( $owner_id )
);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $r )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( sprintf ( 'No contact found for uid=%d' , $owner_id ), Logger :: WARNING );
2018-12-26 05:40:12 +00:00
exit ();
2017-11-08 00:37:53 +00:00
}
$contact = $r [ 0 ];
2018-07-25 23:14:55 +00:00
$set = PermissionSet :: get ( $owner_id , $contact [ 'id' ]);
2018-05-13 12:20:15 +00:00
2018-07-25 23:14:55 +00:00
if ( ! empty ( $set )) {
$sql_extra = " AND `item`.`psid` IN ( " . implode ( ',' , $set ) . " ) " ;
2017-11-08 00:37:53 +00:00
} else {
2020-03-02 07:57:23 +00:00
$sql_extra = sprintf ( " AND `item`.`private` != %s " , Item :: PRIVATE );
2017-11-08 00:37:53 +00:00
}
}
if ( $public_feed ) {
$sort = 'DESC' ;
} else {
$sort = 'ASC' ;
}
if ( ! strlen ( $last_update )) {
$last_update = 'now -30 days' ;
}
if ( isset ( $category )) {
2020-05-03 15:13:40 +00:00
$sql_post_table = sprintf ( " INNER JOIN (SELECT `uri-id` FROM `category-view` WHERE `name` = '%s' AND `type` = %d AND `uid` = %d ORDER BY `uri-id` DESC) AS `category` ON `item`.`uri-id` = `category`.`uri-id` " ,
DBA :: escape ( Strings :: protectSprintf ( $category )), intval ( Category :: CATEGORY ), intval ( $owner_id ));
2017-11-08 00:37:53 +00:00
}
2017-07-20 18:04:32 +00:00
if ( $public_feed && ! $converse ) {
$sql_extra .= " AND `contact`.`self` = 1 " ;
2017-11-08 00:37:53 +00:00
}
2018-01-27 02:38:34 +00:00
$check_date = DateTimeFormat :: utc ( $last_update );
2017-11-08 00:37:53 +00:00
2017-11-08 22:02:50 +00:00
$r = q (
2018-06-16 22:32:57 +00:00
" SELECT `item`.`id`
2017-11-08 00:37:53 +00:00
FROM `item` USE INDEX ( `uid_wall_changed` ) $sql_post_table
STRAIGHT_JOIN `contact` ON `contact` . `id` = `item` . `contact-id`
2018-06-16 22:32:57 +00:00
WHERE `item` . `uid` = % d AND `item` . `wall` AND `item` . `changed` > '%s'
2020-08-30 10:09:54 +00:00
AND `vid` != % d AND `item` . `visible` $sql_extra
2019-07-07 21:30:33 +00:00
ORDER BY `item` . `parent` " . $sort . " , `item` . `received` ASC LIMIT 0 , 300 " ,
2017-11-08 00:37:53 +00:00
intval ( $owner_id ),
2018-07-21 13:10:13 +00:00
DBA :: escape ( $check_date ),
2020-08-30 10:09:54 +00:00
Verb :: getID ( Activity :: ANNOUNCE ),
2018-07-21 13:10:13 +00:00
DBA :: escape ( $sort )
2017-11-08 00:37:53 +00:00
);
2018-06-16 22:32:57 +00:00
$ids = [];
foreach ( $r as $item ) {
$ids [] = $item [ 'id' ];
}
2018-06-17 17:05:17 +00:00
if ( ! empty ( $ids )) {
2018-06-17 21:55:01 +00:00
$ret = Item :: select ( Item :: DELIVER_FIELDLIST , [ 'id' => $ids ]);
2018-06-21 15:14:01 +00:00
$items = Item :: inArray ( $ret );
2018-06-17 17:05:17 +00:00
} else {
$items = [];
}
2018-06-16 22:32:57 +00:00
2017-11-08 00:37:53 +00:00
/*
* Will check further below if this actually returned results .
* We will provide an empty feed if that is the case .
*/
$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 19:01:58 +00:00
$root = self :: addHeader ( $doc , $owner , $author , $alternatelink , true );
2017-11-08 00:37:53 +00:00
/// @TODO This hook can't work anymore
2018-12-26 06:06:24 +00:00
// \Friendica\Core\Hook::callAll('atom_feed', $atom);
2017-11-08 00:37:53 +00:00
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $items ) || $onlyheader ) {
2017-11-08 00:37:53 +00:00
$atom = trim ( $doc -> saveXML ());
2018-12-26 06:06:24 +00:00
Hook :: callAll ( 'atom_feed_end' , $atom );
2017-11-08 00:37:53 +00:00
return $atom ;
}
foreach ( $items as $item ) {
// prevent private email from leaking.
2018-08-11 20:40:44 +00:00
if ( $item [ 'network' ] == Protocol :: MAIL ) {
2017-11-08 00:37:53 +00:00
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
2020-03-02 07:57:23 +00:00
if ( $item [ 'private' ] == Item :: PRIVATE ) {
2017-11-08 00:37:53 +00:00
continue ;
}
} else {
$type = 'text' ;
}
$entry = self :: entry ( $doc , $type , $item , $owner , true );
2019-02-24 15:30:09 +00:00
if ( isset ( $entry )) {
$root -> appendChild ( $entry );
}
2017-11-08 00:37:53 +00:00
}
$atom = trim ( $doc -> saveXML ());
2018-12-26 06:06:24 +00:00
Hook :: callAll ( 'atom_feed_end' , $atom );
2017-11-08 00:37:53 +00:00
return $atom ;
}
/**
2020-01-19 06:05:23 +00:00
* Generate an atom entry for a given item id
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param int $item_id The item id
2017-11-08 00:37:53 +00:00
* @ param boolean $conversation Show the conversation . If false show the single post .
*
* @ return string DFRN feed entry
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-11-08 00:37:53 +00:00
*/
2017-11-08 22:02:50 +00:00
public static function itemFeed ( $item_id , $conversation = false )
{
2017-11-08 00:37:53 +00:00
if ( $conversation ) {
2018-06-16 22:32:57 +00:00
$condition = [ 'parent' => $item_id ];
2017-11-08 00:37:53 +00:00
} else {
2018-06-16 22:32:57 +00:00
$condition = [ 'id' => $item_id ];
}
2018-06-17 21:55:01 +00:00
$ret = Item :: select ( Item :: DELIVER_FIELDLIST , $condition );
2018-06-21 15:14:01 +00:00
$items = Item :: inArray ( $ret );
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $items )) {
2019-04-29 04:40:58 +00:00
return '' ;
2017-11-08 00:37:53 +00:00
}
2018-06-16 22:32:57 +00:00
$item = $items [ 0 ];
2017-11-08 00:37:53 +00:00
2017-12-19 17:15:56 +00:00
if ( $item [ 'uid' ] != 0 ) {
$owner = User :: getOwnerDataById ( $item [ 'uid' ]);
if ( ! $owner ) {
2019-04-29 04:40:58 +00:00
return '' ;
2017-12-19 17:15:56 +00:00
}
2017-12-19 17:38:33 +00:00
} else {
2017-12-21 08:58:36 +00:00
$owner = [ 'uid' => 0 , 'nick' => 'feed-item' ];
2017-11-08 00:37:53 +00:00
}
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
$type = 'html' ;
if ( $conversation ) {
2019-10-24 22:32:35 +00:00
$root = $doc -> createElementNS ( ActivityNamespace :: ATOM1 , 'feed' );
2017-11-08 00:37:53 +00:00
$doc -> appendChild ( $root );
2019-10-24 22:32:35 +00:00
$root -> setAttribute ( " xmlns:thr " , ActivityNamespace :: THREAD );
$root -> setAttribute ( " xmlns:at " , ActivityNamespace :: TOMB );
$root -> setAttribute ( " xmlns:media " , ActivityNamespace :: MEDIA );
$root -> setAttribute ( " xmlns:dfrn " , ActivityNamespace :: DFRN );
$root -> setAttribute ( " xmlns:activity " , ActivityNamespace :: ACTIVITY );
$root -> setAttribute ( " xmlns:georss " , ActivityNamespace :: GEORSS );
$root -> setAttribute ( " xmlns:poco " , ActivityNamespace :: POCO );
$root -> setAttribute ( " xmlns:ostatus " , ActivityNamespace :: OSTATUS );
$root -> setAttribute ( " xmlns:statusnet " , ActivityNamespace :: STATUSNET );
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
//$root = self::addHeader($doc, $owner, "dfrn:owner", "", false);
2017-11-08 00:37:53 +00:00
foreach ( $items as $item ) {
$entry = self :: entry ( $doc , $type , $item , $owner , true , 0 );
2019-02-24 15:30:09 +00:00
if ( isset ( $entry )) {
$root -> appendChild ( $entry );
}
2017-11-08 00:37:53 +00:00
}
} else {
2019-04-29 04:40:58 +00:00
self :: entry ( $doc , $type , $item , $owner , true , 0 , true );
2017-11-08 00:37:53 +00:00
}
$atom = trim ( $doc -> saveXML ());
return $atom ;
}
/**
2020-01-19 06:05:23 +00:00
* Create XML text for DFRN mails
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $item message elements
2017-11-08 00:37:53 +00:00
* @ param array $owner Owner record
*
* @ return string DFRN mail
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ todo Find proper type - hints
2017-11-08 00:37:53 +00:00
*/
2017-11-08 22:02:50 +00:00
public static function mail ( $item , $owner )
{
2017-11-08 00:37:53 +00:00
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2017-11-23 19:01:58 +00:00
$root = self :: addHeader ( $doc , $owner , " dfrn:owner " , " " , false );
2017-11-08 00:37:53 +00:00
$mail = $doc -> createElement ( " dfrn:mail " );
$sender = $doc -> createElement ( " dfrn:sender " );
2017-11-20 17:56:31 +00: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 00:37:53 +00:00
$mail -> appendChild ( $sender );
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $mail , " dfrn:id " , $item [ 'uri' ]);
XML :: addElement ( $doc , $mail , " dfrn:in-reply-to " , $item [ 'parent-uri' ]);
2018-01-27 02:38:34 +00:00
XML :: addElement ( $doc , $mail , " dfrn:sentdate " , DateTimeFormat :: utc ( $item [ 'created' ] . '+00:00' , DateTimeFormat :: ATOM ));
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $mail , " dfrn:subject " , $item [ 'title' ]);
XML :: addElement ( $doc , $mail , " dfrn:content " , $item [ 'body' ]);
2017-11-08 00:37:53 +00:00
$root -> appendChild ( $mail );
2017-07-20 18:04:32 +00:00
return trim ( $doc -> saveXML ());
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Create XML text for DFRN friend suggestions
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $item suggestion elements
2017-11-08 00:37:53 +00:00
* @ param array $owner Owner record
*
* @ return string DFRN suggestions
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ todo Find proper type - hints
2017-11-08 00:37:53 +00:00
*/
2017-11-08 22:02:50 +00:00
public static function fsuggest ( $item , $owner )
{
2017-11-08 00:37:53 +00:00
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2017-11-23 19:01:58 +00:00
$root = self :: addHeader ( $doc , $owner , " dfrn:owner " , " " , false );
2017-11-08 00:37:53 +00:00
$suggest = $doc -> createElement ( " dfrn:suggest " );
2017-11-20 17:56:31 +00: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 00:37:53 +00:00
$root -> appendChild ( $suggest );
2017-07-20 18:04:32 +00:00
return trim ( $doc -> saveXML ());
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Create XML text for DFRN relocations
2017-11-08 00:37:53 +00:00
*
* @ param array $owner Owner record
2017-11-08 22:02:50 +00:00
* @ param int $uid User ID
2017-11-08 00:37:53 +00:00
*
* @ return string DFRN relocations
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ todo Find proper type - hints
2017-11-08 00:37:53 +00:00
*/
2017-11-08 22:02:50 +00:00
public static function relocate ( $owner , $uid )
{
2017-11-08 00:37:53 +00:00
/* get site pubkey. this could be a new installation with no site keys*/
2020-01-19 20:21:13 +00:00
$pubkey = DI :: config () -> get ( 'system' , 'site_pubkey' );
2017-11-08 00:37:53 +00:00
if ( ! $pubkey ) {
2018-01-19 16:58:26 +00:00
$res = Crypto :: newKeypair ( 1024 );
2020-01-19 20:21:53 +00:00
DI :: config () -> set ( 'system' , 'site_prvkey' , $res [ 'prvkey' ]);
DI :: config () -> set ( 'system' , 'site_pubkey' , $res [ 'pubkey' ]);
2017-11-08 00:37:53 +00:00
}
2017-11-08 22:02:50 +00:00
$rp = q (
" SELECT `resource-id` , `scale`, type FROM `photo`
WHERE `profile` = 1 AND `uid` = % d ORDER BY scale ; " ,
$uid
);
2018-01-15 13:05:12 +00:00
$photos = [];
2019-10-18 01:26:15 +00:00
$ext = Images :: supportedTypes ();
2017-11-08 00:37:53 +00:00
foreach ( $rp as $p ) {
2019-12-30 22:00:08 +00:00
$photos [ $p [ 'scale' ]] = DI :: baseUrl () . '/photo/' . $p [ 'resource-id' ] . '-' . $p [ 'scale' ] . '.' . $ext [ $p [ 'type' ]];
2017-11-08 00:37:53 +00:00
}
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2017-11-23 19:01:58 +00:00
$root = self :: addHeader ( $doc , $owner , " dfrn:owner " , " " , false );
2017-11-08 00:37:53 +00:00
$relocate = $doc -> createElement ( " dfrn:relocate " );
2017-11-20 17:56:31 +00: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' ]);
2020-01-19 20:21:13 +00:00
XML :: addElement ( $doc , $relocate , " dfrn:sitepubkey " , DI :: config () -> get ( 'system' , 'site_pubkey' ));
2017-11-08 00:37:53 +00:00
$root -> appendChild ( $relocate );
2017-07-20 18:04:32 +00:00
return trim ( $doc -> saveXML ());
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Adds the header elements for the DFRN protocol
2017-11-08 00:37:53 +00:00
*
2019-01-21 21:51:59 +00:00
* @ param DOMDocument $doc XML document
* @ param array $owner Owner record
* @ param string $authorelement Element name for the author
* @ param string $alternatelink link to profile or category
* @ param bool $public Is it a header for public posts ?
2017-11-08 00:37:53 +00:00
*
* @ return object XML root object
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ todo Find proper type - hints
2017-11-08 00:37:53 +00:00
*/
2019-01-21 21:51:59 +00:00
private static function addHeader ( DOMDocument $doc , $owner , $authorelement , $alternatelink = " " , $public = false )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
if ( $alternatelink == " " ) {
$alternatelink = $owner [ 'url' ];
}
2019-10-24 22:32:35 +00:00
$root = $doc -> createElementNS ( ActivityNamespace :: ATOM1 , 'feed' );
2017-11-08 00:37:53 +00:00
$doc -> appendChild ( $root );
2019-10-24 22:32:35 +00:00
$root -> setAttribute ( " xmlns:thr " , ActivityNamespace :: THREAD );
$root -> setAttribute ( " xmlns:at " , ActivityNamespace :: TOMB );
$root -> setAttribute ( " xmlns:media " , ActivityNamespace :: MEDIA );
$root -> setAttribute ( " xmlns:dfrn " , ActivityNamespace :: DFRN );
$root -> setAttribute ( " xmlns:activity " , ActivityNamespace :: ACTIVITY );
$root -> setAttribute ( " xmlns:georss " , ActivityNamespace :: GEORSS );
$root -> setAttribute ( " xmlns:poco " , ActivityNamespace :: POCO );
$root -> setAttribute ( " xmlns:ostatus " , ActivityNamespace :: OSTATUS );
$root -> setAttribute ( " xmlns:statusnet " , ActivityNamespace :: STATUSNET );
2017-11-08 00:37:53 +00:00
2019-12-30 22:00:08 +00:00
XML :: addElement ( $doc , $root , " id " , DI :: baseUrl () . " /profile/ " . $owner [ " nick " ]);
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " title " , $owner [ " name " ]);
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$attributes = [ " uri " => " https://friendi.ca " , " version " => FRIENDICA_VERSION . " - " . DB_UPDATE_VERSION ];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " generator " , FRIENDICA_PLATFORM , $attributes );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$attributes = [ " rel " => " license " , " href " => " http://creativecommons.org/licenses/by/3.0/ " ];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$attributes = [ " rel " => " alternate " , " type " => " text/html " , " href " => $alternatelink ];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 00:37:53 +00:00
if ( $public ) {
// DFRN itself doesn't uses this. But maybe someone else wants to subscribe to the public feed.
2017-11-16 04:09:11 +00:00
OStatus :: hublinks ( $doc , $root , $owner [ " nick " ]);
2017-11-08 00:37:53 +00:00
2019-12-30 22:00:08 +00:00
$attributes = [ " rel " => " salmon " , " href " => DI :: baseUrl () . " /salmon/ " . $owner [ " nick " ]];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 00:37:53 +00:00
2019-12-30 22:00:08 +00:00
$attributes = [ " rel " => " http://salmon-protocol.org/ns/salmon-replies " , " href " => DI :: baseUrl () . " /salmon/ " . $owner [ " nick " ]];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 00:37:53 +00:00
2019-12-30 22:00:08 +00:00
$attributes = [ " rel " => " http://salmon-protocol.org/ns/salmon-mention " , " href " => DI :: baseUrl () . " /salmon/ " . $owner [ " nick " ]];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 00:37:53 +00:00
}
// For backward compatibility we keep this element
2019-01-06 17:37:48 +00:00
if ( $owner [ 'page-flags' ] == User :: PAGE_FLAGS_COMMUNITY ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " dfrn:community " , 1 );
2017-11-08 00:37:53 +00:00
}
// The former element is replaced by this one
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " dfrn:account_type " , $owner [ " account-type " ]);
2017-11-08 00:37:53 +00:00
2019-01-06 17:37:48 +00:00
/// @todo We need a way to transmit the different page flags like "User::PAGE_FLAGS_PRVGROUP"
2017-11-08 00:37:53 +00:00
2018-01-27 02:38:34 +00:00
XML :: addElement ( $doc , $root , " updated " , DateTimeFormat :: utcNow ( DateTimeFormat :: ATOM ));
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
$author = self :: addAuthor ( $doc , $owner , $authorelement , $public );
2017-11-08 00:37:53 +00:00
$root -> appendChild ( $author );
return $root ;
}
/**
2020-01-19 06:05:23 +00:00
* Adds the author element in the header for the DFRN protocol
2017-11-08 00:37:53 +00:00
*
2019-01-21 21:51:59 +00:00
* @ param DOMDocument $doc XML document
* @ param array $owner Owner record
* @ param string $authorelement Element name for the author
* @ param boolean $public boolean
2017-11-08 00:37:53 +00:00
*
2019-01-21 21:51:59 +00:00
* @ return \DOMElement XML author object
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ todo Find proper type - hints
2017-11-08 00:37:53 +00:00
*/
2019-01-21 21:51:59 +00:00
private static function addAuthor ( DOMDocument $doc , array $owner , $authorelement , $public )
2017-11-08 22:02:50 +00:00
{
2020-02-16 15:39:44 +00:00
// Should the profile be "unsearchable" in the net? Then add the "hide" element
2020-02-16 15:45:26 +00:00
$hide = DBA :: exists ( 'profile' , [ 'uid' => $owner [ 'uid' ], 'net-publish' => false ]);
2017-11-08 00:37:53 +00:00
$author = $doc -> createElement ( $authorelement );
2018-01-27 02:38:34 +00:00
$namdate = DateTimeFormat :: utc ( $owner [ 'name-date' ] . '+00:00' , DateTimeFormat :: ATOM );
$picdate = DateTimeFormat :: utc ( $owner [ 'avatar-date' ] . '+00:00' , DateTimeFormat :: ATOM );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$attributes = [];
2017-11-08 00:37:53 +00:00
2020-02-16 15:39:44 +00:00
if ( ! $public || ! $hide ) {
2018-01-15 13:05:12 +00:00
$attributes = [ " dfrn:updated " => $namdate ];
2017-11-08 00:37:53 +00:00
}
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " name " , $owner [ " name " ], $attributes );
2019-12-30 22:00:08 +00:00
XML :: addElement ( $doc , $author , " uri " , DI :: baseUrl () . '/profile/' . $owner [ " nickname " ], $attributes );
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " dfrn:handle " , $owner [ " addr " ], $attributes );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$attributes = [ " rel " => " photo " , " type " => " image/jpeg " ,
2018-10-23 14:36:57 +00:00
" media:width " => 300 , " media:height " => 300 , " href " => $owner [ 'photo' ]];
2017-11-08 00:37:53 +00:00
2020-02-16 15:39:44 +00:00
if ( ! $public || ! $hide ) {
2017-11-08 00:37:53 +00:00
$attributes [ " dfrn:updated " ] = $picdate ;
}
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " link " , " " , $attributes );
2017-11-08 00:37:53 +00:00
$attributes [ " rel " ] = " avatar " ;
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " link " , " " , $attributes );
2017-11-08 00:37:53 +00:00
2020-02-16 15:39:44 +00:00
if ( $hide ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " dfrn:hide " , " true " );
2017-11-08 00:37:53 +00: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 22:02:50 +00:00
if ( $birthday ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " dfrn:birthday " , $birthday );
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
// Only show contact details when we are allowed to
2020-04-24 13:41:11 +00:00
$profile = DBA :: selectFirst ( 'owner-view' ,
[ 'about' , 'name' , 'homepage' , 'nickname' , 'timezone' , 'locality' , 'region' , 'country-name' , 'pub_keywords' , 'xmpp' , 'dob' ],
[ 'uid' => $owner [ 'uid' ], 'hidewall' => false ]);
if ( DBA :: isResult ( $profile )) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " poco:displayName " , $profile [ " name " ]);
XML :: addElement ( $doc , $author , " poco:updated " , $namdate );
2017-11-08 00:37:53 +00:00
2018-11-22 04:53:45 +00:00
if ( trim ( $profile [ " dob " ]) > DBA :: NULL_DATE ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " poco:birthday " , " 0000- " . date ( " m-d " , strtotime ( $profile [ " dob " ])));
2017-11-08 00:37:53 +00:00
}
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " poco:note " , $profile [ " about " ]);
XML :: addElement ( $doc , $author , " poco:preferredUsername " , $profile [ " nickname " ]);
2017-11-08 00:37:53 +00:00
$savetz = date_default_timezone_get ();
date_default_timezone_set ( $profile [ " timezone " ]);
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " poco:utcOffset " , date ( " P " ));
2017-11-08 00:37:53 +00:00
date_default_timezone_set ( $savetz );
if ( trim ( $profile [ " homepage " ]) != " " ) {
$urls = $doc -> createElement ( " poco:urls " );
2017-11-20 17:56:31 +00: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 00:37:53 +00:00
$author -> appendChild ( $urls );
}
if ( trim ( $profile [ " pub_keywords " ]) != " " ) {
$keywords = explode ( " , " , $profile [ " pub_keywords " ]);
2017-11-10 12:45:33 +00:00
foreach ( $keywords as $keyword ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " poco:tags " , trim ( $keyword ));
2017-11-08 00:37:53 +00:00
}
}
if ( trim ( $profile [ " xmpp " ]) != " " ) {
$ims = $doc -> createElement ( " poco:ims " );
2017-11-20 17:56:31 +00: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 00:37:53 +00:00
$author -> appendChild ( $ims );
}
if ( trim ( $profile [ " locality " ] . $profile [ " region " ] . $profile [ " country-name " ]) != " " ) {
$element = $doc -> createElement ( " poco:address " );
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $element , " poco:formatted " , Profile :: formatLocation ( $profile ));
2017-11-08 00:37:53 +00:00
if ( trim ( $profile [ " locality " ]) != " " ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $element , " poco:locality " , $profile [ " locality " ]);
2017-11-08 00:37:53 +00:00
}
if ( trim ( $profile [ " region " ]) != " " ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $element , " poco:region " , $profile [ " region " ]);
2017-11-08 00:37:53 +00:00
}
if ( trim ( $profile [ " country-name " ]) != " " ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $element , " poco:country " , $profile [ " country-name " ]);
2017-11-08 00:37:53 +00:00
}
$author -> appendChild ( $element );
}
}
return $author ;
}
/**
2020-01-19 06:05:23 +00:00
* Adds the author elements in the " entry " elements of the DFRN protocol
2017-11-08 00:37:53 +00:00
*
2019-01-21 21:51:59 +00:00
* @ param DOMDocument $doc XML document
2017-11-08 22:02:50 +00:00
* @ param string $element Element name for the author
2017-11-08 00:37:53 +00:00
* @ param string $contact_url Link of the contact
2017-11-08 22:02:50 +00:00
* @ param array $item Item elements
2017-11-08 00:37:53 +00:00
*
2019-01-21 21:51:59 +00:00
* @ return \DOMElement XML author object
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ todo Find proper type - hints
2017-11-08 00:37:53 +00:00
*/
2019-01-21 21:51:59 +00:00
private static function addEntryAuthor ( DOMDocument $doc , $element , $contact_url , $item )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$author = $doc -> createElement ( $element );
2020-07-15 17:29:52 +00:00
$contact = Contact :: getByURLForUser ( $contact_url , $item [ " uid " ], false , [ 'url' , 'name' , 'addr' , 'photo' ]);
2019-02-24 19:00:40 +00:00
if ( ! empty ( $contact )) {
XML :: addElement ( $doc , $author , " name " , $contact [ " name " ]);
XML :: addElement ( $doc , $author , " uri " , $contact [ " url " ]);
XML :: addElement ( $doc , $author , " dfrn:handle " , $contact [ " addr " ]);
/// @Todo
/// - Check real image type and image size
/// - Check which of these boths elements we should use
$attributes = [
2017-11-08 00:37:53 +00:00
" rel " => " photo " ,
" type " => " image/jpeg " ,
" media:width " => 80 ,
" media:height " => 80 ,
2018-01-15 13:05:12 +00:00
" href " => $contact [ " photo " ]];
2019-02-24 19:00:40 +00:00
XML :: addElement ( $doc , $author , " link " , " " , $attributes );
2017-11-08 00:37:53 +00:00
2019-02-24 19:00:40 +00:00
$attributes = [
2017-11-08 00:37:53 +00:00
" rel " => " avatar " ,
" type " => " image/jpeg " ,
" media:width " => 80 ,
" media:height " => 80 ,
2018-01-15 13:05:12 +00:00
" href " => $contact [ " photo " ]];
2019-02-24 19:00:40 +00:00
XML :: addElement ( $doc , $author , " link " , " " , $attributes );
}
2017-11-08 00:37:53 +00:00
return $author ;
}
/**
2020-01-19 06:05:23 +00:00
* Adds the activity elements
2017-11-08 00:37:53 +00:00
*
2019-01-21 21:51:59 +00:00
* @ param DOMDocument $doc XML document
* @ param string $element Element name for the activity
* @ param string $activity activity value
2017-11-08 00:37:53 +00:00
*
2019-01-21 21:51:59 +00:00
* @ return \DOMElement XML activity object
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ todo Find proper type - hints
2017-11-08 00:37:53 +00:00
*/
2019-01-21 21:51:59 +00:00
private static function createActivity ( DOMDocument $doc , $element , $activity )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
if ( $activity ) {
$entry = $doc -> createElement ( $element );
2020-04-27 14:35:50 +00:00
$r = XML :: parseString ( $activity );
2017-11-08 00:37:53 +00:00
if ( ! $r ) {
return false ;
}
2017-01-25 14:59:27 +00:00
2017-11-08 00:37:53 +00:00
if ( $r -> type ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " activity:object-type " , $r -> type );
2017-11-08 00:37:53 +00:00
}
2017-01-25 14:59:27 +00:00
2017-11-08 00:37:53 +00:00
if ( $r -> id ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " id " , $r -> id );
2017-11-08 00:37:53 +00:00
}
2017-01-25 14:59:27 +00:00
2017-11-08 00:37:53 +00:00
if ( $r -> title ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " title " , $r -> title );
2017-11-08 00:37:53 +00: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
2020-04-27 14:35:50 +00:00
$data = XML :: parseString ( " <dummy> " . $r -> link . " </dummy> " );
2017-11-08 00:37:53 +00:00
if ( is_object ( $data )) {
2017-11-23 19:01:58 +00:00
foreach ( $data -> link as $link ) {
2018-01-15 13:05:12 +00:00
$attributes = [];
2017-11-10 12:45:33 +00:00
foreach ( $link -> attributes () as $parameter => $value ) {
2017-11-08 00:37:53 +00:00
$attributes [ $parameter ] = $value ;
}
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " link " , " " , $attributes );
2017-11-08 00:37:53 +00:00
}
}
} else {
2018-01-15 13:05:12 +00:00
$attributes = [ " rel " => " alternate " , " type " => " text/html " , " href " => $r -> link ];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " link " , " " , $attributes );
2017-11-08 00:37:53 +00:00
}
}
if ( $r -> content ) {
2018-02-15 02:33:55 +00:00
XML :: addElement ( $doc , $entry , " content " , BBCode :: convert ( $r -> content ), [ " type " => " html " ]);
2017-11-08 00:37:53 +00:00
}
return $entry ;
}
return false ;
}
/**
2020-01-19 06:05:23 +00:00
* Adds the elements for attachments
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param object $doc XML document
2017-11-08 00:37:53 +00:00
* @ param object $root XML root
2017-11-08 22:02:50 +00:00
* @ param array $item Item element
2017-11-08 00:37:53 +00:00
*
2019-01-06 21:06:53 +00:00
* @ return void XML attachment object
* @ todo Find proper type - hints
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function getAttachment ( $doc , $root , $item )
2017-11-08 22:02:50 +00:00
{
$arr = explode ( '[/attach],' , $item [ 'attach' ]);
2017-11-08 00:37:53 +00:00
if ( count ( $arr )) {
foreach ( $arr as $r ) {
$matches = false ;
2017-11-08 22:02:50 +00:00
$cnt = preg_match ( '|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|' , $r , $matches );
2017-11-08 00:37:53 +00:00
if ( $cnt ) {
2018-01-15 13:05:12 +00:00
$attributes = [ " rel " => " enclosure " ,
2017-11-08 00:37:53 +00:00
" href " => $matches [ 1 ],
2018-01-15 13:05:12 +00:00
" type " => $matches [ 3 ]];
2017-11-08 00:37:53 +00:00
if ( intval ( $matches [ 2 ])) {
$attributes [ " length " ] = intval ( $matches [ 2 ]);
}
if ( trim ( $matches [ 4 ]) != " " ) {
$attributes [ " title " ] = trim ( $matches [ 4 ]);
}
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2017-11-08 00:37:53 +00:00
}
}
}
}
/**
2020-01-19 06:05:23 +00:00
* Adds the " entry " elements for the DFRN protocol
2017-11-08 00:37:53 +00:00
*
2019-01-21 21:51:59 +00:00
* @ param DOMDocument $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 00:37:53 +00:00
*
2019-02-24 15:31:16 +00:00
* @ return null | \DOMElement XML entry object
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
* @ todo Find proper type - hints
2017-11-08 00:37:53 +00:00
*/
2019-01-21 21:51:59 +00:00
private static function entry ( DOMDocument $doc , $type , array $item , array $owner , $comment = false , $cid = 0 , $single = false )
2017-11-08 22:02:50 +00:00
{
2018-01-15 13:05:12 +00:00
$mentioned = [];
2017-11-08 00:37:53 +00:00
if ( ! $item [ 'parent' ]) {
2019-02-24 15:30:09 +00:00
Logger :: notice ( 'Item without parent found.' , [ 'type' => $type , 'item' => $item ]);
return null ;
2017-11-08 00:37:53 +00:00
}
if ( $item [ 'deleted' ]) {
2018-01-27 02:38:34 +00:00
$attributes = [ " ref " => $item [ 'uri' ], " when " => DateTimeFormat :: utc ( $item [ 'edited' ] . '+00:00' , DateTimeFormat :: ATOM )];
2017-11-20 17:56:31 +00:00
return XML :: createElement ( $doc , " at:deleted-entry " , " " , $attributes );
2017-11-08 00:37:53 +00:00
}
if ( ! $single ) {
$entry = $doc -> createElement ( " entry " );
} else {
2019-10-24 22:32:35 +00:00
$entry = $doc -> createElementNS ( ActivityNamespace :: ATOM1 , 'entry' );
2017-11-08 00:37:53 +00:00
$doc -> appendChild ( $entry );
2019-10-24 22:32:35 +00:00
$entry -> setAttribute ( " xmlns:thr " , ActivityNamespace :: THREAD );
$entry -> setAttribute ( " xmlns:at " , ActivityNamespace :: TOMB );
$entry -> setAttribute ( " xmlns:media " , ActivityNamespace :: MEDIA );
$entry -> setAttribute ( " xmlns:dfrn " , ActivityNamespace :: DFRN );
$entry -> setAttribute ( " xmlns:activity " , ActivityNamespace :: ACTIVITY );
$entry -> setAttribute ( " xmlns:georss " , ActivityNamespace :: GEORSS );
$entry -> setAttribute ( " xmlns:poco " , ActivityNamespace :: POCO );
$entry -> setAttribute ( " xmlns:ostatus " , ActivityNamespace :: OSTATUS );
$entry -> setAttribute ( " xmlns:statusnet " , ActivityNamespace :: STATUSNET );
2017-11-08 00:37:53 +00:00
}
2020-03-02 07:57:23 +00:00
if ( $item [ 'private' ] == Item :: PRIVATE ) {
2018-01-28 11:18:08 +00:00
$body = Item :: fixPrivatePhotos ( $item [ 'body' ], $owner [ 'uid' ], $item , $cid );
2017-11-08 00:37:53 +00:00
} else {
$body = $item [ 'body' ];
}
// Remove the abstract element. It is only locally important.
2018-02-05 04:38:40 +00:00
$body = BBCode :: stripAbstract ( $body );
2017-11-08 00:37:53 +00:00
2018-02-14 04:58:46 +00:00
$htmlbody = '' ;
2017-11-08 00:37:53 +00:00
if ( $type == 'html' ) {
$htmlbody = $body ;
if ( $item [ 'title' ] != " " ) {
2017-07-20 18:04:32 +00:00
$htmlbody = " [b] " . $item [ 'title' ] . " [/b] \n \n " . $htmlbody ;
2017-11-08 00:37:53 +00:00
}
2020-05-16 16:28:15 +00:00
$htmlbody = BBCode :: convert ( $htmlbody , false , BBCode :: OSTATUS );
2017-11-08 00:37:53 +00:00
}
2017-11-23 19:01:58 +00:00
$author = self :: addEntryAuthor ( $doc , " author " , $item [ " author-link " ], $item );
2017-11-08 00:37:53 +00:00
$entry -> appendChild ( $author );
2017-11-23 19:01:58 +00:00
$dfrnowner = self :: addEntryAuthor ( $doc , " dfrn:owner " , $item [ " owner-link " ], $item );
2017-11-08 00:37:53 +00:00
$entry -> appendChild ( $dfrnowner );
2020-05-28 16:02:36 +00:00
if ( $item [ 'gravity' ] != GRAVITY_PARENT ) {
2017-11-08 00:37:53 +00:00
$parent_item = (( $item [ 'thr-parent' ]) ? $item [ 'thr-parent' ] : $item [ 'parent-uri' ]);
2018-06-21 15:14:01 +00:00
$parent = Item :: selectFirst ([ 'guid' , 'plink' ], [ 'uri' => $parent_item , 'uid' => $item [ 'uid' ]]);
2020-08-20 13:34:37 +00:00
if ( DBA :: isResult ( $parent )) {
$attributes = [ " ref " => $parent_item , " type " => " text/html " ,
" href " => $parent [ 'plink' ],
" dfrn:diaspora_guid " => $parent [ 'guid' ]];
XML :: addElement ( $doc , $entry , " thr:in-reply-to " , " " , $attributes );
}
2017-11-08 00:37:53 +00:00
}
// Add conversation data. This is used for OStatus
2019-12-30 22:00:08 +00:00
$conversation_href = DI :: baseUrl () . " /display/ " . $item [ " parent-guid " ];
2017-11-08 00:37:53 +00:00
$conversation_uri = $conversation_href ;
if ( isset ( $parent_item )) {
2018-07-20 12:19:26 +00:00
$conversation = DBA :: selectFirst ( 'conversation' , [ 'conversation-uri' , 'conversation-href' ], [ 'item-uri' => $item [ 'parent-uri' ]]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $conversation )) {
2018-07-08 09:37:05 +00:00
if ( $conversation [ 'conversation-uri' ] != '' ) {
2018-06-19 21:33:07 +00:00
$conversation_uri = $conversation [ 'conversation-uri' ];
2017-11-08 00:37:53 +00:00
}
2018-07-08 09:37:05 +00:00
if ( $conversation [ 'conversation-href' ] != '' ) {
2018-06-19 21:33:07 +00:00
$conversation_href = $conversation [ 'conversation-href' ];
2017-11-08 00:37:53 +00:00
}
}
}
2018-01-15 13:05:12 +00:00
$attributes = [
2017-11-08 00:37:53 +00:00
" href " => $conversation_href ,
2018-01-15 13:05:12 +00:00
" ref " => $conversation_uri ];
2017-11-08 00:37:53 +00:00
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " ostatus:conversation " , $conversation_uri , $attributes );
2017-11-08 00:37:53 +00:00
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " id " , $item [ " uri " ]);
XML :: addElement ( $doc , $entry , " title " , $item [ " title " ]);
2017-11-08 00:37:53 +00:00
2018-01-27 02:38:34 +00: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 00:37:53 +00:00
// "dfrn:env" is used to read the content
2018-11-08 15:37:08 +00:00
XML :: addElement ( $doc , $entry , " dfrn:env " , Strings :: base64UrlEncode ( $body , true ));
2017-11-08 00:37:53 +00: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 13:05:12 +00:00
XML :: addElement ( $doc , $entry , " content " , (( $type == 'html' ) ? $htmlbody : $body ), [ " type " => $type ]);
2017-11-08 00:37:53 +00:00
// We save this value in "plink". Maybe we should read it from there as well?
2017-11-20 17:56:31 +00:00
XML :: addElement (
2017-11-08 22:02:50 +00:00
$doc ,
$entry ,
" link " ,
" " ,
2018-01-15 13:05:12 +00:00
[ " rel " => " alternate " , " type " => " text/html " ,
2019-12-30 22:00:08 +00:00
" href " => DI :: baseUrl () . " /display/ " . $item [ " guid " ]]
2017-11-08 22:02:50 +00:00
);
2017-11-08 00:37:53 +00: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 06:54:44 +00:00
XML :: addElement ( $doc , $entry , " dfrn:comment-allow " , 1 );
2017-11-08 00:37:53 +00:00
}
if ( $item [ 'location' ]) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " dfrn:location " , $item [ 'location' ]);
2017-11-08 00:37:53 +00:00
}
if ( $item [ 'coord' ]) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " georss:point " , $item [ 'coord' ]);
2017-11-08 00:37:53 +00:00
}
2018-07-25 23:14:55 +00:00
if ( $item [ 'private' ]) {
2020-03-02 07:57:23 +00:00
// Friendica versions prior to 2020.3 can't handle "unlisted" properly. So we can only transmit public and private
XML :: addElement ( $doc , $entry , " dfrn:private " , ( $item [ 'private' ] == Item :: PRIVATE ? Item :: PRIVATE : Item :: PUBLIC ));
2020-03-02 22:35:25 +00:00
XML :: addElement ( $doc , $entry , " dfrn:unlisted " , $item [ 'private' ] == Item :: UNLISTED );
2017-11-08 00:37:53 +00:00
}
if ( $item [ 'extid' ]) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " dfrn:extid " , $item [ 'extid' ]);
2017-11-08 00:37:53 +00:00
}
2018-07-19 13:52:05 +00:00
if ( $item [ 'post-type' ] == Item :: PT_PAGE ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " dfrn:bookmark " , " true " );
2017-11-08 00:37:53 +00:00
}
if ( $item [ 'app' ]) {
2018-01-15 13:05:12 +00:00
XML :: addElement ( $doc , $entry , " statusnet:notice_info " , " " , [ " local_id " => $item [ 'id' ], " source " => $item [ 'app' ]]);
2017-11-08 00:37:53 +00:00
}
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " dfrn:diaspora_guid " , $item [ " guid " ]);
2017-11-08 00:37:53 +00: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' ]) {
2020-04-15 05:57:07 +00:00
$sign = base64_encode ( json_encode ([ 'signed_text' => $item [ 'signed_text' ], 'signature' => '' , 'signer' => '' ]));
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " dfrn:diaspora_signature " , $sign );
2017-11-08 00:37:53 +00:00
}
2018-01-20 23:52:54 +00:00
XML :: addElement ( $doc , $entry , " activity:verb " , self :: constructVerb ( $item ));
2017-11-08 00:37:53 +00:00
if ( $item [ 'object-type' ] != " " ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " activity:object-type " , $item [ 'object-type' ]);
2020-05-27 12:19:06 +00:00
} elseif ( $item [ 'gravity' ] == GRAVITY_PARENT ) {
2019-10-24 22:10:20 +00:00
XML :: addElement ( $doc , $entry , " activity:object-type " , Activity\ObjectType :: NOTE );
2017-11-08 00:37:53 +00:00
} else {
2019-10-24 22:10:20 +00:00
XML :: addElement ( $doc , $entry , " activity:object-type " , Activity\ObjectType :: COMMENT );
2017-11-08 00:37:53 +00:00
}
2017-11-23 19:01:58 +00:00
$actobj = self :: createActivity ( $doc , " activity:object " , $item [ 'object' ]);
2017-11-08 00:37:53 +00:00
if ( $actobj ) {
$entry -> appendChild ( $actobj );
}
2017-11-23 19:01:58 +00:00
$actarg = self :: createActivity ( $doc , " activity:target " , $item [ 'target' ]);
2017-11-08 00:37:53 +00:00
if ( $actarg ) {
$entry -> appendChild ( $actarg );
}
2020-05-02 05:08:05 +00:00
$tags = Tag :: getByURIId ( $item [ 'uri-id' ]);
2017-11-08 00:37:53 +00:00
if ( count ( $tags )) {
2020-05-02 05:08:05 +00:00
foreach ( $tags as $tag ) {
if (( $type != 'html' ) || ( $tag [ 'type' ] == Tag :: HASHTAG )) {
XML :: addElement ( $doc , $entry , " category " , " " , [ " scheme " => " X-DFRN: " . Tag :: TAG_CHARACTER [ $tag [ 'type' ]] . " : " . $tag [ 'url' ], " term " => $tag [ 'name' ]]);
2017-11-08 00:37:53 +00:00
}
2020-05-02 05:08:05 +00:00
if ( $tag [ 'type' ] != Tag :: HASHTAG ) {
$mentioned [ $tag [ 'url' ]] = $tag [ 'url' ];
2017-11-08 00:37:53 +00:00
}
}
}
2017-11-10 12:45:33 +00:00
foreach ( $mentioned as $mention ) {
2018-11-08 16:28:29 +00:00
$condition = [ 'uid' => $owner [ " uid " ], 'nurl' => Strings :: normaliseLink ( $mention )];
2018-08-19 12:46:11 +00:00
$contact = DBA :: selectFirst ( 'contact' , [ 'forum' , 'prv' ], $condition );
2017-11-08 00:37:53 +00:00
2018-08-19 12:46:11 +00:00
if ( DBA :: isResult ( $contact ) && ( $contact [ " forum " ] || $contact [ " prv " ])) {
2017-11-20 17:56:31 +00:00
XML :: addElement (
2017-11-08 22:02:50 +00:00
$doc ,
$entry ,
" link " ,
" " ,
2018-01-15 13:05:12 +00:00
[ " rel " => " mentioned " ,
2019-10-24 22:10:20 +00:00
" ostatus:object-type " => Activity\ObjectType :: GROUP ,
2018-01-15 13:05:12 +00:00
" href " => $mention ]
2017-11-08 22:02:50 +00:00
);
2017-11-08 00:37:53 +00:00
} else {
2017-11-20 17:56:31 +00:00
XML :: addElement (
2017-11-08 22:02:50 +00:00
$doc ,
$entry ,
" link " ,
" " ,
2018-01-15 13:05:12 +00:00
[ " rel " => " mentioned " ,
2019-10-24 22:10:20 +00:00
" ostatus:object-type " => Activity\ObjectType :: PERSON ,
2018-01-15 13:05:12 +00:00
" href " => $mention ]
2017-11-08 22:02:50 +00:00
);
2017-11-08 00:37:53 +00:00
}
}
2017-11-23 19:01:58 +00:00
self :: getAttachment ( $doc , $entry , $item );
2017-11-08 00:37:53 +00:00
return $entry ;
}
/**
2020-01-19 06:05:23 +00:00
* encrypts data via AES
2017-11-08 00:37:53 +00:00
*
* @ param string $data The data that is to be encrypted
2017-11-08 22:02:50 +00:00
* @ param string $key The AES key
2017-11-08 00:37:53 +00:00
*
* @ return string encrypted data
*/
2017-11-23 19:01:58 +00:00
private static function aesEncrypt ( $data , $key )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
return openssl_encrypt ( $data , 'aes-128-ecb' , $key , OPENSSL_RAW_DATA );
}
/**
2020-01-19 06:05:23 +00:00
* decrypts data via AES
2017-11-08 00:37:53 +00:00
*
* @ param string $encrypted The encrypted data
2017-11-08 22:02:50 +00:00
* @ param string $key The AES key
2017-11-08 00:37:53 +00:00
*
* @ return string decrypted data
*/
2017-11-23 19:01:58 +00:00
public static function aesDecrypt ( $encrypted , $key )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
return openssl_decrypt ( $encrypted , 'aes-128-ecb' , $key , OPENSSL_RAW_DATA );
}
/**
2020-01-19 06:05:23 +00:00
* Delivers the atom content to the contacts
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00: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 00:37:53 +00:00
*
2018-04-02 21:46:10 +00:00
* @ return int Deliver status . Negative values mean an error .
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
* @ todo Add array type - hint for $owner , $contact
2017-11-08 00:37:53 +00:00
*/
2019-06-28 09:03:58 +00:00
public static function deliver ( $owner , $contact , $atom , $dissolve = false )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00: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 ;
}
2020-01-19 20:21:13 +00:00
$rino = DI :: config () -> get ( 'system' , 'rino_encrypt' );
2017-11-08 00:37:53 +00:00
$rino = intval ( $rino );
2018-10-30 13:58:45 +00:00
Logger :: log ( " Local rino version: " . $rino , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2020-01-19 20:21:13 +00:00
$ssl_val = intval ( DI :: config () -> get ( 'system' , 'ssl_policy' ));
2017-11-08 00:37:53 +00:00
switch ( $ssl_val ) {
2019-04-08 19:12:10 +00:00
case BaseURL :: SSL_POLICY_FULL :
2017-11-08 00:37:53 +00:00
$ssl_policy = 'full' ;
break ;
2019-04-08 19:12:10 +00:00
case BaseURL :: SSL_POLICY_SELFSIGN :
2017-11-08 00:37:53 +00:00
$ssl_policy = 'self' ;
break ;
2019-04-08 19:12:10 +00:00
case BaseURL :: SSL_POLICY_NONE :
2017-11-08 00:37:53 +00:00
default :
$ssl_policy = 'none' ;
break ;
}
$url = $contact [ 'notify' ] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (( $rino ) ? '&rino=' . $rino : '' );
2018-10-29 21:20:46 +00:00
Logger :: log ( 'dfrn_deliver: ' . $url );
2017-11-08 00:37:53 +00:00
2020-03-04 21:35:09 +00:00
$curlResult = DI :: httpRequest () -> get ( $url );
2017-11-08 00:37:53 +00:00
2018-10-10 19:08:43 +00:00
if ( $curlResult -> isTimeout ()) {
2017-11-08 00:37:53 +00:00
return - 2 ; // timed out
}
2018-10-10 19:08:43 +00:00
$xml = $curlResult -> getBody ();
2017-11-08 00:37:53 +00:00
2018-10-10 19:08:43 +00:00
$curl_stat = $curlResult -> getReturnCode ();
2018-04-03 12:18:05 +00:00
if ( empty ( $curl_stat )) {
2017-11-08 00:37:53 +00:00
return - 3 ; // timed out
}
2018-10-30 13:58:45 +00:00
Logger :: log ( 'dfrn_deliver: ' . $xml , Logger :: DATA );
2017-11-08 00:37:53 +00:00
2018-04-03 12:18:05 +00:00
if ( empty ( $xml )) {
2017-11-08 00:37:53 +00:00
return 3 ;
}
2017-11-08 22:02:50 +00:00
if ( strpos ( $xml , '<?xml' ) === false ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'dfrn_deliver: no valid XML returned' );
2018-10-30 13:58:45 +00:00
Logger :: log ( 'dfrn_deliver: returned XML: ' . $xml , Logger :: DATA );
2017-11-08 00:37:53 +00:00
return 3 ;
}
2018-01-27 16:13:41 +00:00
$res = XML :: parseString ( $xml );
2017-11-08 00:37:53 +00:00
2018-07-23 11:43:18 +00:00
if ( ! is_object ( $res ) || ( intval ( $res -> status ) != 0 ) || ! strlen ( $res -> challenge ) || ! strlen ( $res -> dfrn_id )) {
if ( empty ( $res -> status )) {
$status = 3 ;
} else {
$status = $res -> status ;
}
return $status ;
2017-11-08 00:37:53 +00:00
}
2018-01-15 13:05:12 +00:00
$postvars = [];
2017-11-08 00:37:53 +00:00
$sent_dfrn_id = hex2bin (( string ) $res -> dfrn_id );
$challenge = hex2bin (( string ) $res -> challenge );
$perm = (( $res -> perm ) ? $res -> perm : null );
2019-10-16 12:35:14 +00:00
$dfrn_version = floatval ( $res -> dfrn_version ? : 2.0 );
2017-11-08 00:37:53 +00:00
$rino_remote_version = intval ( $res -> rino );
2019-01-06 17:37:48 +00:00
$page = (( $owner [ 'page-flags' ] == User :: PAGE_FLAGS_COMMUNITY ) ? 1 : 0 );
2017-11-08 00:37:53 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( " Remote rino version: " . $rino_remote_version . " for " . $contact [ " url " ], Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2019-01-06 17:37:48 +00:00
if ( $owner [ 'page-flags' ] == User :: PAGE_FLAGS_PRVGROUP ) {
2017-11-08 00:37:53 +00:00
$page = 2 ;
}
$final_dfrn_id = '' ;
if ( $perm ) {
2018-08-19 12:46:11 +00:00
if ((( $perm == 'rw' ) && ! intval ( $contact [ 'writable' ]))
|| (( $perm == 'r' ) && intval ( $contact [ 'writable' ]))
2017-11-08 22:02:50 +00:00
) {
2018-08-19 12:46:11 +00:00
DBA :: update ( 'contact' , [ 'writable' => ( $perm == 'rw' )], [ 'id' => $contact [ 'id' ]]);
2017-11-08 00:37:53 +00:00
$contact [ 'writable' ] = ( string ) 1 - intval ( $contact [ 'writable' ]);
}
}
if (( $contact [ 'duplex' ] && strlen ( $contact [ 'pubkey' ]))
2019-01-06 17:37:48 +00:00
|| ( $owner [ 'page-flags' ] == User :: PAGE_FLAGS_COMMUNITY && strlen ( $contact [ 'pubkey' ]))
2018-07-25 02:53:46 +00:00
|| ( $contact [ 'rel' ] == Contact :: SHARING && strlen ( $contact [ 'pubkey' ]))
2017-11-08 22:02:50 +00:00
) {
openssl_public_decrypt ( $sent_dfrn_id , $final_dfrn_id , $contact [ 'pubkey' ]);
openssl_public_decrypt ( $challenge , $postvars [ 'challenge' ], $contact [ 'pubkey' ]);
2017-11-08 00:37:53 +00:00
} else {
2017-11-08 22:02:50 +00:00
openssl_private_decrypt ( $sent_dfrn_id , $final_dfrn_id , $contact [ 'prvkey' ]);
openssl_private_decrypt ( $challenge , $postvars [ 'challenge' ], $contact [ 'prvkey' ]);
2017-11-08 00:37:53 +00:00
}
$final_dfrn_id = substr ( $final_dfrn_id , 0 , strpos ( $final_dfrn_id , '.' ));
2017-11-08 22:02:50 +00:00
if ( strpos ( $final_dfrn_id , ':' ) == 1 ) {
$final_dfrn_id = substr ( $final_dfrn_id , 2 );
2017-11-08 00:37:53 +00:00
}
if ( $final_dfrn_id != $orig_id ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'dfrn_deliver: wrong dfrn_id.' );
2017-11-08 00:37:53 +00:00
// did not decode properly - cannot trust this site
return 3 ;
}
$postvars [ 'dfrn_id' ] = $idtosend ;
$postvars [ 'dfrn_version' ] = DFRN_PROTOCOL_VERSION ;
if ( $dissolve ) {
$postvars [ 'dissolve' ] = '1' ;
}
2019-01-06 17:37:48 +00:00
if ((( $contact [ 'rel' ]) && ( $contact [ 'rel' ] != Contact :: SHARING ) && ( ! $contact [ 'blocked' ])) || ( $owner [ 'page-flags' ] == User :: PAGE_FLAGS_COMMUNITY )) {
2017-11-08 00:37:53 +00:00
$postvars [ 'data' ] = $atom ;
$postvars [ 'perm' ] = 'rw' ;
} else {
2017-11-08 22:02:50 +00:00
$postvars [ 'data' ] = str_replace ( '<dfrn:comment-allow>1' , '<dfrn:comment-allow>0' , $atom );
2017-11-08 00:37:53 +00:00
$postvars [ 'perm' ] = 'r' ;
}
$postvars [ 'ssl_policy' ] = $ssl_policy ;
if ( $page ) {
$postvars [ 'page' ] = $page ;
}
if ( $rino > 0 && $rino_remote_version > 0 && ( ! $dissolve )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'rino version: ' . $rino_remote_version );
2017-11-08 00:37:53 +00:00
switch ( $rino_remote_version ) {
case 1 :
$key = openssl_random_pseudo_bytes ( 16 );
2017-11-23 19:01:58 +00:00
$data = self :: aesEncrypt ( $postvars [ 'data' ], $key );
2017-11-08 00:37:53 +00:00
break ;
Cleanups: isResult() more used, readability improved (#5608)
* [diaspora]: Maybe SimpleXMLElement is the right type-hint?
* Changes proposed + pre-renaming:
- pre-renamed $db -> $connection
- added TODOs for not allowing bad method invocations (there is a
BadMethodCallException in SPL)
* If no record is found, below $r[0] will fail with a E_NOTICE and the code
doesn't behave as expected.
* Ops, one more left ...
* Continued:
- added documentation for Contact::updateSslPolicy() method
- added type-hint for $contact of same method
- empty lines added + TODO where the bug origins that $item has no element 'body'
* Added empty lines for better readability
* Cleaned up:
- no more x() (deprecated) usage but empty() instead
- fixed mixing of space/tab indending
- merged else/if block goether in elseif() (lesser nested code blocks)
* Re-fixed DBM -> DBA switch
* Fixes/rewrites:
- use empty()/isset() instead of deprecated x()
- merged 2 nested if() blocks into one
- avoided nested if() block inside else block by rewriting it to elseif()
- $contact_id is an integer, let's test on > 0 here
- added a lot spaces and some empty lines for better readability
* Rewrite:
- moved all CONTACT_* constants from boot.php to Contact class
* CR request:
- renamed Contact::CONTACT_IS_* -> Contact::* ;-)
* Rewrites:
- moved PAGE_* to Friendica\Model\Profile class
- fixed mixure with "Contact::* rewrite"
* Ops, one still there (return is no function)
* Rewrite to Proxy class:
- introduced new Friendica\Network\Proxy class for in exchange of proxy_*()
functions
- moved also all PROXY_* constants there as Proxy::*
- removed now no longer needed mod/proxy.php loading as composer's auto-load
will do this for us
- renamed those proxy_*() functions to better names:
+ proxy_init() -> Proxy::init() (public)
+ proxy_url() -> Proxy::proxifyUrl() (public)
+ proxy_parse_html() -> Proxy::proxifyHtml() (public)
+ proxy_is_local_image() -> Proxy::isLocalImage() (private)
+ proxy_parse_query() -> Proxy::parseQuery() (private)
+ proxy_img_cb() -> Proxy::replaceUrl() (private)
* CR request:
- moved all PAGE_* constants to Friendica\Model\Contact class
- fixed all references of both classes
* Ops, need to set $a here ...
* CR request:
- moved Proxy class to Friendica\Module
- extended BaseModule
* Ops, no need for own instance of $a when self::getApp() is around.
* Proxy-rewrite:
- proxy_url() and proxy_parse_html() are both non-module functions (now
methods)
- so they must be splitted into a seperate class
- also the SIZE_* and DEFAULT_TIME constants are both not relevant to module
* No instances from utility classes
* Fixed error:
- proxify*() is now located in `Friendica\Util\ProxyUtils`
* Moved back to original place, ops? How did they move here? Well, it was not
intended by me.
* Removed duplicate (left-over from split) constants and static array. Thank to
MrPetovan finding it.
* Renamed ProxyUtils -> Proxy and aliased it back to ProxyUtils.
* Rewrite:
- stopped using deprecated NETWORK_* constants, now Protocol::* should be used
- still left them intact for slow/lazy developers ...
* Ops, was added accidentally ...
* Ops, why these wrong moves?
* Ops, one to much (thanks to MrPetovan)
* Ops, wrong moving ...
* moved back to original place ...
* spaces added
* empty lines add for better readability.
* convertered spaces -> tab for code indenting.
* CR request: Add space between if and brace.
* CR requests fixed + move reverted
- ops, src/Module/*.php has been moved to src/Network/ accidentally
- reverted some parts in src/Database/DBA.php as pointed out by Annando
- removed internal TODO items
- added some spaces for better readability
2018-08-24 05:05:49 +00:00
2017-11-08 00:37:53 +00:00
default :
2018-10-29 21:20:46 +00:00
Logger :: log ( " rino: invalid requested version ' $rino_remote_version ' " );
2017-11-08 00:37:53 +00:00
return - 8 ;
}
$postvars [ 'rino' ] = $rino_remote_version ;
$postvars [ 'data' ] = bin2hex ( $data );
if ( $dfrn_version >= 2.1 ) {
if (( $contact [ 'duplex' ] && strlen ( $contact [ 'pubkey' ]))
2019-01-06 17:37:48 +00:00
|| ( $owner [ 'page-flags' ] == User :: PAGE_FLAGS_COMMUNITY && strlen ( $contact [ 'pubkey' ]))
2018-07-25 02:53:46 +00:00
|| ( $contact [ 'rel' ] == Contact :: SHARING && strlen ( $contact [ 'pubkey' ]))
2017-11-08 22:02:50 +00:00
) {
openssl_public_encrypt ( $key , $postvars [ 'key' ], $contact [ 'pubkey' ]);
2017-11-08 00:37:53 +00:00
} else {
2017-11-08 22:02:50 +00:00
openssl_private_encrypt ( $key , $postvars [ 'key' ], $contact [ 'prvkey' ]);
2017-11-08 00:37:53 +00:00
}
} else {
2019-01-06 17:37:48 +00:00
if (( $contact [ 'duplex' ] && strlen ( $contact [ 'prvkey' ])) || ( $owner [ 'page-flags' ] == User :: PAGE_FLAGS_COMMUNITY )) {
2017-11-08 22:02:50 +00:00
openssl_private_encrypt ( $key , $postvars [ 'key' ], $contact [ 'prvkey' ]);
2017-11-08 00:37:53 +00:00
} else {
2017-11-08 22:02:50 +00:00
openssl_public_encrypt ( $key , $postvars [ 'key' ], $contact [ 'pubkey' ]);
2017-11-08 00:37:53 +00:00
}
}
2018-10-29 21:20:46 +00:00
Logger :: log ( 'md5 rawkey ' . md5 ( $postvars [ 'key' ]));
2017-11-08 00:37:53 +00:00
$postvars [ 'key' ] = bin2hex ( $postvars [ 'key' ]);
}
2020-06-29 20:22:00 +00:00
Logger :: debug ( 'dfrn_deliver' , [ 'post' => $postvars ]);
2017-11-08 00:37:53 +00:00
2020-03-04 21:18:28 +00:00
$postResult = DI :: httpRequest () -> post ( $contact [ 'notify' ], $postvars );
2018-10-10 19:08:43 +00:00
$xml = $postResult -> getBody ();
2017-11-08 00:37:53 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( 'dfrn_deliver: ' . " RECEIVED: " . $xml , Logger :: DATA );
2017-11-08 00:37:53 +00:00
2018-10-10 19:08:43 +00:00
$curl_stat = $postResult -> getReturnCode ();
2018-04-03 12:18:05 +00:00
if ( empty ( $curl_stat ) || empty ( $xml )) {
2017-11-08 00:37:53 +00:00
return - 9 ; // timed out
}
2018-10-10 19:08:43 +00:00
if (( $curl_stat == 503 ) && stristr ( $postResult -> getHeader (), 'retry-after' )) {
2017-11-08 00:37:53 +00:00
return - 10 ;
}
2017-11-08 22:02:50 +00:00
if ( strpos ( $xml , '<?xml' ) === false ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'dfrn_deliver: phase 2: no valid XML returned' );
2018-10-30 13:58:45 +00:00
Logger :: log ( 'dfrn_deliver: phase 2: returned XML: ' . $xml , Logger :: DATA );
2017-11-08 00:37:53 +00:00
return 3 ;
}
2018-01-27 16:13:41 +00:00
$res = XML :: parseString ( $xml );
2017-11-08 00:37:53 +00:00
2018-04-07 10:02:43 +00:00
if ( ! isset ( $res -> status )) {
2017-11-08 00:37:53 +00:00
return - 11 ;
}
2018-04-07 10:02:43 +00:00
// Possibly old servers had returned an empty value when everything was okay
if ( empty ( $res -> status )) {
$res -> status = 200 ;
}
2017-11-08 00:37:53 +00:00
if ( ! empty ( $res -> message )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Delivery returned status ' . $res -> status . ' - ' . $res -> message , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
}
return intval ( $res -> status );
2018-04-02 12:53:48 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Transmits atom content to the contacts via the Diaspora transport layer
2018-04-02 12:53:48 +00:00
*
2019-01-06 21:06:53 +00: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 12:53:48 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param bool $public_batch
2018-04-02 21:46:10 +00:00
* @ return int Deliver status . Negative values mean an error .
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-04-02 12:53:48 +00:00
*/
2018-04-03 12:18:05 +00:00
public static function transmit ( $owner , $contact , $atom , $public_batch = false )
2018-04-02 12:53:48 +00:00
{
2018-04-27 05:11:52 +00:00
if ( ! $public_batch ) {
if ( empty ( $contact [ 'addr' ])) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Empty contact handle for ' . $contact [ 'id' ] . ' - ' . $contact [ 'url' ] . ' - trying to update it.' );
2018-04-27 05:11:52 +00:00
if ( Contact :: updateFromProbe ( $contact [ 'id' ])) {
2018-07-20 12:19:26 +00:00
$new_contact = DBA :: selectFirst ( 'contact' , [ 'addr' ], [ 'id' => $contact [ 'id' ]]);
2018-04-27 05:11:52 +00:00
$contact [ 'addr' ] = $new_contact [ 'addr' ];
}
if ( empty ( $contact [ 'addr' ])) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Unable to find contact handle for ' . $contact [ 'id' ] . ' - ' . $contact [ 'url' ]);
2018-04-27 05:11:52 +00:00
return - 21 ;
}
2018-04-03 12:18:05 +00:00
}
2020-08-06 10:31:05 +00:00
$fcontact = FContact :: getByURL ( $contact [ 'addr' ]);
2018-04-27 05:11:52 +00:00
if ( empty ( $fcontact )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Unable to find contact details for ' . $contact [ 'id' ] . ' - ' . $contact [ 'addr' ]);
2018-04-27 05:11:52 +00:00
return - 22 ;
2018-04-03 12:18:05 +00:00
}
2018-04-30 16:46:49 +00:00
$pubkey = $fcontact [ 'pubkey' ];
} else {
$pubkey = '' ;
2018-04-03 12:18:05 +00:00
}
2018-04-02 12:53:48 +00:00
2018-04-30 16:46:49 +00:00
$envelope = Diaspora :: buildMessage ( $atom , $owner , $contact , $owner [ 'uprvkey' ], $pubkey , $public_batch );
2018-04-02 12:53:48 +00:00
2018-04-22 10:04:30 +00: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 12:53:48 +00:00
2019-07-10 03:16:50 +00:00
if ( empty ( $dest_url )) {
2019-07-10 03:19:54 +00:00
Logger :: info ( 'Empty destination' , [ 'public' => $public_batch , 'contact' => $contact ]);
2019-07-10 03:16:50 +00:00
return - 24 ;
}
2018-04-02 12:53:48 +00:00
$content_type = ( $public_batch ? " application/magic-envelope+xml " : " application/json " );
2020-03-04 21:18:28 +00:00
$postResult = DI :: httpRequest () -> post ( $dest_url , $envelope , [ " Content-Type: " . $content_type ]);
2018-10-10 19:15:26 +00:00
$xml = $postResult -> getBody ();
2018-04-02 12:53:48 +00:00
2018-10-10 19:15:26 +00:00
$curl_stat = $postResult -> getReturnCode ();
2018-04-03 12:18:05 +00:00
if ( empty ( $curl_stat ) || empty ( $xml )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Empty answer from ' . $contact [ 'id' ] . ' - ' . $dest_url );
2018-04-02 21:46:10 +00:00
return - 9 ; // timed out
}
2018-10-10 19:15:26 +00:00
if (( $curl_stat == 503 ) && ( stristr ( $postResult -> getHeader (), 'retry-after' ))) {
2018-04-02 21:46:10 +00:00
return - 10 ;
}
if ( strpos ( $xml , '<?xml' ) === false ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'No valid XML returned from ' . $contact [ 'id' ] . ' - ' . $dest_url );
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Returned XML: ' . $xml , Logger :: DATA );
2018-04-02 21:46:10 +00:00
return 3 ;
}
$res = XML :: parseString ( $xml );
2018-04-03 12:18:05 +00:00
if ( empty ( $res -> status )) {
2018-04-07 10:02:43 +00:00
return - 23 ;
2018-04-02 21:46:10 +00:00
}
if ( ! empty ( $res -> message )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Transmit to ' . $dest_url . ' returned status ' . $res -> status . ' - ' . $res -> message , Logger :: DEBUG );
2018-04-02 21:46:10 +00:00
}
return intval ( $res -> status );
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Fetch the author data from head or entry items
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00: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 19:01:58 +00:00
* @ param string $xml optional , default empty
2017-11-08 00:37:53 +00:00
*
2017-12-17 20:27:50 +00:00
* @ return array Relevant data of the author
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
* @ todo Find good type - hints for all parameter
2017-11-08 00:37:53 +00:00
*/
2017-11-08 22:02:50 +00:00
private static function fetchauthor ( $xpath , $context , $importer , $element , $onlyfetch , $xml = " " )
{
2018-01-15 13:05:12 +00:00
$author = [];
2018-07-08 11:46:05 +00:00
$author [ " name " ] = XML :: getFirstNodeValue ( $xpath , $element . " /atom:name/text() " , $context );
$author [ " link " ] = XML :: getFirstNodeValue ( $xpath , $element . " /atom:uri/text() " , $context );
2017-11-08 00:37:53 +00:00
2018-06-19 21:33:07 +00:00
$fields = [ 'id' , 'uid' , 'url' , 'network' , 'avatar-date' , 'avatar' , 'name-date' , 'uri-date' , 'addr' ,
'name' , 'nick' , 'about' , 'location' , 'keywords' , 'xmpp' , 'bdyear' , 'bd' , 'hidden' , 'contact-type' ];
2020-09-13 14:15:28 +00:00
$condition = [ " `uid` = ? AND `nurl` = ? AND `network` != ? AND NOT `pending` AND NOT `blocked` AND `rel` IN (?, ?) " ,
$importer [ " importer_uid " ], Strings :: normaliseLink ( $author [ " link " ]), Protocol :: STATUSNET ,
Contact :: SHARING , Contact :: FRIEND ];
2018-07-20 12:19:26 +00:00
$contact_old = DBA :: selectFirst ( 'contact' , $fields , $condition );
2017-11-08 00:37:53 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $contact_old )) {
2018-02-14 04:58:46 +00:00
$author [ " contact-id " ] = $contact_old [ " id " ];
$author [ " network " ] = $contact_old [ " network " ];
2017-11-08 00:37:53 +00:00
} else {
if ( ! $onlyfetch ) {
2019-02-23 04:00:16 +00:00
Logger :: debug ( " Contact " . $author [ " link " ] . " wasn't found for user " . $importer [ " importer_uid " ] . " XML: " . $xml );
2017-11-08 00:37:53 +00:00
}
2018-07-16 05:48:51 +00:00
$author [ " contact-unknown " ] = true ;
2020-09-13 14:15:28 +00:00
$contact = Contact :: getByURL ( $author [ " link " ], null , [ " contact-id " , " network " ]);
$author [ " contact-id " ] = $contact [ " id " ] ? ? $importer [ " id " ];
$author [ " network " ] = $contact [ " network " ] ? ? $importer [ " network " ];
2017-11-08 00:37:53 +00:00
$onlyfetch = true ;
}
// Until now we aren't serving different sizes - but maybe later
2018-01-15 13:05:12 +00:00
$avatarlist = [];
2017-11-08 00:37:53 +00:00
/// @todo check if "avatar" or "photo" would be the best field in the specification
2017-07-20 18:04:32 +00:00
$avatars = $xpath -> query ( $element . " /atom:link[@rel='avatar'] " , $context );
2017-11-23 19:01:58 +00:00
foreach ( $avatars as $avatar ) {
2017-11-08 00:37:53 +00:00
$href = " " ;
$width = 0 ;
2017-11-23 19:01:58 +00:00
foreach ( $avatar -> attributes as $attributes ) {
2017-01-26 08:38:52 +00:00
/// @TODO Rewrite these similar if() to one switch
2017-11-08 00:37:53 +00:00
if ( $attributes -> name == " href " ) {
$href = $attributes -> textContent ;
}
if ( $attributes -> name == " width " ) {
$width = $attributes -> textContent ;
}
if ( $attributes -> name == " updated " ) {
2018-02-26 21:53:42 +00:00
$author [ " avatar-date " ] = $attributes -> textContent ;
2017-11-08 00:37:53 +00:00
}
}
if (( $width > 0 ) && ( $href != " " )) {
$avatarlist [ $width ] = $href ;
}
}
2017-07-20 18:04:32 +00:00
2017-11-08 00:37:53 +00:00
if ( count ( $avatarlist ) > 0 ) {
krsort ( $avatarlist );
$author [ " avatar " ] = current ( $avatarlist );
}
2018-08-17 03:19:42 +00:00
if ( empty ( $author [ 'avatar' ]) && ! empty ( $author [ 'link' ])) {
$cid = Contact :: getIdForURL ( $author [ 'link' ], 0 );
if ( ! empty ( $cid )) {
$contact = DBA :: selectFirst ( 'contact' , [ 'avatar' ], [ 'id' => $cid ]);
if ( DBA :: isResult ( $contact )) {
$author [ 'avatar' ] = $contact [ 'avatar' ];
}
}
}
2018-08-26 07:56:33 +00:00
if ( empty ( $author [ 'avatar' ])) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Empty author: ' . $xml );
2019-02-24 18:40:04 +00:00
$author [ 'avatar' ] = '' ;
2018-08-26 07:56:33 +00:00
}
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $contact_old ) && ! $onlyfetch ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Check if contact details for contact " . $contact_old [ " id " ] . " ( " . $contact_old [ " nick " ] . " ) have to be updated. " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2020-07-10 19:49:11 +00:00
$poco = [ " url " => $contact_old [ " url " ], " network " => $contact_old [ " network " ]];
2017-11-08 00:37:53 +00:00
// When was the last change to name or uri?
$name_element = $xpath -> query ( $element . " /atom:name " , $context ) -> item ( 0 );
2017-11-23 19:01:58 +00:00
foreach ( $name_element -> attributes as $attributes ) {
2017-11-08 00:37:53 +00:00
if ( $attributes -> name == " updated " ) {
$poco [ " name-date " ] = $attributes -> textContent ;
}
}
$link_element = $xpath -> query ( $element . " /atom:link " , $context ) -> item ( 0 );
2017-11-23 19:01:58 +00:00
foreach ( $link_element -> attributes as $attributes ) {
2017-11-08 00:37:53 +00:00
if ( $attributes -> name == " updated " ) {
$poco [ " uri-date " ] = $attributes -> textContent ;
}
}
// Update contact data
2018-07-08 11:46:05 +00:00
$value = XML :: getFirstNodeValue ( $xpath , $element . " /dfrn:handle/text() " , $context );
2017-11-08 00:37:53 +00:00
if ( $value != " " ) {
$poco [ " addr " ] = $value ;
}
2018-07-08 11:46:05 +00:00
$value = XML :: getFirstNodeValue ( $xpath , $element . " /poco:displayName/text() " , $context );
2017-11-08 00:37:53 +00:00
if ( $value != " " ) {
$poco [ " name " ] = $value ;
}
2018-07-08 11:46:05 +00:00
$value = XML :: getFirstNodeValue ( $xpath , $element . " /poco:preferredUsername/text() " , $context );
2017-11-08 00:37:53 +00:00
if ( $value != " " ) {
$poco [ " nick " ] = $value ;
}
2018-07-08 11:46:05 +00:00
$value = XML :: getFirstNodeValue ( $xpath , $element . " /poco:note/text() " , $context );
2017-11-08 00:37:53 +00:00
if ( $value != " " ) {
$poco [ " about " ] = $value ;
}
2018-07-08 11:46:05 +00:00
$value = XML :: getFirstNodeValue ( $xpath , $element . " /poco:address/poco:formatted/text() " , $context );
2017-11-08 00:37:53 +00:00
if ( $value != " " ) {
$poco [ " location " ] = $value ;
}
/// @todo Only search for elements with "poco:type" = "xmpp"
2018-07-08 11:46:05 +00:00
$value = XML :: getFirstNodeValue ( $xpath , $element . " /poco:ims/poco:value/text() " , $context );
2017-11-08 00:37:53 +00:00
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.
2018-07-08 11:46:05 +00:00
$hide = intval ( XML :: getFirstNodeValue ( $xpath , $element . " /dfrn:hide/text() " , $context ) == " true " );
2017-11-08 00:37:53 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( " Hidden status for contact " . $contact_old [ " url " ] . " : " . $hide , Logger :: DEBUG );
2017-11-08 00:37:53 +00: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 04:58:46 +00:00
$contact_old [ " hidden " ] = true ;
2017-11-08 00:37:53 +00:00
}
// Save the keywords into the contact table
2018-01-15 13:05:12 +00:00
$tags = [];
2017-11-08 00:37:53 +00:00
$tagelements = $xpath -> evaluate ( $element . " /poco:tags/text() " , $context );
2017-11-23 19:01:58 +00:00
foreach ( $tagelements as $tag ) {
2017-11-08 00:37:53 +00:00
$tags [ $tag -> nodeValue ] = $tag -> nodeValue ;
}
if ( count ( $tags )) {
$poco [ " keywords " ] = implode ( " , " , $tags );
}
// "dfrn:birthday" contains the birthday converted to UTC
2018-07-08 11:46:05 +00:00
$birthday = XML :: getFirstNodeValue ( $xpath , $element . " /poco:birthday/text() " , $context );
2017-11-08 00:37:53 +00:00
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"
2018-07-08 11:46:05 +00:00
$value = XML :: getFirstNodeValue ( $xpath , $element . " /poco:birthday/text() " , $context );
2017-11-08 00:37:53 +00:00
2018-11-22 04:53:45 +00:00
if ( ! in_array ( $value , [ " " , " 0000-00-00 " , DBA :: NULL_DATE ])) {
2017-11-08 00:37:53 +00:00
$bdyear = date ( " Y " );
2018-11-22 04:53:45 +00:00
$value = str_replace ([ " 0000 " , " 0001 " ], $bdyear , $value );
2017-11-08 00:37:53 +00:00
if ( strtotime ( $value ) < time ()) {
$value = str_replace ( $bdyear , $bdyear + 1 , $value );
}
$poco [ " bd " ] = $value ;
}
2018-02-14 04:58:46 +00:00
$contact = array_merge ( $contact_old , $poco );
2017-11-08 00:37:53 +00:00
2018-02-14 04:58:46 +00:00
if ( $contact_old [ " bdyear " ] != $contact [ " bdyear " ]) {
2018-11-22 05:15:09 +00:00
Event :: createBirthday ( $contact , $birthday );
2017-11-08 00:37:53 +00:00
}
2019-07-04 21:19:23 +00:00
$fields = [ 'name' => $contact [ 'name' ], 'nick' => $contact [ 'nick' ], 'about' => $contact [ 'about' ],
'location' => $contact [ 'location' ], 'addr' => $contact [ 'addr' ], 'keywords' => $contact [ 'keywords' ],
'bdyear' => $contact [ 'bdyear' ], 'bd' => $contact [ 'bd' ], 'hidden' => $contact [ 'hidden' ],
'xmpp' => $contact [ 'xmpp' ], 'name-date' => DateTimeFormat :: utc ( $contact [ 'name-date' ]),
2019-07-12 14:55:23 +00:00
'unsearchable' => $contact [ 'hidden' ], 'uri-date' => DateTimeFormat :: utc ( $contact [ 'uri-date' ])];
2017-11-08 00:37:53 +00:00
2019-07-04 21:19:23 +00:00
DBA :: update ( 'contact' , $fields , [ 'id' => $contact [ 'id' ], 'network' => $contact [ 'network' ]], $contact_old );
2017-11-08 00:37:53 +00:00
2019-07-04 21:19:23 +00:00
// Update the public contact. Don't set the "hidden" value, this is used differently for public contacts
unset ( $fields [ 'hidden' ]);
$condition = [ 'uid' => 0 , 'nurl' => Strings :: normaliseLink ( $contact_old [ 'url' ])];
DBA :: update ( 'contact' , $fields , $condition , true );
2017-11-08 00:37:53 +00:00
2020-07-25 11:48:52 +00:00
Contact :: updateAvatar ( $contact [ 'id' ], $author [ 'avatar' ]);
2017-11-08 00:37:53 +00:00
2019-07-04 21:19:23 +00:00
$pcid = Contact :: getIdForURL ( $contact_old [ 'url' ]);
if ( ! empty ( $pcid )) {
2020-07-25 11:48:52 +00:00
Contact :: updateAvatar ( $pcid , $author [ 'avatar' ]);
2019-07-04 21:19:23 +00:00
}
2017-11-08 00:37:53 +00:00
}
2018-02-26 21:53:42 +00:00
return $author ;
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Transforms activity objects into an XML string
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param object $xpath XPath object
2017-11-08 00:37:53 +00:00
* @ param object $activity Activity object
2017-12-17 20:27:50 +00:00
* @ param string $element element name
2017-11-08 00:37:53 +00:00
*
* @ return string XML string
* @ todo Find good type - hints for all parameter
*/
2017-11-23 19:01:58 +00:00
private static function transformActivity ( $xpath , $activity , $element )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
if ( ! is_object ( $activity )) {
return " " ;
}
$obj_doc = new DOMDocument ( " 1.0 " , " utf-8 " );
$obj_doc -> formatOutput = true ;
2019-10-24 22:32:35 +00:00
$obj_element = $obj_doc -> createElementNS ( ActivityNamespace :: ATOM1 , $element );
2017-11-08 00:37:53 +00:00
$activity_type = $xpath -> query ( " activity:object-type/text() " , $activity ) -> item ( 0 ) -> nodeValue ;
2017-11-20 17:56:31 +00:00
XML :: addElement ( $obj_doc , $obj_element , " type " , $activity_type );
2017-11-08 00:37:53 +00: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 05:00:50 +00:00
foreach ( $links as $link ) {
2017-11-08 00:37:53 +00: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 );
}
/**
2020-01-19 06:05:23 +00:00
* Processes the mail elements
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00: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 19:01:58 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Exception
* @ todo Find good type - hints for all parameter
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function processMail ( $xpath , $mail , $importer )
2017-11-08 22:02:50 +00:00
{
2018-10-29 21:20:46 +00:00
Logger :: log ( " Processing mails " );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$msg = [];
2017-11-08 00:37:53 +00: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 22:08:16 +00:00
$msg [ " created " ] = DateTimeFormat :: utc ( $xpath -> query ( " dfrn:sentdate/text() " , $mail ) -> item ( 0 ) -> nodeValue );
2017-11-08 00:37:53 +00:00
$msg [ " title " ] = $xpath -> query ( " dfrn:subject/text() " , $mail ) -> item ( 0 ) -> nodeValue ;
$msg [ " body " ] = $xpath -> query ( " dfrn:content/text() " , $mail ) -> item ( 0 ) -> nodeValue ;
2019-05-08 05:44:22 +00:00
Mail :: insert ( $msg );
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Processes the suggestion elements
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param object $xpath XPath object
2017-11-08 00:37:53 +00:00
* @ param object $suggestion suggestion elements
2017-11-08 22:02:50 +00:00
* @ param array $importer Record of the importer user mixed with contact of the content
2017-11-23 19:01:58 +00:00
* @ return boolean
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ todo Find good type - hints for all parameter
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function processSuggestion ( $xpath , $suggestion , $importer )
2017-11-08 22:02:50 +00:00
{
2019-05-19 17:58:20 +00:00
Logger :: log ( 'Processing suggestions' );
2017-11-08 00:37:53 +00:00
/// @TODO Rewrite this to one statement
2018-01-15 13:05:12 +00:00
$suggest = [];
2019-05-19 17:58:20 +00: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 ;
2017-11-08 00:37:53 +00:00
// Does our member already have a friend matching this description?
/*
* 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
*/
2019-05-19 17:58:20 +00:00
$condition = [ 'nurl' => Strings :: normaliseLink ( $suggest [ 'url' ]), 'uid' => $suggest [ 'uid' ]];
2018-08-19 12:46:11 +00:00
if ( DBA :: exists ( 'contact' , $condition )) {
2017-11-08 00:37:53 +00:00
return false ;
}
// Do we already have an fcontact record for this person?
$fid = 0 ;
2019-05-19 17:58:20 +00:00
$fcontact = DBA :: selectFirst ( 'fcontact' , [ 'id' ], [ 'url' => $suggest [ 'url' ]]);
2018-08-19 12:46:11 +00:00
if ( DBA :: isResult ( $fcontact )) {
2019-05-19 17:58:20 +00:00
$fid = $fcontact [ 'id' ];
2017-11-08 00:37:53 +00:00
2018-08-19 12:46:11 +00:00
// OK, we do. Do we already have an introduction for this person?
2019-05-19 17:58:20 +00:00
if ( DBA :: exists ( 'intro' , [ 'uid' => $suggest [ 'uid' ], 'fid' => $fid ])) {
2018-08-19 12:46:11 +00:00
/*
* 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 00:37:53 +00:00
return false ;
}
}
2019-05-19 17:58:20 +00:00
2017-11-08 00:37:53 +00:00
if ( ! $fid ) {
2019-05-19 17:58:20 +00:00
$fields = [ 'name' => $suggest [ 'name' ], 'url' => $suggest [ 'url' ],
'photo' => $suggest [ 'photo' ], 'request' => $suggest [ 'request' ]];
DBA :: insert ( 'fcontact' , $fields );
$fid = DBA :: lastInsertId ();
2017-11-08 00:37:53 +00:00
}
2018-08-19 12:46:11 +00:00
2017-11-08 00:37:53 +00:00
/*
* If no record in fcontact is found , below INSERT statement will not
* link an introduction to it .
*/
2019-05-19 17:58:20 +00:00
if ( empty ( $fid )) {
2018-07-19 22:39:05 +00:00
// Database record did not get created. Quietly give up.
2018-12-26 05:40:12 +00:00
exit ();
2017-11-08 00:37:53 +00:00
}
2017-03-23 22:37:58 +00:00
2018-11-08 13:45:46 +00:00
$hash = Strings :: getRandomHex ();
2017-11-08 00:37:53 +00:00
2019-05-19 17:58:20 +00:00
$fields = [ 'uid' => $suggest [ 'uid' ], 'fid' => $fid , 'contact-id' => $suggest [ 'cid' ],
'note' => $suggest [ 'body' ], 'hash' => $hash , 'datetime' => DateTimeFormat :: utcNow (), 'blocked' => false ];
DBA :: insert ( 'intro' , $fields );
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
notification (
2018-01-15 13:05:12 +00:00
[
2020-02-04 21:11:11 +00:00
'type' => Type :: SUGGEST ,
2019-05-19 17:58:20 +00:00
'notify_flags' => $importer [ 'notify-flags' ],
'language' => $importer [ 'language' ],
'to_name' => $importer [ 'username' ],
'to_email' => $importer [ 'email' ],
'uid' => $importer [ 'importer_uid' ],
'item' => $suggest ,
2019-12-30 22:00:08 +00:00
'link' => DI :: baseUrl () . '/notifications/intros' ,
2019-05-19 17:58:20 +00:00
'source_name' => $importer [ 'name' ],
'source_link' => $importer [ 'url' ],
'source_photo' => $importer [ 'photo' ],
2019-10-23 22:25:43 +00:00
'verb' => Activity :: REQ_FRIEND ,
2019-05-19 17:58:20 +00:00
'otype' => 'intro' ]
2017-11-08 22:02:50 +00:00
);
2017-11-08 00:37:53 +00:00
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Processes the relocation elements
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param object $xpath XPath object
2017-11-08 00:37:53 +00:00
* @ param object $relocation relocation elements
2017-11-08 22:02:50 +00:00
* @ param array $importer Record of the importer user mixed with contact of the content
2017-11-23 19:01:58 +00:00
* @ return boolean
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
* @ todo Find good type - hints for all parameter
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function processRelocation ( $xpath , $relocation , $importer )
2017-11-08 22:02:50 +00:00
{
2018-10-29 21:20:46 +00:00
Logger :: log ( " Processing relocations " );
2017-11-08 00:37:53 +00:00
/// @TODO Rewrite this to one statement
2018-01-15 13:05:12 +00:00
$relocate = [];
2017-11-08 00:37:53 +00: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 22:02:50 +00:00
$r = q (
2018-07-19 22:39:05 +00:00
" SELECT `photo`, `url` FROM `contact` WHERE `id` = %d AND `uid` = %d " ,
2017-11-08 00:37:53 +00:00
intval ( $importer [ " id " ]),
2017-11-08 22:02:50 +00:00
intval ( $importer [ " importer_uid " ])
);
2017-11-08 00:37:53 +00:00
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $r )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " Query failed to execute, no result returned in " . __FUNCTION__ );
2017-11-08 00:37:53 +00:00
return false ;
}
$old = $r [ 0 ];
// Update the contact table. We try to find every entry.
2018-05-02 19:26:15 +00:00
$fields = [ 'name' => $relocate [ " name " ], 'avatar' => $relocate [ " avatar " ],
2018-11-08 16:28:29 +00:00
'url' => $relocate [ " url " ], 'nurl' => Strings :: normaliseLink ( $relocate [ " url " ]),
2018-05-02 19:26:15 +00:00
'addr' => $relocate [ " addr " ], 'request' => $relocate [ " request " ],
'confirm' => $relocate [ " confirm " ], 'notify' => $relocate [ " notify " ],
'poll' => $relocate [ " poll " ], 'site-pubkey' => $relocate [ " sitepubkey " ]];
2018-11-08 16:28:29 +00:00
$condition = [ " (`id` = ?) OR (`nurl` = ?) " , $importer [ " id " ], Strings :: normaliseLink ( $old [ " url " ])];
2018-05-02 19:26:15 +00:00
2018-07-20 12:19:26 +00:00
DBA :: update ( 'contact' , $fields , $condition );
2017-11-08 00:37:53 +00:00
2020-07-25 11:48:52 +00:00
Contact :: updateAvatar ( $importer [ " id " ], $relocate [ " avatar " ], true );
2017-11-08 00:37:53 +00:00
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Contacts are updated.' );
2017-11-08 00:37:53 +00:00
/// @TODO
/// merge with current record, current contents have priority
/// update record, set url-updated
/// update profile photos
/// schedule a scan?
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Updates an item
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00: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 19:01:58 +00:00
* @ return mixed
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ todo set proper type - hints ( array ? )
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function updateContent ( $current , $item , $importer , $entrytype )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$changed = false ;
2018-01-24 22:22:31 +00:00
if ( self :: isEditedTimestampNewer ( $current , $item )) {
2017-11-08 00:37:53 +00:00
// do not accept (ignore) an earlier edit than one we currently have.
2018-01-27 02:38:34 +00:00
if ( DateTimeFormat :: utc ( $item [ " edited " ]) < $current [ " edited " ]) {
2017-11-08 00:37:53 +00:00
return false ;
}
2019-10-16 12:35:14 +00:00
$fields = [ 'title' => $item [ 'title' ] ? ? '' , 'body' => $item [ 'body' ] ? ? '' ,
2020-05-05 05:11:59 +00:00
'changed' => DateTimeFormat :: utcNow (),
2018-01-27 02:38:34 +00:00
'edited' => DateTimeFormat :: utc ( $item [ " edited " ])];
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$condition = [ " `uri` = ? AND `uid` IN (0, ?) " , $item [ " uri " ], $importer [ " importer_uid " ]];
2018-02-06 12:40:22 +00:00
Item :: update ( $fields , $condition );
2017-11-08 00:37:53 +00:00
$changed = true ;
}
return $changed ;
}
/**
2020-01-19 06:05:23 +00:00
* Detects the entry type of the item
2017-11-08 00:37:53 +00:00
*
* @ param array $importer Record of the importer user mixed with contact of the content
2017-11-08 22:02:50 +00:00
* @ param array $item the new item record
2017-11-08 00:37:53 +00:00
*
* @ return int Is it a toplevel entry , a comment or a relayed comment ?
2019-01-06 21:06:53 +00:00
* @ throws \Exception
* @ todo set proper type - hints ( array ? )
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function getEntryType ( $importer , $item )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
if ( $item [ " parent-uri " ] != $item [ " uri " ]) {
$community = false ;
2019-01-06 17:37:48 +00:00
if ( $importer [ " page-flags " ] == User :: PAGE_FLAGS_COMMUNITY || $importer [ " page-flags " ] == User :: PAGE_FLAGS_PRVGROUP ) {
2017-11-08 00:37:53 +00:00
$sql_extra = " " ;
$community = true ;
2018-10-29 21:20:46 +00:00
Logger :: log ( " possible community action " );
2017-11-08 00:37:53 +00:00
} 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 ;
2018-06-21 06:21:51 +00:00
$parent = Item :: selectFirst ([ 'parent-uri' ], [ 'uri' => $item [ " parent-uri " ]]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $parent )) {
2017-11-08 22:02:50 +00:00
$r = q (
" SELECT `item`.`forum_mode`, `item`.`wall` FROM `item`
2017-11-08 00:37:53 +00: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 " ,
2018-07-21 13:10:13 +00:00
DBA :: escape ( $parent [ " parent-uri " ]),
DBA :: escape ( $parent [ " parent-uri " ]),
DBA :: escape ( $parent [ " parent-uri " ]),
2017-11-08 00:37:53 +00:00
intval ( $importer [ " importer_uid " ])
);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $r )) {
2017-11-08 00:37:53 +00: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 ;
2018-10-29 21:20:46 +00:00
Logger :: log ( " not a community action " );
2017-11-08 00:37:53 +00:00
}
if ( $is_a_remote_action ) {
2018-05-13 08:34:33 +00:00
return DFRN :: REPLY_RC ;
2017-11-08 00:37:53 +00:00
} else {
2018-05-13 08:34:33 +00:00
return DFRN :: REPLY ;
2017-11-08 00:37:53 +00:00
}
} else {
2018-05-13 08:34:33 +00:00
return DFRN :: TOP_LEVEL ;
2017-11-08 00:37:53 +00:00
}
}
/**
2020-01-19 06:05:23 +00:00
* Send a " poke "
2017-11-08 00:37:53 +00:00
*
2019-04-29 04:40:58 +00:00
* @ param array $item The new item record
2017-11-08 22:02:50 +00:00
* @ param array $importer Record of the importer user mixed with contact of the content
2017-11-23 19:01:58 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ todo set proper type - hints ( array ? )
2017-11-08 00:37:53 +00:00
*/
2019-04-29 04:40:58 +00:00
private static function doPoke ( array $item , array $importer )
2017-11-08 22:02:50 +00:00
{
$verb = urldecode ( substr ( $item [ " verb " ], strpos ( $item [ " verb " ], " # " ) + 1 ));
2017-11-08 00:37:53 +00:00
if ( ! $verb ) {
return ;
}
2020-04-27 14:35:50 +00:00
$xo = XML :: parseString ( $item [ " object " ]);
2017-11-08 00:37:53 +00:00
2019-10-24 22:10:20 +00:00
if (( $xo -> type == Activity\ObjectType :: PERSON ) && ( $xo -> id )) {
2017-11-08 00:37:53 +00:00
// somebody was poked/prodded. Was it me?
2019-01-07 17:51:48 +00:00
$Blink = '' ;
2017-11-08 00:37:53 +00:00
foreach ( $xo -> link as $l ) {
$atts = $l -> attributes ();
switch ( $atts [ " rel " ]) {
case " alternate " :
$Blink = $atts [ " href " ];
break ;
default :
break ;
}
}
2019-12-30 22:00:08 +00:00
if ( $Blink && Strings :: compareLink ( $Blink , DI :: baseUrl () . " /profile/ " . $importer [ " nickname " ])) {
2018-07-20 12:19:26 +00:00
$author = DBA :: selectFirst ( 'contact' , [ 'name' , 'thumb' , 'url' ], [ 'id' => $item [ 'author-id' ]]);
2018-06-16 22:32:57 +00:00
2018-08-29 13:00:01 +00:00
$parent = Item :: selectFirst ([ 'id' ], [ 'uri' => $item [ 'parent-uri' ], 'uid' => $importer [ " importer_uid " ]]);
2020-05-27 12:19:06 +00:00
$item [ 'parent' ] = $parent [ 'id' ];
2018-08-29 13:00:01 +00:00
2017-11-08 00:37:53 +00:00
// send a notification
2017-11-08 22:02:50 +00:00
notification (
2018-01-15 13:05:12 +00:00
[
2020-02-04 21:15:33 +00:00
" type " => Type :: POKE ,
2017-11-08 00:37:53 +00:00
" notify_flags " => $importer [ " notify-flags " ],
" language " => $importer [ " language " ],
" to_name " => $importer [ " username " ],
" to_email " => $importer [ " email " ],
" uid " => $importer [ " importer_uid " ],
" item " => $item ,
2019-12-30 22:00:08 +00:00
" link " => DI :: baseUrl () . " /display/ " . urlencode ( $item [ 'guid' ]),
2018-06-16 22:32:57 +00:00
" source_name " => $author [ " name " ],
" source_link " => $author [ " url " ],
" source_photo " => $author [ " thumb " ],
2017-11-08 00:37:53 +00:00
" verb " => $item [ " verb " ],
" otype " => " person " ,
" activity " => $verb ,
2020-05-27 12:19:06 +00:00
" parent " => $item [ 'parent' ]]
2017-11-08 22:02:50 +00:00
);
2017-11-08 00:37:53 +00:00
}
}
}
/**
2020-01-19 06:05:23 +00:00
* Processes several actions , depending on the verb
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00: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 00:37:53 +00:00
*
* @ return bool Should the processing of the entries be continued ?
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ todo set proper type - hints ( array ? )
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function processVerbs ( $entrytype , $importer , & $item , & $is_like )
2017-11-08 22:02:50 +00:00
{
2018-10-30 13:58:45 +00:00
Logger :: log ( " Process verb " . $item [ " verb " ] . " and object-type " . $item [ " object-type " ] . " for entrytype " . $entrytype , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2019-06-10 10:36:42 +00:00
if (( $entrytype == DFRN :: TOP_LEVEL ) && ! empty ( $importer [ 'id' ])) {
2017-11-08 00:37:53 +00: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
2019-07-27 15:40:43 +00:00
$contact = Contact :: selectFirst ([], [ 'id' => $importer [ 'id' ]]);
2017-11-08 00:37:53 +00:00
2019-12-15 22:28:01 +00:00
$activity = DI :: activity ();
2019-10-23 00:05:11 +00:00
2017-11-08 00:37:53 +00:00
// Big question: Do we need these functions? They were part of the "consume_feed" function.
// This function once was responsible for DFRN and OStatus.
2019-10-23 22:25:43 +00:00
if ( $activity -> match ( $item [ " verb " ], Activity :: FOLLOW )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " New follower " );
2019-05-19 22:43:19 +00:00
Contact :: addRelationship ( $importer , $contact , $item );
2017-11-08 00:37:53 +00:00
return false ;
}
2019-10-23 22:25:43 +00:00
if ( $activity -> match ( $item [ " verb " ], Activity :: UNFOLLOW )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " Lost follower " );
2018-01-28 17:26:39 +00:00
Contact :: removeFollower ( $importer , $contact , $item );
2017-11-08 00:37:53 +00:00
return false ;
}
2019-10-23 22:25:43 +00:00
if ( $activity -> match ( $item [ " verb " ], Activity :: REQ_FRIEND )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " New friend request " );
2019-05-19 22:43:19 +00:00
Contact :: addRelationship ( $importer , $contact , $item , true );
2017-11-08 00:37:53 +00:00
return false ;
}
2019-10-23 22:25:43 +00:00
if ( $activity -> match ( $item [ " verb " ], Activity :: UNFRIEND )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " Lost sharer " );
2018-01-28 17:26:39 +00:00
Contact :: removeSharer ( $importer , $contact , $item );
2017-11-08 00:37:53 +00:00
return false ;
}
} else {
2019-10-23 22:25:43 +00:00
if (( $item [ " verb " ] == Activity :: LIKE )
|| ( $item [ " verb " ] == Activity :: DISLIKE )
|| ( $item [ " verb " ] == Activity :: ATTEND )
|| ( $item [ " verb " ] == Activity :: ATTENDNO )
|| ( $item [ " verb " ] == Activity :: ATTENDMAYBE )
2020-08-09 22:46:18 +00:00
|| ( $item [ " verb " ] == Activity :: ANNOUNCE )
2017-11-08 22:02:50 +00:00
) {
2017-11-08 00:37:53 +00:00
$is_like = true ;
2018-06-27 18:09:33 +00:00
$item [ " gravity " ] = GRAVITY_ACTIVITY ;
2017-11-08 00:37:53 +00:00
// only one like or dislike per person
// splitted into two queries for performance issues
2018-06-27 19:37:13 +00:00
$condition = [ 'uid' => $item [ " uid " ], 'author-id' => $item [ " author-id " ], 'gravity' => GRAVITY_ACTIVITY ,
2018-06-21 06:21:51 +00:00
'verb' => $item [ " verb " ], 'parent-uri' => $item [ " parent-uri " ]];
2018-06-27 19:37:13 +00:00
if ( Item :: exists ( $condition )) {
2017-11-08 00:37:53 +00:00
return false ;
}
2018-06-27 19:37:13 +00:00
$condition = [ 'uid' => $item [ " uid " ], 'author-id' => $item [ " author-id " ], 'gravity' => GRAVITY_ACTIVITY ,
2018-06-21 06:21:51 +00:00
'verb' => $item [ " verb " ], 'thr-parent' => $item [ " parent-uri " ]];
2018-06-27 19:37:13 +00:00
if ( Item :: exists ( $condition )) {
2017-11-08 00:37:53 +00:00
return false ;
}
2018-10-06 09:38:51 +00:00
// The owner of an activity must be the author
$item [ " owner-name " ] = $item [ " author-name " ];
$item [ " owner-link " ] = $item [ " author-link " ];
$item [ " owner-avatar " ] = $item [ " author-avatar " ];
$item [ " owner-id " ] = $item [ " author-id " ];
2017-11-08 00:37:53 +00:00
} else {
$is_like = false ;
}
2019-10-24 22:10:20 +00:00
if (( $item [ " verb " ] == Activity :: TAG ) && ( $item [ " object-type " ] == Activity\ObjectType :: TAGTERM )) {
2020-04-27 14:35:50 +00:00
$xo = XML :: parseString ( $item [ " object " ]);
$xt = XML :: parseString ( $item [ " target " ]);
2017-11-08 00:37:53 +00:00
2019-10-24 22:10:20 +00:00
if ( $xt -> type == Activity\ObjectType :: NOTE ) {
2020-04-17 13:34:29 +00:00
$item_tag = Item :: selectFirst ([ 'id' , 'uri-id' , 'tag' ], [ 'uri' => $xt -> id , 'uid' => $importer [ " importer_uid " ]]);
2018-07-19 22:39:05 +00:00
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item_tag )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " Query failed to execute, no result returned in " . __FUNCTION__ );
2017-11-08 00:37:53 +00:00
return false ;
}
// extract tag, if not duplicate, add to parent item
if ( $xo -> content ) {
2020-04-17 13:34:29 +00:00
Tag :: store ( $item_tag [ 'uri-id' ], Tag :: HASHTAG , $xo -> content );
2017-11-08 00:37:53 +00:00
}
}
}
}
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Processes the link elements
2017-11-08 00:37:53 +00:00
*
* @ param object $links link elements
2017-11-08 22:02:50 +00:00
* @ param array $item the item record
2017-11-23 19:01:58 +00:00
* @ return void
2017-11-08 00:37:53 +00:00
* @ todo set proper type - hints
*/
2017-11-23 19:01:58 +00:00
private static function parseLinks ( $links , & $item )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$rel = " " ;
$href = " " ;
$type = " " ;
$length = " 0 " ;
$title = " " ;
2017-11-23 19:01:58 +00:00
foreach ( $links as $link ) {
foreach ( $link -> attributes as $attributes ) {
2018-01-19 16:50:43 +00: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 00:37:53 +00:00
}
}
if (( $rel != " " ) && ( $href != " " )) {
switch ( $rel ) {
case " alternate " :
$item [ " plink " ] = $href ;
break ;
case " enclosure " :
2018-08-31 05:08:22 +00:00
if ( ! empty ( $item [ " attach " ])) {
2017-11-08 00:37:53 +00:00
$item [ " attach " ] .= " , " ;
2018-08-31 05:08:22 +00:00
} else {
$item [ " attach " ] = " " ;
2017-11-08 00:37:53 +00:00
}
$item [ " attach " ] .= '[attach]href="' . $href . '" length="' . $length . '" type="' . $type . '" title="' . $title . '"[/attach]' ;
break ;
}
}
}
}
/**
2020-01-19 06:05:23 +00:00
* Processes the entry elements which contain the items and comments
2017-11-08 00:37:53 +00:00
*
2017-11-23 19:01:58 +00: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
2019-01-21 21:51:59 +00:00
* @ param string $xml xml
2017-11-23 19:01:58 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
* @ todo Add type - hints
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function processEntry ( $header , $xpath , $entry , $importer , $xml )
2017-11-08 22:02:50 +00:00
{
2018-10-29 21:20:46 +00:00
Logger :: log ( " Processing entries " );
2017-11-08 00:37:53 +00:00
$item = $header ;
2018-08-05 10:23:57 +00:00
$item [ " protocol " ] = Conversation :: PARCEL_DFRN ;
2017-11-08 00:37:53 +00:00
$item [ " source " ] = $xml ;
// Get the uri
2018-07-08 11:46:05 +00:00
$item [ " uri " ] = XML :: getFirstNodeValue ( $xpath , " atom:id/text() " , $entry );
2017-11-08 00:37:53 +00:00
2018-07-08 11:46:05 +00:00
$item [ " edited " ] = XML :: getFirstNodeValue ( $xpath , " atom:updated/text() " , $entry );
2017-11-08 00:37:53 +00:00
2018-07-07 18:14:16 +00:00
$current = Item :: selectFirst ([ 'id' , 'uid' , 'edited' , 'body' ],
2018-02-14 04:58:46 +00:00
[ 'uri' => $item [ " uri " ], 'uid' => $importer [ " importer_uid " ]]
2017-11-08 00:37:53 +00:00
);
// Is there an existing item?
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $current ) && ! self :: isEditedTimestampNewer ( $current , $item )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item " . $item [ " uri " ] . " ( " . $item [ 'edited' ] . " ) already existed. " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return ;
}
// Fetch the owner
2018-08-26 07:56:33 +00:00
$owner = self :: fetchauthor ( $xpath , $entry , $importer , " dfrn:owner " , true , $xml );
2017-11-08 00:37:53 +00:00
2018-07-16 05:48:51 +00:00
$owner_unknown = ( isset ( $owner [ " contact-unknown " ]) && $owner [ " contact-unknown " ]);
2018-07-31 05:54:25 +00:00
$item [ " owner-name " ] = $owner [ " name " ];
2017-11-08 00:37:53 +00:00
$item [ " owner-link " ] = $owner [ " link " ];
2018-07-31 05:54:25 +00:00
$item [ " owner-avatar " ] = $owner [ " avatar " ];
2018-06-16 22:32:57 +00:00
$item [ " owner-id " ] = Contact :: getIdForURL ( $owner [ " link " ], 0 );
2017-11-08 00:37:53 +00:00
// fetch the author
2018-08-26 07:56:33 +00:00
$author = self :: fetchauthor ( $xpath , $entry , $importer , " atom:author " , true , $xml );
2017-11-08 00:37:53 +00:00
2018-07-31 05:54:25 +00:00
$item [ " author-name " ] = $author [ " name " ];
2017-11-08 00:37:53 +00:00
$item [ " author-link " ] = $author [ " link " ];
2018-07-31 05:54:25 +00:00
$item [ " author-avatar " ] = $author [ " avatar " ];
2018-06-16 22:32:57 +00:00
$item [ " author-id " ] = Contact :: getIdForURL ( $author [ " link " ], 0 );
2017-11-08 00:37:53 +00:00
2018-07-08 11:46:05 +00:00
$item [ " title " ] = XML :: getFirstNodeValue ( $xpath , " atom:title/text() " , $entry );
2017-11-08 00:37:53 +00:00
2018-07-08 11:46:05 +00:00
$item [ " created " ] = XML :: getFirstNodeValue ( $xpath , " atom:published/text() " , $entry );
2017-11-08 00:37:53 +00:00
2018-07-08 11:46:05 +00:00
$item [ " body " ] = XML :: getFirstNodeValue ( $xpath , " dfrn:env/text() " , $entry );
2018-01-15 13:05:12 +00:00
$item [ " body " ] = str_replace ([ ' ' , " \t " , " \r " , " \n " ], [ '' , '' , '' , '' ], $item [ " body " ]);
2020-03-28 19:41:25 +00:00
2020-01-07 22:00:39 +00:00
$item [ " body " ] = Strings :: base64UrlDecode ( $item [ " body " ]);
2017-11-08 00:37:53 +00:00
2018-01-28 17:36:37 +00:00
$item [ " body " ] = BBCode :: limitBodySize ( $item [ " body " ]);
2017-11-08 00:37:53 +00: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;
2018-07-08 11:46:05 +00:00
$item [ " location " ] = XML :: getFirstNodeValue ( $xpath , " dfrn:location/text() " , $entry );
2017-11-08 00:37:53 +00:00
2018-07-08 11:46:05 +00:00
$item [ " coord " ] = XML :: getFirstNodeValue ( $xpath , " georss:point " , $entry );
2017-11-08 00:37:53 +00:00
2018-07-08 11:46:05 +00:00
$item [ " private " ] = XML :: getFirstNodeValue ( $xpath , " dfrn:private/text() " , $entry );
2017-11-08 00:37:53 +00:00
2020-03-02 22:35:25 +00:00
$unlisted = XML :: getFirstNodeValue ( $xpath , " dfrn:unlisted/text() " , $entry );
if ( ! empty ( $unlisted ) && ( $item [ 'private' ] != Item :: PRIVATE )) {
$item [ 'private' ] = Item :: UNLISTED ;
}
2018-07-08 11:46:05 +00:00
$item [ " extid " ] = XML :: getFirstNodeValue ( $xpath , " dfrn:extid/text() " , $entry );
2017-11-08 00:37:53 +00:00
2018-07-08 11:46:05 +00:00
if ( XML :: getFirstNodeValue ( $xpath , " dfrn:bookmark/text() " , $entry ) == " true " ) {
2018-07-19 13:52:05 +00:00
$item [ " post-type " ] = Item :: PT_PAGE ;
2017-11-08 00:37:53 +00:00
}
$notice_info = $xpath -> query ( " statusnet:notice_info " , $entry );
2019-01-24 18:54:45 +00:00
if ( $notice_info && ( $notice_info -> length > 0 )) {
foreach ( $notice_info -> item ( 0 ) -> attributes as $attributes ) {
2017-11-08 00:37:53 +00:00
if ( $attributes -> name == " source " ) {
$item [ " app " ] = strip_tags ( $attributes -> textContent );
}
}
}
2018-07-08 11:46:05 +00:00
$item [ " guid " ] = XML :: getFirstNodeValue ( $xpath , " dfrn:diaspora_guid/text() " , $entry );
2017-11-08 00:37:53 +00:00
2020-04-14 16:52:53 +00:00
$item [ 'uri-id' ] = ItemURI :: insert ([ 'uri' => $item [ 'uri' ], 'guid' => $item [ 'guid' ]]);
2020-04-20 05:43:13 +00:00
Tag :: storeFromBody ( $item [ 'uri-id' ], $item [ " body " ]);
2020-04-15 16:37:09 +00:00
2018-01-28 11:18:08 +00:00
// We store the data from "dfrn:diaspora_signature" in a different table, this is done in "Item::insert"
2018-11-05 12:40:18 +00:00
$dsprsig = XML :: unescape ( XML :: getFirstNodeValue ( $xpath , " dfrn:diaspora_signature/text() " , $entry ));
2017-11-08 00:37:53 +00:00
if ( $dsprsig != " " ) {
2020-05-13 05:48:26 +00:00
$signature = json_decode ( base64_decode ( $dsprsig ));
// We don't store the old style signatures anymore that also contained the "signature" and "signer"
if ( ! empty ( $signature -> signed_text ) && empty ( $signature -> signature ) && empty ( $signature -> signer )) {
$item [ " diaspora_signed_text " ] = $signature -> signed_text ;
}
2017-11-08 00:37:53 +00:00
}
2018-07-08 11:46:05 +00:00
$item [ " verb " ] = XML :: getFirstNodeValue ( $xpath , " activity:verb/text() " , $entry );
2017-11-08 00:37:53 +00:00
2018-07-08 11:46:05 +00:00
if ( XML :: getFirstNodeValue ( $xpath , " activity:object-type/text() " , $entry ) != " " ) {
$item [ " object-type " ] = XML :: getFirstNodeValue ( $xpath , " activity:object-type/text() " , $entry );
2017-11-08 00:37:53 +00:00
}
$object = $xpath -> query ( " activity:object " , $entry ) -> item ( 0 );
2017-11-23 19:01:58 +00:00
$item [ " object " ] = self :: transformActivity ( $xpath , $object , " object " );
2017-11-08 00:37:53 +00:00
if ( trim ( $item [ " object " ]) != " " ) {
2020-04-27 14:35:50 +00:00
$r = XML :: parseString ( $item [ " object " ]);
2017-11-08 00:37:53 +00:00
if ( isset ( $r -> type )) {
$item [ " object-type " ] = $r -> type ;
}
}
$target = $xpath -> query ( " activity:target " , $entry ) -> item ( 0 );
2017-11-23 19:01:58 +00:00
$item [ " target " ] = self :: transformActivity ( $xpath , $target , " target " );
2017-11-08 00:37:53 +00:00
$categories = $xpath -> query ( " atom:category " , $entry );
if ( $categories ) {
2017-11-23 19:01:58 +00:00
foreach ( $categories as $category ) {
2017-11-08 00:37:53 +00:00
$term = " " ;
$scheme = " " ;
2017-11-23 19:01:58 +00:00
foreach ( $category -> attributes as $attributes ) {
2017-11-08 00:37:53 +00: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 " )) {
2020-05-16 16:28:15 +00:00
$termurl = array_pop ( $parts );
2020-08-22 05:59:19 +00:00
$termurl = array_pop ( $parts ) . ':' . $termurl ;
2020-04-20 05:43:13 +00:00
Tag :: store ( $item [ 'uri-id' ], Tag :: IMPLICIT_MENTION , $term , $termurl );
2017-11-08 00:37:53 +00:00
}
}
}
}
$links = $xpath -> query ( " atom:link " , $entry );
if ( $links ) {
2017-11-23 19:01:58 +00:00
self :: parseLinks ( $links , $item );
2017-11-08 00:37:53 +00:00
}
2018-07-08 11:46:05 +00:00
$item [ 'conversation-uri' ] = XML :: getFirstNodeValue ( $xpath , 'ostatus:conversation/text()' , $entry );
2017-11-08 00:37:53 +00:00
$conv = $xpath -> query ( 'ostatus:conversation' , $entry );
2019-01-24 18:54:45 +00:00
if ( is_object ( $conv -> item ( 0 ))) {
foreach ( $conv -> item ( 0 ) -> attributes as $attributes ) {
2017-11-08 00:37:53 +00: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 );
2019-01-24 18:54:45 +00:00
if ( is_object ( $inreplyto -> item ( 0 ))) {
foreach ( $inreplyto -> item ( 0 ) -> attributes as $attributes ) {
2017-11-08 00:37:53 +00: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 19:01:58 +00:00
$entrytype = self :: getEntryType ( $importer , $item );
2017-11-08 00:37:53 +00:00
// Now assign the rest of the values that depend on the type of the message
2018-05-13 08:34:33 +00:00
if ( in_array ( $entrytype , [ DFRN :: REPLY , DFRN :: REPLY_RC ])) {
2017-11-08 00:37:53 +00:00
if ( ! isset ( $item [ " object-type " ])) {
2019-10-24 22:10:20 +00:00
$item [ " object-type " ] = Activity\ObjectType :: COMMENT ;
2017-11-08 00:37:53 +00:00
}
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 " ];
}
}
2019-12-05 05:28:28 +00:00
// Ensure to have the correct share data
$item = Item :: addShareDataFromOriginal ( $item );
2018-05-13 08:34:33 +00:00
if ( $entrytype == DFRN :: REPLY_RC ) {
2017-11-08 00:37:53 +00:00
$item [ " wall " ] = 1 ;
2018-05-13 08:34:33 +00:00
} elseif ( $entrytype == DFRN :: TOP_LEVEL ) {
2017-11-08 00:37:53 +00:00
if ( ! isset ( $item [ " object-type " ])) {
2019-10-24 22:10:20 +00:00
$item [ " object-type " ] = Activity\ObjectType :: NOTE ;
2017-11-08 00:37:53 +00:00
}
// Is it an event?
2019-10-24 22:10:20 +00:00
if (( $item [ " object-type " ] == Activity\ObjectType :: EVENT ) && ! $owner_unknown ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item " . $item [ " uri " ] . " seems to contain an event. " , Logger :: DEBUG );
2018-03-17 01:45:02 +00:00
$ev = Event :: fromBBCode ( $item [ " body " ]);
2018-11-30 14:06:22 +00:00
if (( ! empty ( $ev [ 'desc' ]) || ! empty ( $ev [ 'summary' ])) && ! empty ( $ev [ 'start' ])) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Event in item " . $item [ " uri " ] . " was found. " , Logger :: DEBUG );
2018-01-19 16:50:43 +00:00
$ev [ " cid " ] = $importer [ " id " ];
2018-03-30 06:20:00 +00:00
$ev [ " uid " ] = $importer [ " importer_uid " ];
2018-01-19 16:50:43 +00:00
$ev [ " uri " ] = $item [ " uri " ];
$ev [ " edited " ] = $item [ " edited " ];
$ev [ " private " ] = $item [ " private " ];
$ev [ " guid " ] = $item [ " guid " ];
2018-07-24 14:01:31 +00:00
$ev [ " plink " ] = $item [ " plink " ];
2017-11-08 00:37:53 +00:00
2018-08-19 12:46:11 +00:00
$condition = [ 'uri' => $item [ " uri " ], 'uid' => $importer [ " importer_uid " ]];
$event = DBA :: selectFirst ( 'event' , [ 'id' ], $condition );
if ( DBA :: isResult ( $event )) {
$ev [ " id " ] = $event [ " id " ];
2017-11-08 00:37:53 +00:00
}
2018-03-17 01:45:02 +00:00
$event_id = Event :: store ( $ev );
2018-10-30 13:58:45 +00:00
Logger :: log ( " Event " . $event_id . " was stored " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return ;
}
}
}
2017-11-23 19:01:58 +00:00
if ( ! self :: processVerbs ( $entrytype , $importer , $item , $is_like )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Exiting because 'processVerbs' told us so " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return ;
}
2018-07-16 06:34:12 +00:00
// This check is done here to be able to receive connection requests in "processVerbs"
if (( $entrytype == DFRN :: TOP_LEVEL ) && $owner_unknown ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item won't be stored because user " . $importer [ " importer_uid " ] . " doesn't follow " . $item [ " owner-link " ] . " . " , Logger :: DEBUG );
2018-07-16 06:34:12 +00:00
return ;
}
2017-11-08 00:37:53 +00:00
// Update content if 'updated' changes
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $current )) {
2018-02-14 04:58:46 +00:00
if ( self :: updateContent ( $current , $item , $importer , $entrytype )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item " . $item [ " uri " ] . " was updated. " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
} else {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item " . $item [ " uri " ] . " already existed. " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
}
return ;
}
2018-05-13 08:34:33 +00:00
if ( in_array ( $entrytype , [ DFRN :: REPLY , DFRN :: REPLY_RC ])) {
2020-09-13 14:15:28 +00:00
// Will be overwritten for sharing accounts in Item::insert
if ( empty ( $item [ 'post-type' ]) && ( $entrytype == DFRN :: REPLY )) {
$item [ 'post-type' ] = Item :: PT_COMMENT ;
}
2018-01-28 11:18:08 +00:00
$posted_id = Item :: insert ( $item );
2017-11-08 00:37:53 +00:00
if ( $posted_id ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Reply from contact " . $item [ " contact-id " ] . " was stored with id " . $posted_id , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2018-04-24 14:58:39 +00:00
if ( $item [ 'uid' ] == 0 ) {
2018-04-24 13:21:25 +00:00
Item :: distribute ( $posted_id );
}
2017-11-08 00:37:53 +00:00
return true ;
}
2018-05-13 08:34:33 +00:00
} else { // $entrytype == DFRN::TOP_LEVEL
2018-04-22 10:58:03 +00:00
if (( $importer [ " uid " ] == 0 ) && ( $importer [ " importer_uid " ] != 0 )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Contact " . $importer [ " id " ] . " isn't known to user " . $importer [ " importer_uid " ] . " . The post will be ignored. " , Logger :: DEBUG );
2018-03-30 06:20:00 +00:00
return ;
}
2018-11-08 15:46:50 +00:00
if ( ! Strings :: compareLink ( $item [ " owner-link " ], $importer [ " url " ])) {
2017-11-08 00:37:53 +00: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 11:18:08 +00:00
* the tgroup delivery code called from Item :: insert will correct it if it ' s a forum ,
2017-11-08 00:37:53 +00:00
* but we ' re going to unconditionally correct it here so that the post will always be owned by our contact .
*/
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Correcting item owner.' , Logger :: DEBUG );
2018-06-16 22:32:57 +00:00
$item [ " owner-link " ] = $importer [ " url " ];
$item [ " owner-id " ] = Contact :: getIdForURL ( $importer [ " url " ], 0 );
2017-11-08 00:37:53 +00:00
}
2018-07-25 02:53:46 +00:00
if (( $importer [ " rel " ] == Contact :: FOLLOWER ) && ( ! self :: tgroupCheck ( $importer [ " importer_uid " ], $item ))) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Contact " . $importer [ " id " ] . " is only follower and tgroup check was negative. " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return ;
}
// This is my contact on another system, but it's really me.
// Turn this into a wall post.
2018-01-28 11:18:08 +00:00
$notify = Item :: isRemoteSelf ( $importer , $item );
2017-11-08 00:37:53 +00:00
2020-05-12 21:49:12 +00:00
$posted_id = Item :: insert ( $item , $notify );
2017-11-08 00:37:53 +00:00
2018-05-04 21:12:13 +00:00
if ( $notify ) {
2018-05-04 21:33:15 +00:00
$posted_id = $notify ;
2018-05-04 21:12:13 +00:00
}
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item was stored with id " . $posted_id , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2018-04-24 13:21:25 +00:00
if ( $item [ 'uid' ] == 0 ) {
Item :: distribute ( $posted_id );
}
2019-10-23 22:25:43 +00:00
if ( stristr ( $item [ " verb " ], Activity :: POKE )) {
2019-04-29 04:40:58 +00:00
$item [ 'id' ] = $posted_id ;
self :: doPoke ( $item , $importer );
2017-11-23 19:01:58 +00:00
}
2017-11-08 00:37:53 +00:00
}
}
/**
2020-01-19 06:05:23 +00:00
* Deletes items
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param object $xpath XPath object
2017-11-08 00:37:53 +00:00
* @ param object $deletion deletion elements
2017-11-08 22:02:50 +00:00
* @ param array $importer Record of the importer user mixed with contact of the content
2017-11-23 19:01:58 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Exception
* @ todo set proper type - hints
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function processDeletion ( $xpath , $deletion , $importer )
2017-11-08 22:02:50 +00:00
{
2018-10-29 21:20:46 +00:00
Logger :: log ( " Processing deletions " );
2018-02-14 04:58:46 +00:00
$uri = null ;
2017-01-26 08:38:52 +00:00
2017-11-23 19:01:58 +00:00
foreach ( $deletion -> attributes as $attributes ) {
2017-11-08 00:37:53 +00:00
if ( $attributes -> name == " ref " ) {
$uri = $attributes -> textContent ;
}
}
if ( ! $uri || ! $importer [ " id " ]) {
return false ;
}
2018-07-07 18:14:16 +00:00
$condition = [ 'uri' => $uri , 'uid' => $importer [ " importer_uid " ]];
2020-05-27 12:19:06 +00:00
$item = Item :: selectFirst ([ 'id' , 'parent' , 'contact-id' , 'file' , 'deleted' , 'gravity' ], $condition );
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item with uri " . $uri . " for user " . $importer [ " importer_uid " ] . " wasn't found. " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return ;
2018-02-06 12:40:22 +00:00
}
2017-11-08 00:37:53 +00:00
2018-07-07 18:14:16 +00:00
if ( strstr ( $item [ 'file' ], '[' )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item with uri " . $uri . " for user " . $importer [ " importer_uid " ] . " is filed. So it won't be deleted. " , Logger :: DEBUG );
2018-07-07 18:14:16 +00:00
return ;
}
2018-03-01 21:52:36 +00:00
// When it is a starting post it has to belong to the person that wants to delete it
2020-05-27 12:19:06 +00:00
if (( $item [ 'gravity' ] == GRAVITY_PARENT ) && ( $item [ 'contact-id' ] != $importer [ " id " ])) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item with uri " . $uri . " don't belong to contact " . $importer [ " id " ] . " - ignoring deletion. " , Logger :: DEBUG );
2018-03-01 21:52:36 +00:00
return ;
}
// Comments can be deleted by the thread owner or comment owner
2020-05-27 12:19:06 +00:00
if (( $item [ 'gravity' ] != GRAVITY_PARENT ) && ( $item [ 'contact-id' ] != $importer [ " id " ])) {
2018-03-01 21:52:36 +00:00
$condition = [ 'id' => $item [ 'parent' ], 'contact-id' => $importer [ " id " ]];
2018-06-27 19:37:13 +00:00
if ( ! Item :: exists ( $condition )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item with uri " . $uri . " wasn't found or mustn't be deleted by contact " . $importer [ " id " ] . " - ignoring deletion. " , Logger :: DEBUG );
2018-03-01 21:52:36 +00:00
return ;
}
}
2018-05-29 05:22:57 +00:00
if ( $item [ " deleted " ]) {
2018-02-06 12:40:22 +00:00
return ;
}
2018-01-18 06:54:44 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( 'deleting item ' . $item [ 'id' ] . ' uri=' . $uri , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2020-03-03 06:47:28 +00:00
Item :: markForDeletion ([ 'id' => $item [ 'id' ]]);
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Imports a DFRN message
2017-11-08 00:37:53 +00:00
*
2017-12-17 20:27:50 +00: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 00:37:53 +00:00
* @ return integer Import status
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
* @ todo set proper type - hints
2017-11-08 00:37:53 +00:00
*/
2017-11-08 22:02:50 +00:00
public static function import ( $xml , $importer , $sort_by_date = false )
{
2017-11-08 00:37:53 +00:00
if ( $xml == " " ) {
return 400 ;
}
$doc = new DOMDocument ();
@ $doc -> loadXML ( $xml );
2017-12-17 20:24:57 +00:00
$xpath = new DOMXPath ( $doc );
2019-10-24 22:32:35 +00:00
$xpath -> registerNamespace ( " atom " , ActivityNamespace :: ATOM1 );
$xpath -> registerNamespace ( " thr " , ActivityNamespace :: THREAD );
$xpath -> registerNamespace ( " at " , ActivityNamespace :: TOMB );
$xpath -> registerNamespace ( " media " , ActivityNamespace :: MEDIA );
$xpath -> registerNamespace ( " dfrn " , ActivityNamespace :: DFRN );
$xpath -> registerNamespace ( " activity " , ActivityNamespace :: ACTIVITY );
$xpath -> registerNamespace ( " georss " , ActivityNamespace :: GEORSS );
$xpath -> registerNamespace ( " poco " , ActivityNamespace :: POCO );
$xpath -> registerNamespace ( " ostatus " , ActivityNamespace :: OSTATUS );
$xpath -> registerNamespace ( " statusnet " , ActivityNamespace :: STATUSNET );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$header = [];
2018-03-30 06:20:00 +00:00
$header [ " uid " ] = $importer [ " importer_uid " ];
2018-08-11 20:40:44 +00:00
$header [ " network " ] = Protocol :: DFRN ;
2017-11-08 00:37:53 +00:00
$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-10-30 13:58:45 +00:00
Logger :: log ( " Import DFRN message for user " . $importer [ " importer_uid " ] . " from contact " . $importer [ " id " ], Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2018-04-21 21:59:02 +00:00
// is it a public forum? Private forums aren't exposed with this method
2018-07-08 11:46:05 +00:00
$forum = intval ( XML :: getFirstNodeValue ( $xpath , " /atom:feed/dfrn:community/text() " ));
2018-04-21 21:59:02 +00:00
2017-11-08 00:37:53 +00:00
// The account type is new since 3.5.1
if ( $xpath -> query ( " /atom:feed/dfrn:account_type " ) -> length > 0 ) {
2019-01-12 16:09:27 +00:00
// Hint: We are using separate update calls for uid=0 and uid!=0 since a combined call is bad for the database performance
2018-07-08 11:46:05 +00:00
$accounttype = intval ( XML :: getFirstNodeValue ( $xpath , " /atom:feed/dfrn:account_type/text() " ));
2017-11-08 00:37:53 +00:00
if ( $accounttype != $importer [ " contact-type " ]) {
2019-01-12 16:09:27 +00:00
DBA :: update ( 'contact' , [ 'contact-type' => $accounttype ], [ 'id' => $importer [ 'id' ]]);
// Updating the public contact as well
DBA :: update ( 'contact' , [ 'contact-type' => $accounttype ], [ 'uid' => 0 , 'nurl' => $importer [ 'nurl' ]]);
2017-11-08 00:37:53 +00:00
}
2018-04-21 21:59:02 +00:00
// A forum contact can either have set "forum" or "prv" - but not both
2019-01-06 22:08:35 +00:00
if ( $accounttype == User :: ACCOUNT_TYPE_COMMUNITY ) {
2019-01-12 16:09:27 +00:00
// It's a forum, so either set the public or private forum flag
$condition = [ '(`forum` != ? OR `prv` != ?) AND `id` = ?' , $forum , ! $forum , $importer [ 'id' ]];
DBA :: update ( 'contact' , [ 'forum' => $forum , 'prv' => ! $forum ], $condition );
// Updating the public contact as well
$condition = [ '(`forum` != ? OR `prv` != ?) AND `uid` = 0 AND `nurl` = ?' , $forum , ! $forum , $importer [ 'nurl' ]];
2018-07-20 12:19:26 +00:00
DBA :: update ( 'contact' , [ 'forum' => $forum , 'prv' => ! $forum ], $condition );
2019-01-12 16:09:27 +00:00
} else {
// It's not a forum, so remove the flags
$condition = [ '(`forum` OR `prv`) AND `id` = ?' , $importer [ 'id' ]];
DBA :: update ( 'contact' , [ 'forum' => false , 'prv' => false ], $condition );
// Updating the public contact as well
$condition = [ '(`forum` OR `prv`) AND `uid` = 0 AND `nurl` = ?' , $importer [ 'nurl' ]];
DBA :: update ( 'contact' , [ 'forum' => false , 'prv' => false ], $condition );
2018-04-21 21:59:02 +00:00
}
} elseif ( $forum != $importer [ " forum " ]) { // Deprecated since 3.5.1
2018-01-15 13:05:12 +00:00
$condition = [ '`forum` != ? AND `id` = ?' , $forum , $importer [ " id " ]];
2018-07-20 12:19:26 +00:00
DBA :: update ( 'contact' , [ 'forum' => $forum ], $condition );
2019-01-12 16:09:27 +00:00
// Updating the public contact as well
$condition = [ '`forum` != ? AND `uid` = 0 AND `nurl` = ?' , $forum , $importer [ 'nurl' ]];
DBA :: update ( 'contact' , [ 'forum' => $forum ], $condition );
2017-11-08 00:37:53 +00:00
}
2018-04-21 21:59:02 +00:00
2017-11-08 00:37:53 +00:00
// We are processing relocations even if we are ignoring a contact
$relocations = $xpath -> query ( " /atom:feed/dfrn:relocate " );
2017-11-23 19:01:58 +00:00
foreach ( $relocations as $relocation ) {
self :: processRelocation ( $xpath , $relocation , $importer );
2017-11-08 00:37:53 +00:00
}
2018-04-01 05:07:35 +00: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 00:37:53 +00:00
2018-04-01 05:07:35 +00:00
$suggestions = $xpath -> query ( " /atom:feed/dfrn:suggest " );
foreach ( $suggestions as $suggestion ) {
self :: processSuggestion ( $xpath , $suggestion , $importer );
}
2017-11-08 00:37:53 +00:00
}
$deletions = $xpath -> query ( " /atom:feed/at:deleted-entry " );
2017-11-23 19:01:58 +00:00
foreach ( $deletions as $deletion ) {
self :: processDeletion ( $xpath , $deletion , $importer );
2017-11-08 00:37:53 +00:00
}
if ( ! $sort_by_date ) {
$entries = $xpath -> query ( " /atom:feed/atom:entry " );
2017-11-23 19:01:58 +00:00
foreach ( $entries as $entry ) {
self :: processEntry ( $header , $xpath , $entry , $importer , $xml );
2017-11-08 00:37:53 +00:00
}
} else {
2018-01-15 13:05:12 +00:00
$newentries = [];
2017-11-08 00:37:53 +00:00
$entries = $xpath -> query ( " /atom:feed/atom:entry " );
2017-11-23 19:01:58 +00:00
foreach ( $entries as $entry ) {
2018-07-08 11:46:05 +00:00
$created = XML :: getFirstNodeValue ( $xpath , " atom:published/text() " , $entry );
2017-11-08 00:37:53 +00:00
$newentries [ strtotime ( $created )] = $entry ;
}
// Now sort after the publishing date
ksort ( $newentries );
2017-11-23 19:01:58 +00:00
foreach ( $newentries as $entry ) {
self :: processEntry ( $header , $xpath , $entry , $importer , $xml );
2017-11-08 00:37:53 +00:00
}
}
2018-10-30 13:58:45 +00:00
Logger :: log ( " Import done for user " . $importer [ " importer_uid " ] . " from contact " . $importer [ " id " ], Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return 200 ;
}
2018-01-13 14:36:21 +00:00
2018-01-20 23:52:54 +00:00
/**
2020-01-19 06:05:23 +00:00
* Returns the activity verb
2018-01-20 23:52:54 +00:00
*
* @ param array $item Item array
*
* @ return string activity verb
*/
private static function constructVerb ( array $item )
{
if ( $item [ 'verb' ]) {
return $item [ 'verb' ];
}
2019-10-23 22:25:43 +00:00
return Activity :: POST ;
2018-01-20 23:52:54 +00:00
}
2018-01-24 20:27:32 +00: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 ;
}
2018-08-19 12:46:11 +00:00
$user = DBA :: selectFirst ( 'user' , [ 'page-flags' , 'nickname' ], [ 'uid' => $uid ]);
if ( ! DBA :: isResult ( $user )) {
2018-01-24 20:27:32 +00:00
return false ;
}
2019-01-06 17:37:48 +00:00
$community_page = ( $user [ 'page-flags' ] == User :: PAGE_FLAGS_COMMUNITY );
$prvgroup = ( $user [ 'page-flags' ] == User :: PAGE_FLAGS_PRVGROUP );
2018-01-24 20:27:32 +00:00
2019-12-30 22:00:08 +00:00
$link = Strings :: normaliseLink ( DI :: baseUrl () . '/profile/' . $user [ 'nickname' ]);
2018-01-24 20:27:32 +00:00
/*
* Diaspora uses their own hardwired link URL in @- tags
* instead of the one we supply with webfinger
*/
2019-12-30 22:00:08 +00:00
$dlink = Strings :: normaliseLink ( DI :: baseUrl () . '/u/' . $user [ 'nickname' ]);
2018-01-24 20:27:32 +00:00
$cnt = preg_match_all ( '/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism' , $item [ 'body' ], $matches , PREG_SET_ORDER );
if ( $cnt ) {
foreach ( $matches as $mtch ) {
2018-11-08 15:46:50 +00:00
if ( Strings :: compareLink ( $link , $mtch [ 1 ]) || Strings :: compareLink ( $dlink , $mtch [ 1 ])) {
2018-01-24 20:27:32 +00:00
$mention = true ;
2018-10-29 21:20:46 +00:00
Logger :: log ( 'mention found: ' . $mtch [ 2 ]);
2018-01-24 20:27:32 +00:00
}
}
}
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 .
*
2019-01-06 21:06:53 +00:00
* @ param $existing
* @ param $update
* @ return bool
* @ throws \Exception
2018-01-24 20:27:32 +00:00
*/
2018-01-24 22:22:31 +00:00
private static function isEditedTimestampNewer ( $existing , $update )
2018-01-24 20:27:32 +00:00
{
2018-11-30 14:06:22 +00:00
if ( empty ( $existing [ 'edited' ])) {
2018-01-24 20:27:32 +00:00
return true ;
}
2018-11-30 14:06:22 +00:00
if ( empty ( $update [ 'edited' ])) {
2018-01-24 20:27:32 +00:00
return false ;
}
2018-01-27 02:38:34 +00:00
$existing_edited = DateTimeFormat :: utc ( $existing [ 'edited' ]);
$update_edited = DateTimeFormat :: utc ( $update [ 'edited' ]);
2018-01-24 20:27:32 +00:00
return ( strcmp ( $existing_edited , $update_edited ) < 0 );
}
2019-07-27 11:09:12 +00:00
/**
* Checks if the given contact url does support DFRN
*
* @ param string $url profile url
* @ return boolean
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
*/
2020-08-06 18:53:45 +00:00
public static function isSupportedByContactUrl ( $url )
2019-07-27 11:09:12 +00:00
{
2020-08-06 18:53:45 +00:00
$probe = Probe :: uri ( $url , Protocol :: DFRN );
2019-07-27 11:09:12 +00:00
return $probe [ 'network' ] == Protocol :: DFRN ;
}
2017-11-08 00:37:53 +00:00
}