2017-11-08 00:37:53 +00:00
< ? php
/**
2022-01-02 07:27:47 +00:00
* @ copyright Copyright ( C ) 2010 - 2022 , the Friendica project
2020-02-09 15:18:46 +00:00
*
* @ 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 ;
2022-06-22 09:35:15 +00:00
use DOMElement ;
use DOMNode ;
2018-07-20 02:15:21 +00:00
use DOMXPath ;
2022-10-17 10:37:48 +00:00
use Friendica\App ;
2018-02-05 00:23:49 +00:00
use Friendica\Content\Text\BBCode ;
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 ;
2021-01-09 18:17:49 +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 ;
2021-03-10 22:31:33 +00:00
use Friendica\Model\GServer ;
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 ;
2021-01-23 09:53:44 +00:00
use Friendica\Model\Notification ;
2021-10-03 15:02:20 +00:00
use Friendica\Model\Photo ;
2020-10-31 13:26:08 +00:00
use Friendica\Model\Post ;
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 ;
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 ;
2021-06-30 18:44:41 +00:00
use Friendica\Util\Proxy ;
2018-11-08 13:45:46 +00:00
use Friendica\Util\Strings ;
2017-11-10 12:45:33 +00:00
use Friendica\Util\XML ;
2022-07-17 11:47:12 +00:00
use GuzzleHttp\Psr7\Uri ;
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
2022-10-05 21:11:09 +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
*/
2022-06-16 12:59:29 +00:00
public static function getImporter ( int $cid , int $uid = 0 ) : array
2018-08-19 13:37:56 +00:00
{
$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
*/
2022-06-16 12:59:29 +00:00
public static function entries ( array $items , array $owner ) : string
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2022-06-22 09:35:15 +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.
2022-06-22 09:35:15 +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
2022-06-22 09:35:15 +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
}
/**
2021-01-27 10:01:42 +00:00
* Generate an atom entry for a given uri id and user
2017-11-08 00:37:53 +00:00
*
2021-01-27 10:01:42 +00:00
* @ param int $uri_id The uri id
* @ param int $uid The user 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
*/
2022-06-16 12:59:29 +00:00
public static function itemFeed ( int $uri_id , int $uid , bool $conversation = false ) : string
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
if ( $conversation ) {
2021-01-27 10:01:42 +00:00
$condition = [ 'parent-uri-id' => $uri_id ];
2017-11-08 00:37:53 +00:00
} else {
2021-01-27 10:01:42 +00:00
$condition = [ 'uri-id' => $uri_id ];
2018-06-16 22:32:57 +00:00
}
2021-01-27 10:01:42 +00:00
$condition [ 'uid' ] = $uid ;
2021-01-16 04:14:58 +00:00
$items = Post :: selectToArray ( Item :: DELIVER_FIELDLIST , $condition );
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 );
2022-06-22 09:35:15 +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
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
*
2020-11-13 10:00:31 +00:00
* @ param array $mail Mail record
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
*/
2022-06-16 12:59:29 +00:00
public static function mail ( array $mail , array $owner ) : string
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2022-06-22 09:35:15 +00:00
$root = self :: addHeader ( $doc , $owner , 'dfrn:owner' , '' , false );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$mailElement = $doc -> createElement ( 'dfrn:mail' );
$senderElement = $doc -> createElement ( 'dfrn:sender' );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $senderElement , 'dfrn:name' , $owner [ 'name' ]);
XML :: addElement ( $doc , $senderElement , 'dfrn:uri' , $owner [ 'url' ]);
XML :: addElement ( $doc , $senderElement , 'dfrn:avatar' , $owner [ 'thumb' ]);
2017-11-08 00:37:53 +00:00
2020-11-13 10:00:31 +00:00
$mailElement -> appendChild ( $senderElement );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $mailElement , 'dfrn:id' , $mail [ 'uri' ]);
XML :: addElement ( $doc , $mailElement , 'dfrn:in-reply-to' , $mail [ 'parent-uri' ]);
XML :: addElement ( $doc , $mailElement , 'dfrn:sentdate' , DateTimeFormat :: utc ( $mail [ 'created' ] . '+00:00' , DateTimeFormat :: ATOM ));
XML :: addElement ( $doc , $mailElement , 'dfrn:subject' , $mail [ 'title' ]);
XML :: addElement ( $doc , $mailElement , 'dfrn:content' , $mail [ 'body' ]);
2017-11-08 00:37:53 +00:00
2020-11-13 10:00:31 +00:00
$root -> appendChild ( $mailElement );
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
* 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
*/
2022-06-16 12:59:29 +00:00
public static function fsuggest ( array $item , array $owner ) : string
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2022-06-22 09:35:15 +00:00
$root = self :: addHeader ( $doc , $owner , 'dfrn:owner' , '' , false );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$suggest = $doc -> createElement ( 'dfrn:suggest' );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +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
*/
2022-06-16 12:59:29 +00:00
public static function relocate ( array $owner , int $uid ) : string
2017-11-08 22:02:50 +00:00
{
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
}
2021-10-03 15:02:20 +00:00
$profilephotos = Photo :: selectToArray ([ 'resource-id' , 'scale' ], [ 'profile' => true , 'uid' => $uid ], [ 'order' => [ 'scale' ]]);
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
2021-10-03 15:02:20 +00:00
foreach ( $profilephotos 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 ;
2022-06-22 09:35:15 +00:00
$root = self :: addHeader ( $doc , $owner , 'dfrn:owner' , '' , false );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$relocate = $doc -> createElement ( 'dfrn:relocate' );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +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' ]);
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 ?
2022-06-22 09:35:15 +00:00
* @ return DOMElement XML root element
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2022-06-16 12:59:29 +00:00
* @ todo Find proper type - hint for returned type
2017-11-08 00:37:53 +00:00
*/
2022-06-22 09:35:15 +00:00
private static function addHeader ( DOMDocument $doc , array $owner , string $authorelement , string $alternatelink = '' , bool $public = false ) : DOMElement
2017-11-08 22:02:50 +00:00
{
2022-06-22 09:35:15 +00:00
if ( $alternatelink == '' ) {
2017-11-08 00:37:53 +00:00
$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 );
2022-06-22 09:35:15 +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
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $root , 'id' , DI :: baseUrl () . '/profile/' . $owner [ 'nick' ]);
XML :: addElement ( $doc , $root , 'title' , $owner [ 'name' ]);
2017-11-08 00:37:53 +00:00
2022-10-17 10:37:48 +00:00
$attributes = [ 'uri' => 'https://friendi.ca' , 'version' => App :: VERSION . '-' . DB_UPDATE_VERSION ];
XML :: addElement ( $doc , $root , 'generator' , App :: PLATFORM , $attributes );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$attributes = [ 'rel' => 'license' , 'href' => 'http://creativecommons.org/licenses/by/3.0/' ];
XML :: addElement ( $doc , $root , 'link' , '' , $attributes );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$attributes = [ 'rel' => 'alternate' , 'type' => 'text/html' , 'href' => $alternatelink ];
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.
2022-06-22 09:35:15 +00:00
OStatus :: addHubLink ( $doc , $root , $owner [ 'nick' ]);
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$attributes = [ 'rel' => 'salmon' , 'href' => DI :: baseUrl () . '/salmon/' . $owner [ 'nick' ]];
XML :: addElement ( $doc , $root , 'link' , '' , $attributes );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$attributes = [ 'rel' => 'http://salmon-protocol.org/ns/salmon-replies' , 'href' => DI :: baseUrl () . '/salmon/' . $owner [ 'nick' ]];
XML :: addElement ( $doc , $root , 'link' , '' , $attributes );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$attributes = [ 'rel' => 'http://salmon-protocol.org/ns/salmon-mention' , 'href' => DI :: baseUrl () . '/salmon/' . $owner [ 'nick' ]];
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 ) {
2022-06-22 09:35:15 +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
2022-06-22 09:35:15 +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
2022-06-22 09:35:15 +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 ;
}
2021-11-04 20:29:59 +00:00
/**
* Determine the next birthday , but only if the birthday is published
* in the default profile . We _could_ also look for a private profile that the
* recipient can see , but somebody could get mad at us if they start getting
* public birthday greetings when they haven ' t made this info public .
*
* Assuming we are able to publish this info , we are then going to convert
* the start time from the owner ' s timezone to UTC .
*
* This will potentially solve the problem found with some social networks
* where birthdays are converted to the viewer ' s timezone and salutations from
* elsewhere in the world show up on the wrong day . We will convert it to the
* viewer ' s timezone also , but first we are going to convert it from the birthday
* person ' s timezone to GMT - so the viewer may find the birthday starting at
* 6 : 00 PM the day before , but that will correspond to midnight to the birthday person .
2022-06-16 12:59:29 +00:00
*
* @ param int $uid User id
* @ param string $tz Time zone string , like UTC
* @ return string Formatted birthday string
2021-11-04 20:29:59 +00:00
*/
2022-06-16 12:59:29 +00:00
private static function determineNextBirthday ( int $uid , string $tz ) : string
2021-11-04 20:29:59 +00:00
{
$birthday = '' ;
if ( ! strlen ( $tz )) {
$tz = 'UTC' ;
}
$profile = DBA :: selectFirst ( 'profile' , [ 'dob' ], [ 'uid' => $uid ]);
if ( DBA :: isResult ( $profile )) {
$tmp_dob = substr ( $profile [ 'dob' ], 5 );
if ( intval ( $tmp_dob )) {
$y = DateTimeFormat :: timezoneNow ( $tz , 'Y' );
$bd = $y . '-' . $tmp_dob . ' 00:00' ;
$t_dob = strtotime ( $bd );
$now = strtotime ( DateTimeFormat :: timezoneNow ( $tz ));
if ( $t_dob < $now ) {
$bd = $y + 1 . '-' . $tmp_dob . ' 00:00' ;
}
$birthday = DateTimeFormat :: convert ( $bd , 'UTC' , $tz , DateTimeFormat :: ATOM );
}
}
return $birthday ;
}
2017-11-08 00:37:53 +00:00
/**
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
2022-06-22 09:35:15 +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
*/
2022-06-22 09:35:15 +00:00
private static function addAuthor ( DOMDocument $doc , array $owner , string $authorelement , bool $public ) : DOMElement
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 ) {
2022-06-22 09:35:15 +00:00
$attributes = [ 'dfrn:updated' => $namdate ];
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $author , 'name' , $owner [ 'name' ], $attributes );
XML :: addElement ( $doc , $author , 'uri' , DI :: baseUrl () . '/profile/' . $owner [ 'nickname' ], $attributes );
XML :: addElement ( $doc , $author , 'dfrn:handle' , $owner [ 'addr' ], $attributes );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$attributes = [
'rel' => 'photo' ,
'type' => 'image/jpeg' ,
'media:width' => Proxy :: PIXEL_SMALL ,
'media:height' => Proxy :: PIXEL_SMALL ,
'href' => User :: getAvatarUrl ( $owner , Proxy :: SIZE_SMALL ),
];
2017-11-08 00:37:53 +00:00
2020-02-16 15:39:44 +00:00
if ( ! $public || ! $hide ) {
2022-06-22 09:35:15 +00:00
$attributes [ 'dfrn:updated' ] = $picdate ;
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $author , 'link' , '' , $attributes );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$attributes [ 'rel' ] = 'avatar' ;
XML :: addElement ( $doc , $author , 'link' , '' , $attributes );
2017-11-08 00:37:53 +00:00
2020-02-16 15:39:44 +00:00
if ( $hide ) {
2022-06-22 09:35:15 +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 ;
}
2021-11-04 20:29:59 +00:00
$birthday = self :: determineNextBirthday ( $owner [ 'uid' ], $owner [ 'timezone' ]);
2017-11-08 00:37:53 +00:00
2017-11-08 22:02:50 +00:00
if ( $birthday ) {
2022-06-22 09:35:15 +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 )) {
2022-06-22 09:35:15 +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
2022-06-22 09:35:15 +00:00
if ( trim ( $profile [ 'dob' ]) > DBA :: NULL_DATE ) {
XML :: addElement ( $doc , $author , 'poco:birthday' , '0000-' . date ( 'm-d' , strtotime ( $profile [ 'dob' ])));
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +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
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $author , 'poco:utcOffset' , DateTimeFormat :: timezoneNow ( $profile [ 'timezone' ], 'P' ));
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
if ( trim ( $profile [ 'homepage' ]) != '' ) {
$urls = $doc -> createElement ( 'poco:urls' );
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 );
}
2022-06-22 09:35:15 +00:00
if ( trim ( $profile [ 'pub_keywords' ]) != '' ) {
$keywords = explode ( ',' , $profile [ 'pub_keywords' ]);
2017-11-08 00:37:53 +00:00
2017-11-10 12:45:33 +00:00
foreach ( $keywords as $keyword ) {
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $author , 'poco:tags' , trim ( $keyword ));
2017-11-08 00:37:53 +00:00
}
}
2022-06-22 09:35:15 +00:00
if ( trim ( $profile [ 'xmpp' ]) != '' ) {
$ims = $doc -> createElement ( 'poco:ims' );
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 );
}
2022-06-22 09:35:15 +00:00
if ( trim ( $profile [ 'locality' ] . $profile [ 'region' ] . $profile [ 'country-name' ]) != '' ) {
$element = $doc -> createElement ( 'poco:address' );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $element , 'poco:formatted' , Profile :: formatLocation ( $profile ));
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
if ( trim ( $profile [ 'locality' ]) != '' ) {
XML :: addElement ( $doc , $element , 'poco:locality' , $profile [ 'locality' ]);
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
if ( trim ( $profile [ 'region' ]) != '' ) {
XML :: addElement ( $doc , $element , 'poco:region' , $profile [ 'region' ]);
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
if ( trim ( $profile [ 'country-name' ]) != '' ) {
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
2022-06-22 09:35:15 +00:00
* @ param string $element Element name for the author
* @ param string $contact_url Link of the contact
* @ param array $item Item elements
* @ 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
*/
2022-06-22 09:35:15 +00:00
private static function addEntryAuthor ( DOMDocument $doc , string $element , string $contact_url , array $item ) : DOMElement
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$author = $doc -> createElement ( $element );
2022-06-22 09:35:15 +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 )) {
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $author , 'name' , $contact [ 'name' ]);
XML :: addElement ( $doc , $author , 'uri' , $contact [ 'url' ]);
XML :: addElement ( $doc , $author , 'dfrn:handle' , $contact [ 'addr' ]);
2019-02-24 19:00:40 +00:00
/// @Todo
/// - Check real image type and image size
/// - Check which of these boths elements we should use
$attributes = [
2022-06-22 09:35:15 +00:00
'rel' => 'photo' ,
'type' => 'image/jpeg' ,
'media:width' => 80 ,
'media:height' => 80 ,
'href' => $contact [ 'photo' ],
];
XML :: addElement ( $doc , $author , 'link' , '' , $attributes );
2017-11-08 00:37:53 +00:00
2019-02-24 19:00:40 +00:00
$attributes = [
2022-06-22 09:35:15 +00:00
'rel' => 'avatar' ,
'type' => 'image/jpeg' ,
'media:width' => 80 ,
'media:height' => 80 ,
'href' => $contact [ 'photo' ],
];
XML :: addElement ( $doc , $author , 'link' , '' , $attributes );
2019-02-24 19:00:40 +00:00
}
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
2021-07-10 12:58:48 +00:00
* @ param int $uriid Uri - Id of the post
2022-06-22 09:35:15 +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
*/
2022-06-16 12:59:29 +00:00
private static function createActivity ( DOMDocument $doc , string $element , string $activity , int $uriid )
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 ) {
2021-07-10 12:58:48 +00:00
XML :: addElement ( $doc , $entry , " content " , BBCode :: convertForUriId ( $uriid , $r -> content , BBCode :: EXTERNAL ), [ " 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
*/
2022-06-16 12:59:29 +00:00
private static function getAttachment ( $doc , $root , array $item )
2017-11-08 22:02:50 +00:00
{
2020-11-06 04:14:29 +00:00
foreach ( Post\Media :: getByURIId ( $item [ 'uri-id' ], [ Post\Media :: DOCUMENT , Post\Media :: TORRENT , Post\Media :: UNKNOWN ]) as $attachment ) {
$attributes = [ 'rel' => 'enclosure' ,
'href' => $attachment [ 'url' ],
'type' => $attachment [ 'mimetype' ]];
2017-11-08 00:37:53 +00:00
2020-11-06 04:14:29 +00:00
if ( ! empty ( $attachment [ 'size' ])) {
$attributes [ 'length' ] = intval ( $attachment [ 'size' ]);
}
if ( ! empty ( $attachment [ 'description' ])) {
$attributes [ 'title' ] = $attachment [ 'description' ];
2017-11-08 00:37:53 +00:00
}
2020-11-06 04:14:29 +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
*/
2022-06-16 12:59:29 +00:00
private static function entry ( DOMDocument $doc , string $type , array $item , array $owner , bool $comment = false , int $cid = 0 , bool $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' ]) {
2022-08-30 19:45:30 +00:00
Logger :: warning ( 'Item without parent found.' , [ 'type' => $type , 'item' => $item ]);
2019-02-24 15:30:09 +00:00
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
}
2021-05-17 19:20:31 +00:00
$body = Post\Media :: addAttachmentsToBody ( $item [ 'uri-id' ], $item [ 'body' ] ? ? '' );
2021-04-29 21:05:22 +00:00
2020-03-02 07:57:23 +00:00
if ( $item [ 'private' ] == Item :: PRIVATE ) {
2021-04-29 21:05:22 +00:00
$body = Item :: fixPrivatePhotos ( $body , $owner [ 'uid' ], $item , $cid );
2017-11-08 00:37:53 +00:00
}
// 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
}
2021-07-10 12:58:48 +00:00
$htmlbody = BBCode :: convertForUriId ( $item [ 'uri-id' ], $htmlbody , BBCode :: ACTIVITYPUB );
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 );
2022-09-12 21:12:11 +00:00
if ( $item [ 'gravity' ] != Item :: GRAVITY_PARENT ) {
2021-01-16 04:14:58 +00:00
$parent = Post :: selectFirst ([ 'guid' , 'plink' ], [ 'uri' => $item [ 'thr-parent' ], 'uid' => $item [ 'uid' ]]);
2020-08-20 13:34:37 +00:00
if ( DBA :: isResult ( $parent )) {
2020-11-11 07:47:48 +00:00
$attributes = [ " ref " => $item [ 'thr-parent' ], " type " => " text/html " ,
2020-08-20 13:34:37 +00:00
" 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
2018-01-15 13:05:12 +00:00
$attributes = [
2022-07-27 17:39:00 +00:00
'href' => $item [ 'conversation' ],
'ref' => $item [ 'conversation' ],
2022-06-22 09:35:15 +00:00
];
2017-11-08 00:37:53 +00:00
2022-07-27 17:39:00 +00:00
XML :: addElement ( $doc , $entry , 'ostatus:conversation' , $item [ 'conversation' ], $attributes );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $entry , 'id' , $item [ 'uri' ]);
XML :: addElement ( $doc , $entry , 'title' , $item [ 'title' ]);
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +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
2022-06-22 09:35:15 +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"
2022-06-22 09:35:15 +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 ,
2022-06-22 09:35:15 +00:00
'link' ,
'' ,
[
'rel' => 'alternate' ,
'type' => 'text/html' ,
'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 ) {
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $entry , 'dfrn:comment-allow' , 1 );
2017-11-08 00:37:53 +00:00
}
if ( $item [ 'location' ]) {
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $entry , 'dfrn:location' , $item [ 'location' ]);
2017-11-08 00:37:53 +00:00
}
if ( $item [ 'coord' ]) {
2022-06-22 09:35:15 +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
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $entry , 'dfrn:private' , ( $item [ 'private' ] == Item :: PRIVATE ? Item :: PRIVATE : Item :: PUBLIC ));
XML :: addElement ( $doc , $entry , 'dfrn:unlisted' , $item [ 'private' ] == Item :: UNLISTED );
2017-11-08 00:37:53 +00:00
}
if ( $item [ 'extid' ]) {
2022-06-22 09:35:15 +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 ) {
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $entry , 'dfrn:bookmark' , 'true' );
2017-11-08 00:37:53 +00:00
}
if ( $item [ 'app' ]) {
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $entry , 'statusnet:notice_info' , '' , [ 'local_id' => $item [ 'id' ], 'source' => $item [ 'app' ]]);
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +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' => '' ]));
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $entry , 'dfrn:diaspora_signature' , $sign );
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $entry , 'activity:verb' , self :: constructVerb ( $item ));
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
if ( $item [ 'object-type' ] != '' ) {
XML :: addElement ( $doc , $entry , 'activity:object-type' , $item [ 'object-type' ]);
2022-09-12 21:12:11 +00:00
} elseif ( $item [ 'gravity' ] == Item :: GRAVITY_PARENT ) {
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $entry , 'activity:object-type' , Activity\ObjectType :: NOTE );
2017-11-08 00:37:53 +00:00
} else {
2022-06-22 09:35:15 +00:00
XML :: addElement ( $doc , $entry , 'activity:object-type' , Activity\ObjectType :: COMMENT );
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
$actobj = self :: createActivity ( $doc , 'activity:object' , $item [ 'object' ] ? ? '' , $item [ 'uri-id' ]);
2017-11-08 00:37:53 +00:00
if ( $actobj ) {
$entry -> appendChild ( $actobj );
}
2022-06-22 09:35:15 +00:00
$actarg = self :: createActivity ( $doc , 'activity:target' , $item [ 'target' ] ? ? '' , $item [ 'uri-id' ]);
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 )) {
2022-06-22 09:35:15 +00:00
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 ) {
2022-06-22 09:35:15 +00:00
$condition = [ 'uid' => $owner [ 'uid' ], 'nurl' => Strings :: normaliseLink ( $mention )];
2022-02-09 06:52:16 +00:00
$contact = DBA :: selectFirst ( 'contact' , [ 'contact-type' ], $condition );
2017-11-08 00:37:53 +00:00
2022-02-09 06:52:16 +00:00
if ( DBA :: isResult ( $contact ) && ( $contact [ 'contact-type' ] == Contact :: TYPE_COMMUNITY )) {
2017-11-20 17:56:31 +00:00
XML :: addElement (
2017-11-08 22:02:50 +00:00
$doc ,
$entry ,
2022-06-22 09:35:15 +00:00
'link' ,
'' ,
[
'rel' => 'mentioned' ,
'ostatus:object-type' => Activity\ObjectType :: GROUP ,
'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 ,
2022-06-22 09:35:15 +00:00
'link' ,
'' ,
[
'rel' => 'mentioned' ,
'ostatus:object-type' => Activity\ObjectType :: PERSON ,
'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 ;
}
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
* @ 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
*/
2022-06-16 12:59:29 +00:00
public static function transmit ( array $owner , array $contact , string $atom , bool $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' ])) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( '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' ])) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( '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 )) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( '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
}
2022-07-29 10:44:34 +00:00
$pubkey = $fcontact [ 'pubkey' ] ? ? '' ;
2018-04-30 16:46:49 +00:00
} 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
2022-06-22 09:35:15 +00:00
if ( $public_batch && empty ( $contact [ 'batch' ])) {
$parts = parse_url ( $contact [ 'notify' ]);
2018-04-22 10:04:30 +00:00
$path_parts = explode ( '/' , $parts [ 'path' ]);
array_pop ( $path_parts );
$parts [ 'path' ] = implode ( '/' , $path_parts );
2022-07-29 16:05:04 +00:00
$contact [ 'batch' ] = ( string ) Uri :: fromParts ( $parts );
2018-04-22 10:04:30 +00:00
}
2022-06-22 09:35:15 +00:00
$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 ;
}
2022-06-22 09:35:15 +00:00
$content_type = ( $public_batch ? 'application/magic-envelope+xml' : 'application/json' );
2018-04-02 12:53:48 +00:00
2021-08-25 19:54:54 +00:00
$postResult = DI :: httpClient () -> 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 )) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( 'Empty answer from ' . $contact [ 'id' ] . ' - ' . $dest_url );
2018-04-02 21:46:10 +00:00
return - 9 ; // timed out
}
2021-08-20 17:48:14 +00:00
if (( $curl_stat == 503 ) && $postResult -> inHeader ( 'retry-after' )) {
2018-04-02 21:46:10 +00:00
return - 10 ;
}
if ( strpos ( $xml , '<?xml' ) === false ) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( 'No valid XML returned from ' . $contact [ 'id' ] . ' - ' . $dest_url );
Logger :: debug ( 'Returned XML: ' . $xml );
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 )) {
2021-10-20 18:53:52 +00:00
Logger :: info ( 'Transmit to ' . $dest_url . ' returned status ' . $res -> status . ' - ' . $res -> message );
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
*
2021-05-11 12:10:25 +00:00
* @ param \DOMXPath $xpath XPath object
* @ param \DOMNode $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
* @ 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
*/
2022-06-18 03:01:51 +00:00
private static function fetchauthor ( \DOMXPath $xpath , \DOMNode $context , array $importer , string $element , bool $onlyfetch , string $xml = '' ) : array
2017-11-08 22:02:50 +00:00
{
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-11-04 08:57:21 +00:00
$condition = [ " `uid` = ? AND `nurl` = ? AND `network` != ? AND NOT `pending` AND NOT `blocked` " ,
$importer [ " importer_uid " ], Strings :: normaliseLink ( $author [ " link " ]), Protocol :: STATUSNET ];
if ( $importer [ 'account-type' ] != User :: ACCOUNT_TYPE_COMMUNITY ) {
$condition = DBA :: mergeConditions ( $condition , [ 'rel' => [ 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 {
2020-11-04 13:05:07 +00:00
Logger :: info ( 'Contact not found' , [ 'condition' => $condition ]);
2017-11-08 00:37:53 +00:00
2018-07-16 05:48:51 +00:00
$author [ " contact-unknown " ] = true ;
2020-09-17 02:45:51 +00:00
$contact = Contact :: getByURL ( $author [ " link " ], null , [ " id " , " network " ]);
2020-09-13 14:15:28 +00:00
$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' ])) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( '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 ) {
2021-10-20 18:53:52 +00:00
Logger :: info ( " Check if contact details for contact " . $contact_old [ " id " ] . " ( " . $contact_old [ " nick " ] . " ) have to be updated. " );
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
2021-10-20 18:53:52 +00:00
Logger :: info ( " Hidden status for contact " . $contact_old [ " url " ] . " : " . $hide );
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
2021-05-11 12:10:25 +00:00
$birthday = XML :: getFirstNodeValue ( $xpath , $element . " /dfrn:birthday/text() " , $context );
try {
$birthday_date = new \DateTime ( $birthday );
if ( $birthday_date > new \DateTime ()) {
$poco [ " bdyear " ] = $birthday_date -> format ( " Y " );
}
} catch ( \Exception $e ) {
// Invalid birthday
2017-11-08 00:37:53 +00:00
}
// "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
2021-09-10 18:21:19 +00:00
Contact :: update ( $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' ])];
2021-09-10 18:21:19 +00:00
Contact :: update ( $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
*/
2022-06-16 12:59:29 +00:00
private static function transformActivity ( $xpath , $activity , string $element ) : string
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
*
2022-06-22 09:35:15 +00:00
* @ param DOMXPath $xpath XPath object
* @ param DOMNode $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
2017-11-08 00:37:53 +00:00
*/
2022-06-22 09:35:15 +00:00
private static function processMail ( DOMXPath $xpath , DOMNode $mail , array $importer )
2017-11-08 22:02:50 +00:00
{
2021-10-20 18:53:52 +00:00
Logger :: notice ( " Processing mails " );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$msg = [];
2022-06-22 09:35:15 +00:00
$msg [ 'uid' ] = $importer [ 'importer_uid' ];
$msg [ 'from-name' ] = XML :: getFirstValue ( $xpath , 'dfrn:sender/dfrn:name/text()' , $mail );
$msg [ 'from-url' ] = XML :: getFirstValue ( $xpath , 'dfrn:sender/dfrn:uri/text()' , $mail );
$msg [ 'from-photo' ] = XML :: getFirstValue ( $xpath , 'dfrn:sender/dfrn:avatar/text()' , $mail );
$msg [ 'contact-id' ] = $importer [ 'id' ];
$msg [ 'uri' ] = XML :: getFirstValue ( $xpath , 'dfrn:id/text()' , $mail );
$msg [ 'parent-uri' ] = XML :: getFirstValue ( $xpath , 'dfrn:in-reply-to/text()' , $mail );
$msg [ 'created' ] = DateTimeFormat :: utc ( XML :: getFirstValue ( $xpath , 'dfrn:sentdate/text()' , $mail ));
$msg [ 'title' ] = XML :: getFirstValue ( $xpath , 'dfrn:subject/text()' , $mail );
$msg [ 'body' ] = XML :: getFirstValue ( $xpath , 'dfrn:content/text()' , $mail );
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
*
2022-06-22 09:35:15 +00:00
* @ param DOMXPath $xpath XPath object
* @ param DOMNode $suggestion suggestion elements
* @ 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
2017-11-08 00:37:53 +00:00
*/
2022-06-22 09:35:15 +00:00
private static function processSuggestion ( DOMXPath $xpath , DOMNode $suggestion , array $importer )
2017-11-08 22:02:50 +00:00
{
2020-09-28 21:33:40 +00:00
Logger :: notice ( 'Processing suggestions' );
2017-11-08 00:37:53 +00:00
2020-09-29 03:56:57 +00:00
$url = $xpath -> evaluate ( 'string(dfrn:url[1]/text())' , $suggestion );
2020-09-28 21:33:40 +00:00
$cid = Contact :: getIdForURL ( $url );
2020-09-29 03:56:57 +00:00
$note = $xpath -> evaluate ( 'string(dfrn:note[1]/text())' , $suggestion );
2017-11-08 00:37:53 +00:00
2021-10-17 20:19:02 +00:00
return self :: addSuggestion ( $importer [ 'importer_uid' ], $cid , $importer [ 'id' ], $note );
}
/**
* Suggest a given contact to a given user from a given contact
*
* @ param integer $uid
* @ param integer $cid
* @ param integer $from_cid
* @ return bool Was the adding successful ?
*/
2022-06-16 12:59:29 +00:00
private static function addSuggestion ( int $uid , int $cid , int $from_cid , string $note = '' ) : bool
2021-10-17 20:19:02 +00:00
{
$owner = User :: getOwnerDataById ( $uid );
$contact = Contact :: getById ( $cid );
$from_contact = Contact :: getById ( $from_cid );
if ( DBA :: exists ( 'contact' , [ 'nurl' => Strings :: normaliseLink ( $contact [ 'url' ]), 'uid' => $uid ])) {
return false ;
}
// Quit if we already have an introduction for this person
2021-10-19 21:11:47 +00:00
if ( DI :: intro () -> suggestionExistsForUser ( $cid , $uid )) {
2021-10-17 20:19:02 +00:00
return false ;
}
$suggest = [];
$suggest [ 'uid' ] = $uid ;
$suggest [ 'cid' ] = $from_cid ;
$suggest [ 'url' ] = $contact [ 'url' ];
$suggest [ 'name' ] = $contact [ 'name' ];
$suggest [ 'photo' ] = $contact [ 'photo' ];
$suggest [ 'request' ] = $contact [ 'request' ];
$suggest [ 'title' ] = '' ;
$suggest [ 'body' ] = $note ;
2021-10-18 20:49:25 +00:00
DI :: intro () -> save ( DI :: introFactory () -> createNew (
$suggest [ 'uid' ],
$suggest [ 'cid' ],
$suggest [ 'body' ],
null ,
$cid
));
2021-10-17 20:19:02 +00:00
2021-10-19 19:45:36 +00:00
DI :: notify () -> createFromArray ([
2021-10-17 20:19:02 +00:00
'type' => Notification\Type :: SUGGEST ,
'otype' => Notification\ObjectType :: INTRO ,
'verb' => Activity :: REQ_FRIEND ,
'uid' => $owner [ 'uid' ],
'cid' => $from_contact [ 'uid' ],
'item' => $suggest ,
'link' => DI :: baseUrl () . '/notifications/intros' ,
]);
return true ;
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Processes the relocation elements
2017-11-08 00:37:53 +00:00
*
2022-06-22 09:35:15 +00:00
* @ param DOMXPath $xpath XPath object
* @ param DOMNode $relocation relocation elements
* @ 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
*/
2022-06-22 09:35:15 +00:00
private static function processRelocation ( DOMXPath $xpath , DOMNode $relocation , array $importer ) : bool
2017-11-08 22:02:50 +00:00
{
2021-10-20 18:53:52 +00:00
Logger :: notice ( " 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 = [];
2022-06-22 09:35:15 +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 ;
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
if (( $relocate [ 'avatar' ] == '' ) && ( $relocate [ 'photo' ] != '' )) {
$relocate [ 'avatar' ] = $relocate [ 'photo' ];
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
if ( $relocate [ 'addr' ] == '' ) {
$relocate [ 'addr' ] = preg_replace ( " =(https?://)(.*)/profile/(.*)=ism " , '$3@$2' , $relocate [ 'url' ]);
2017-11-08 00:37:53 +00:00
}
// update contact
2022-06-22 09:35:15 +00:00
$old = Contact :: selectFirst ([ 'photo' , 'url' ], [ 'id' => $importer [ 'id' ], 'uid' => $importer [ 'importer_uid' ]]);
2021-10-03 15:02:20 +00:00
if ( ! DBA :: isResult ( $old )) {
2022-08-30 19:45:30 +00:00
Logger :: warning ( 'Existing contact had not been fetched' , [ 'id' => $importer [ 'id' ]]);
2017-11-08 00:37:53 +00:00
return false ;
}
// Update the contact table. We try to find every entry.
2022-06-22 09:35:15 +00:00
$fields = [
'name' => $relocate [ 'name' ],
'avatar' => $relocate [ 'avatar' ],
'url' => $relocate [ 'url' ],
'nurl' => Strings :: normaliseLink ( $relocate [ 'url' ]),
'addr' => $relocate [ 'addr' ],
'request' => $relocate [ 'request' ],
'confirm' => $relocate [ 'confirm' ],
'notify' => $relocate [ 'notify' ],
'poll' => $relocate [ 'poll' ],
'site-pubkey' => $relocate [ 'sitepubkey' ],
];
$condition = [ " (`id` = ?) OR (`nurl` = ?) " , $importer [ 'id' ], Strings :: normaliseLink ( $old [ 'url' ])];
2018-05-02 19:26:15 +00:00
2021-09-10 18:21:19 +00:00
Contact :: update ( $fields , $condition );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
Contact :: updateAvatar ( $importer [ 'id' ], $relocate [ 'avatar' ], true );
2017-11-08 00:37:53 +00:00
2021-10-20 18:53:52 +00:00
Logger :: notice ( '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
*/
2022-06-16 12:59:29 +00:00
private static function updateContent ( array $current , array $item , array $importer , int $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.
2022-06-22 09:35:15 +00:00
if ( DateTimeFormat :: utc ( $item [ 'edited' ]) < $current [ 'edited' ]) {
2017-11-08 00:37:53 +00:00
return false ;
}
2022-06-22 09:35:15 +00:00
$fields = [
'title' => $item [ 'title' ] ? ? '' ,
'body' => $item [ 'body' ] ? ? '' ,
'changed' => DateTimeFormat :: utcNow (),
'edited' => DateTimeFormat :: utc ( $item [ 'edited' ]),
];
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +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
*/
2022-06-16 12:59:29 +00:00
private static function getEntryType ( array $importer , array $item ) : int
2017-11-08 22:02:50 +00:00
{
2022-06-22 09:35:15 +00:00
if ( $item [ 'thr-parent' ] != $item [ 'uri' ]) {
2017-11-08 00:37:53 +00:00
// was the top-level post for this action written by somebody on this site?
// Specifically, the recipient?
2022-08-30 19:45:30 +00:00
if ( Post :: exists ([ 'uri' => $item [ 'thr-parent' ], 'uid' => $importer [ 'importer_uid' ], 'self' => true , 'wall' => true ])) {
2022-10-05 21:11:09 +00:00
return self :: REPLY_RC ;
2017-11-08 00:37:53 +00:00
} else {
2022-10-05 21:11:09 +00:00
return self :: REPLY ;
2017-11-08 00:37:53 +00:00
}
} else {
2022-10-05 21:11:09 +00:00
return self :: TOP_LEVEL ;
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
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
2017-11-08 00:37:53 +00:00
*/
2022-10-05 21:11:09 +00:00
private static function processVerbs ( int $entrytype , array $importer , array & $item )
2017-11-08 22:02:50 +00:00
{
2022-06-22 14:13:46 +00:00
Logger :: info ( 'Process verb ' . $item [ 'verb' ] . ' and object-type ' . $item [ 'object-type' ] . ' for entrytype ' . $entrytype );
2017-11-08 00:37:53 +00:00
2022-10-05 21:11:09 +00:00
if (( $entrytype == self :: 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.
2022-06-22 09:35:15 +00:00
if ( $activity -> match ( $item [ 'verb' ], Activity :: FOLLOW )) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( " 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 ;
}
2022-06-22 09:35:15 +00:00
if ( $activity -> match ( $item [ 'verb' ], Activity :: UNFOLLOW )) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( " Lost follower " );
2021-09-13 18:22:55 +00:00
Contact :: removeFollower ( $contact );
2017-11-08 00:37:53 +00:00
return false ;
}
2022-06-22 09:35:15 +00:00
if ( $activity -> match ( $item [ 'verb' ], Activity :: REQ_FRIEND )) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( " 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 ;
}
2022-06-22 09:35:15 +00:00
if ( $activity -> match ( $item [ 'verb' ], Activity :: UNFRIEND )) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( " Lost sharer " );
2021-10-17 01:24:34 +00:00
Contact :: removeSharer ( $contact );
2017-11-08 00:37:53 +00:00
return false ;
}
} else {
2022-06-22 09:35:15 +00:00
if (( $item [ 'verb' ] == Activity :: LIKE )
|| ( $item [ 'verb' ] == Activity :: DISLIKE )
|| ( $item [ 'verb' ] == Activity :: ATTEND )
|| ( $item [ 'verb' ] == Activity :: ATTENDNO )
|| ( $item [ 'verb' ] == Activity :: ATTENDMAYBE )
|| ( $item [ 'verb' ] == Activity :: ANNOUNCE )
2017-11-08 22:02:50 +00:00
) {
2022-09-12 21:12:11 +00:00
$item [ 'gravity' ] = Item :: GRAVITY_ACTIVITY ;
2017-11-08 00:37:53 +00:00
// only one like or dislike per person
2020-11-11 07:47:48 +00:00
// split into two queries for performance issues
2022-06-22 09:35:15 +00:00
$condition = [
2022-09-12 21:12:11 +00:00
'uid' => $item [ 'uid' ],
'author-id' => $item [ 'author-id' ],
'gravity' => Item :: GRAVITY_ACTIVITY ,
'verb' => $item [ 'verb' ],
2022-06-22 09:35:15 +00:00
'parent-uri' => $item [ 'thr-parent' ],
];
2021-01-16 04:14:58 +00:00
if ( Post :: exists ( $condition )) {
2017-11-08 00:37:53 +00:00
return false ;
}
2022-09-12 21:12:11 +00:00
$condition = [ 'uid' => $item [ 'uid' ], 'author-id' => $item [ 'author-id' ], 'gravity' => Item :: GRAVITY_ACTIVITY ,
2020-11-11 07:47:48 +00:00
'verb' => $item [ 'verb' ], 'thr-parent' => $item [ 'thr-parent' ]];
2021-01-16 04:14:58 +00:00
if ( Post :: 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
2022-06-22 09:35:15 +00:00
$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
}
2022-06-22 09:35:15 +00:00
if (( $item [ 'verb' ] == Activity :: TAG ) && ( $item [ 'object-type' ] == Activity\ObjectType :: TAGTERM )) {
$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 ) {
2022-06-22 09:35:15 +00:00
$item_tag = Post :: selectFirst ([ 'id' , 'uri-id' ], [ 'uri' => $xt -> id , 'uid' => $importer [ 'importer_uid' ]]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item_tag )) {
2022-08-30 19:45:30 +00:00
Logger :: warning ( 'Post had not been fetched' , [ 'uri' => $xt -> id , 'uid' => $importer [ 'importer_uid' ]]);
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
*/
2022-06-16 12:59:29 +00:00
private static function parseLinks ( $links , array & $item )
2017-11-08 22:02:50 +00:00
{
2022-06-22 09:35:15 +00:00
$rel = '' ;
$href = '' ;
2020-11-07 08:22:59 +00:00
$type = null ;
$length = null ;
$title = null ;
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 ) {
2022-06-22 09:35:15 +00:00
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
}
}
2022-06-22 09:35:15 +00:00
if (( $rel != '' ) && ( $href != '' )) {
2017-11-08 00:37:53 +00:00
switch ( $rel ) {
2022-06-22 09:35:15 +00:00
case 'alternate' :
$item [ 'plink' ] = $href ;
2017-11-08 00:37:53 +00:00
break ;
2022-06-22 09:35:15 +00:00
case 'enclosure' :
2020-11-07 08:22:59 +00:00
Post\Media :: insert ([ 'uri-id' => $item [ 'uri-id' ], 'type' => Post\Media :: DOCUMENT ,
'url' => $href , 'mimetype' => $type , 'size' => $length , 'description' => $title ]);
2017-11-08 00:37:53 +00:00
break ;
}
}
}
}
2020-09-30 17:37:46 +00:00
/**
* Checks if an incoming message is wanted
*
* @ param array $item
2022-03-12 07:34:30 +00:00
* @ param array $imporer
2020-09-30 17:37:46 +00:00
* @ return boolean Is the message wanted ?
*/
2022-06-16 12:59:29 +00:00
private static function isSolicitedMessage ( array $item , array $importer ) : bool
2020-09-30 17:37:46 +00:00
{
if ( DBA :: exists ( 'contact' , [ " `nurl` = ? AND `uid` != ? AND `rel` IN (?, ?) " ,
Strings :: normaliseLink ( $item [ " author-link " ]), 0 , Contact :: FRIEND , Contact :: SHARING ])) {
2022-03-12 07:48:31 +00:00
Logger :: debug ( 'Author has got followers - accepted' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ], 'author' => $item [ " author-link " ]]);
2020-09-30 17:37:46 +00:00
return true ;
}
2022-03-12 07:34:30 +00:00
if ( $importer [ 'importer_uid' ] != 0 ) {
Logger :: debug ( 'Message is directed to a user - accepted' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ], 'importer' => $importer [ 'importer_uid' ]]);
return true ;
}
if ( $item [ 'uri' ] != $item [ 'thr-parent' ]) {
Logger :: debug ( 'Message is no parent - accepted' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ]]);
return true ;
}
2022-03-11 14:00:05 +00:00
$tags = array_column ( Tag :: getByURIId ( $item [ 'uri-id' ], [ Tag :: HASHTAG ]), 'name' );
2022-03-12 07:34:30 +00:00
if ( Relay :: isSolicitedPost ( $tags , $item [ 'body' ], $item [ 'author-id' ], $item [ 'uri' ], Protocol :: DFRN )) {
2022-03-12 07:48:31 +00:00
Logger :: debug ( 'Post is accepted because of the relay settings' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ], 'author' => $item [ " author-link " ]]);
2022-03-12 07:34:30 +00:00
return true ;
} else {
return false ;
}
2020-09-30 17:37:46 +00:00
}
2017-11-08 00:37:53 +00:00
/**
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
*
2022-06-22 09:35:15 +00:00
* @ param array $header Array of the header elements that always stay the same
* @ param DOMXPath $xpath XPath object
* @ param DOMNode $entry entry elements
* @ param array $importer Record of the importer user mixed with contact of the content
* @ param string $xml XML
2022-06-16 12:59:29 +00:00
* @ param int $protocol Protocol
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
*/
2022-06-22 09:35:15 +00:00
private static function processEntry ( array $header , DOMXPath $xpath , DOMNode $entry , array $importer , string $xml , int $protocol )
2017-11-08 22:02:50 +00:00
{
2021-10-20 18:53:52 +00:00
Logger :: notice ( " Processing entries " );
2017-11-08 00:37:53 +00:00
$item = $header ;
2022-06-22 09:35:15 +00:00
$item [ 'source' ] = $xml ;
2017-11-08 00:37:53 +00:00
// Get the uri
2022-06-22 09:35:15 +00:00
$item [ 'uri' ] = XML :: getFirstNodeValue ( $xpath , 'atom:id/text()' , $entry );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$item [ 'edited' ] = XML :: getFirstNodeValue ( $xpath , 'atom:updated/text()' , $entry );
2017-11-08 00:37:53 +00:00
2021-01-16 04:14:58 +00:00
$current = Post :: selectFirst ([ 'id' , 'uid' , 'edited' , 'body' ],
2022-06-22 09:35:15 +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 )) {
2022-06-22 09:35:15 +00:00
Logger :: info ( " Item " . $item [ 'uri' ] . " ( " . $item [ 'edited' ] . " ) already existed. " );
2017-11-08 00:37:53 +00:00
return ;
}
// Fetch the owner
2022-06-22 09:35:15 +00:00
$owner = self :: fetchauthor ( $xpath , $entry , $importer , 'dfrn:owner' , true , $xml );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$owner_unknown = ( isset ( $owner [ 'contact-unknown' ]) && $owner [ 'contact-unknown' ]);
2018-07-16 05:48:51 +00:00
2022-06-22 09:35:15 +00:00
$item [ 'owner-name' ] = $owner [ 'name' ];
$item [ 'owner-link' ] = $owner [ 'link' ];
$item [ 'owner-avatar' ] = $owner [ 'avatar' ];
$item [ 'owner-id' ] = Contact :: getIdForURL ( $owner [ 'link' ], 0 );
2017-11-08 00:37:53 +00:00
// fetch the author
2022-06-22 09:35:15 +00:00
$author = self :: fetchauthor ( $xpath , $entry , $importer , 'atom:author' , true , $xml );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$item [ 'author-name' ] = $author [ 'name' ];
$item [ 'author-link' ] = $author [ 'link' ];
$item [ 'author-avatar' ] = $author [ 'avatar' ];
$item [ 'author-id' ] = Contact :: getIdForURL ( $author [ 'link' ], 0 );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$item [ 'title' ] = XML :: getFirstNodeValue ( $xpath , 'atom:title/text()' , $entry );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
if ( ! empty ( $item [ 'title' ])) {
$item [ 'post-type' ] = Item :: PT_ARTICLE ;
2021-04-07 06:02:06 +00:00
} else {
2022-06-22 09:35:15 +00:00
$item [ 'post-type' ] = Item :: PT_NOTE ;
2021-04-07 06:02:06 +00:00
}
2022-06-22 09:35:15 +00:00
$item [ 'created' ] = XML :: getFirstNodeValue ( $xpath , 'atom:published/text()' , $entry );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$item [ 'body' ] = XML :: getFirstNodeValue ( $xpath , 'dfrn:env/text()' , $entry );
$item [ 'body' ] = str_replace ([ ' ' , " \t " , " \r " , " \n " ], [ '' , '' , '' , '' ], $item [ 'body' ]);
2020-03-28 19:41:25 +00:00
2022-06-22 09:35:15 +00:00
$item [ 'body' ] = Strings :: base64UrlDecode ( $item [ 'body' ]);
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +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
2022-06-22 09:35:15 +00:00
//$item['body'] = $xpath->query('atom:content/text()', $entry)->item(0)->nodeValue;
$item [ 'location' ] = XML :: getFirstNodeValue ( $xpath , 'dfrn:location/text()' , $entry );
$item [ 'coord' ] = XML :: getFirstNodeValue ( $xpath , 'georss:point' , $entry );
$item [ 'private' ] = XML :: getFirstNodeValue ( $xpath , 'dfrn:private/text()' , $entry );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$unlisted = XML :: getFirstNodeValue ( $xpath , 'dfrn:unlisted/text()' , $entry );
2020-03-02 22:35:25 +00:00
if ( ! empty ( $unlisted ) && ( $item [ 'private' ] != Item :: PRIVATE )) {
$item [ 'private' ] = Item :: UNLISTED ;
}
2022-06-22 09:35:15 +00:00
$item [ 'extid' ] = XML :: getFirstNodeValue ( $xpath , 'dfrn:extid/text()' , $entry );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
if ( XML :: getFirstNodeValue ( $xpath , 'dfrn:bookmark/text()' , $entry ) == 'true' ) {
$item [ 'post-type' ] = Item :: PT_PAGE ;
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +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 ) {
2022-06-22 09:35:15 +00:00
if ( $attributes -> name == 'source' ) {
$item [ 'app' ] = strip_tags ( $attributes -> textContent );
2017-11-08 00:37:53 +00:00
}
}
}
2022-06-22 09:35:15 +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' ]]);
2022-10-27 05:44:44 +00:00
$item [ 'body' ] = DI :: contentItem () -> improveSharedDataInBody ( $item );
2021-05-07 06:26:41 +00:00
2022-06-22 09:35:15 +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"
2022-06-22 09:35:15 +00:00
$dsprsig = XML :: unescape ( XML :: getFirstNodeValue ( $xpath , 'dfrn:diaspora_signature/text()' , $entry ));
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 )) {
2022-06-22 09:35:15 +00:00
$item [ 'diaspora_signed_text' ] = $signature -> signed_text ;
2020-05-13 05:48:26 +00:00
}
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
$item [ 'verb' ] = XML :: getFirstNodeValue ( $xpath , 'activity:verb/text()' , $entry );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +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
}
2022-06-22 09:35:15 +00:00
$object = $xpath -> query ( 'activity:object' , $entry ) -> item ( 0 );
$item [ 'object' ] = self :: transformActivity ( $xpath , $object , 'object' );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
if ( trim ( $item [ 'object' ]) != '' ) {
$r = XML :: parseString ( $item [ 'object' ]);
2017-11-08 00:37:53 +00:00
if ( isset ( $r -> type )) {
2022-06-22 09:35:15 +00:00
$item [ 'object-type' ] = $r -> type ;
2017-11-08 00:37:53 +00:00
}
}
2022-06-22 09:35:15 +00:00
$target = $xpath -> query ( 'activity:target' , $entry ) -> item ( 0 );
$item [ 'target' ] = self :: transformActivity ( $xpath , $target , 'target' );
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$categories = $xpath -> query ( 'atom:category' , $entry );
2017-11-08 00:37:53 +00:00
if ( $categories ) {
2017-11-23 19:01:58 +00:00
foreach ( $categories as $category ) {
2022-06-22 09:35:15 +00:00
$term = '' ;
$scheme = '' ;
2017-11-23 19:01:58 +00:00
foreach ( $category -> attributes as $attributes ) {
2022-06-22 09:35:15 +00:00
if ( $attributes -> name == 'term' ) {
2017-11-08 00:37:53 +00:00
$term = $attributes -> textContent ;
}
2022-06-22 09:35:15 +00:00
if ( $attributes -> name == 'scheme' ) {
2017-11-08 00:37:53 +00:00
$scheme = $attributes -> textContent ;
}
}
2022-06-22 09:35:15 +00:00
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
}
}
}
}
2022-06-22 09:35:15 +00:00
$links = $xpath -> query ( 'atom:link' , $entry );
2017-11-08 00:37:53 +00:00
if ( $links ) {
2017-11-23 19:01:58 +00:00
self :: parseLinks ( $links , $item );
2017-11-08 00:37:53 +00:00
}
2022-07-27 17:39:00 +00:00
$item [ 'conversation' ] = 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 ) {
2022-06-22 09:35:15 +00:00
if ( $attributes -> name == 'ref' ) {
2022-07-27 17:39:00 +00:00
$item [ 'conversation' ] = $attributes -> textContent ;
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
if ( $attributes -> name == 'href' ) {
2022-07-27 17:39:00 +00:00
$item [ 'conversation' ] = $attributes -> textContent ;
2017-11-08 00:37:53 +00:00
}
}
}
// Is it a reply or a top level posting?
2020-11-11 07:47:48 +00:00
$item [ 'thr-parent' ] = $item [ 'uri' ];
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$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 ) {
2022-06-22 09:35:15 +00:00
if ( $attributes -> name == 'ref' ) {
2020-11-11 07:47:48 +00:00
$item [ 'thr-parent' ] = $attributes -> textContent ;
2017-11-08 00:37:53 +00:00
}
}
}
2020-09-30 17:37:46 +00:00
// Check if the message is wanted
2022-03-12 07:34:30 +00:00
if ( ! self :: isSolicitedMessage ( $item , $importer )) {
DBA :: delete ( 'item-uri' , [ 'uri' => $item [ 'uri' ]]);
return 403 ;
2020-09-30 17:37:46 +00:00
}
2021-05-07 06:26:41 +00:00
2017-11-08 00:37:53 +00:00
// 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
2022-10-05 21:11:09 +00:00
if ( in_array ( $entrytype , [ self :: REPLY , self :: REPLY_RC ])) {
2022-09-12 21:12:11 +00:00
$item [ 'gravity' ] = Item :: GRAVITY_COMMENT ;
2022-10-05 21:11:09 +00:00
2022-06-22 09:35:15 +00:00
if ( ! isset ( $item [ 'object-type' ])) {
$item [ 'object-type' ] = Activity\ObjectType :: COMMENT ;
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
if ( $item [ 'contact-id' ] != $owner [ 'contact-id' ]) {
$item [ 'contact-id' ] = $owner [ 'contact-id' ];
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
if (( $item [ 'network' ] != $owner [ 'network' ]) && ( $owner [ 'network' ] != '' )) {
$item [ 'network' ] = $owner [ 'network' ];
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
if ( $item [ 'contact-id' ] != $author [ 'contact-id' ]) {
$item [ 'contact-id' ] = $author [ 'contact-id' ];
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
if (( $item [ 'network' ] != $author [ 'network' ]) && ( $author [ 'network' ] != '' )) {
$item [ 'network' ] = $author [ 'network' ];
2017-11-08 00:37:53 +00:00
}
}
2022-10-05 21:11:09 +00:00
if ( $entrytype == self :: REPLY_RC ) {
2022-06-22 09:35:15 +00:00
$item [ 'wall' ] = 1 ;
2022-10-05 21:11:09 +00:00
} elseif ( $entrytype == self :: TOP_LEVEL ) {
2022-09-12 21:12:11 +00:00
$item [ 'gravity' ] = Item :: GRAVITY_PARENT ;
2022-10-05 21:11:09 +00:00
2022-06-22 09:35:15 +00:00
if ( ! isset ( $item [ 'object-type' ])) {
$item [ 'object-type' ] = Activity\ObjectType :: NOTE ;
2017-11-08 00:37:53 +00:00
}
// Is it an event?
2022-06-22 09:35:15 +00:00
if (( $item [ 'object-type' ] == Activity\ObjectType :: EVENT ) && ! $owner_unknown ) {
Logger :: info ( " Item " . $item [ 'uri' ] . " seems to contain an event. " );
$ev = Event :: fromBBCode ( $item [ 'body' ]);
2018-11-30 14:06:22 +00:00
if (( ! empty ( $ev [ 'desc' ]) || ! empty ( $ev [ 'summary' ])) && ! empty ( $ev [ 'start' ])) {
2022-06-22 09:35:15 +00:00
Logger :: info ( " Event in item " . $item [ 'uri' ] . " was found. " );
$ev [ 'cid' ] = $importer [ 'id' ];
$ev [ 'uid' ] = $importer [ 'importer_uid' ];
$ev [ 'uri' ] = $item [ 'uri' ];
$ev [ 'edited' ] = $item [ 'edited' ];
$ev [ 'private' ] = $item [ 'private' ];
$ev [ 'guid' ] = $item [ 'guid' ];
$ev [ 'plink' ] = $item [ 'plink' ];
$ev [ 'network' ] = $item [ 'network' ];
$ev [ 'protocol' ] = $item [ 'protocol' ];
$ev [ 'direction' ] = $item [ 'direction' ];
$ev [ 'source' ] = $item [ 'source' ];
$condition = [ 'uri' => $item [ 'uri' ], 'uid' => $importer [ 'importer_uid' ]];
2018-08-19 12:46:11 +00:00
$event = DBA :: selectFirst ( 'event' , [ 'id' ], $condition );
if ( DBA :: isResult ( $event )) {
2022-06-22 09:35:15 +00:00
$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 );
2021-07-18 15:05:46 +00:00
Logger :: info ( 'Event was stored' , [ 'id' => $event_id ]);
2021-08-10 10:24:14 +00:00
$item = Event :: getItemArrayForImportedId ( $event_id , $item );
2017-11-08 00:37:53 +00:00
}
}
}
2022-10-05 21:11:09 +00:00
if ( ! self :: processVerbs ( $entrytype , $importer , $item )) {
2021-10-20 18:53:52 +00:00
Logger :: info ( " Exiting because 'processVerbs' told us so " );
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"
2022-10-05 21:11:09 +00:00
if (( $entrytype == self :: TOP_LEVEL ) && $owner_unknown ) {
2022-06-22 09:35:15 +00:00
Logger :: info ( " Item won't be stored because user " . $importer [ 'importer_uid' ] . " doesn't follow " . $item [ 'owner-link' ] . " . " );
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 )) {
2022-06-22 09:35:15 +00:00
Logger :: info ( " Item " . $item [ 'uri' ] . " was updated. " );
2017-11-08 00:37:53 +00:00
} else {
2022-06-22 09:35:15 +00:00
Logger :: info ( " Item " . $item [ 'uri' ] . " already existed. " );
2017-11-08 00:37:53 +00:00
}
return ;
}
2022-10-05 21:11:09 +00:00
if ( in_array ( $entrytype , [ self :: REPLY , self :: REPLY_RC ])) {
if (( $item [ 'uid' ] != 0 ) && ! Post :: exists ([ 'uid' => $item [ 'uid' ], 'uri' => $item [ 'thr-parent' ]])) {
if ( DI :: pConfig () -> get ( $item [ 'uid' ], 'system' , 'accept_only_sharer' ) == Item :: COMPLETION_NONE ) {
Logger :: info ( 'Completion is set to "none", so we stop here.' , [ 'uid' => $item [ 'uid' ], 'owner-id' => $item [ 'owner-id' ], 'author-id' => $item [ 'author-id' ], 'gravity' => $item [ 'gravity' ], 'uri' => $item [ 'uri' ]]);
return ;
}
if ( ! Contact :: isSharing ( $item [ 'owner-id' ], $item [ 'uid' ]) && ! Contact :: isSharing ( $item [ 'author-id' ], $item [ 'uid' ])) {
Logger :: info ( 'Contact is not sharing with the user' , [ 'uid' => $item [ 'uid' ], 'owner-id' => $item [ 'owner-id' ], 'author-id' => $item [ 'author-id' ], 'gravity' => $item [ 'gravity' ], 'uri' => $item [ 'uri' ]]);
return ;
}
2022-09-12 21:12:11 +00:00
if (( $item [ 'gravity' ] == Item :: GRAVITY_ACTIVITY ) && DI :: pConfig () -> get ( $item [ 'uid' ], 'system' , 'accept_only_sharer' ) == Item :: COMPLETION_COMMENT ) {
2022-10-05 21:11:09 +00:00
Logger :: info ( 'Completion is set to "comment", but this is an activity. so we stop here.' , [ 'uid' => $item [ 'uid' ], 'owner-id' => $item [ 'owner-id' ], 'author-id' => $item [ 'author-id' ], 'gravity' => $item [ 'gravity' ], 'uri' => $item [ 'uri' ]]);
return ;
}
Logger :: debug ( 'Post is accepted.' , [ 'uid' => $item [ 'uid' ], 'owner-id' => $item [ 'owner-id' ], 'author-id' => $item [ 'author-id' ], 'gravity' => $item [ 'gravity' ], 'uri' => $item [ 'uri' ]]);
} else {
Logger :: debug ( 'Thread parent exists.' , [ 'uid' => $item [ 'uid' ], 'owner-id' => $item [ 'owner-id' ], 'author-id' => $item [ 'author-id' ], 'gravity' => $item [ 'gravity' ], 'uri' => $item [ 'uri' ]]);
}
2020-09-13 14:15:28 +00:00
// Will be overwritten for sharing accounts in Item::insert
2022-10-05 21:11:09 +00:00
if ( empty ( $item [ 'post-reason' ]) && ( $entrytype == self :: REPLY )) {
2021-04-07 06:02:06 +00:00
$item [ 'post-reason' ] = Item :: PR_COMMENT ;
2020-09-13 14:15:28 +00:00
}
2018-01-28 11:18:08 +00:00
$posted_id = Item :: insert ( $item );
2017-11-08 00:37:53 +00:00
if ( $posted_id ) {
2022-06-22 09:35:15 +00:00
Logger :: info ( " Reply from contact " . $item [ 'contact-id' ] . " was stored with id " . $posted_id );
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 ;
}
2022-10-05 21:11:09 +00:00
} else { // $entrytype == self::TOP_LEVEL
if (( $item [ 'uid' ] != 0 ) && ! Contact :: isSharing ( $item [ 'owner-id' ], $item [ 'uid' ]) && ! Contact :: isSharing ( $item [ 'author-id' ], $item [ 'uid' ])) {
Logger :: info ( 'Contact is not sharing with the user' , [ 'uid' => $item [ 'uid' ], 'owner-id' => $item [ 'owner-id' ], 'author-id' => $item [ 'author-id' ], 'gravity' => $item [ 'gravity' ], 'uri' => $item [ 'uri' ]]);
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
}
2022-06-22 09:35:15 +00:00
Logger :: info ( " Item was stored with id " . $posted_id );
2017-11-08 00:37:53 +00:00
2018-04-24 13:21:25 +00:00
if ( $item [ 'uid' ] == 0 ) {
Item :: distribute ( $posted_id );
}
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
*
2022-06-22 09:35:15 +00:00
* @ param DOMXPath $xpath XPath object
* @ param DOMNode $deletion deletion 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
2017-11-08 00:37:53 +00:00
*/
2022-06-22 09:35:15 +00:00
private static function processDeletion ( DOMXPath $xpath , DOMNode $deletion , array $importer )
2017-11-08 22:02:50 +00:00
{
2021-10-20 18:53:52 +00:00
Logger :: notice ( " 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 ) {
2022-06-22 09:35:15 +00:00
if ( $attributes -> name == 'ref' ) {
2017-11-08 00:37:53 +00:00
$uri = $attributes -> textContent ;
}
}
2022-06-22 09:35:15 +00:00
if ( ! $uri || ! $importer [ 'id' ]) {
2017-11-08 00:37:53 +00:00
return false ;
}
2022-06-22 09:35:15 +00:00
$condition = [ 'uri' => $uri , 'uid' => $importer [ 'importer_uid' ]];
2021-01-21 07:16:41 +00:00
$item = Post :: selectFirst ([ 'id' , 'parent' , 'contact-id' , 'uri-id' , 'deleted' , 'gravity' ], $condition );
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2022-06-22 14:13:46 +00:00
Logger :: info ( 'Item with URI ' . $uri . ' for user ' . $importer [ 'importer_uid' ] . ' was not found.' );
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
2021-01-21 07:16:41 +00:00
if ( DBA :: exists ( 'post-category' , [ 'uri-id' => $item [ 'uri-id' ], 'uid' => $importer [ 'importer_uid' ], 'type' => Post\Category :: FILE ])) {
2022-06-22 14:13:46 +00:00
Logger :: notice ( 'Item is filed. It will not be deleted.' , [ 'uri' => $uri , 'uri-id' => $item [ 'uri_id' ], 'uid' => $importer [ 'importer_uid' ]]);
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
2022-09-12 21:12:11 +00:00
if (( $item [ 'gravity' ] == Item :: GRAVITY_PARENT ) && ( $item [ 'contact-id' ] != $importer [ 'id' ])) {
2022-06-22 14:13:46 +00:00
Logger :: info ( 'Item with URI ' . $uri . ' do not belong to contact ' . $importer [ 'id' ] . ' - ignoring deletion.' );
2018-03-01 21:52:36 +00:00
return ;
}
// Comments can be deleted by the thread owner or comment owner
2022-09-12 21:12:11 +00:00
if (( $item [ 'gravity' ] != Item :: GRAVITY_PARENT ) && ( $item [ 'contact-id' ] != $importer [ 'id' ])) {
2022-06-22 09:35:15 +00:00
$condition = [ 'id' => $item [ 'parent' ], 'contact-id' => $importer [ 'id' ]];
2021-01-16 04:14:58 +00:00
if ( ! Post :: exists ( $condition )) {
2022-06-22 14:13:46 +00:00
Logger :: info ( 'Item with URI ' . $uri . ' was not found or must not be deleted by contact ' . $importer [ 'id' ] . ' - ignoring deletion.' );
2018-03-01 21:52:36 +00:00
return ;
}
}
2022-06-22 09:35:15 +00:00
if ( $item [ 'deleted' ]) {
2018-02-06 12:40:22 +00:00
return ;
}
2018-01-18 06:54:44 +00:00
2021-10-20 18:53:52 +00:00
Logger :: info ( 'deleting item ' . $item [ 'id' ] . ' uri=' . $uri );
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
*
2021-01-09 12:59:30 +00:00
* @ param string $xml The DFRN message
* @ param array $importer Record of the importer user mixed with contact of the content
* @ param int $protocol Transport protocol
* @ param int $direction Is the message pushed or pulled ?
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
2017-11-08 00:37:53 +00:00
*/
2022-06-16 12:59:29 +00:00
public static function import ( string $xml , array $importer , int $protocol , int $direction ) : int
2017-11-08 22:02:50 +00:00
{
2022-06-22 09:35:15 +00:00
if ( $xml == '' ) {
2017-11-08 00:37:53 +00:00
return 400 ;
}
$doc = new DOMDocument ();
@ $doc -> loadXML ( $xml );
2017-12-17 20:24:57 +00:00
$xpath = new DOMXPath ( $doc );
2022-06-22 09:35:15 +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 = [];
2022-06-22 09:35:15 +00:00
$header [ 'uid' ] = $importer [ 'importer_uid' ];
$header [ 'network' ] = Protocol :: DFRN ;
2022-07-31 15:54:35 +00:00
$header [ 'protocol' ] = $protocol ;
2022-06-22 09:35:15 +00:00
$header [ 'wall' ] = 0 ;
$header [ 'origin' ] = 0 ;
$header [ 'contact-id' ] = $importer [ 'id' ];
2022-07-31 15:54:35 +00:00
$header = Diaspora :: setDirection ( $header , $direction );
2017-11-08 00:37:53 +00:00
2021-01-10 21:30:30 +00:00
if ( $direction === Conversation :: RELAY ) {
2021-04-07 06:02:06 +00:00
$header [ 'post-reason' ] = Item :: PR_RELAY ;
2021-01-09 18:17:49 +00:00
}
2017-11-08 00:37:53 +00:00
// Update the contact table if the data has changed
// The "atom:author" is only present in feeds
2022-06-22 09:35:15 +00:00
if ( $xpath -> query ( '/atom:feed/atom:author' ) -> length > 0 ) {
self :: fetchauthor ( $xpath , $doc -> firstChild , $importer , 'atom:author' , false , $xml );
2017-11-08 00:37:53 +00:00
}
// Only the "dfrn:owner" in the head section contains all data
2022-06-22 09:35:15 +00:00
if ( $xpath -> query ( '/atom:feed/dfrn:owner' ) -> length > 0 ) {
self :: fetchauthor ( $xpath , $doc -> firstChild , $importer , 'dfrn:owner' , false , $xml );
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
Logger :: info ( " Import DFRN message for user " . $importer [ 'importer_uid' ] . " from contact " . $importer [ 'id' ]);
2017-11-08 00:37:53 +00:00
2021-07-15 13:28:32 +00:00
if ( ! empty ( $importer [ 'gsid' ]) && ( $protocol == Conversation :: PARCEL_DIASPORA_DFRN )) {
GServer :: setProtocol ( $importer [ 'gsid' ], Post\DeliveryData :: DFRN );
2021-03-10 22:31:33 +00:00
}
2018-04-21 21:59:02 +00:00
// is it a public forum? Private forums aren't exposed with this method
2022-06-22 09:35:15 +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
2022-06-22 09:35:15 +00:00
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
2022-06-22 09:35:15 +00:00
$accounttype = intval ( XML :: getFirstNodeValue ( $xpath , '/atom:feed/dfrn:account_type/text()' ));
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
if ( $accounttype != $importer [ 'contact-type' ]) {
2021-09-10 18:21:19 +00:00
Contact :: update ([ 'contact-type' => $accounttype ], [ 'id' => $importer [ 'id' ]]);
2019-01-12 16:09:27 +00:00
// Updating the public contact as well
2021-09-10 18:21:19 +00:00
Contact :: update ([ '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' ]];
2021-09-10 18:21:19 +00:00
Contact :: update ([ 'forum' => $forum , 'prv' => ! $forum ], $condition );
2019-01-12 16:09:27 +00:00
// Updating the public contact as well
$condition = [ '(`forum` != ? OR `prv` != ?) AND `uid` = 0 AND `nurl` = ?' , $forum , ! $forum , $importer [ 'nurl' ]];
2021-09-10 18:21:19 +00:00
Contact :: update ([ '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' ]];
2021-09-10 18:21:19 +00:00
Contact :: update ([ 'forum' => false , 'prv' => false ], $condition );
2019-01-12 16:09:27 +00:00
// Updating the public contact as well
$condition = [ '(`forum` OR `prv`) AND `uid` = 0 AND `nurl` = ?' , $importer [ 'nurl' ]];
2021-09-10 18:21:19 +00:00
Contact :: update ([ 'forum' => false , 'prv' => false ], $condition );
2018-04-21 21:59:02 +00:00
}
2022-06-22 09:35:15 +00:00
} elseif ( $forum != $importer [ 'forum' ]) { // Deprecated since 3.5.1
$condition = [ '`forum` != ? AND `id` = ?' , $forum , $importer [ 'id' ]];
2021-09-10 18:21:19 +00:00
Contact :: update ([ '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' ]];
2021-09-10 18:21:19 +00:00
Contact :: update ([ '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
2022-06-22 09:35:15 +00:00
$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
}
2022-06-22 09:35:15 +00:00
if (( $importer [ 'uid' ] != 0 ) && ! $importer [ 'readonly' ]) {
$mails = $xpath -> query ( '/atom:feed/dfrn:mail' );
2018-04-01 05:07:35 +00:00
foreach ( $mails as $mail ) {
self :: processMail ( $xpath , $mail , $importer );
}
2017-11-08 00:37:53 +00:00
2022-06-22 09:35:15 +00:00
$suggestions = $xpath -> query ( '/atom:feed/dfrn:suggest' );
2018-04-01 05:07:35 +00:00
foreach ( $suggestions as $suggestion ) {
self :: processSuggestion ( $xpath , $suggestion , $importer );
}
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
$deletions = $xpath -> query ( '/atom:feed/at:deleted-entry' );
2020-12-06 09:54:34 +00:00
if ( ! empty ( $deletions )) {
foreach ( $deletions as $deletion ) {
self :: processDeletion ( $xpath , $deletion , $importer );
}
2020-12-10 07:22:49 +00:00
if ( count ( $deletions ) > 0 ) {
2022-06-22 09:35:15 +00:00
Logger :: notice ( count ( $deletions ) . ' deletions had been processed' );
2020-12-10 07:22:49 +00:00
return 200 ;
}
2017-11-08 00:37:53 +00:00
}
2022-06-22 09:35:15 +00:00
$entries = $xpath -> query ( '/atom:feed/atom:entry' );
2021-01-09 12:59:30 +00:00
foreach ( $entries as $entry ) {
self :: processEntry ( $header , $xpath , $entry , $importer , $xml , $protocol );
2017-11-08 00:37:53 +00:00
}
2021-01-09 12:59:30 +00:00
2022-06-22 09:35:15 +00:00
Logger :: info ( " Import done for user " . $importer [ 'importer_uid' ] . " from contact " . $importer [ 'id' ]);
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
*/
2022-06-16 12:59:29 +00:00
private static function constructVerb ( array $item ) : string
2018-01-20 23:52:54 +00:00
{
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
/**
* 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 .
*
2022-06-16 12:59:29 +00:00
* @ param array $existing
* @ param array $update
2019-01-06 21:06:53 +00:00
* @ return bool
* @ throws \Exception
2018-01-24 20:27:32 +00:00
*/
2022-06-16 12:59:29 +00:00
private static function isEditedTimestampNewer ( array $existing , array $update ) : bool
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
*/
2022-06-16 12:59:29 +00:00
public static function isSupportedByContactUrl ( string $url ) : bool
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
}