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
*
*/
2018-03-04 22:39:41 +00:00
2017-11-08 22:02:50 +00:00
namespace Friendica\Protocol ;
2017-11-08 00:37:53 +00:00
2019-02-09 04:10:36 +00:00
use Friendica\Content\Feature ;
2018-02-15 02:33:55 +00:00
use Friendica\Content\Text\BBCode ;
2018-03-04 22:39:41 +00:00
use Friendica\Content\Text\Markdown ;
2021-10-23 08:49:27 +00:00
use Friendica\Core\Cache\Enum\Duration ;
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-01-27 02:38:34 +00:00
use Friendica\Core\System ;
2017-11-08 00:37:53 +00:00
use Friendica\Core\Worker ;
2018-07-20 12:19:26 +00:00
use Friendica\Database\DBA ;
2019-12-30 22:00:08 +00:00
use Friendica\DI ;
2017-12-07 14:04:24 +00:00
use Friendica\Model\Contact ;
2018-08-05 10:23:57 +00:00
use Friendica\Model\Conversation ;
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-13 23:54:28 +00:00
use Friendica\Model\ItemURI ;
2019-05-08 05:44:22 +00:00
use Friendica\Model\Mail ;
2020-05-02 19:34:02 +00:00
use Friendica\Model\Post ;
2020-04-15 16:37:09 +00:00
use Friendica\Model\Tag ;
2017-12-09 18:45:17 +00:00
use Friendica\Model\User ;
2022-04-02 18:26:11 +00:00
use Friendica\Network\HTTPClient\Client\HttpClientAccept ;
2017-11-08 00:37:53 +00:00
use Friendica\Network\Probe ;
2017-12-30 16:51:49 +00:00
use Friendica\Util\Crypto ;
2018-01-27 02:38:34 +00:00
use Friendica\Util\DateTimeFormat ;
2018-07-20 02:15:21 +00:00
use Friendica\Util\Map ;
2018-01-27 04:09:48 +00:00
use Friendica\Util\Network ;
2018-11-08 13:45:46 +00:00
use Friendica\Util\Strings ;
2017-11-10 12:45:33 +00:00
use Friendica\Util\XML ;
2019-06-06 04:26:02 +00:00
use Friendica\Worker\Delivery ;
2022-07-17 11:47:12 +00:00
use GuzzleHttp\Psr7\Uri ;
2017-11-08 12:02:55 +00:00
use SimpleXMLElement ;
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* This class contain functions to create and send Diaspora XML files
2017-11-08 00:37:53 +00:00
*/
2017-11-08 22:02:50 +00:00
class Diaspora
{
2022-03-12 07:08:10 +00:00
const PUSHED = 0 ;
const FETCHED = 1 ;
const FORCED_FETCH = 2 ;
2018-01-12 20:52:43 +00:00
/**
2020-01-19 06:05:23 +00:00
* Return a list of participating contacts for a thread
2018-01-12 20:52:43 +00:00
*
* This is used for the participation feature .
* One of the parameters is a contact array .
* This is done to avoid duplicates .
*
2020-05-06 15:20:49 +00:00
* @ param array $item Item that is about to be delivered
2020-05-02 08:52:11 +00:00
* @ param array $contacts The previously fetched contacts
2018-01-12 20:52:43 +00:00
*
* @ return array of relay servers
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-01-12 20:52:43 +00:00
*/
2022-06-17 08:44:13 +00:00
public static function participantsForThread ( array $item , array $contacts ) : array
2018-01-12 20:52:43 +00:00
{
2022-06-18 03:01:51 +00:00
if ( ! in_array ( $item [ 'private' ], [ Item :: PUBLIC , Item :: UNLISTED ]) || in_array ( $item [ 'verb' ], [ Activity :: FOLLOW , Activity :: TAG ])) {
2020-05-06 19:00:56 +00:00
Logger :: info ( 'Item is private or a participation request. It will not be relayed' , [ 'guid' => $item [ 'guid' ], 'private' => $item [ 'private' ], 'verb' => $item [ 'verb' ]]);
2020-05-02 08:52:11 +00:00
return $contacts ;
}
2018-01-12 20:52:43 +00:00
2021-01-16 04:14:58 +00:00
$items = Post :: select ([ 'author-id' , 'author-link' , 'parent-author-link' , 'parent-guid' , 'guid' ],
2022-09-12 21:12:11 +00:00
[ 'parent' => $item [ 'parent' ], 'gravity' => [ Item :: GRAVITY_COMMENT , Item :: GRAVITY_ACTIVITY ]]);
2021-01-16 04:14:58 +00:00
while ( $item = Post :: fetch ( $items )) {
2020-05-02 08:52:11 +00:00
$contact = DBA :: selectFirst ( 'contact' , [ 'id' , 'url' , 'name' , 'protocol' , 'batch' , 'network' ],
[ 'id' => $item [ 'author-id' ]]);
2020-05-06 15:20:49 +00:00
if ( ! DBA :: isResult ( $contact ) || empty ( $contact [ 'batch' ]) ||
( $contact [ 'network' ] != Protocol :: DIASPORA ) ||
Strings :: compareLink ( $item [ 'parent-author-link' ], $item [ 'author-link' ])) {
2020-05-02 08:52:11 +00:00
continue ;
2019-05-11 05:58:22 +00:00
}
2018-01-12 20:52:43 +00:00
$exists = false ;
foreach ( $contacts as $entry ) {
if ( $entry [ 'batch' ] == $contact [ 'batch' ]) {
$exists = true ;
}
}
if ( ! $exists ) {
2020-05-06 15:20:49 +00:00
Logger :: info ( 'Add participant to receiver list' , [ 'parent' => $item [ 'parent-guid' ], 'item' => $item [ 'guid' ], 'participant' => $contact [ 'url' ]]);
2018-01-12 20:52:43 +00:00
$contacts [] = $contact ;
}
}
2020-05-02 08:52:11 +00:00
DBA :: close ( $items );
2018-01-12 20:52:43 +00:00
return $contacts ;
}
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* verify the envelope and return the verified data
2017-11-08 00:37:53 +00:00
*
* @ param string $envelope The magic envelope
*
2022-06-17 08:44:13 +00:00
* @ return string | bool verified data or false on error
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-17 08:44:13 +00:00
private static function verifyMagicEnvelope ( string $envelope )
2017-11-08 22:02:50 +00:00
{
2020-04-27 14:35:50 +00:00
$basedom = XML :: parseString ( $envelope , true );
2017-11-08 00:37:53 +00:00
if ( ! is_object ( $basedom )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Envelope is no XML file' );
2017-11-08 00:37:53 +00:00
return false ;
}
2022-08-12 12:00:02 +00:00
$children = $basedom -> children ( ActivityNamespace :: SALMON_ME );
2017-11-08 00:37:53 +00:00
if ( sizeof ( $children ) == 0 ) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'XML has no children' );
2017-11-08 00:37:53 +00:00
return false ;
}
2022-07-19 23:06:05 +00:00
$handle = '' ;
2017-11-08 00:37:53 +00:00
2018-11-08 15:37:08 +00:00
$data = Strings :: base64UrlDecode ( $children -> data );
2017-11-08 00:37:53 +00:00
$type = $children -> data -> attributes () -> type [ 0 ];
$encoding = $children -> encoding ;
$alg = $children -> alg ;
2018-11-08 15:37:08 +00:00
$sig = Strings :: base64UrlDecode ( $children -> sig );
2017-11-08 00:37:53 +00:00
$key_id = $children -> sig -> attributes () -> key_id [ 0 ];
2022-06-18 03:01:51 +00:00
if ( $key_id != '' ) {
2018-11-08 15:37:08 +00:00
$handle = Strings :: base64UrlDecode ( $key_id );
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2018-11-08 15:37:08 +00:00
$b64url_data = Strings :: base64UrlEncode ( $data );
2022-06-18 03:01:51 +00:00
$msg = str_replace ([ " \n " , " \r " , " " , " \t " ], [ '' , '' , '' , '' ], $b64url_data );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$signable_data = $msg . '.' . Strings :: base64UrlEncode ( $type ) . '.' . Strings :: base64UrlEncode ( $encoding ) . '.' . Strings :: base64UrlEncode ( $alg );
2017-11-08 00:37:53 +00:00
2018-03-09 05:38:15 +00:00
if ( $handle == '' ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'No author could be decoded. Discarding. Message: ' . $envelope );
2018-03-09 05:38:15 +00:00
return false ;
}
2017-11-08 00:37:53 +00:00
$key = self :: key ( $handle );
2018-03-09 05:31:13 +00:00
if ( $key == '' ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( " Couldn't get a key for handle " . $handle . " . Discarding. " );
2018-03-09 05:31:13 +00:00
return false ;
}
2017-11-08 00:37:53 +00:00
2017-12-30 16:51:49 +00:00
$verify = Crypto :: rsaVerify ( $signable_data , $sig , $key );
2017-11-08 00:37:53 +00:00
if ( ! $verify ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Message from ' . $handle . ' did not verify. Discarding.' );
2017-11-08 00:37:53 +00:00
return false ;
}
return $data ;
}
/**
2020-01-19 06:05:23 +00:00
* encrypts data via AES
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param string $key The AES key
* @ param string $iv The IV ( is used for CBC encoding )
2017-11-08 00:37:53 +00:00
* @ param string $data The data that is to be encrypted
*
* @ return string encrypted data
*/
2022-06-17 08:44:13 +00:00
private static function aesEncrypt ( string $key , string $iv , string $data ) : string
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
return openssl_encrypt ( $data , 'aes-256-cbc' , str_pad ( $key , 32 , " \0 " ), OPENSSL_RAW_DATA , str_pad ( $iv , 16 , " \0 " ));
}
/**
2020-01-19 06:05:23 +00:00
* decrypts data via AES
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param string $key The AES key
* @ param string $iv The IV ( is used for CBC encoding )
2017-11-08 00:37:53 +00:00
* @ param string $encrypted The encrypted data
*
* @ return string decrypted data
*/
2022-06-17 08:44:13 +00:00
private static function aesDecrypt ( string $key , string $iv , string $encrypted ) : string
2017-11-08 22:02:50 +00:00
{
return openssl_decrypt ( $encrypted , 'aes-256-cbc' , str_pad ( $key , 32 , " \0 " ), OPENSSL_RAW_DATA , str_pad ( $iv , 16 , " \0 " ));
2017-11-08 00:37:53 +00:00
}
/**
2022-06-17 09:48:52 +00:00
* Decodes incoming Diaspora message in the new format . This method returns false on an error .
2017-11-08 00:37:53 +00:00
*
2018-09-06 09:20:45 +00:00
* @ param string $raw raw post message
2019-10-20 11:00:08 +00:00
* @ param string $privKey The private key of the importer
2018-09-06 09:20:45 +00:00
* @ param boolean $no_exit Don ' t do an http exit on error
2017-11-08 00:37:53 +00:00
*
2022-06-17 09:48:52 +00:00
* @ return bool | array
2017-11-08 00:37:53 +00:00
* 'message' -> decoded Diaspora XML message
* 'author' -> author diaspora handle
* 'key' -> author public key ( converted to pkcs #8)
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-17 09:48:52 +00:00
public static function decodeRaw ( string $raw , string $privKey = '' , bool $no_exit = false )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$data = json_decode ( $raw );
// Is it a private post? Then decrypt the outer Salmon
if ( is_object ( $data )) {
$encrypted_aes_key_bundle = base64_decode ( $data -> aes_key );
$ciphertext = base64_decode ( $data -> encrypted_magic_envelope );
$outer_key_bundle = '' ;
2019-10-20 11:00:08 +00:00
@ openssl_private_decrypt ( $encrypted_aes_key_bundle , $outer_key_bundle , $privKey );
2017-11-08 00:37:53 +00:00
$j_outer_key_bundle = json_decode ( $outer_key_bundle );
if ( ! is_object ( $j_outer_key_bundle )) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Outer Salmon did not verify. Discarding.' );
2018-09-06 09:20:45 +00:00
if ( $no_exit ) {
2022-06-17 09:48:52 +00:00
return false ;
2018-09-06 09:20:45 +00:00
} else {
2019-05-02 03:16:10 +00:00
throw new \Friendica\Network\HTTPException\BadRequestException ();
2018-09-06 09:20:45 +00:00
}
2017-11-08 00:37:53 +00:00
}
$outer_iv = base64_decode ( $j_outer_key_bundle -> iv );
$outer_key = base64_decode ( $j_outer_key_bundle -> key );
2017-11-23 19:01:58 +00:00
$xml = self :: aesDecrypt ( $outer_key , $outer_iv , $ciphertext );
2017-11-08 00:37:53 +00:00
} else {
$xml = $raw ;
}
2020-04-27 14:35:50 +00:00
$basedom = XML :: parseString ( $xml , true );
2017-11-08 00:37:53 +00:00
if ( ! is_object ( $basedom )) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Received data does not seem to be an XML. Discarding. ' . $xml );
2018-09-06 09:20:45 +00:00
if ( $no_exit ) {
2022-06-17 09:48:52 +00:00
return false ;
2018-09-06 09:20:45 +00:00
} else {
2019-05-02 03:16:10 +00:00
throw new \Friendica\Network\HTTPException\BadRequestException ();
2018-09-06 09:20:45 +00:00
}
2017-11-08 00:37:53 +00:00
}
2019-10-24 22:32:35 +00:00
$base = $basedom -> children ( ActivityNamespace :: SALMON_ME );
2017-11-08 00:37:53 +00:00
// Not sure if this cleaning is needed
2022-06-18 03:01:51 +00:00
$data = str_replace ([ " " , " \t " , " \r " , " \n " ], [ '' , '' , '' , '' ], $base -> data );
2017-11-08 00:37:53 +00:00
// Build the signed data
$type = $base -> data [ 0 ] -> attributes () -> type [ 0 ];
$encoding = $base -> encoding ;
$alg = $base -> alg ;
2022-06-18 03:01:51 +00:00
$signed_data = $data . '.' . Strings :: base64UrlEncode ( $type ) . '.' . Strings :: base64UrlEncode ( $encoding ) . '.' . Strings :: base64UrlEncode ( $alg );
2017-11-08 00:37:53 +00:00
// This is the signature
2018-11-08 15:37:08 +00:00
$signature = Strings :: base64UrlDecode ( $base -> sig );
2017-11-08 00:37:53 +00:00
// Get the senders' public key
$key_id = $base -> sig [ 0 ] -> attributes () -> key_id [ 0 ];
$author_addr = base64_decode ( $key_id );
2018-03-09 05:31:13 +00:00
if ( $author_addr == '' ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'No author could be decoded. Discarding. Message: ' . $xml );
2018-09-06 09:20:45 +00:00
if ( $no_exit ) {
2022-06-17 09:48:52 +00:00
return false ;
2018-09-06 09:20:45 +00:00
} else {
2019-05-02 03:16:10 +00:00
throw new \Friendica\Network\HTTPException\BadRequestException ();
2018-09-06 09:20:45 +00:00
}
2018-03-09 05:31:13 +00:00
}
2017-11-08 00:37:53 +00:00
$key = self :: key ( $author_addr );
2018-03-09 05:38:15 +00:00
if ( $key == '' ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( " Couldn't get a key for handle " . $author_addr . " . Discarding. " );
2018-09-06 09:20:45 +00:00
if ( $no_exit ) {
2022-06-17 09:48:52 +00:00
return false ;
2018-09-06 09:20:45 +00:00
} else {
2019-05-02 03:16:10 +00:00
throw new \Friendica\Network\HTTPException\BadRequestException ();
2018-09-06 09:20:45 +00:00
}
2018-03-09 05:38:15 +00:00
}
2017-11-08 00:37:53 +00:00
2017-12-30 16:51:49 +00:00
$verify = Crypto :: rsaVerify ( $signed_data , $signature , $key );
2017-11-08 00:37:53 +00:00
if ( ! $verify ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Message did not verify. Discarding.' );
2018-09-06 09:20:45 +00:00
if ( $no_exit ) {
return false ;
} else {
2019-05-02 03:16:10 +00:00
throw new \Friendica\Network\HTTPException\BadRequestException ();
2018-09-06 09:20:45 +00:00
}
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:04:04 +00:00
return [
'message' => ( string ) Strings :: base64UrlDecode ( $base -> data ),
'author' => XML :: unescape ( $author_addr ),
'key' => ( string ) $key
];
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 20:44:01 +00:00
* Decodes incoming Diaspora message in the deprecated format
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param string $xml urldecoded Diaspora salmon
2019-10-20 11:00:08 +00:00
* @ param string $privKey The private key of the importer
2017-11-08 00:37:53 +00:00
*
* @ return array
* 'message' -> decoded Diaspora XML message
* 'author' -> author diaspora handle
* 'key' -> author public key ( converted to pkcs #8)
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-18 03:06:00 +00:00
public static function decode ( string $xml , string $privKey = '' )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$public = false ;
2018-01-27 16:13:41 +00:00
$basedom = XML :: parseString ( $xml );
2017-11-08 00:37:53 +00:00
if ( ! is_object ( $basedom )) {
2020-06-29 20:22:00 +00:00
Logger :: notice ( 'XML is not parseable.' );
2022-06-18 03:06:00 +00:00
return false ;
2017-11-08 00:37:53 +00:00
}
$children = $basedom -> children ( 'https://joindiaspora.com/protocol' );
2018-02-14 04:58:46 +00:00
$inner_aes_key = null ;
$inner_iv = null ;
2017-11-08 00:37:53 +00:00
if ( $children -> header ) {
$public = true ;
2017-11-08 22:02:50 +00:00
$author_link = str_replace ( 'acct:' , '' , $children -> header -> author_id );
2017-11-08 00:37:53 +00:00
} else {
// This happens with posts from a relais
2019-10-20 11:00:08 +00:00
if ( empty ( $privKey )) {
2020-06-29 20:22:00 +00:00
Logger :: info ( 'This is no private post in the old format' );
2022-06-18 03:06:00 +00:00
return false ;
2017-11-08 00:37:53 +00:00
}
$encrypted_header = json_decode ( base64_decode ( $children -> encrypted_header ));
$encrypted_aes_key_bundle = base64_decode ( $encrypted_header -> aes_key );
$ciphertext = base64_decode ( $encrypted_header -> ciphertext );
$outer_key_bundle = '' ;
2019-10-20 11:00:08 +00:00
openssl_private_decrypt ( $encrypted_aes_key_bundle , $outer_key_bundle , $privKey );
2017-11-08 00:37:53 +00:00
$j_outer_key_bundle = json_decode ( $outer_key_bundle );
$outer_iv = base64_decode ( $j_outer_key_bundle -> iv );
$outer_key = base64_decode ( $j_outer_key_bundle -> key );
2017-11-23 19:01:58 +00:00
$decrypted = self :: aesDecrypt ( $outer_key , $outer_iv , $ciphertext );
2017-11-08 00:37:53 +00:00
2020-06-29 20:22:00 +00:00
Logger :: info ( 'decrypted' , [ 'data' => $decrypted ]);
2018-01-27 16:13:41 +00:00
$idom = XML :: parseString ( $decrypted );
2017-11-08 00:37:53 +00:00
$inner_iv = base64_decode ( $idom -> iv );
$inner_aes_key = base64_decode ( $idom -> aes_key );
2017-11-08 22:02:50 +00:00
$author_link = str_replace ( 'acct:' , '' , $idom -> author_id );
2017-11-08 00:37:53 +00:00
}
2019-10-24 22:32:35 +00:00
$dom = $basedom -> children ( ActivityNamespace :: SALMON_ME );
2017-11-08 00:37:53 +00:00
// figure out where in the DOM tree our data is hiding
2018-02-14 04:58:46 +00:00
$base = null ;
2017-11-08 22:02:50 +00:00
if ( $dom -> provenance -> data ) {
2017-11-08 00:37:53 +00:00
$base = $dom -> provenance ;
2017-11-08 22:02:50 +00:00
} elseif ( $dom -> env -> data ) {
2017-11-08 00:37:53 +00:00
$base = $dom -> env ;
2017-11-08 22:02:50 +00:00
} elseif ( $dom -> data ) {
2017-11-08 00:37:53 +00:00
$base = $dom ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
if ( ! $base ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'unable to locate salmon data in xml' );
2019-05-02 03:16:10 +00:00
throw new \Friendica\Network\HTTPException\BadRequestException ();
2017-11-08 00:37:53 +00:00
}
// Stash the signature away for now. We have to find their key or it won't be good for anything.
2018-11-08 15:37:08 +00:00
$signature = Strings :: base64UrlDecode ( $base -> sig );
2017-11-08 00:37:53 +00:00
// unpack the data
// strip whitespace so our data element will return to one big base64 blob
2022-06-18 03:01:51 +00:00
$data = str_replace ([ " " , " \t " , " \r " , " \n " ], [ '' , '' , '' , '' ], $base -> data );
2017-11-08 00:37:53 +00:00
// stash away some other stuff for later
$type = $base -> data [ 0 ] -> attributes () -> type [ 0 ];
$keyhash = $base -> sig [ 0 ] -> attributes () -> keyhash [ 0 ];
$encoding = $base -> encoding ;
$alg = $base -> alg ;
2018-11-08 15:37:08 +00:00
$signed_data = $data . '.' . Strings :: base64UrlEncode ( $type ) . '.' . Strings :: base64UrlEncode ( $encoding ) . '.' . Strings :: base64UrlEncode ( $alg );
2017-11-08 00:37:53 +00:00
// decode the data
2018-11-08 15:37:08 +00:00
$data = Strings :: base64UrlDecode ( $data );
2017-11-08 00:37:53 +00:00
2017-11-08 22:02:50 +00:00
if ( $public ) {
2017-11-08 00:37:53 +00:00
$inner_decrypted = $data ;
2017-11-08 22:02:50 +00:00
} else {
2017-11-08 00:37:53 +00:00
// Decode the encrypted blob
$inner_encrypted = base64_decode ( $data );
2017-11-23 19:01:58 +00:00
$inner_decrypted = self :: aesDecrypt ( $inner_aes_key , $inner_iv , $inner_encrypted );
2017-11-08 00:37:53 +00:00
}
if ( ! $author_link ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Could not retrieve author URI.' );
2019-05-02 03:16:10 +00:00
throw new \Friendica\Network\HTTPException\BadRequestException ();
2017-11-08 00:37:53 +00:00
}
// Once we have the author URI, go to the web and try to find their public key
// (first this will look it up locally if it is in the fcontact cache)
// This will also convert diaspora public key from pkcs#1 to pkcs#8
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Fetching key for ' . $author_link );
2017-11-08 00:37:53 +00:00
$key = self :: key ( $author_link );
if ( ! $key ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Could not retrieve author key.' );
2019-05-02 03:16:10 +00:00
throw new \Friendica\Network\HTTPException\BadRequestException ();
2017-11-08 00:37:53 +00:00
}
2017-12-30 16:51:49 +00:00
$verify = Crypto :: rsaVerify ( $signed_data , $signature , $key );
2017-11-08 00:37:53 +00:00
if ( ! $verify ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Message did not verify. Discarding.' );
2019-05-02 03:16:10 +00:00
throw new \Friendica\Network\HTTPException\BadRequestException ();
2017-11-08 00:37:53 +00:00
}
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Message verified.' );
2017-11-08 00:37:53 +00:00
2022-06-18 03:04:04 +00:00
return [
'message' => ( string ) $inner_decrypted ,
'author' => XML :: unescape ( $author_link ),
'key' => ( string ) $key
];
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Dispatches public messages and find the fitting receivers
2017-11-08 00:37:53 +00:00
*
2022-03-12 07:08:10 +00:00
* @ param array $msg The post that will be dispatched
2022-03-12 15:27:56 +00:00
* @ param int $direction Indicates if the message had been fetched or pushed ( self :: PUSHED , self :: FETCHED , self :: FORCED_FETCH )
2017-11-08 00:37:53 +00:00
*
2022-06-17 08:44:13 +00:00
* @ return int | bool The message id of the generated message , " true " or " false " if there was an error
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-17 08:44:13 +00:00
public static function dispatchPublic ( array $msg , int $direction )
2017-11-08 22:02:50 +00:00
{
2022-07-19 23:06:05 +00:00
$enabled = intval ( DI :: config () -> get ( 'system' , 'diaspora_enabled' ));
2017-11-08 00:37:53 +00:00
if ( ! $enabled ) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Diaspora is disabled' );
2017-11-08 00:37:53 +00:00
return false ;
}
2018-04-24 13:21:25 +00:00
if ( ! ( $fields = self :: validPosting ( $msg ))) {
2022-08-30 19:45:30 +00:00
Logger :: warning ( 'Invalid posting' );
2017-11-08 00:37:53 +00:00
return false ;
}
2022-06-18 03:01:51 +00:00
$importer = [
'uid' => 0 ,
'page-flags' => User :: PAGE_FLAGS_FREELOVE
];
2022-03-12 07:08:10 +00:00
$success = self :: dispatch ( $importer , $msg , $fields , $direction );
2017-11-08 00:37:53 +00:00
2018-04-27 14:03:10 +00:00
return $success ;
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Dispatches the different message types to the different functions
2017-11-08 00:37:53 +00:00
*
2022-03-12 07:08:10 +00:00
* @ param array $importer Array of the importer user
* @ param array $msg The post that will be dispatched
* @ param SimpleXMLElement $fields SimpleXML object that contains the message
2022-03-12 15:27:56 +00:00
* @ param int $direction Indicates if the message had been fetched or pushed ( self :: PUSHED , self :: FETCHED , self :: FORCED_FETCH )
2017-11-08 00:37:53 +00:00
*
2022-06-17 08:44:13 +00:00
* @ return int | bool The message id of the generated message , " true " or " false " if there was an error
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-17 08:44:13 +00:00
public static function dispatch ( array $importer , array $msg , SimpleXMLElement $fields = null , int $direction = self :: PUSHED )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
// The sender is the handle of the contact that sent the message.
// This will often be different with relayed messages (for example "like" and "comment")
2022-06-18 03:01:51 +00:00
$sender = $msg [ 'author' ];
2017-11-08 00:37:53 +00:00
// This is only needed for private postings since this is already done for public ones before
if ( is_null ( $fields )) {
2018-04-24 18:34:35 +00:00
$private = true ;
2018-04-24 13:21:25 +00:00
if ( ! ( $fields = self :: validPosting ( $msg ))) {
2022-08-30 19:45:30 +00:00
Logger :: warning ( 'Invalid posting' );
2017-11-08 00:37:53 +00:00
return false ;
}
2018-04-24 18:34:35 +00:00
} else {
$private = false ;
2017-11-08 00:37:53 +00:00
}
$type = $fields -> getName ();
2022-06-18 03:01:51 +00:00
Logger :: info ( 'Received message' , [ 'type' => $type , 'sender' => $sender , 'user' => $importer [ 'uid' ]]);
2017-11-08 00:37:53 +00:00
switch ( $type ) {
2022-06-18 03:01:51 +00:00
case 'account_migration' :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Message with type ' . $type . ' is not private, quitting.' );
2018-04-24 18:34:35 +00:00
return false ;
}
2017-11-08 00:37:53 +00:00
return self :: receiveAccountMigration ( $importer , $fields );
2022-06-18 03:01:51 +00:00
case 'account_deletion' :
2018-04-24 18:34:35 +00:00
return self :: receiveAccountDeletion ( $fields );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'comment' :
return self :: receiveComment ( $importer , $sender , $fields , $msg [ 'message' ], $direction );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'contact' :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Message with type ' . $type . ' is not private, quitting.' );
2018-04-24 18:34:35 +00:00
return false ;
}
2017-11-23 19:01:58 +00:00
return self :: receiveContactRequest ( $importer , $fields );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'conversation' :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Message with type ' . $type . ' is not private, quitting.' );
2018-04-24 18:34:35 +00:00
return false ;
}
2017-11-23 19:01:58 +00:00
return self :: receiveConversation ( $importer , $msg , $fields );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'like' :
2022-03-12 07:08:10 +00:00
return self :: receiveLike ( $importer , $sender , $fields , $direction );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'message' :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Message with type ' . $type . ' is not private, quitting.' );
2018-04-24 18:34:35 +00:00
return false ;
}
2017-11-23 19:01:58 +00:00
return self :: receiveMessage ( $importer , $fields );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'participation' :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Message with type ' . $type . ' is not private, quitting.' );
2018-04-24 18:34:35 +00:00
return false ;
}
2022-03-12 07:08:10 +00:00
return self :: receiveParticipation ( $importer , $fields , $direction );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'photo' : // Not implemented
2017-11-23 19:01:58 +00:00
return self :: receivePhoto ( $importer , $fields );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'poll_participation' : // Not implemented
2017-11-23 19:01:58 +00:00
return self :: receivePollParticipation ( $importer , $fields );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'profile' :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Message with type ' . $type . ' is not private, quitting.' );
2018-04-24 18:34:35 +00:00
return false ;
}
2017-11-23 19:01:58 +00:00
return self :: receiveProfile ( $importer , $fields );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'reshare' :
return self :: receiveReshare ( $importer , $fields , $msg [ 'message' ], $direction );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'retraction' :
2017-11-23 19:01:58 +00:00
return self :: receiveRetraction ( $importer , $sender , $fields );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'status_message' :
return self :: receiveStatusMessage ( $importer , $fields , $msg [ 'message' ], $direction );
2017-11-08 00:37:53 +00:00
default :
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Unknown message type ' . $type );
2017-11-08 00:37:53 +00:00
return false ;
}
}
/**
2020-01-19 06:05:23 +00:00
* Checks if a posting is valid and fetches the data fields .
2017-11-08 00:37:53 +00:00
*
* This function does not only check the signature .
* It also does the conversion between the old and the new diaspora format .
*
* @ param array $msg Array with the XML , the sender handle and the sender signature
*
2019-01-21 21:51:59 +00:00
* @ return bool | SimpleXMLElement If the posting is valid then an array with an SimpleXML object is returned
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-17 08:44:13 +00:00
private static function validPosting ( array $msg )
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
$data = XML :: parseString ( $msg [ 'message' ]);
2017-11-08 00:37:53 +00:00
if ( ! is_object ( $data )) {
2020-06-29 20:22:00 +00:00
Logger :: info ( 'No valid XML' , [ 'message' => $msg [ 'message' ]]);
2017-11-08 00:37:53 +00:00
return false ;
}
// Is this the new or the old version?
2022-06-18 03:01:51 +00:00
if ( $data -> getName () == 'XML' ) {
2017-11-08 00:37:53 +00:00
$oldXML = true ;
2017-11-08 22:02:50 +00:00
foreach ( $data -> post -> children () as $child ) {
2017-11-08 00:37:53 +00:00
$element = $child ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
} else {
$oldXML = false ;
$element = $data ;
}
$type = $element -> getName ();
$orig_type = $type ;
2022-08-22 09:54:29 +00:00
Logger :: debug ( 'Got message' , [ 'type' => $type , 'message' => $msg [ 'message' ]]);
2017-11-08 00:37:53 +00:00
// All retractions are handled identically from now on.
// In the new version there will only be "retraction".
2022-06-18 03:01:51 +00:00
if ( in_array ( $type , [ 'signed_retraction' , 'relayable_retraction' ]))
$type = 'retraction' ;
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
if ( $type == 'request' ) {
$type = 'contact' ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$fields = new SimpleXMLElement ( '<' . $type . '/>' );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$signed_data = '' ;
2018-02-14 04:58:46 +00:00
$author_signature = null ;
$parent_author_signature = null ;
2017-11-08 00:37:53 +00:00
2017-11-08 22:02:50 +00:00
foreach ( $element -> children () as $fieldname => $entry ) {
2017-11-08 00:37:53 +00:00
if ( $oldXML ) {
// Translation for the old XML structure
2022-06-18 03:01:51 +00:00
if ( $fieldname == 'diaspora_handle' ) {
$fieldname = 'author' ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
if ( $fieldname == 'participant_handles' ) {
$fieldname = 'participants' ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
if ( in_array ( $type , [ 'like' , 'participation' ])) {
if ( $fieldname == 'target_type' ) {
$fieldname = 'parent_type' ;
2017-11-08 00:37:53 +00:00
}
}
2022-06-18 03:01:51 +00:00
if ( $fieldname == 'sender_handle' ) {
$fieldname = 'author' ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
if ( $fieldname == 'recipient_handle' ) {
$fieldname = 'recipient' ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
if ( $fieldname == 'root_diaspora_id' ) {
$fieldname = 'root_author' ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
if ( $type == 'status_message' ) {
if ( $fieldname == 'raw_message' ) {
$fieldname = 'text' ;
2017-11-08 00:37:53 +00:00
}
}
2022-06-18 03:01:51 +00:00
if ( $type == 'retraction' ) {
if ( $fieldname == 'post_guid' ) {
$fieldname = 'target_guid' ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
if ( $fieldname == 'type' ) {
$fieldname = 'target_type' ;
2017-11-08 00:37:53 +00:00
}
}
}
2022-06-18 03:01:51 +00:00
if (( $fieldname == 'author_signature' ) && ( $entry != '' )) {
2017-11-08 00:37:53 +00:00
$author_signature = base64_decode ( $entry );
2022-06-18 03:01:51 +00:00
} elseif (( $fieldname == 'parent_author_signature' ) && ( $entry != '' )) {
2017-11-08 00:37:53 +00:00
$parent_author_signature = base64_decode ( $entry );
2022-06-18 03:01:51 +00:00
} elseif ( ! in_array ( $fieldname , [ 'author_signature' , 'parent_author_signature' , 'target_author_signature' ])) {
if ( $signed_data != '' ) {
$signed_data .= ';' ;
2017-11-08 00:37:53 +00:00
}
$signed_data .= $entry ;
}
2022-06-18 03:01:51 +00:00
if ( ! in_array ( $fieldname , [ 'parent_author_signature' , 'target_author_signature' ])
|| ( $orig_type == 'relayable_retraction' )
2017-11-08 22:02:50 +00:00
) {
2017-11-10 12:45:33 +00:00
XML :: copy ( $entry , $fields , $fieldname );
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
}
// This is something that shouldn't happen at all.
2022-06-18 03:01:51 +00:00
if ( in_array ( $type , [ 'status_message' , 'reshare' , 'profile' ])) {
if ( $msg [ 'author' ] != $fields -> author ) {
2022-07-20 05:38:53 +00:00
Logger :: notice ( 'Message handle is not the same as envelope sender. Quitting this message.' , [ 'author1' => $msg [ 'author' ], 'author2' => $fields -> author ]);
2017-11-08 00:37:53 +00:00
return false ;
}
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
// Only some message types have signatures. So we quit here for the other types.
2022-06-18 03:01:51 +00:00
if ( ! in_array ( $type , [ 'comment' , 'like' ])) {
2018-04-24 13:21:25 +00:00
return $fields ;
2017-11-08 00:37:53 +00:00
}
2022-08-22 09:54:29 +00:00
if ( ! isset ( $author_signature ) && ( $msg [ 'author' ] == $fields -> author )) {
Logger :: debug ( 'No author signature, but the sender matches the author' , [ 'type' => $type , 'msg-author' => $msg [ 'author' ], 'message' => $msg [ 'message' ]]);
return $fields ;
}
2017-11-08 00:37:53 +00:00
// No author_signature? This is a must, so we quit.
if ( ! isset ( $author_signature )) {
2022-08-22 09:54:29 +00:00
Logger :: info ( 'No author signature' , [ 'type' => $type , 'msg-author' => $msg [ 'author' ], 'fields-author' => $fields -> author , 'message' => $msg [ 'message' ]]);
2017-11-08 00:37:53 +00:00
return false ;
}
if ( isset ( $parent_author_signature )) {
2022-06-18 03:01:51 +00:00
$key = self :: key ( $msg [ 'author' ]);
2018-04-26 20:41:06 +00:00
if ( empty ( $key )) {
2022-06-18 03:01:51 +00:00
Logger :: info ( 'No key found for parent' , [ 'author' => $msg [ 'author' ]]);
2018-04-26 20:41:06 +00:00
return false ;
}
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
if ( ! Crypto :: rsaVerify ( $signed_data , $parent_author_signature , $key , 'sha256' )) {
2022-09-04 09:22:15 +00:00
Logger :: info ( 'No valid parent author signature' , [ 'author' => $msg [ 'author' ], 'type' => $type , 'signed data' => $signed_data , 'message' => $msg [ 'message' ], 'signature' => $parent_author_signature ]);
2017-11-08 00:37:53 +00:00
return false ;
}
}
$key = self :: key ( $fields -> author );
2018-04-26 20:41:06 +00:00
if ( empty ( $key )) {
2020-06-29 20:22:00 +00:00
Logger :: info ( 'No key found' , [ 'author' => $fields -> author ]);
2018-04-26 20:41:06 +00:00
return false ;
}
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
if ( ! Crypto :: rsaVerify ( $signed_data , $author_signature , $key , 'sha256' )) {
2022-09-04 09:22:15 +00:00
Logger :: info ( 'No valid author signature for author' , [ 'author' => $fields -> author , 'type' => $type , 'signed data' => $signed_data , 'message' => $msg [ 'message' ], 'signature' => $author_signature ]);
2017-11-08 00:37:53 +00:00
return false ;
} else {
2018-04-24 13:21:25 +00:00
return $fields ;
2017-11-08 00:37:53 +00:00
}
}
/**
2020-01-19 06:05:23 +00:00
* Fetches the public key for a given handle
2017-11-08 00:37:53 +00:00
*
* @ param string $handle The handle
*
* @ return string The public key
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-11-08 00:37:53 +00:00
*/
2022-07-17 11:47:12 +00:00
private static function key ( string $handle = null ) : string
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$handle = strval ( $handle );
2022-07-20 05:38:53 +00:00
Logger :: notice ( 'Fetching diaspora key' , [ 'handle' => $handle , 'callstack' => System :: callstack ( 20 )]);
2017-11-08 00:37:53 +00:00
2022-06-17 08:44:13 +00:00
$fcontact = FContact :: getByURL ( $handle );
2022-07-17 11:47:12 +00:00
if ( ! empty ( $fcontact [ 'pubkey' ])) {
2022-06-18 03:01:51 +00:00
return $fcontact [ 'pubkey' ];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
return '' ;
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Get a contact id for a given handle
2017-11-08 00:37:53 +00:00
*
2019-01-06 21:06:53 +00:00
* @ todo Move to Friendica\Model\Contact
2017-12-17 20:27:50 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param int $uid The user id
2017-11-08 00:37:53 +00:00
* @ param string $handle The handle in the format user @ domain . tld
*
2019-01-21 21:51:59 +00:00
* @ return array Contact data
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-17 08:44:13 +00:00
private static function contactByHandle ( int $uid , string $handle ) : array
2017-11-08 22:02:50 +00:00
{
2020-08-05 12:35:38 +00:00
return Contact :: getByURL ( $handle , null , [], $uid );
2017-11-08 00:37:53 +00:00
}
2019-07-27 11:09:12 +00:00
/**
* Checks if the given contact url does support ActivityPub
*
* @ param string $url profile url
2019-07-27 21:45:36 +00:00
* @ param boolean $update true = always update , false = never update , null = update when not found or outdated
2019-07-27 11:09:12 +00:00
* @ return boolean
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
*/
2022-06-17 08:44:13 +00:00
public static function isSupportedByContactUrl ( string $url , $update = null )
2019-07-27 11:09:12 +00:00
{
2020-08-06 10:31:05 +00:00
return ! empty ( FContact :: getByURL ( $url , $update ));
2019-07-27 11:09:12 +00:00
}
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* Check if posting is allowed for this contact
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param array $contact The contact that is checked
* @ param bool $is_comment Is the check for a comment ?
2017-11-08 00:37:53 +00:00
*
* @ return bool is the contact allowed to post ?
*/
2022-06-17 08:44:13 +00:00
private static function postAllow ( array $importer , array $contact , bool $is_comment = false ) : bool
2017-11-23 19:01:58 +00:00
{
2017-11-08 00:37:53 +00:00
/*
* Perhaps we were already sharing with this person . Now they ' re sharing with us .
* That makes us friends .
* Normally this should have handled by getting a request - but this could get lost
*/
2017-12-07 20:10:09 +00:00
// It is deactivated by now, due to side effects. See issue https://github.com/friendica/friendica/pull/4033
// It is not removed by now. Possibly the code is needed?
2019-01-06 17:37:48 +00:00
//if (!$is_comment && $contact["rel"] == Contact::FOLLOWER && in_array($importer["page-flags"], array(User::PAGE_FLAGS_FREELOVE))) {
2022-02-21 15:16:38 +00:00
// Contact::update(
2018-07-25 02:53:46 +00:00
// array('rel' => Contact::FRIEND, 'writable' => true),
2017-12-07 20:10:09 +00:00
// array('id' => $contact["id"], 'uid' => $contact["uid"])
// );
//
2018-07-25 02:53:46 +00:00
// $contact["rel"] = Contact::FRIEND;
2021-10-02 17:11:54 +00:00
// Logger::notice("defining user ".$contact["nick"]." as friend");
2017-12-07 20:10:09 +00:00
//}
2017-11-08 00:37:53 +00:00
2019-03-09 03:40:08 +00:00
// Contact server is blocked
if ( Network :: isUrlBlocked ( $contact [ 'url' ])) {
return false ;
// We don't seem to like that person
2022-06-18 03:01:51 +00:00
} elseif ( $contact [ 'blocked' ]) {
2017-11-08 00:37:53 +00:00
// Maybe blocked, don't accept.
return false ;
2017-11-23 19:01:58 +00:00
// We are following this person?
2022-06-18 03:01:51 +00:00
} elseif (( $contact [ 'rel' ] == Contact :: SHARING ) || ( $contact [ 'rel' ] == Contact :: FRIEND )) {
2017-11-08 00:37:53 +00:00
// Yes, then it is fine.
return true ;
2017-11-23 19:01:58 +00:00
// Is the message a global user or a comment?
2022-06-18 03:01:51 +00:00
} elseif (( $importer [ 'uid' ] == 0 ) || $is_comment ) {
2017-11-08 00:37:53 +00:00
// Messages for the global users and comments are always accepted
return true ;
}
return false ;
}
/**
2020-01-19 06:05:23 +00:00
* Fetches the contact id for a handle and checks if posting is allowed
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param string $handle The checked handle in the format user @ domain . tld
* @ param bool $is_comment Is the check for a comment ?
2017-11-08 00:37:53 +00:00
*
2022-06-17 08:44:13 +00:00
* @ return array | bool The contact data or false on error
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
private static function allowedContactByHandle ( array $importer , string $handle , bool $is_comment = false )
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
$contact = self :: contactByHandle ( $importer [ 'uid' ], $handle );
2017-11-08 00:37:53 +00:00
if ( ! $contact ) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'A Contact for handle ' . $handle . ' and user ' . $importer [ 'uid' ] . ' was not found' );
2017-11-08 00:37:53 +00:00
// If a contact isn't found, we accept it anyway if it is a comment
2022-06-18 03:01:51 +00:00
if ( $is_comment && ( $importer [ 'uid' ] != 0 )) {
2018-07-23 11:43:18 +00:00
return self :: contactByHandle ( 0 , $handle );
} elseif ( $is_comment ) {
2017-11-08 00:37:53 +00:00
return $importer ;
} else {
return false ;
}
}
2017-11-23 19:01:58 +00:00
if ( ! self :: postAllow ( $importer , $contact , $is_comment )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'The handle: ' . $handle . ' is not allowed to post to user ' . $importer [ 'uid' ]);
2017-11-08 00:37:53 +00:00
return false ;
}
return $contact ;
}
/**
2020-01-19 06:05:23 +00:00
* Does the message already exists on the system ?
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param int $uid The user id
2017-11-08 00:37:53 +00:00
* @ param string $guid The guid of the message
*
* @ return int | bool message id if the message already was stored into the system - or false .
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
private static function messageExists ( int $uid , string $guid )
2017-11-08 22:02:50 +00:00
{
2021-01-16 04:14:58 +00:00
$item = Post :: selectFirst ([ 'id' ], [ 'uid' => $uid , 'guid' => $guid ]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $item )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Message ' . $guid . ' already exists for user ' . $uid );
2022-06-18 03:01:51 +00:00
return $item [ 'id' ];
2017-11-08 00:37:53 +00:00
}
return false ;
}
/**
2020-01-19 06:05:23 +00:00
* Checks for links to posts in a message
2017-11-08 00:37:53 +00:00
*
* @ param array $item The item array
2017-11-23 19:01:58 +00:00
* @ return void
2017-11-08 00:37:53 +00:00
*/
2018-07-19 11:07:14 +00:00
private static function fetchGuid ( array $item )
2017-11-08 22:02:50 +00:00
{
preg_replace_callback (
2022-06-18 03:01:51 +00:00
" =diaspora://.*?/post/([0-9A-Za-z \ -_@.:] { 15,254}[0-9A-Za-z])=ism " ,
2017-11-08 00:37:53 +00:00
function ( $match ) use ( $item ) {
2017-12-17 20:29:16 +00:00
self :: fetchGuidSub ( $match , $item );
2017-11-08 22:02:50 +00:00
},
2022-06-18 03:01:51 +00:00
$item [ 'body' ]
2017-11-08 22:02:50 +00:00
);
2017-11-08 00:37:53 +00:00
2017-11-08 22:02:50 +00:00
preg_replace_callback (
2018-10-19 02:39:06 +00:00
" & \ [url=/?posts/([^ \ [ \ ]]*) \ ](.*) \ [ \ /url \ ]&Usi " ,
2017-11-08 00:37:53 +00:00
function ( $match ) use ( $item ) {
2017-12-17 20:29:16 +00:00
self :: fetchGuidSub ( $match , $item );
2017-11-08 22:02:50 +00:00
},
2022-06-18 03:01:51 +00:00
$item [ 'body' ]
2017-11-08 22:02:50 +00:00
);
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Checks for relative / people /* links in an item body to match local
2017-11-08 00:37:53 +00:00
* contacts or prepends the remote host taken from the author link .
*
2017-11-08 22:02:50 +00:00
* @ param string $body The item body to replace links from
2017-11-08 00:37:53 +00:00
* @ param string $author_link The author link for missing local contact fallback
*
2017-12-17 20:27:50 +00:00
* @ return string the replaced string
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
public static function replacePeopleGuid ( string $body , string $author_link ) : string
2017-11-08 22:02:50 +00:00
{
$return = preg_replace_callback (
" & \ [url=/people/([^ \ [ \ ]]*) \ ](.*) \ [ \ /url \ ]&Usi " ,
2017-11-08 00:37:53 +00:00
function ( $match ) use ( $author_link ) {
// $match
// 0 => '[url=/people/0123456789abcdef]Foo Bar[/url]'
// 1 => '0123456789abcdef'
// 2 => 'Foo Bar'
2020-08-06 10:27:06 +00:00
$handle = FContact :: getUrlByGuid ( $match [ 1 ]);
2017-11-08 00:37:53 +00:00
if ( $handle ) {
2022-06-18 03:01:51 +00:00
$return = '@[url=' . $handle . ']' . $match [ 2 ] . '[/url]' ;
2017-11-08 00:37:53 +00:00
} else {
// No local match, restoring absolute remote URL from author scheme and host
$author_url = parse_url ( $author_link );
2022-06-18 03:01:51 +00:00
$return = '[url=' . $author_url [ 'scheme' ] . '://' . $author_url [ 'host' ] . '/people/' . $match [ 1 ] . ']' . $match [ 2 ] . '[/url]' ;
2017-11-08 00:37:53 +00:00
}
return $return ;
2017-11-08 22:02:50 +00:00
},
$body
);
2017-11-08 00:37:53 +00:00
return $return ;
}
/**
2020-01-19 06:05:23 +00:00
* sub function of " fetchGuid " which checks for links in messages
2017-11-08 00:37:53 +00:00
*
* @ param array $match array containing a link that has to be checked for a message link
2017-11-08 22:02:50 +00:00
* @ param array $item The item array
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
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
private static function fetchGuidSub ( array $match , array $item )
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
if ( ! self :: storeByGuid ( $match [ 1 ], $item [ 'author-link' ], true )) {
self :: storeByGuid ( $match [ 1 ], $item [ 'owner-link' ], true );
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Fetches an item with a given guid from a given server
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param string $guid the message guid
2017-11-08 00:37:53 +00:00
* @ param string $server The server address
2022-03-12 07:08:10 +00:00
* @ param bool $force Forced fetch
2017-11-08 00:37:53 +00:00
*
2022-06-17 08:44:13 +00:00
* @ return int | bool the message id of the stored message or false
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-11-08 00:37:53 +00:00
*/
2022-10-09 21:16:36 +00:00
public static function storeByGuid ( string $guid , string $server , bool $force )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$serverparts = parse_url ( $server );
2018-07-23 11:43:18 +00:00
2022-06-18 03:01:51 +00:00
if ( empty ( $serverparts [ 'host' ]) || empty ( $serverparts [ 'scheme' ])) {
2018-07-23 11:43:18 +00:00
return false ;
}
2022-06-18 03:01:51 +00:00
$server = $serverparts [ 'scheme' ] . '://' . $serverparts [ 'host' ];
2017-11-08 00:37:53 +00:00
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Trying to fetch item ' . $guid . ' from ' . $server );
2017-11-08 00:37:53 +00:00
$msg = self :: message ( $guid , $server );
2017-11-08 22:02:50 +00:00
if ( ! $msg ) {
2017-11-08 00:37:53 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Successfully fetched item ' . $guid . ' from ' . $server );
2017-11-08 00:37:53 +00:00
// Now call the dispatcher
2022-03-12 07:08:10 +00:00
return self :: dispatchPublic ( $msg , $force ? self :: FORCED_FETCH : self :: FETCHED );
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Fetches a message from a server
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param string $guid message guid
2017-11-08 00:37:53 +00:00
* @ param string $server The url of the server
2017-11-08 22:02:50 +00:00
* @ param int $level Endless loop prevention
2017-11-08 00:37:53 +00:00
*
* @ return array
* 'message' => The message XML
* 'author' => The author handle
* 'key' => The public key of the author
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
public static function message ( string $guid , string $server , int $level = 0 )
2017-11-08 22:02:50 +00:00
{
if ( $level > 5 ) {
2017-11-08 00:37:53 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
// This will work for new Diaspora servers and Friendica servers from 3.5
2022-06-18 03:01:51 +00:00
$source_url = $server . '/fetch/post/' . urlencode ( $guid );
2017-11-08 00:37:53 +00:00
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Fetch post from ' . $source_url );
2017-11-08 00:37:53 +00:00
2022-04-02 19:16:22 +00:00
$envelope = DI :: httpClient () -> fetch ( $source_url , HttpClientAccept :: MAGIC );
2017-11-08 00:37:53 +00:00
if ( $envelope ) {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Envelope was fetched.' );
2017-11-23 19:01:58 +00:00
$x = self :: verifyMagicEnvelope ( $envelope );
2017-11-08 22:02:50 +00:00
if ( ! $x ) {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Envelope could not be verified.' );
2017-11-08 22:02:50 +00:00
} else {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Envelope was verified.' );
2017-11-08 22:02:50 +00:00
}
} else {
2017-11-08 00:37:53 +00:00
$x = false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
if ( ! $x ) {
2019-01-06 07:43:11 +00:00
return false ;
2017-11-08 00:37:53 +00:00
}
2018-01-27 16:13:41 +00:00
$source_xml = XML :: parseString ( $x );
2017-11-08 00:37:53 +00:00
2017-11-08 22:02:50 +00:00
if ( ! is_object ( $source_xml )) {
2017-11-08 00:37:53 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
if ( $source_xml -> post -> reshare ) {
// Reshare of a reshare - old Diaspora version
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Message is a reshare' );
2017-11-08 00:37:53 +00:00
return self :: message ( $source_xml -> post -> reshare -> root_guid , $server , ++ $level );
2022-07-19 23:06:05 +00:00
} elseif ( $source_xml -> getName () == 'reshare' ) {
2017-11-08 00:37:53 +00:00
// Reshare of a reshare - new Diaspora version
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Message is a new reshare' );
2017-11-08 00:37:53 +00:00
return self :: message ( $source_xml -> root_guid , $server , ++ $level );
}
2022-07-19 23:06:05 +00:00
$author = '' ;
2017-11-08 00:37:53 +00:00
// Fetch the author - for the old and the new Diaspora version
2018-07-08 09:37:05 +00:00
if ( $source_xml -> post -> status_message && $source_xml -> post -> status_message -> diaspora_handle ) {
2017-11-08 00:37:53 +00:00
$author = ( string ) $source_xml -> post -> status_message -> diaspora_handle ;
2022-06-18 03:01:51 +00:00
} elseif ( $source_xml -> author && ( $source_xml -> getName () == 'status_message' )) {
2017-11-08 00:37:53 +00:00
$author = ( string ) $source_xml -> author ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
// If this isn't a "status_message" then quit
if ( ! $author ) {
2021-10-02 17:11:54 +00:00
Logger :: info ( " Message doesn't seem to be a status message " );
2017-11-08 00:37:53 +00:00
return false ;
}
2022-06-18 03:01:51 +00:00
return [
'message' => $x ,
'author' => $author ,
'key' => self :: key ( $author )
];
2017-11-08 00:37:53 +00:00
}
2019-07-21 07:37:50 +00:00
/**
2020-01-19 06:05:23 +00:00
* Fetches an item with a given URL
2019-07-21 07:37:50 +00:00
*
* @ param string $url the message url
2022-06-17 08:44:13 +00:00
* @ param int $uid User id
2019-07-21 07:37:50 +00:00
*
2022-06-17 08:44:13 +00:00
* @ return int | bool the message id of the stored message or false
2019-07-21 07:37:50 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
*/
2022-06-17 08:44:13 +00:00
public static function fetchByURL ( string $url , int $uid = 0 )
2019-07-21 07:37:50 +00:00
{
2019-07-22 06:03:18 +00:00
// Check for Diaspora (and Friendica) typical paths
2022-06-18 03:01:51 +00:00
if ( ! preg_match ( '=(https?://.+)/(?:posts|display|objects)/([a-zA-Z0-9-_@.:%]+[a-zA-Z0-9])=i' , $url , $matches )) {
2020-03-09 05:50:01 +00:00
Logger :: info ( 'Invalid url' , [ 'url' => $url ]);
2019-07-21 07:37:50 +00:00
return false ;
}
2019-07-22 06:03:18 +00:00
$guid = urldecode ( $matches [ 2 ]);
2019-07-21 07:37:50 +00:00
2021-01-16 04:14:58 +00:00
$item = Post :: selectFirst ([ 'id' ], [ 'guid' => $guid , 'uid' => $uid ]);
2019-07-21 07:37:50 +00:00
if ( DBA :: isResult ( $item )) {
2020-03-09 05:50:01 +00:00
Logger :: info ( 'Found' , [ 'id' => $item [ 'id' ]]);
2019-07-21 07:37:50 +00:00
return $item [ 'id' ];
}
2020-03-09 05:50:01 +00:00
Logger :: info ( 'Fetch GUID from origin' , [ 'guid' => $guid , 'server' => $matches [ 1 ]]);
2022-03-12 07:08:10 +00:00
$ret = self :: storeByGuid ( $guid , $matches [ 1 ], true );
2020-03-09 05:50:01 +00:00
Logger :: info ( 'Result' , [ 'ret' => $ret ]);
2019-07-21 07:37:50 +00:00
2021-01-16 04:14:58 +00:00
$item = Post :: selectFirst ([ 'id' ], [ 'guid' => $guid , 'uid' => $uid ]);
2019-07-21 07:37:50 +00:00
if ( DBA :: isResult ( $item )) {
2020-03-09 05:50:01 +00:00
Logger :: info ( 'Found' , [ 'id' => $item [ 'id' ]]);
2019-07-21 07:37:50 +00:00
return $item [ 'id' ];
} else {
2020-03-09 05:50:01 +00:00
Logger :: info ( 'Not found' , [ 'guid' => $guid , 'uid' => $uid ]);
2019-07-21 07:37:50 +00:00
return false ;
}
}
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* Fetches the item record of a given guid
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param int $uid The user id
* @ param string $guid message guid
* @ param string $author The handle of the item
* @ param array $contact The contact of the item owner
2017-11-08 00:37:53 +00:00
*
2022-06-17 08:44:13 +00:00
* @ return array | bool the item record or false on failure
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
private static function parentItem ( int $uid , string $guid , string $author , array $contact )
2017-11-08 22:02:50 +00:00
{
2018-06-16 22:32:57 +00:00
$fields = [ 'id' , 'parent' , 'body' , 'wall' , 'uri' , 'guid' , 'private' , 'origin' ,
2020-05-27 12:19:06 +00:00
'author-name' , 'author-link' , 'author-avatar' , 'gravity' ,
2018-06-16 22:32:57 +00:00
'owner-name' , 'owner-link' , 'owner-avatar' ];
2022-07-19 23:06:05 +00:00
2018-06-16 22:32:57 +00:00
$condition = [ 'uid' => $uid , 'guid' => $guid ];
2021-01-16 04:14:58 +00:00
$item = Post :: selectFirst ( $fields , $condition );
2017-11-08 00:37:53 +00:00
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2020-08-06 10:31:05 +00:00
$person = FContact :: getByURL ( $author );
2022-06-18 03:01:51 +00:00
$result = self :: storeByGuid ( $guid , $person [ 'url' ], false );
2017-11-08 00:37:53 +00:00
2018-08-01 06:47:18 +00:00
// We don't have an url for items that arrived at the public dispatcher
2022-06-18 03:01:51 +00:00
if ( ! $result && ! empty ( $contact [ 'url' ])) {
$result = self :: storeByGuid ( $guid , $contact [ 'url' ], false );
2017-11-08 00:37:53 +00:00
}
if ( $result ) {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Fetched missing item ' . $guid . ' - result: ' . $result );
2017-11-08 00:37:53 +00:00
2021-01-16 04:14:58 +00:00
$item = Post :: selectFirst ( $fields , $condition );
2017-11-08 00:37:53 +00:00
}
}
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Parent item not found: parent: ' . $guid . ' - user: ' . $uid );
2017-11-08 00:37:53 +00:00
return false ;
} else {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Parent item found: parent: ' . $guid . ' - user: ' . $uid );
2018-06-16 22:32:57 +00:00
return $item ;
2017-11-08 00:37:53 +00:00
}
}
/**
2020-01-19 06:05:23 +00:00
* returns contact details
2017-11-08 00:37:53 +00:00
*
2018-02-14 21:18:16 +00:00
* @ param array $def_contact The default contact if the person isn ' t found
* @ param array $person The record of the person
* @ param int $uid The user id
2017-11-08 00:37:53 +00:00
*
* @ return array
* 'cid' => contact id
* 'network' => network type
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
private static function authorContactByUrl ( array $def_contact , array $person , int $uid ) : array
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
$condition = [ 'nurl' => Strings :: normaliseLink ( $person [ 'url' ]), 'uid' => $uid ];
2018-07-20 12:19:26 +00:00
$contact = DBA :: selectFirst ( 'contact' , [ 'id' , 'network' ], $condition );
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $contact )) {
2022-06-18 03:01:51 +00:00
$cid = $contact [ 'id' ];
$network = $contact [ 'network' ];
2018-02-14 21:18:16 +00:00
} else {
2022-06-18 03:01:51 +00:00
$cid = $def_contact [ 'id' ];
2018-08-11 20:40:44 +00:00
$network = Protocol :: DIASPORA ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
return [
'cid' => $cid ,
'network' => $network
];
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Is the profile a hubzilla profile ?
2017-11-08 00:37:53 +00:00
*
* @ param string $url The profile link
*
* @ return bool is it a hubzilla server ?
*/
2022-06-17 08:44:13 +00:00
private static function isHubzilla ( string $url ) : bool
2017-11-08 22:02:50 +00:00
{
2022-06-17 08:44:13 +00:00
return strstr ( $url , '/channel/' );
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Generate a post link with a given handle and message guid
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param string $addr The user handle
* @ param string $guid message guid
* @ param string $parent_guid optional parent guid
2017-11-08 00:37:53 +00:00
*
* @ return string the post link
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-17 08:44:13 +00:00
private static function plink ( string $addr , string $guid , string $parent_guid = '' ) : string
2017-11-08 22:02:50 +00:00
{
2020-07-15 04:42:04 +00:00
$contact = Contact :: getByURL ( $addr );
2020-03-25 16:42:53 +00:00
if ( empty ( $contact )) {
Logger :: info ( 'No contact data for address' , [ 'addr' => $addr ]);
return '' ;
}
2017-11-08 00:37:53 +00:00
2020-03-25 16:42:53 +00:00
if ( empty ( $contact [ 'baseurl' ])) {
$contact [ 'baseurl' ] = 'https://' . substr ( $addr , strpos ( $addr , '@' ) + 1 );
Logger :: info ( 'Create baseurl from address' , [ 'baseurl' => $contact [ 'baseurl' ], 'url' => $contact [ 'url' ]]);
}
$platform = '' ;
$gserver = DBA :: selectFirst ( 'gserver' , [ 'platform' ], [ 'nurl' => Strings :: normaliseLink ( $contact [ 'baseurl' ])]);
if ( ! empty ( $gserver [ 'platform' ])) {
$platform = strtolower ( $gserver [ 'platform' ]);
Logger :: info ( 'Detected platform' , [ 'platform' => $platform , 'url' => $contact [ 'url' ]]);
}
2020-03-25 17:25:23 +00:00
if ( ! in_array ( $platform , [ 'diaspora' , 'friendica' , 'hubzilla' , 'socialhome' ])) {
2020-03-25 16:42:53 +00:00
if ( self :: isHubzilla ( $contact [ 'url' ])) {
Logger :: info ( 'Detected unknown platform as Hubzilla' , [ 'platform' => $platform , 'url' => $contact [ 'url' ]]);
$platform = 'hubzilla' ;
} elseif ( $contact [ 'network' ] == Protocol :: DFRN ) {
Logger :: info ( 'Detected unknown platform as Friendica' , [ 'platform' => $platform , 'url' => $contact [ 'url' ]]);
$platform = 'friendica' ;
2017-11-08 00:37:53 +00:00
}
}
2020-03-25 16:42:53 +00:00
if ( $platform == 'friendica' ) {
return str_replace ( '/profile/' . $contact [ 'nick' ] . '/' , '/display/' . $guid , $contact [ 'url' ] . '/' );
2017-11-08 00:37:53 +00:00
}
2020-03-25 16:42:53 +00:00
if ( $platform == 'hubzilla' ) {
return $contact [ 'baseurl' ] . '/item/' . $guid ;
}
2020-03-25 17:25:23 +00:00
if ( $platform == 'socialhome' ) {
return $contact [ 'baseurl' ] . '/content/' . $guid ;
}
2020-03-25 16:42:53 +00:00
if ( $platform != 'diaspora' ) {
Logger :: info ( 'Unknown platform' , [ 'platform' => $platform , 'url' => $contact [ 'url' ]]);
return '' ;
2017-11-08 00:37:53 +00:00
}
if ( $parent_guid != '' ) {
2020-03-25 16:42:53 +00:00
return $contact [ 'baseurl' ] . '/posts/' . $parent_guid . '#' . $guid ;
2017-11-08 00:37:53 +00:00
} else {
2020-03-25 16:42:53 +00:00
return $contact [ 'baseurl' ] . '/posts/' . $guid ;
2017-11-08 00:37:53 +00:00
}
}
/**
2020-01-19 06:05:23 +00:00
* Receives account migration
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2017-11-08 00:37:53 +00:00
*
* @ return bool Success
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-18 03:18:38 +00:00
private static function receiveAccountMigration ( array $importer , SimpleXMLElement $data ) : bool
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$old_handle = XML :: unescape ( $data -> author );
$new_handle = XML :: unescape ( $data -> profile -> author );
$signature = XML :: unescape ( $data -> signature );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$contact = self :: contactByHandle ( $importer [ 'uid' ], $old_handle );
2017-11-08 00:37:53 +00:00
if ( ! $contact ) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Cannot find contact for sender: ' . $old_handle . ' and user ' . $importer [ 'uid' ]);
2017-11-08 00:37:53 +00:00
return false ;
}
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Got migration for ' . $old_handle . ', to ' . $new_handle . ' with user ' . $importer [ 'uid' ]);
2017-11-08 00:37:53 +00:00
// Check signature
2022-06-18 03:01:51 +00:00
$signed_text = 'AccountMigration:' . $old_handle . ':' . $new_handle ;
2017-11-08 00:37:53 +00:00
$key = self :: key ( $old_handle );
2022-06-18 03:01:51 +00:00
if ( ! Crypto :: rsaVerify ( $signed_text , $signature , $key , 'sha256' )) {
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'No valid signature for migration.' );
2017-11-08 00:37:53 +00:00
return false ;
}
// Update the profile
2017-11-23 19:01:58 +00:00
self :: receiveProfile ( $importer , $data -> profile );
2017-11-08 00:37:53 +00:00
2020-08-01 16:15:18 +00:00
// change the technical stuff in contact
2017-11-08 00:37:53 +00:00
$data = Probe :: uri ( $new_handle );
2018-08-11 20:40:44 +00:00
if ( $data [ 'network' ] == Protocol :: PHANTOM ) {
2022-06-18 03:01:51 +00:00
Logger :: notice ( " Account for " . $new_handle . " couldn't be probed. " );
2017-11-08 00:37:53 +00:00
return false ;
}
2022-06-18 03:01:51 +00:00
$fields = [
2022-07-28 22:06:49 +00:00
'url' => $data [ 'url' ],
'nurl' => Strings :: normaliseLink ( $data [ 'url' ]),
'name' => $data [ 'name' ],
'nick' => $data [ 'nick' ],
'addr' => $data [ 'addr' ],
'batch' => $data [ 'batch' ],
'notify' => $data [ 'notify' ],
'poll' => $data [ 'poll' ],
'network' => $data [ 'network' ],
2022-06-18 03:01:51 +00:00
];
2017-11-08 00:37:53 +00:00
2021-09-10 18:21:19 +00:00
Contact :: update ( $fields , [ 'addr' => $old_handle ]);
2017-11-08 00:37:53 +00:00
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Contacts are updated.' );
2017-11-08 00:37:53 +00:00
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Processes an account deletion
2017-11-08 00:37:53 +00:00
*
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2017-11-08 00:37:53 +00:00
*
* @ return bool Success
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2017-11-08 00:37:53 +00:00
*/
2022-06-18 03:18:38 +00:00
private static function receiveAccountDeletion ( SimpleXMLElement $data ) : bool
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$author = XML :: unescape ( $data -> author );
2017-11-08 00:37:53 +00:00
2018-07-20 12:19:26 +00:00
$contacts = DBA :: select ( 'contact' , [ 'id' ], [ 'addr' => $author ]);
while ( $contact = DBA :: fetch ( $contacts )) {
2022-06-18 03:01:51 +00:00
Contact :: remove ( $contact [ 'id' ]);
2017-11-08 00:37:53 +00:00
}
2020-04-28 13:33:03 +00:00
DBA :: close ( $contacts );
2017-11-08 00:37:53 +00:00
2021-10-02 17:11:54 +00:00
Logger :: notice ( 'Removed contacts for ' . $author );
2018-04-24 18:34:35 +00:00
2017-11-08 00:37:53 +00:00
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Fetch the uri from our database if we already have this item ( maybe from ourselves )
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param string $author Author handle
* @ param string $guid Message guid
2017-11-08 00:37:53 +00:00
* @ param boolean $onlyfound Only return uri when found in the database
*
2022-06-17 08:44:13 +00:00
* @ return string The constructed uri or the one from our database or empty string on if $onlyfound is true
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-17 08:44:13 +00:00
private static function getUriFromGuid ( string $author , string $guid , bool $onlyfound = false ) : string
2017-11-08 22:02:50 +00:00
{
2021-01-16 04:14:58 +00:00
$item = Post :: selectFirst ([ 'uri' ], [ 'guid' => $guid ]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $item )) {
2022-06-18 03:01:51 +00:00
return $item [ 'uri' ];
2017-11-08 00:37:53 +00:00
} elseif ( ! $onlyfound ) {
2020-08-06 10:31:05 +00:00
$person = FContact :: getByURL ( $author );
2018-06-16 06:44:19 +00:00
2018-09-15 07:37:34 +00:00
$parts = parse_url ( $person [ 'url' ]);
unset ( $parts [ 'path' ]);
2022-07-29 16:05:04 +00:00
$host_url = ( string ) Uri :: fromParts ( $parts );
2018-06-16 06:44:19 +00:00
2018-10-02 20:12:38 +00:00
return $host_url . '/objects/' . $guid ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
return '' ;
2017-11-08 00:37:53 +00:00
}
2020-04-15 20:59:45 +00:00
/**
* Store the mentions in the tag table
*
* @ param integer $uriid
* @ param string $text
*/
2020-04-13 23:54:28 +00:00
private static function storeMentions ( int $uriid , string $text )
{
preg_match_all ( '/([@!]){(?:([^}]+?); ?)?([^} ]+)}/' , $text , $matches , PREG_SET_ORDER );
if ( empty ( $matches )) {
return ;
}
/*
* Matching values for the preg match
* [ 1 ] = mention type ( @ or ! )
* [ 2 ] = name ( optional )
* [ 3 ] = profile URL
*/
foreach ( $matches as $match ) {
if ( empty ( $match )) {
continue ;
}
2020-08-06 10:31:05 +00:00
$person = FContact :: getByURL ( $match [ 3 ]);
2020-04-13 23:54:28 +00:00
if ( empty ( $person )) {
continue ;
}
2020-04-17 06:35:20 +00:00
Tag :: storeByHash ( $uriid , $match [ 1 ], $person [ 'name' ] ? : $person [ 'nick' ], $person [ 'url' ]);
2020-04-13 23:54:28 +00:00
}
}
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* Processes an incoming comment
2017-11-08 00:37:53 +00:00
*
2022-03-12 07:08:10 +00:00
* @ param array $importer Array of the importer user
* @ param string $sender The sender of the message
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2022-03-12 07:08:10 +00:00
* @ param string $xml The original XML of the message
2022-03-12 15:27:56 +00:00
* @ param int $direction Indicates if the message had been fetched or pushed ( self :: PUSHED , self :: FETCHED , self :: FORCED_FETCH )
2017-11-08 00:37:53 +00:00
*
* @ return int The message id of the generated comment or " false " if there was an error
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-18 03:18:38 +00:00
private static function receiveComment ( array $importer , string $sender , SimpleXMLElement $data , string $xml , int $direction ) : bool
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$author = XML :: unescape ( $data -> author );
$guid = XML :: unescape ( $data -> guid );
$parent_guid = XML :: unescape ( $data -> parent_guid );
2018-11-05 12:40:18 +00:00
$text = XML :: unescape ( $data -> text );
2017-11-08 00:37:53 +00:00
if ( isset ( $data -> created_at )) {
2021-10-02 17:11:54 +00:00
$created_at = DateTimeFormat :: utc ( XML :: unescape ( $data -> created_at ));
2017-11-08 00:37:53 +00:00
} else {
2018-01-27 02:38:34 +00:00
$created_at = DateTimeFormat :: utcNow ();
2017-11-08 00:37:53 +00:00
}
if ( isset ( $data -> thread_parent_guid )) {
2021-10-02 17:11:54 +00:00
$thread_parent_guid = XML :: unescape ( $data -> thread_parent_guid );
2022-06-18 03:01:51 +00:00
$thr_parent = self :: getUriFromGuid ( '' , $thread_parent_guid , true );
2017-11-08 00:37:53 +00:00
} else {
2022-06-18 03:01:51 +00:00
$thr_parent = '' ;
2017-11-08 00:37:53 +00:00
}
2017-11-23 19:01:58 +00:00
$contact = self :: allowedContactByHandle ( $importer , $sender , true );
2017-11-08 00:37:53 +00:00
if ( ! $contact ) {
return false ;
}
2021-03-10 22:31:33 +00:00
if ( ! empty ( $contact [ 'gsid' ])) {
GServer :: setProtocol ( $contact [ 'gsid' ], Post\DeliveryData :: DIASPORA );
}
2022-06-18 03:01:51 +00:00
$message_id = self :: messageExists ( $importer [ 'uid' ], $guid );
2017-11-08 00:37:53 +00:00
if ( $message_id ) {
return true ;
}
2022-06-18 03:01:51 +00:00
$toplevel_parent_item = self :: parentItem ( $importer [ 'uid' ], $parent_guid , $author , $contact );
2020-11-11 07:47:48 +00:00
if ( ! $toplevel_parent_item ) {
2017-11-08 00:37:53 +00:00
return false ;
}
2020-08-06 10:31:05 +00:00
$person = FContact :: getByURL ( $author );
2017-11-08 00:37:53 +00:00
if ( ! is_array ( $person )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Unable to find author details' );
2017-11-08 00:37:53 +00:00
return false ;
}
// Fetch the contact id - if we know this contact
2022-06-18 03:01:51 +00:00
$author_contact = self :: authorContactByUrl ( $contact , $person , $importer [ 'uid' ]);
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$datarray = [];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'uid' ] = $importer [ 'uid' ];
$datarray [ 'contact-id' ] = $author_contact [ 'cid' ];
$datarray [ 'network' ] = $author_contact [ 'network' ];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'author-link' ] = $person [ 'url' ];
$datarray [ 'author-id' ] = Contact :: getIdForURL ( $person [ 'url' ], 0 );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'owner-link' ] = $contact [ 'url' ];
$datarray [ 'owner-id' ] = Contact :: getIdForURL ( $contact [ 'url' ], 0 );
2017-11-08 00:37:53 +00:00
2020-09-13 14:15:28 +00:00
// Will be overwritten for sharing accounts in Item::insert
2022-07-31 15:54:35 +00:00
$datarray = self :: setDirection ( $datarray , $direction );
2020-09-13 14:15:28 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'guid' ] = $guid ;
$datarray [ 'uri' ] = self :: getUriFromGuid ( $author , $guid );
2020-04-13 23:54:28 +00:00
$datarray [ 'uri-id' ] = ItemURI :: insert ([ 'uri' => $datarray [ 'uri' ], 'guid' => $datarray [ 'guid' ]]);
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'verb' ] = Activity :: POST ;
2022-09-12 21:12:11 +00:00
$datarray [ 'gravity' ] = Item :: GRAVITY_COMMENT ;
2017-11-08 00:37:53 +00:00
2020-11-11 07:47:48 +00:00
$datarray [ 'thr-parent' ] = $thr_parent ? : $toplevel_parent_item [ 'uri' ];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'object-type' ] = Activity\ObjectType :: COMMENT ;
$datarray [ 'post-type' ] = Item :: PT_NOTE ;
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'protocol' ] = Conversation :: PARCEL_DIASPORA ;
$datarray [ 'source' ] = $xml ;
2022-07-31 15:54:35 +00:00
$datarray = self :: setDirection ( $datarray , $direction );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'changed' ] = $datarray [ 'created' ] = $datarray [ 'edited' ] = $created_at ;
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'plink' ] = self :: plink ( $author , $guid , $toplevel_parent_item [ 'guid' ]);
2018-03-04 22:39:41 +00:00
$body = Markdown :: toBBCode ( $text );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'body' ] = self :: replacePeopleGuid ( $body , $person [ 'url' ]);
2017-11-08 00:37:53 +00:00
2020-04-14 07:56:53 +00:00
self :: storeMentions ( $datarray [ 'uri-id' ], $text );
2022-06-18 03:01:51 +00:00
Tag :: storeRawTagsFromBody ( $datarray [ 'uri-id' ], $datarray [ 'body' ]);
2020-04-14 07:56:53 +00:00
2017-11-23 19:01:58 +00:00
self :: fetchGuid ( $datarray );
2017-11-08 00:37:53 +00:00
2018-05-15 04:33:28 +00:00
// If we are the origin of the parent we store the original data.
// We notify our followers during the item storage.
2022-06-18 03:01:51 +00:00
if ( $toplevel_parent_item [ 'origin' ]) {
2018-05-15 04:33:28 +00:00
$datarray [ 'diaspora_signed_text' ] = json_encode ( $data );
}
2020-12-14 00:00:10 +00:00
if ( Item :: isTooOld ( $datarray )) {
Logger :: info ( 'Comment is too old' , [ 'created' => $datarray [ 'created' ], 'uid' => $datarray [ 'uid' ], 'guid' => $datarray [ 'guid' ]]);
return false ;
}
2018-01-28 11:18:08 +00:00
$message_id = Item :: insert ( $datarray );
2017-11-08 00:37:53 +00:00
if ( $message_id <= 0 ) {
return false ;
}
if ( $message_id ) {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Stored comment ' . $datarray [ 'guid' ] . ' with message id ' . $message_id );
2018-04-24 13:21:25 +00:00
if ( $datarray [ 'uid' ] == 0 ) {
2018-05-15 04:33:28 +00:00
Item :: distribute ( $message_id , json_encode ( $data ));
2018-04-24 13:21:25 +00:00
}
2017-11-08 00:37:53 +00:00
}
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* processes and stores private messages
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param array $contact The contact of the message
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2017-11-08 22:02:50 +00:00
* @ param array $msg Array of the processed message , author handle and key
* @ param object $mesg The private message
* @ param array $conversation The conversation record to which this message belongs
2017-11-08 00:37:53 +00:00
*
* @ return bool " true " if it was successful
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2022-06-18 03:18:38 +00:00
* @ todo Find type - hint for $mesg and update documentation
2017-11-08 00:37:53 +00:00
*/
2022-06-18 03:18:38 +00:00
private static function receiveConversationMessage ( array $importer , array $contact , SimpleXMLElement $data , array $msg , $mesg , array $conversation ) : bool
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$author = XML :: unescape ( $data -> author );
$guid = XML :: unescape ( $data -> guid );
$subject = XML :: unescape ( $data -> subject );
2017-11-08 00:37:53 +00:00
// "diaspora_handle" is the element name from the old version
// "author" is the element name from the new version
if ( $mesg -> author ) {
2021-10-02 17:11:54 +00:00
$msg_author = XML :: unescape ( $mesg -> author );
2017-11-08 00:37:53 +00:00
} elseif ( $mesg -> diaspora_handle ) {
2021-10-02 17:11:54 +00:00
$msg_author = XML :: unescape ( $mesg -> diaspora_handle );
2017-11-08 00:37:53 +00:00
} else {
return false ;
}
2021-10-02 17:11:54 +00:00
$msg_guid = XML :: unescape ( $mesg -> guid );
$msg_conversation_guid = XML :: unescape ( $mesg -> conversation_guid );
2018-11-05 12:40:18 +00:00
$msg_text = XML :: unescape ( $mesg -> text );
2021-10-02 17:11:54 +00:00
$msg_created_at = DateTimeFormat :: utc ( XML :: unescape ( $mesg -> created_at ));
2017-11-08 00:37:53 +00:00
if ( $msg_conversation_guid != $guid ) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Message conversation guid does not belong to the current conversation.' , [ 'guid' => $guid ]);
2017-11-08 00:37:53 +00:00
return false ;
}
2018-03-04 22:39:41 +00:00
$body = Markdown :: toBBCode ( $msg_text );
2022-07-19 23:06:05 +00:00
$message_uri = $msg_author . ':' . $msg_guid ;
2017-11-08 00:37:53 +00:00
2020-08-06 10:31:05 +00:00
$person = FContact :: getByURL ( $msg_author );
2017-11-08 00:37:53 +00:00
2019-05-08 05:44:22 +00:00
return Mail :: insert ([
2018-12-30 06:08:51 +00:00
'uid' => $importer [ 'uid' ],
'guid' => $msg_guid ,
'convid' => $conversation [ 'id' ],
'from-name' => $person [ 'name' ],
'from-photo' => $person [ 'photo' ],
'from-url' => $person [ 'url' ],
'contact-id' => $contact [ 'id' ],
'title' => $subject ,
'body' => $body ,
'uri' => $message_uri ,
'parent-uri' => $author . ':' . $guid ,
'created' => $msg_created_at
]);
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Processes new private messages ( answers to private messages are processed elsewhere )
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param array $msg Array of the processed message , author handle and key
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2017-11-08 00:37:53 +00:00
*
* @ return bool Success
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-18 03:18:38 +00:00
private static function receiveConversation ( array $importer , array $msg , SimpleXMLElement $data )
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$author = XML :: unescape ( $data -> author );
$guid = XML :: unescape ( $data -> guid );
$subject = XML :: unescape ( $data -> subject );
$created_at = DateTimeFormat :: utc ( XML :: unescape ( $data -> created_at ));
$participants = XML :: unescape ( $data -> participants );
2017-11-08 00:37:53 +00:00
$messages = $data -> message ;
if ( ! count ( $messages )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Empty conversation' );
2017-11-08 00:37:53 +00:00
return false ;
}
2022-06-18 03:01:51 +00:00
$contact = self :: allowedContactByHandle ( $importer , $msg [ 'author' ], true );
2017-11-08 22:02:50 +00:00
if ( ! $contact ) {
2017-11-08 00:37:53 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2021-03-10 22:31:33 +00:00
if ( ! empty ( $contact [ 'gsid' ])) {
GServer :: setProtocol ( $contact [ 'gsid' ], Post\DeliveryData :: DIASPORA );
}
2022-06-18 03:01:51 +00:00
$conversation = DBA :: selectFirst ( 'conv' , [], [ 'uid' => $importer [ 'uid' ], 'guid' => $guid ]);
2018-08-19 12:46:11 +00:00
if ( ! DBA :: isResult ( $conversation )) {
2021-10-02 17:11:54 +00:00
$r = DBA :: insert ( 'conv' , [
'uid' => $importer [ 'uid' ],
'guid' => $guid ,
'creator' => $author ,
'created' => $created_at ,
'updated' => DateTimeFormat :: utcNow (),
'subject' => $subject ,
2022-07-19 23:06:05 +00:00
'recips' => $participants
]);
2017-11-08 22:02:50 +00:00
if ( $r ) {
2022-06-18 03:01:51 +00:00
$conversation = DBA :: selectFirst ( 'conv' , [], [ 'uid' => $importer [ 'uid' ], 'guid' => $guid ]);
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
}
if ( ! $conversation ) {
2022-08-30 19:45:30 +00:00
Logger :: warning ( 'Unable to create conversation.' );
2017-11-08 00:37:53 +00:00
return false ;
}
2017-11-08 22:02:50 +00:00
foreach ( $messages as $mesg ) {
2017-11-23 19:01:58 +00:00
self :: receiveConversationMessage ( $importer , $contact , $data , $msg , $mesg , $conversation );
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Processes " like " messages
2017-11-08 00:37:53 +00:00
*
2022-03-12 07:08:10 +00:00
* @ param array $importer Array of the importer user
* @ param string $sender The sender of the message
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2022-03-12 15:27:56 +00:00
* @ param int $direction Indicates if the message had been fetched or pushed ( self :: PUSHED , self :: FETCHED , self :: FORCED_FETCH )
2017-11-08 00:37:53 +00:00
*
2022-06-17 08:44:13 +00:00
* @ return bool Success or failure
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-18 03:18:38 +00:00
private static function receiveLike ( array $importer , string $sender , SimpleXMLElement $data , int $direction ) : bool
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$author = XML :: unescape ( $data -> author );
$guid = XML :: unescape ( $data -> guid );
$parent_guid = XML :: unescape ( $data -> parent_guid );
$parent_type = XML :: unescape ( $data -> parent_type );
$positive = XML :: unescape ( $data -> positive );
2017-11-08 00:37:53 +00:00
// likes on comments aren't supported by Diaspora - only on posts
// But maybe this will be supported in the future, so we will accept it.
2022-06-18 03:01:51 +00:00
if ( ! in_array ( $parent_type , [ 'Post' , 'Comment' ])) {
2017-11-08 00:37:53 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
$contact = self :: allowedContactByHandle ( $importer , $sender , true );
2017-11-08 22:02:50 +00:00
if ( ! $contact ) {
2017-11-08 00:37:53 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2021-03-10 22:31:33 +00:00
if ( ! empty ( $contact [ 'gsid' ])) {
GServer :: setProtocol ( $contact [ 'gsid' ], Post\DeliveryData :: DIASPORA );
}
2022-06-18 03:01:51 +00:00
$message_id = self :: messageExists ( $importer [ 'uid' ], $guid );
2017-11-08 22:02:50 +00:00
if ( $message_id ) {
2017-11-08 00:37:53 +00:00
return true ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$toplevel_parent_item = self :: parentItem ( $importer [ 'uid' ], $parent_guid , $author , $contact );
2020-11-11 07:47:48 +00:00
if ( ! $toplevel_parent_item ) {
2017-11-08 00:37:53 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2020-08-06 10:31:05 +00:00
$person = FContact :: getByURL ( $author );
2017-11-08 00:37:53 +00:00
if ( ! is_array ( $person )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Unable to find author details' );
2017-11-08 00:37:53 +00:00
return false ;
}
// Fetch the contact id - if we know this contact
2022-06-18 03:01:51 +00:00
$author_contact = self :: authorContactByUrl ( $contact , $person , $importer [ 'uid' ]);
2017-11-08 00:37:53 +00:00
// "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora
// We would accept this anyhow.
2022-06-18 03:01:51 +00:00
if ( $positive == 'true' ) {
2019-10-23 22:25:43 +00:00
$verb = Activity :: LIKE ;
2017-11-08 22:02:50 +00:00
} else {
2019-10-23 22:25:43 +00:00
$verb = Activity :: DISLIKE ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$datarray = [];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'protocol' ] = Conversation :: PARCEL_DIASPORA ;
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'uid' ] = $importer [ 'uid' ];
$datarray [ 'contact-id' ] = $author_contact [ 'cid' ];
$datarray [ 'network' ] = $author_contact [ 'network' ];
2017-11-08 00:37:53 +00:00
2022-07-31 15:54:35 +00:00
$datarray = self :: setDirection ( $datarray , $direction );
2022-06-18 03:01:51 +00:00
$datarray [ 'owner-link' ] = $datarray [ 'author-link' ] = $person [ 'url' ];
$datarray [ 'owner-id' ] = $datarray [ 'author-id' ] = Contact :: getIdForURL ( $person [ 'url' ], 0 );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'guid' ] = $guid ;
$datarray [ 'uri' ] = self :: getUriFromGuid ( $author , $guid );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'verb' ] = $verb ;
2022-09-12 21:12:11 +00:00
$datarray [ 'gravity' ] = Item :: GRAVITY_ACTIVITY ;
2020-11-11 07:47:48 +00:00
$datarray [ 'thr-parent' ] = $toplevel_parent_item [ 'uri' ];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'object-type' ] = Activity\ObjectType :: NOTE ;
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'body' ] = $verb ;
2017-11-08 00:37:53 +00:00
2018-07-15 18:36:20 +00:00
// Diaspora doesn't provide a date for likes
2022-06-18 03:01:51 +00:00
$datarray [ 'changed' ] = $datarray [ 'created' ] = $datarray [ 'edited' ] = DateTimeFormat :: utcNow ();
2018-07-15 18:36:20 +00:00
2018-05-15 04:33:28 +00:00
// like on comments have the comment as parent. So we need to fetch the toplevel parent
2022-09-12 21:12:11 +00:00
if ( $toplevel_parent_item [ 'gravity' ] != Item :: GRAVITY_PARENT ) {
2021-01-16 04:14:58 +00:00
$toplevel = Post :: selectFirst ([ 'origin' ], [ 'id' => $toplevel_parent_item [ 'parent' ]]);
2022-06-18 03:01:51 +00:00
$origin = $toplevel [ 'origin' ];
2018-05-15 04:33:28 +00:00
} else {
2022-06-18 03:01:51 +00:00
$origin = $toplevel_parent_item [ 'origin' ];
2018-05-15 04:33:28 +00:00
}
// If we are the origin of the parent we store the original data.
// We notify our followers during the item storage.
if ( $origin ) {
$datarray [ 'diaspora_signed_text' ] = json_encode ( $data );
}
2020-12-14 00:00:10 +00:00
if ( Item :: isTooOld ( $datarray )) {
Logger :: info ( 'Like is too old' , [ 'created' => $datarray [ 'created' ], 'uid' => $datarray [ 'uid' ], 'guid' => $datarray [ 'guid' ]]);
return false ;
}
2018-01-28 11:18:08 +00:00
$message_id = Item :: insert ( $datarray );
2017-11-08 00:37:53 +00:00
if ( $message_id <= 0 ) {
return false ;
}
if ( $message_id ) {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Stored like ' . $datarray [ 'guid' ] . ' with message id ' . $message_id );
2018-04-24 13:21:25 +00:00
if ( $datarray [ 'uid' ] == 0 ) {
2018-05-15 04:33:28 +00:00
Item :: distribute ( $message_id , json_encode ( $data ));
2018-04-24 13:21:25 +00:00
}
2017-11-08 00:37:53 +00:00
}
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Processes private messages
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2017-11-08 00:37:53 +00:00
*
* @ return bool Success ?
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-18 03:18:38 +00:00
private static function receiveMessage ( array $importer , SimpleXMLElement $data ) : bool
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$author = XML :: unescape ( $data -> author );
$guid = XML :: unescape ( $data -> guid );
$conversation_guid = XML :: unescape ( $data -> conversation_guid );
2018-11-05 12:40:18 +00:00
$text = XML :: unescape ( $data -> text );
2021-10-02 17:11:54 +00:00
$created_at = DateTimeFormat :: utc ( XML :: unescape ( $data -> created_at ));
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
$contact = self :: allowedContactByHandle ( $importer , $author , true );
2017-11-08 00:37:53 +00:00
if ( ! $contact ) {
return false ;
}
2021-03-10 22:31:33 +00:00
if ( ! empty ( $contact [ 'gsid' ])) {
GServer :: setProtocol ( $contact [ 'gsid' ], Post\DeliveryData :: DIASPORA );
}
2017-11-08 00:37:53 +00:00
$conversation = null ;
2022-06-18 03:01:51 +00:00
$condition = [ 'uid' => $importer [ 'uid' ], 'guid' => $conversation_guid ];
2018-08-19 12:46:11 +00:00
$conversation = DBA :: selectFirst ( 'conv' , [], $condition );
if ( ! DBA :: isResult ( $conversation )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Conversation not available.' );
2017-11-08 00:37:53 +00:00
return false ;
}
2022-06-18 03:01:51 +00:00
$message_uri = $author . ':' . $guid ;
2017-11-08 00:37:53 +00:00
2020-08-06 10:31:05 +00:00
$person = FContact :: getByURL ( $author );
2017-11-08 00:37:53 +00:00
if ( ! $person ) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Unable to find author details' );
2017-11-08 00:37:53 +00:00
return false ;
}
2018-03-04 22:39:41 +00:00
$body = Markdown :: toBBCode ( $text );
2017-11-08 00:37:53 +00:00
2022-07-19 23:06:05 +00:00
$body = self :: replacePeopleGuid ( $body , $person [ 'url' ]);
2017-11-08 00:37:53 +00:00
2019-05-08 05:44:22 +00:00
return Mail :: insert ([
2018-12-30 06:08:51 +00:00
'uid' => $importer [ 'uid' ],
'guid' => $guid ,
'convid' => $conversation [ 'id' ],
'from-name' => $person [ 'name' ],
'from-photo' => $person [ 'photo' ],
'from-url' => $person [ 'url' ],
'contact-id' => $contact [ 'id' ],
'title' => $conversation [ 'subject' ],
'body' => $body ,
'reply' => 1 ,
'uri' => $message_uri ,
2022-07-19 23:06:05 +00:00
'parent-uri' => $author . ':' . $conversation [ 'guid' ],
2018-12-30 06:08:51 +00:00
'created' => $created_at
]);
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Processes participations - unsupported by now
2017-11-08 00:37:53 +00:00
*
2022-03-12 07:08:10 +00:00
* @ param array $importer Array of the importer user
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2022-03-12 15:27:56 +00:00
* @ param int $direction Indicates if the message had been fetched or pushed ( self :: PUSHED , self :: FETCHED , self :: FORCED_FETCH )
2017-11-08 00:37:53 +00:00
*
2020-05-02 08:52:11 +00:00
* @ return bool success
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-18 03:18:38 +00:00
private static function receiveParticipation ( array $importer , SimpleXMLElement $data , int $direction ) : bool
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$author = strtolower ( XML :: unescape ( $data -> author ));
$guid = XML :: unescape ( $data -> guid );
$parent_guid = XML :: unescape ( $data -> parent_guid );
2018-01-12 20:52:43 +00:00
2020-05-02 08:52:11 +00:00
$contact = self :: allowedContactByHandle ( $importer , $author , true );
if ( ! $contact ) {
2018-01-14 22:53:00 +00:00
return false ;
}
2021-03-10 22:31:33 +00:00
if ( ! empty ( $contact [ 'gsid' ])) {
GServer :: setProtocol ( $contact [ 'gsid' ], Post\DeliveryData :: DIASPORA );
}
2022-06-18 03:01:51 +00:00
if ( self :: messageExists ( $importer [ 'uid' ], $guid )) {
2020-05-02 08:52:11 +00:00
return true ;
}
2022-06-18 03:01:51 +00:00
$toplevel_parent_item = self :: parentItem ( $importer [ 'uid' ], $parent_guid , $author , $contact );
2020-11-11 07:47:48 +00:00
if ( ! $toplevel_parent_item ) {
2018-01-12 20:52:43 +00:00
return false ;
}
2020-11-11 07:47:48 +00:00
if ( ! $toplevel_parent_item [ 'origin' ]) {
2020-05-06 19:00:56 +00:00
Logger :: info ( 'Not our origin. Participation is ignored' , [ 'parent_guid' => $parent_guid , 'guid' => $guid , 'author' => $author ]);
}
2020-11-11 07:47:48 +00:00
if ( ! in_array ( $toplevel_parent_item [ 'private' ], [ Item :: PUBLIC , Item :: UNLISTED ])) {
2020-05-02 08:52:11 +00:00
Logger :: info ( 'Item is not public, participation is ignored' , [ 'parent_guid' => $parent_guid , 'guid' => $guid , 'author' => $author ]);
2018-01-12 20:52:43 +00:00
return false ;
}
2020-08-06 10:31:05 +00:00
$person = FContact :: getByURL ( $author );
2020-05-02 08:52:11 +00:00
if ( ! is_array ( $person )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Person not found: ' . $author );
2020-05-02 08:52:11 +00:00
return false ;
2018-01-12 20:52:43 +00:00
}
2022-06-18 03:01:51 +00:00
$author_contact = self :: authorContactByUrl ( $contact , $person , $importer [ 'uid' ]);
2018-03-08 21:04:11 +00:00
2020-05-02 08:52:11 +00:00
// Store participation
$datarray = [];
2022-06-18 03:01:51 +00:00
$datarray [ 'protocol' ] = Conversation :: PARCEL_DIASPORA ;
2020-05-02 08:52:11 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'uid' ] = $importer [ 'uid' ];
$datarray [ 'contact-id' ] = $author_contact [ 'cid' ];
$datarray [ 'network' ] = $author_contact [ 'network' ];
2020-05-02 08:52:11 +00:00
2022-07-31 15:54:35 +00:00
$datarray = self :: setDirection ( $datarray , $direction );
2022-06-18 03:01:51 +00:00
$datarray [ 'owner-link' ] = $datarray [ 'author-link' ] = $person [ 'url' ];
$datarray [ 'owner-id' ] = $datarray [ 'author-id' ] = Contact :: getIdForURL ( $person [ 'url' ], 0 );
2020-05-02 08:52:11 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'guid' ] = $guid ;
$datarray [ 'uri' ] = self :: getUriFromGuid ( $author , $guid );
2020-05-02 08:52:11 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'verb' ] = Activity :: FOLLOW ;
2022-09-12 21:12:11 +00:00
$datarray [ 'gravity' ] = Item :: GRAVITY_ACTIVITY ;
2020-11-11 07:47:48 +00:00
$datarray [ 'thr-parent' ] = $toplevel_parent_item [ 'uri' ];
2020-05-02 08:52:11 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'object-type' ] = Activity\ObjectType :: NOTE ;
2020-05-02 08:52:11 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'body' ] = Activity :: FOLLOW ;
2020-05-02 08:52:11 +00:00
// Diaspora doesn't provide a date for a participation
2022-06-18 03:01:51 +00:00
$datarray [ 'changed' ] = $datarray [ 'created' ] = $datarray [ 'edited' ] = DateTimeFormat :: utcNow ();
2020-05-02 08:52:11 +00:00
2020-12-14 00:00:10 +00:00
if ( Item :: isTooOld ( $datarray )) {
Logger :: info ( 'Participation is too old' , [ 'created' => $datarray [ 'created' ], 'uid' => $datarray [ 'uid' ], 'guid' => $datarray [ 'guid' ]]);
return false ;
}
2020-05-02 08:52:11 +00:00
$message_id = Item :: insert ( $datarray );
Logger :: info ( 'Participation stored' , [ 'id' => $message_id , 'guid' => $guid , 'parent_guid' => $parent_guid , 'author' => $author ]);
2018-01-14 22:53:00 +00:00
// Send all existing comments and likes to the requesting server
2022-09-03 13:32:41 +00:00
$comments = Post :: select ([ 'id' , 'uri-id' , 'parent-author-network' , 'author-network' , 'verb' , 'gravity' ],
2022-09-12 21:12:11 +00:00
[ 'parent' => $toplevel_parent_item [ 'id' ], 'gravity' => [ Item :: GRAVITY_COMMENT , Item :: GRAVITY_ACTIVITY ]]);
2021-01-16 04:14:58 +00:00
while ( $comment = Post :: fetch ( $comments )) {
2022-09-12 21:12:11 +00:00
if (( $comment [ 'gravity' ] == Item :: GRAVITY_ACTIVITY ) && ! in_array ( $comment [ 'verb' ], [ Activity :: LIKE , Activity :: DISLIKE ])) {
2022-09-03 13:32:41 +00:00
Logger :: info ( 'Unsupported activities are not relayed' , [ 'item' => $comment [ 'id' ], 'verb' => $comment [ 'verb' ]]);
2018-06-16 22:32:57 +00:00
continue ;
}
2019-06-10 14:19:24 +00:00
2020-06-27 12:18:36 +00:00
if ( $comment [ 'author-network' ] == Protocol :: ACTIVITYPUB ) {
Logger :: info ( 'Comments from ActivityPub authors are not relayed' , [ 'item' => $comment [ 'id' ]]);
continue ;
}
if ( $comment [ 'parent-author-network' ] == Protocol :: ACTIVITYPUB ) {
Logger :: info ( 'Comments to comments from ActivityPub authors are not relayed' , [ 'item' => $comment [ 'id' ]]);
continue ;
}
2022-06-18 03:01:51 +00:00
Logger :: info ( 'Deliver participation' , [ 'item' => $comment [ 'id' ], 'contact' => $author_contact [ 'cid' ]]);
2022-10-17 05:49:55 +00:00
if ( Worker :: add ( Worker :: PRIORITY_HIGH , 'Delivery' , Delivery :: POST , $comment [ 'uri-id' ], $author_contact [ 'cid' ], $datarray [ 'uid' ])) {
2020-05-02 19:34:02 +00:00
Post\DeliveryData :: incrementQueueCount ( $comment [ 'uri-id' ], 1 );
2019-09-02 03:25:05 +00:00
}
2018-01-14 22:53:00 +00:00
}
2018-07-20 12:19:26 +00:00
DBA :: close ( $comments );
2018-01-12 20:52:43 +00:00
2017-11-08 00:37:53 +00:00
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Processes photos - unneeded
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2017-11-08 00:37:53 +00:00
*
* @ return bool always true
*/
2018-07-20 05:10:16 +00:00
private static function receivePhoto ( array $importer , $data )
2017-11-08 22:02:50 +00:00
{
// There doesn't seem to be a reason for this function,
// since the photo data is transmitted in the status message as well
2017-11-08 00:37:53 +00:00
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Processes poll participations - unssupported
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param object $data The message object
2017-11-08 00:37:53 +00:00
*
* @ return bool always true
*/
2018-07-20 05:10:16 +00:00
private static function receivePollParticipation ( array $importer , $data )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
// We don't support polls by now
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Processes incoming profile updates
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2017-11-08 00:37:53 +00:00
*
* @ return bool Success
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-18 03:18:38 +00:00
private static function receiveProfile ( array $importer , SimpleXMLElement $data ) : bool
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$author = strtolower ( XML :: unescape ( $data -> author ));
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$contact = self :: contactByHandle ( $importer [ 'uid' ], $author );
2017-11-08 22:02:50 +00:00
if ( ! $contact ) {
2017-11-08 00:37:53 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$name = XML :: unescape ( $data -> first_name ) . (( strlen ( $data -> last_name )) ? ' ' . XML :: unescape ( $data -> last_name ) : '' );
2018-11-05 12:40:18 +00:00
$image_url = XML :: unescape ( $data -> image_url );
$birthday = XML :: unescape ( $data -> birthday );
$about = Markdown :: toBBCode ( XML :: unescape ( $data -> bio ));
$location = Markdown :: toBBCode ( XML :: unescape ( $data -> location ));
2022-06-18 03:01:51 +00:00
$searchable = ( XML :: unescape ( $data -> searchable ) == 'true' );
$nsfw = ( XML :: unescape ( $data -> nsfw ) == 'true' );
2018-11-05 12:40:18 +00:00
$tags = XML :: unescape ( $data -> tag_string );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$tags = explode ( '#' , $tags );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$keywords = [];
2017-11-08 00:37:53 +00:00
foreach ( $tags as $tag ) {
$tag = trim ( strtolower ( $tag ));
2022-06-18 03:01:51 +00:00
if ( $tag != '' ) {
2017-11-08 00:37:53 +00:00
$keywords [] = $tag ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
$keywords = implode ( ', ' , $keywords );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$handle_parts = explode ( '@' , $author );
2017-11-08 00:37:53 +00:00
$nick = $handle_parts [ 0 ];
2022-06-18 03:01:51 +00:00
if ( $name === '' ) {
2017-11-08 00:37:53 +00:00
$name = $handle_parts [ 0 ];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
if ( preg_match ( '|^https?://|' , $image_url ) === 0 ) {
// @TODO No HTTPS here?
$image_url = 'http://' . $handle_parts [ 1 ] . $image_url ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
Contact :: updateAvatar ( $contact [ 'id' ], $image_url );
2017-11-08 00:37:53 +00:00
// Generic birthday. We don't know the timezone. The year is irrelevant.
2022-06-18 03:01:51 +00:00
$birthday = str_replace ( '1000' , '1901' , $birthday );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
if ( $birthday != '' ) {
$birthday = DateTimeFormat :: utc ( $birthday , 'Y-m-d' );
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
// this is to prevent multiple birthday notifications in a single year
// if we already have a stored birthday and the 'm-d' part hasn't changed, preserve the entry, which will preserve the notify year
2022-06-18 03:01:51 +00:00
if ( substr ( $birthday , 5 ) === substr ( $contact [ 'bd' ], 5 )) {
$birthday = $contact [ 'bd' ];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2018-05-11 15:27:19 +00:00
$fields = [ 'name' => $name , 'location' => $location ,
2020-02-16 10:55:18 +00:00
'name-date' => DateTimeFormat :: utcNow (), 'about' => $about ,
2019-07-04 04:08:55 +00:00
'addr' => $author , 'nick' => $nick , 'keywords' => $keywords ,
'unsearchable' => ! $searchable , 'sensitive' => $nsfw ];
2018-05-11 15:27:19 +00:00
if ( ! empty ( $birthday )) {
$fields [ 'bd' ] = $birthday ;
}
2021-09-10 18:21:19 +00:00
Contact :: update ( $fields , [ 'id' => $contact [ 'id' ]]);
2017-11-08 00:37:53 +00:00
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Profile of contact ' . $contact [ 'id' ] . ' stored for user ' . $importer [ 'uid' ]);
2017-11-08 00:37:53 +00:00
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Processes incoming friend requests
2017-11-08 00:37:53 +00:00
*
* @ param array $importer Array of the importer user
2017-11-08 22:02:50 +00:00
* @ param array $contact The contact that send the request
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
*/
2018-07-19 11:07:14 +00:00
private static function receiveRequestMakeFriend ( array $importer , array $contact )
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
if ( $contact [ 'rel' ] == Contact :: SHARING ) {
2022-02-21 15:16:38 +00:00
Contact :: update (
2018-07-25 02:53:46 +00:00
[ 'rel' => Contact :: FRIEND , 'writable' => true ],
2022-06-18 03:01:51 +00:00
[ 'id' => $contact [ 'id' ], 'uid' => $importer [ 'uid' ]]
2017-11-08 22:02:50 +00:00
);
2017-11-08 00:37:53 +00:00
}
}
/**
2020-01-19 06:05:23 +00:00
* Processes incoming sharing notification
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2017-11-08 00:37:53 +00:00
*
* @ return bool Success
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-18 03:18:38 +00:00
private static function receiveContactRequest ( array $importer , SimpleXMLElement $data ) : bool
2017-11-08 22:02:50 +00:00
{
2018-11-05 12:40:18 +00:00
$author = XML :: unescape ( $data -> author );
$recipient = XML :: unescape ( $data -> recipient );
2017-11-08 00:37:53 +00:00
if ( ! $author || ! $recipient ) {
return false ;
}
// the current protocol version doesn't know these fields
// That means that we will assume their existance
if ( isset ( $data -> following )) {
2022-06-18 03:01:51 +00:00
$following = ( XML :: unescape ( $data -> following ) == 'true' );
2017-11-08 00:37:53 +00:00
} else {
$following = true ;
}
if ( isset ( $data -> sharing )) {
2022-06-18 03:01:51 +00:00
$sharing = ( XML :: unescape ( $data -> sharing ) == 'true' );
2017-11-08 00:37:53 +00:00
} else {
$sharing = true ;
}
2022-06-18 03:01:51 +00:00
$contact = self :: contactByHandle ( $importer [ 'uid' ], $author );
2017-11-08 00:37:53 +00:00
// perhaps we were already sharing with this person. Now they're sharing with us.
// That makes us friends.
if ( $contact ) {
if ( $following ) {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Author ' . $author . ' (Contact ' . $contact [ 'id' ] . ') wants to follow us.' );
2017-11-23 19:01:58 +00:00
self :: receiveRequestMakeFriend ( $importer , $contact );
2017-11-08 00:37:53 +00:00
// refetch the contact array
2022-06-18 03:01:51 +00:00
$contact = self :: contactByHandle ( $importer [ 'uid' ], $author );
2017-11-08 00:37:53 +00:00
// If we are now friends, we are sending a share message.
// Normally we needn't to do so, but the first message could have been vanished.
2022-06-18 03:01:51 +00:00
if ( in_array ( $contact [ 'rel' ], [ Contact :: FRIEND ])) {
$user = DBA :: selectFirst ( 'user' , [], [ 'uid' => $importer [ 'uid' ]]);
2018-08-19 12:46:11 +00:00
if ( DBA :: isResult ( $user )) {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Sending share message to author ' . $author . ' - Contact: ' . $contact [ 'id' ] . ' - User: ' . $importer [ 'uid' ]);
2019-01-07 17:09:10 +00:00
self :: sendShare ( $user , $contact );
2017-11-08 00:37:53 +00:00
}
}
return true ;
} else {
2022-06-18 03:01:51 +00:00
Logger :: info ( " Author " . $author . " doesn't want to follow us anymore. " );
2021-09-13 18:22:55 +00:00
Contact :: removeFollower ( $contact );
2017-11-08 00:37:53 +00:00
return true ;
}
}
2022-06-18 03:01:51 +00:00
if ( ! $following && $sharing && in_array ( $importer [ 'page-flags' ], [ User :: PAGE_FLAGS_SOAPBOX , User :: PAGE_FLAGS_NORMAL ])) {
Logger :: info ( " Author " . $author . " wants to share with us - but doesn't want to listen. Request is ignored. " );
2017-11-08 00:37:53 +00:00
return false ;
} elseif ( ! $following && ! $sharing ) {
2022-06-18 03:01:51 +00:00
Logger :: info ( " Author " . $author . " doesn't want anything - and we don't know the author. Request is ignored. " );
2017-11-08 00:37:53 +00:00
return false ;
} elseif ( ! $following && $sharing ) {
2022-06-18 03:01:51 +00:00
Logger :: info ( " Author " . $author . " wants to share with us. " );
2017-11-08 00:37:53 +00:00
} elseif ( $following && $sharing ) {
2022-06-18 03:01:51 +00:00
Logger :: info ( " Author " . $author . " wants to have a bidirectional conection. " );
2017-11-08 00:37:53 +00:00
} elseif ( $following && ! $sharing ) {
2022-06-18 03:01:51 +00:00
Logger :: info ( " Author " . $author . " wants to listen to us. " );
2017-11-08 00:37:53 +00:00
}
2020-08-06 10:31:05 +00:00
$ret = FContact :: getByURL ( $author );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
if ( ! $ret || ( $ret [ 'network' ] != Protocol :: DIASPORA )) {
Logger :: notice ( " Cannot resolve diaspora handle " . $author . " for " . $recipient );
2017-11-08 00:37:53 +00:00
return false ;
}
2019-11-06 19:17:40 +00:00
$cid = Contact :: getIdForURL ( $ret [ 'url' ], $importer [ 'uid' ]);
2019-11-03 13:35:41 +00:00
if ( ! empty ( $cid )) {
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $cid , 'network' => Protocol :: NATIVE_SUPPORT ]);
2017-11-08 00:37:53 +00:00
} else {
2019-11-03 13:35:41 +00:00
$contact = [];
}
2017-11-08 00:37:53 +00:00
2019-11-03 13:35:41 +00:00
$item = [ 'author-id' => Contact :: getIdForURL ( $ret [ 'url' ]),
'author-link' => $ret [ 'url' ]];
2017-11-08 00:37:53 +00:00
2019-11-03 13:35:41 +00:00
$result = Contact :: addRelationship ( $importer , $contact , $item , false );
if ( $result === true ) {
$contact_record = self :: contactByHandle ( $importer [ 'uid' ], $author );
if ( ! $contact_record ) {
Logger :: info ( 'unable to locate newly created contact record.' );
2022-06-17 08:44:13 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2019-11-03 13:35:41 +00:00
$user = DBA :: selectFirst ( 'user' , [], [ 'uid' => $importer [ 'uid' ]]);
2018-08-19 12:46:11 +00:00
if ( DBA :: isResult ( $user )) {
2019-01-07 17:09:10 +00:00
self :: sendShare ( $user , $contact_record );
2017-11-08 00:37:53 +00:00
// Send the profile data, maybe it weren't transmitted before
2019-11-03 13:35:41 +00:00
self :: sendProfile ( $importer [ 'uid' ], [ $contact_record ]);
2017-11-08 00:37:53 +00:00
}
}
return true ;
}
2019-04-01 22:07:23 +00:00
/**
2020-01-19 06:05:23 +00:00
* Stores a reshare activity
2019-04-01 22:07:23 +00:00
*
* @ param array $item Array of reshare post
* @ param integer $parent_message_id Id of the parent post
* @ param string $guid GUID string of reshare action
* @ param string $author Author handle
*/
2022-06-17 08:44:13 +00:00
private static function addReshareActivity ( array $item , int $parent_message_id , string $guid , string $author )
2019-04-01 22:07:23 +00:00
{
2021-01-16 04:14:58 +00:00
$parent = Post :: selectFirst ([ 'uri' , 'guid' ], [ 'id' => $parent_message_id ]);
2019-04-01 22:07:23 +00:00
$datarray = [];
$datarray [ 'uid' ] = $item [ 'uid' ];
$datarray [ 'contact-id' ] = $item [ 'contact-id' ];
$datarray [ 'network' ] = $item [ 'network' ];
$datarray [ 'author-link' ] = $item [ 'author-link' ];
$datarray [ 'author-id' ] = $item [ 'author-id' ];
$datarray [ 'owner-link' ] = $datarray [ 'author-link' ];
$datarray [ 'owner-id' ] = $datarray [ 'author-id' ];
$datarray [ 'guid' ] = $parent [ 'guid' ] . '-' . $guid ;
$datarray [ 'uri' ] = self :: getUriFromGuid ( $author , $datarray [ 'guid' ]);
2020-11-11 07:47:48 +00:00
$datarray [ 'thr-parent' ] = $parent [ 'uri' ];
2019-04-01 22:07:23 +00:00
2019-10-23 22:25:43 +00:00
$datarray [ 'verb' ] = $datarray [ 'body' ] = Activity :: ANNOUNCE ;
2022-09-12 21:12:11 +00:00
$datarray [ 'gravity' ] = Item :: GRAVITY_ACTIVITY ;
2019-10-24 22:10:20 +00:00
$datarray [ 'object-type' ] = Activity\ObjectType :: NOTE ;
2019-04-01 22:07:23 +00:00
$datarray [ 'protocol' ] = $item [ 'protocol' ];
2021-01-09 12:59:30 +00:00
$datarray [ 'source' ] = $item [ 'source' ];
$datarray [ 'direction' ] = $item [ 'direction' ];
2022-07-31 15:54:35 +00:00
$datarray [ 'post-reason' ] = $item [ 'post-reason' ];
2019-04-01 22:07:23 +00:00
$datarray [ 'plink' ] = self :: plink ( $author , $datarray [ 'guid' ]);
$datarray [ 'private' ] = $item [ 'private' ];
$datarray [ 'changed' ] = $datarray [ 'created' ] = $datarray [ 'edited' ] = $item [ 'created' ];
2020-12-14 00:00:10 +00:00
if ( Item :: isTooOld ( $datarray )) {
Logger :: info ( 'Reshare activity is too old' , [ 'created' => $datarray [ 'created' ], 'uid' => $datarray [ 'uid' ], 'guid' => $datarray [ 'guid' ]]);
return false ;
}
2019-04-01 22:07:23 +00:00
$message_id = Item :: insert ( $datarray );
if ( $message_id ) {
Logger :: info ( 'Stored reshare activity.' , [ 'guid' => $guid , 'id' => $message_id ]);
if ( $datarray [ 'uid' ] == 0 ) {
Item :: distribute ( $message_id );
}
}
}
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* Processes a reshare message
2017-11-08 00:37:53 +00:00
*
2022-03-12 07:08:10 +00:00
* @ param array $importer Array of the importer user
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2022-03-12 07:08:10 +00:00
* @ param string $xml The original XML of the message
2022-03-12 15:27:56 +00:00
* @ param int $direction Indicates if the message had been fetched or pushed ( self :: PUSHED , self :: FETCHED , self :: FORCED_FETCH )
2017-11-08 00:37:53 +00:00
*
2022-06-17 08:44:13 +00:00
* @ return bool Success or failure
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-18 03:18:38 +00:00
private static function receiveReshare ( array $importer , SimpleXMLElement $data , string $xml , int $direction ) : bool
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$author = XML :: unescape ( $data -> author );
$guid = XML :: unescape ( $data -> guid );
$created_at = DateTimeFormat :: utc ( XML :: unescape ( $data -> created_at ));
$root_author = XML :: unescape ( $data -> root_author );
$root_guid = XML :: unescape ( $data -> root_guid );
2017-11-08 00:37:53 +00:00
/// @todo handle unprocessed property "provider_display_name"
2021-10-02 17:11:54 +00:00
$public = XML :: unescape ( $data -> public );
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
$contact = self :: allowedContactByHandle ( $importer , $author , false );
2017-11-08 00:37:53 +00:00
if ( ! $contact ) {
return false ;
}
2021-03-10 22:31:33 +00:00
if ( ! empty ( $contact [ 'gsid' ])) {
GServer :: setProtocol ( $contact [ 'gsid' ], Post\DeliveryData :: DIASPORA );
}
2022-06-18 03:01:51 +00:00
$message_id = self :: messageExists ( $importer [ 'uid' ], $guid );
2017-11-08 00:37:53 +00:00
if ( $message_id ) {
return true ;
}
2022-10-09 21:16:36 +00:00
$original_person = FContact :: getByURL ( $root_author );
if ( ! $original_person ) {
2017-11-08 00:37:53 +00:00
return false ;
}
2018-01-15 13:05:12 +00:00
$datarray = [];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'uid' ] = $importer [ 'uid' ];
$datarray [ 'contact-id' ] = $contact [ 'id' ];
$datarray [ 'network' ] = Protocol :: DIASPORA ;
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'author-link' ] = $contact [ 'url' ];
$datarray [ 'author-id' ] = Contact :: getIdForURL ( $contact [ 'url' ], 0 );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'owner-link' ] = $datarray [ 'author-link' ];
$datarray [ 'owner-id' ] = $datarray [ 'author-id' ];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'guid' ] = $guid ;
$datarray [ 'uri' ] = $datarray [ 'thr-parent' ] = self :: getUriFromGuid ( $author , $guid );
2020-04-13 23:54:28 +00:00
$datarray [ 'uri-id' ] = ItemURI :: insert ([ 'uri' => $datarray [ 'uri' ], 'guid' => $datarray [ 'guid' ]]);
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'verb' ] = Activity :: POST ;
2022-09-12 21:12:11 +00:00
$datarray [ 'gravity' ] = Item :: GRAVITY_PARENT ;
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'protocol' ] = Conversation :: PARCEL_DIASPORA ;
$datarray [ 'source' ] = $xml ;
2022-07-31 15:54:35 +00:00
$datarray = self :: setDirection ( $datarray , $direction );
2017-11-08 00:37:53 +00:00
2022-10-09 21:16:36 +00:00
$datarray [ 'body' ] = DI :: contentItem () -> createSharedPostByGuid ( $root_guid , $importer [ 'uid' ], $original_person [ 'url' ]);
2022-10-10 12:30:07 +00:00
$datarray [ 'body' ] = Diaspora :: replacePeopleGuid ( $datarray [ 'body' ], $datarray [ 'author-link' ]);
2017-11-08 00:37:53 +00:00
2022-10-09 21:16:36 +00:00
/// @todo Copy tag data from original post
2022-06-18 03:01:51 +00:00
Tag :: storeFromBody ( $datarray [ 'uri-id' ], $datarray [ 'body' ]);
2020-04-19 07:24:36 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'plink' ] = self :: plink ( $author , $guid );
$datarray [ 'private' ] = (( $public == 'false' ) ? Item :: PRIVATE : Item :: PUBLIC );
$datarray [ 'changed' ] = $datarray [ 'created' ] = $datarray [ 'edited' ] = $created_at ;
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
self :: fetchGuid ( $datarray );
2020-12-14 00:00:10 +00:00
if ( Item :: isTooOld ( $datarray )) {
Logger :: info ( 'Reshare is too old' , [ 'created' => $datarray [ 'created' ], 'uid' => $datarray [ 'uid' ], 'guid' => $datarray [ 'guid' ]]);
return false ;
}
2018-01-28 11:18:08 +00:00
$message_id = Item :: insert ( $datarray );
2017-11-08 00:37:53 +00:00
2017-12-29 18:52:26 +00:00
self :: sendParticipation ( $contact , $datarray );
2022-06-18 03:01:51 +00:00
$root_message_id = self :: messageExists ( $importer [ 'uid' ], $root_guid );
2019-04-01 22:07:23 +00:00
if ( $root_message_id ) {
self :: addReshareActivity ( $datarray , $root_message_id , $guid , $author );
}
2017-11-08 00:37:53 +00:00
if ( $message_id ) {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Stored reshare ' . $datarray [ 'guid' ] . ' with message id ' . $message_id );
2018-04-24 13:21:25 +00:00
if ( $datarray [ 'uid' ] == 0 ) {
Item :: distribute ( $message_id );
}
2017-11-08 00:37:53 +00:00
return true ;
} else {
return false ;
}
}
/**
2020-01-19 06:05:23 +00:00
* Processes retractions
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param array $contact The contact of the item owner
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2017-11-08 00:37:53 +00:00
*
* @ return bool success
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-18 03:18:38 +00:00
private static function itemRetraction ( array $importer , array $contact , SimpleXMLElement $data ) : bool
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$author = XML :: unescape ( $data -> author );
$target_guid = XML :: unescape ( $data -> target_guid );
$target_type = XML :: unescape ( $data -> target_type );
2017-11-08 00:37:53 +00:00
2020-08-06 10:31:05 +00:00
$person = FContact :: getByURL ( $author );
2017-11-08 00:37:53 +00:00
if ( ! is_array ( $person )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Unable to find author detail for ' . $author );
2017-11-08 00:37:53 +00:00
return false ;
}
2022-06-18 03:01:51 +00:00
if ( empty ( $contact [ 'url' ])) {
$contact [ 'url' ] = $person [ 'url' ];
2017-11-08 00:37:53 +00:00
}
// Fetch items that are about to be deleted
2021-01-21 07:16:41 +00:00
$fields = [ 'uid' , 'id' , 'parent' , 'author-link' , 'uri-id' ];
2017-11-08 00:37:53 +00:00
// When we receive a public retraction, we delete every item that we find.
if ( $importer [ 'uid' ] == 0 ) {
2018-07-07 16:38:01 +00:00
$condition = [ 'guid' => $target_guid , 'deleted' => false ];
2017-11-08 00:37:53 +00:00
} else {
2018-07-07 16:38:01 +00:00
$condition = [ 'guid' => $target_guid , 'deleted' => false , 'uid' => $importer [ 'uid' ]];
2017-11-08 00:37:53 +00:00
}
2018-07-07 16:38:01 +00:00
2021-01-16 04:14:58 +00:00
$r = Post :: select ( $fields , $condition );
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $r )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Target guid ' . $target_guid . ' was not found on this system for user ' . $importer [ 'uid' ] . '.' );
2017-11-08 00:37:53 +00:00
return false ;
}
2021-01-16 04:14:58 +00:00
while ( $item = Post :: fetch ( $r )) {
2021-01-21 07:16:41 +00:00
if ( DBA :: exists ( 'post-category' , [ 'uri-id' => $item [ 'uri-id' ], 'uid' => $item [ 'uid' ], 'type' => Post\Category :: FILE ])) {
2021-10-02 17:11:54 +00:00
Logger :: info ( " Target guid " . $target_guid . " for user " . $item [ 'uid' ] . " is filed. So it won't be deleted. " );
2018-07-07 18:14:16 +00:00
continue ;
}
2017-11-08 00:37:53 +00:00
// Fetch the parent item
2021-01-16 04:14:58 +00:00
$parent = Post :: selectFirst ([ 'author-link' ], [ 'id' => $item [ 'parent' ]]);
2017-11-08 00:37:53 +00:00
// Only delete it if the parent author really fits
2022-06-18 03:01:51 +00:00
if ( ! Strings :: compareLink ( $parent [ 'author-link' ], $contact [ 'url' ]) && ! Strings :: compareLink ( $item [ 'author-link' ], $contact [ 'url' ])) {
Logger :: info ( " Thread author " . $parent [ 'author-link' ] . " and item author " . $item [ 'author-link' ] . " don't fit to expected contact " . $contact [ 'url' ]);
2017-11-08 00:37:53 +00:00
continue ;
}
2020-03-03 06:47:28 +00:00
Item :: markForDeletion ([ 'id' => $item [ 'id' ]]);
2017-11-08 00:37:53 +00:00
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Deleted target ' . $target_guid . ' (' . $item [ 'id' ] . ') from user ' . $item [ 'uid' ] . ' parent: ' . $item [ 'parent' ]);
2017-11-08 00:37:53 +00:00
}
2021-01-16 04:14:58 +00:00
DBA :: close ( $r );
2017-11-08 00:37:53 +00:00
return true ;
}
/**
2020-01-19 06:05:23 +00:00
* Receives retraction messages
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param string $sender The sender of the message
2022-06-18 03:18:38 +00:00
* @ param SimpleXMLElement $data The message object
2017-11-08 00:37:53 +00:00
*
* @ return bool Success
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-18 03:18:38 +00:00
private static function receiveRetraction ( array $importer , string $sender , SimpleXMLElement $data )
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$target_type = XML :: unescape ( $data -> target_type );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$contact = self :: contactByHandle ( $importer [ 'uid' ], $sender );
if ( ! $contact && ( in_array ( $target_type , [ 'Contact' , 'Person' ]))) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Cannot find contact for sender: ' . $sender . ' and user ' . $importer [ 'uid' ]);
2017-11-08 00:37:53 +00:00
return false ;
}
2018-07-22 23:22:41 +00:00
if ( ! $contact ) {
$contact = [];
}
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Got retraction for ' . $target_type . ', sender ' . $sender . ' and user ' . $importer [ 'uid' ]);
2017-11-08 00:37:53 +00:00
switch ( $target_type ) {
2022-06-18 03:01:51 +00:00
case 'Comment' :
case 'Like' :
case 'Post' :
case 'Reshare' :
case 'StatusMessage' :
2017-11-23 19:01:58 +00:00
return self :: itemRetraction ( $importer , $contact , $data );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
case 'PollParticipation' :
case 'Photo' :
2018-05-04 06:34:02 +00:00
// Currently unsupported
break ;
2017-11-08 00:37:53 +00:00
default :
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Unknown target type ' . $target_type );
2017-11-08 00:37:53 +00:00
return false ;
}
return true ;
}
2020-09-30 17:37:46 +00:00
/**
* Checks if an incoming message is wanted
*
2022-03-12 07:48:31 +00:00
* @ param array $item
2020-09-30 17:37:46 +00:00
* @ param string $author
* @ param string $body
2022-03-12 15:27:56 +00:00
* @ param int $direction Indicates if the message had been fetched or pushed ( self :: PUSHED , self :: FETCHED , self :: FORCED_FETCH )
2022-03-12 07:08:10 +00:00
*
2020-09-30 17:37:46 +00:00
* @ return boolean Is the message wanted ?
*/
2022-06-17 08:44:13 +00:00
private static function isSolicitedMessage ( array $item , string $author , string $body , int $direction ) : bool
2020-09-30 17:37:46 +00:00
{
$contact = Contact :: getByURL ( $author );
2022-06-18 03:01:51 +00:00
if ( DBA :: exists ( 'contact' , [ '`nurl` = ? AND `uid` != ? AND `rel` IN (?, ?)' , $contact [ 'nurl' ], 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' => $author ]);
2020-09-30 17:37:46 +00:00
return true ;
}
2022-03-12 07:08:10 +00:00
if ( $direction == self :: FORCED_FETCH ) {
2022-03-12 07:48:31 +00:00
Logger :: debug ( 'Post is a forced fetch - accepted' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ], 'author' => $author ]);
2022-03-12 07:08:10 +00:00
return true ;
}
2022-03-12 07:48:31 +00:00
$tags = array_column ( Tag :: getByURIId ( $item [ 'uri-id' ], [ Tag :: HASHTAG ]), 'name' );
if ( Relay :: isSolicitedPost ( $tags , $body , $contact [ 'id' ], $item [ 'uri' ], Protocol :: DIASPORA )) {
Logger :: debug ( 'Post is accepted because of the relay settings' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ], 'author' => $author ]);
2022-03-12 07:34:30 +00:00
return true ;
} else {
return false ;
}
2020-09-30 17:37:46 +00:00
}
2020-10-29 05:20:26 +00:00
/**
* Store an attached photo in the post - media table
*
* @ param int $uriid
* @ param object $photo
* @ return void
*/
2020-10-29 08:48:08 +00:00
private static function storePhotoAsMedia ( int $uriid , $photo )
2020-10-29 05:20:26 +00:00
{
2022-06-17 08:44:13 +00:00
// @TODO Need to find object type, roland@f.haeder.net
Logger :: debug ( 'photo=' . get_class ( $photo ));
2020-10-29 05:20:26 +00:00
$data = [];
$data [ 'uri-id' ] = $uriid ;
$data [ 'type' ] = Post\Media :: IMAGE ;
$data [ 'url' ] = XML :: unescape ( $photo -> remote_photo_path ) . XML :: unescape ( $photo -> remote_photo_name );
$data [ 'height' ] = ( int ) XML :: unescape ( $photo -> height ? ? 0 );
$data [ 'width' ] = ( int ) XML :: unescape ( $photo -> width ? ? 0 );
$data [ 'description' ] = XML :: unescape ( $photo -> text ? ? '' );
Post\Media :: insert ( $data );
}
2022-07-31 15:54:35 +00:00
/**
* Set direction and post reason
*
* @ param array $datarray
* @ param integer $direction
*
* @ return array
*/
public static function setDirection ( array $datarray , int $direction ) : array
{
$datarray [ 'direction' ] = in_array ( $direction , [ self :: FETCHED , self :: FORCED_FETCH ]) ? Conversation :: PULL : Conversation :: PUSH ;
if ( in_array ( $direction , [ self :: FETCHED , self :: FORCED_FETCH ])) {
$datarray [ 'post-reason' ] = Item :: PR_FETCHED ;
} elseif ( $datarray [ 'uid' ] == 0 ) {
$datarray [ 'post-reason' ] = Item :: PR_GLOBAL ;
} else {
$datarray [ 'post-reason' ] = Item :: PR_PUSHED ;
}
return $datarray ;
}
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* Receives status messages
2017-11-08 00:37:53 +00:00
*
2022-03-12 07:08:10 +00:00
* @ param array $importer Array of the importer user
* @ param SimpleXMLElement $data The message object
* @ param string $xml The original XML of the message
2022-03-12 15:27:56 +00:00
* @ param int $direction Indicates if the message had been fetched or pushed ( self :: PUSHED , self :: FETCHED , self :: FORCED_FETCH )
2022-03-12 07:34:30 +00:00
*
2022-06-17 08:44:13 +00:00
* @ return int | bool The message id of the newly created item or false on error
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-17 08:44:13 +00:00
private static function receiveStatusMessage ( array $importer , SimpleXMLElement $data , string $xml , int $direction )
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$author = XML :: unescape ( $data -> author );
$guid = XML :: unescape ( $data -> guid );
$created_at = DateTimeFormat :: utc ( XML :: unescape ( $data -> created_at ));
$public = XML :: unescape ( $data -> public );
2018-11-05 12:40:18 +00:00
$text = XML :: unescape ( $data -> text );
2021-10-02 17:11:54 +00:00
$provider_display_name = XML :: unescape ( $data -> provider_display_name );
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
$contact = self :: allowedContactByHandle ( $importer , $author , false );
2017-11-08 00:37:53 +00:00
if ( ! $contact ) {
return false ;
}
2021-03-10 22:31:33 +00:00
if ( ! empty ( $contact [ 'gsid' ])) {
GServer :: setProtocol ( $contact [ 'gsid' ], Post\DeliveryData :: DIASPORA );
}
2022-06-18 03:01:51 +00:00
$message_id = self :: messageExists ( $importer [ 'uid' ], $guid );
2017-11-08 00:37:53 +00:00
if ( $message_id ) {
return true ;
}
2018-01-15 13:05:12 +00:00
$address = [];
2017-11-08 00:37:53 +00:00
if ( $data -> location ) {
2017-11-08 22:02:50 +00:00
foreach ( $data -> location -> children () as $fieldname => $data ) {
2021-10-02 17:11:54 +00:00
$address [ $fieldname ] = XML :: unescape ( $data );
2017-11-08 00:37:53 +00:00
}
}
2020-10-29 05:20:26 +00:00
$raw_body = $body = Markdown :: toBBCode ( $text );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$datarray = [];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'guid' ] = $guid ;
$datarray [ 'uri' ] = $datarray [ 'thr-parent' ] = self :: getUriFromGuid ( $author , $guid );
2020-10-29 05:20:26 +00:00
$datarray [ 'uri-id' ] = ItemURI :: insert ([ 'uri' => $datarray [ 'uri' ], 'guid' => $datarray [ 'guid' ]]);
2017-11-08 00:37:53 +00:00
// Attach embedded pictures to the body
if ( $data -> photo ) {
2017-11-08 22:02:50 +00:00
foreach ( $data -> photo as $photo ) {
2020-10-29 08:48:08 +00:00
self :: storePhotoAsMedia ( $datarray [ 'uri-id' ], $photo );
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
$datarray [ 'object-type' ] = Activity\ObjectType :: IMAGE ;
$datarray [ 'post-type' ] = Item :: PT_IMAGE ;
2022-01-23 05:50:39 +00:00
} elseif ( $data -> poll ) {
2022-06-18 03:01:51 +00:00
$datarray [ 'object-type' ] = Activity\ObjectType :: NOTE ;
$datarray [ 'post-type' ] = Item :: PT_POLL ;
2017-11-08 00:37:53 +00:00
} else {
2022-06-18 03:01:51 +00:00
$datarray [ 'object-type' ] = Activity\ObjectType :: NOTE ;
$datarray [ 'post-type' ] = Item :: PT_NOTE ;
2017-11-08 00:37:53 +00:00
}
/// @todo enable support for polls
//if ($data->poll) {
2021-10-03 10:34:41 +00:00
// foreach ($data->poll as $poll)
2017-11-08 00:37:53 +00:00
// print_r($poll);
// die("poll!\n");
//}
/// @todo enable support for events
2022-06-18 03:01:51 +00:00
$datarray [ 'uid' ] = $importer [ 'uid' ];
$datarray [ 'contact-id' ] = $contact [ 'id' ];
$datarray [ 'network' ] = Protocol :: DIASPORA ;
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'author-link' ] = $contact [ 'url' ];
$datarray [ 'author-id' ] = Contact :: getIdForURL ( $contact [ 'url' ], 0 );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'owner-link' ] = $datarray [ 'author-link' ];
$datarray [ 'owner-id' ] = $datarray [ 'author-id' ];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'verb' ] = Activity :: POST ;
2022-09-12 21:12:11 +00:00
$datarray [ 'gravity' ] = Item :: GRAVITY_PARENT ;
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'protocol' ] = Conversation :: PARCEL_DIASPORA ;
$datarray [ 'source' ] = $xml ;
2017-11-08 00:37:53 +00:00
2022-07-31 15:54:35 +00:00
$datarray = self :: setDirection ( $datarray , $direction );
2020-09-14 17:48:57 +00:00
2022-06-18 03:01:51 +00:00
$datarray [ 'body' ] = self :: replacePeopleGuid ( $body , $contact [ 'url' ]);
$datarray [ 'raw-body' ] = self :: replacePeopleGuid ( $raw_body , $contact [ 'url' ]);
2017-11-08 00:37:53 +00:00
2020-04-14 07:56:53 +00:00
self :: storeMentions ( $datarray [ 'uri-id' ], $text );
2022-06-18 03:01:51 +00:00
Tag :: storeRawTagsFromBody ( $datarray [ 'uri-id' ], $datarray [ 'body' ]);
2020-04-14 07:56:53 +00:00
2022-03-12 07:48:31 +00:00
if ( ! self :: isSolicitedMessage ( $datarray , $author , $body , $direction )) {
2020-09-30 19:05:19 +00:00
DBA :: delete ( 'item-uri' , [ 'uri' => $datarray [ 'uri' ]]);
2020-09-30 17:37:46 +00:00
return false ;
}
2022-06-18 03:01:51 +00:00
if ( $provider_display_name != '' ) {
$datarray [ 'app' ] = $provider_display_name ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
$datarray [ 'plink' ] = self :: plink ( $author , $guid );
$datarray [ 'private' ] = (( $public == 'false' ) ? Item :: PRIVATE : Item :: PUBLIC );
$datarray [ 'changed' ] = $datarray [ 'created' ] = $datarray [ 'edited' ] = $created_at ;
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
if ( isset ( $address [ 'address' ])) {
$datarray [ 'location' ] = $address [ 'address' ];
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
if ( isset ( $address [ 'lat' ]) && isset ( $address [ 'lng' ])) {
2022-07-19 23:06:05 +00:00
$datarray [ 'coord' ] = $address [ 'lat' ] . ' ' . $address [ 'lng' ];
2017-11-08 00:37:53 +00:00
}
2017-11-23 19:01:58 +00:00
self :: fetchGuid ( $datarray );
2020-12-14 00:00:10 +00:00
if ( Item :: isTooOld ( $datarray )) {
Logger :: info ( 'Status is too old' , [ 'created' => $datarray [ 'created' ], 'uid' => $datarray [ 'uid' ], 'guid' => $datarray [ 'guid' ]]);
return false ;
}
2018-01-28 11:18:08 +00:00
$message_id = Item :: insert ( $datarray );
2017-11-08 00:37:53 +00:00
2017-12-29 18:52:26 +00:00
self :: sendParticipation ( $contact , $datarray );
2017-11-08 00:37:53 +00:00
if ( $message_id ) {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Stored item ' . $datarray [ 'guid' ] . ' with message id ' . $message_id );
2018-04-24 13:21:25 +00:00
if ( $datarray [ 'uid' ] == 0 ) {
Item :: distribute ( $message_id );
}
2017-11-08 00:37:53 +00:00
return true ;
} else {
return false ;
}
}
/* ************************************************************************************** *
* Here are all the functions that are needed to transmit data with the Diaspora protocol *
* ************************************************************************************** */
/**
2020-01-19 06:05:23 +00:00
* returnes the handle of a contact
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $contact contact array
2017-11-08 00:37:53 +00:00
*
* @ return string the handle in the format user @ domain . tld
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
private static function myHandle ( array $contact ) : string
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
if ( ! empty ( $contact [ 'addr' ])) {
return $contact [ 'addr' ];
2017-11-08 00:37:53 +00:00
}
// Normally we should have a filled "addr" field - but in the past this wasn't the case
// So - just in case - we build the the address here.
2022-06-18 03:01:51 +00:00
if ( $contact [ 'nickname' ] != '' ) {
$nick = $contact [ 'nickname' ];
2017-11-08 00:37:53 +00:00
} else {
2022-06-18 03:01:51 +00:00
$nick = $contact [ 'nick' ];
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
return $nick . '@' . substr ( DI :: baseUrl (), strpos ( DI :: baseUrl (), '://' ) + 3 );
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Creates the data for a private message in the new format
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param string $msg The message that is to be transmitted
* @ param array $user The record of the sender
* @ param array $contact Target of the communication
* @ param string $prvkey The private key of the sender
* @ param string $pubkey The public key of the receiver
2017-11-08 00:37:53 +00:00
*
* @ return string The encrypted data
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
public static function encodePrivateData ( string $msg , array $user , array $contact , string $prvkey , string $pubkey ) : string
2017-11-08 22:02:50 +00:00
{
2022-07-19 23:06:05 +00:00
Logger :: debug ( 'Message: ' . $msg );
2017-11-08 00:37:53 +00:00
// without a public key nothing will work
if ( ! $pubkey ) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'pubkey missing: contact id: ' . $contact [ 'id' ]);
2017-11-08 00:37:53 +00:00
return false ;
}
2021-05-11 13:12:12 +00:00
$aes_key = random_bytes ( 32 );
2017-11-08 00:37:53 +00:00
$b_aes_key = base64_encode ( $aes_key );
2021-05-11 13:12:12 +00:00
$iv = random_bytes ( 16 );
2017-11-08 00:37:53 +00:00
$b_iv = base64_encode ( $iv );
2017-11-23 19:01:58 +00:00
$ciphertext = self :: aesEncrypt ( $aes_key , $iv , $msg );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$json = json_encode ([ 'iv' => $b_iv , 'key' => $b_aes_key ]);
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$encrypted_key_bundle = '' ;
2020-05-18 02:58:08 +00:00
if ( !@ openssl_public_encrypt ( $json , $encrypted_key_bundle , $pubkey )) {
return false ;
}
2017-11-08 00:37:53 +00:00
2017-11-08 22:02:50 +00:00
$json_object = json_encode (
2022-06-18 03:01:51 +00:00
[
'aes_key' => base64_encode ( $encrypted_key_bundle ),
'encrypted_magic_envelope' => base64_encode ( $ciphertext )
]
2017-11-08 22:02:50 +00:00
);
2017-11-08 00:37:53 +00:00
return $json_object ;
}
/**
2020-01-19 06:05:23 +00:00
* Creates the envelope for the " fetch " endpoint and for the new format
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param string $msg The message that is to be transmitted
* @ param array $user The record of the sender
2017-11-08 00:37:53 +00:00
*
* @ return string The envelope
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
public static function buildMagicEnvelope ( string $msg , array $user ) : string
2017-11-08 22:02:50 +00:00
{
2018-11-08 15:37:08 +00:00
$b64url_data = Strings :: base64UrlEncode ( $msg );
2022-06-18 03:01:51 +00:00
$data = str_replace ([ " \n " , " \r " , " " , " \t " ], [ '' , '' , '' , '' ], $b64url_data );
2017-11-08 00:37:53 +00:00
2018-11-08 15:37:08 +00:00
$key_id = Strings :: base64UrlEncode ( self :: myHandle ( $user ));
2022-06-18 03:01:51 +00:00
$type = 'application/xml' ;
$encoding = 'base64url' ;
$alg = 'RSA-SHA256' ;
$signable_data = $data . '.' . Strings :: base64UrlEncode ( $type ) . '.' . Strings :: base64UrlEncode ( $encoding ) . '.' . Strings :: base64UrlEncode ( $alg );
2017-11-08 00:37:53 +00:00
// Fallback if the private key wasn't transmitted in the expected field
2022-06-18 03:01:51 +00:00
if ( $user [ 'uprvkey' ] == '' ) {
2017-11-08 00:37:53 +00:00
$user [ 'uprvkey' ] = $user [ 'prvkey' ];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$signature = Crypto :: rsaSign ( $signable_data , $user [ 'uprvkey' ]);
2018-11-08 15:37:08 +00:00
$sig = Strings :: base64UrlEncode ( $signature );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$xmldata = [
'me:env' => [
'me:data' => $data ,
'@attributes' => [ 'type' => $type ],
'me:encoding' => $encoding ,
'me:alg' => $alg ,
'me:sig' => $sig ,
'@attributes2' => [ 'key_id' => $key_id ]
]
];
2017-11-08 00:37:53 +00:00
2022-08-12 12:00:02 +00:00
$namespaces = [ 'me' => ActivityNamespace :: SALMON_ME ];
2017-11-08 00:37:53 +00:00
2017-11-20 17:56:31 +00:00
return XML :: fromArray ( $xmldata , $xml , false , $namespaces );
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Create the envelope for a message
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param string $msg The message that is to be transmitted
* @ param array $user The record of the sender
* @ param array $contact Target of the communication
* @ param string $prvkey The private key of the sender
* @ param string $pubkey The public key of the receiver
* @ param bool $public Is the message public ?
2017-11-08 00:37:53 +00:00
*
* @ return string The message that will be transmitted to other servers
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
public static function buildMessage ( string $msg , array $user , array $contact , string $prvkey , string $pubkey , bool $public = false ) : string
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
// The message is put into an envelope with the sender's signature
2017-11-23 19:01:58 +00:00
$envelope = self :: buildMagicEnvelope ( $msg , $user );
2017-11-08 00:37:53 +00:00
// Private messages are put into a second envelope, encrypted with the receivers public key
if ( ! $public ) {
2017-11-23 19:01:58 +00:00
$envelope = self :: encodePrivateData ( $envelope , $user , $contact , $prvkey , $pubkey );
2017-11-08 00:37:53 +00:00
}
return $envelope ;
}
/**
2020-01-19 06:05:23 +00:00
* Creates a signature for a message
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $owner the array of the owner of the message
2017-11-08 00:37:53 +00:00
* @ param array $message The message that is to be signed
*
* @ return string The signature
*/
2022-06-17 08:44:13 +00:00
private static function signature ( array $owner , array $message ) : string
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$sigmsg = $message ;
2022-06-18 03:01:51 +00:00
unset ( $sigmsg [ 'author_signature' ]);
unset ( $sigmsg [ 'parent_author_signature' ]);
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$signed_text = implode ( ';' , $sigmsg );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
return base64_encode ( Crypto :: rsaSign ( $signed_text , $owner [ 'uprvkey' ], 'sha256' ));
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Transmit a message to a target server
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $owner the array of the item owner
* @ param array $contact Target of the communication
* @ param string $envelope The message that is to be transmitted
* @ param bool $public_batch Is it a public post ?
* @ param string $guid message guid
2019-04-05 04:42:04 +00:00
*
2017-11-08 00:37:53 +00:00
* @ return int Result of the transmission
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-18 03:01:51 +00:00
private static function transmit ( array $owner , array $contact , string $envelope , bool $public_batch , string $guid = '' ) : int
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
$enabled = intval ( DI :: config () -> get ( 'system' , 'diaspora_enabled' ));
2017-11-08 22:02:50 +00:00
if ( ! $enabled ) {
2017-11-08 00:37:53 +00:00
return 200 ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2018-11-08 17:59:00 +00:00
$logid = Strings :: getRandomHex ( 4 );
2018-01-04 19:48:56 +00:00
2018-03-16 20:34:28 +00:00
// We always try to use the data from the fcontact table.
// This is important for transmitting data to Friendica servers.
2018-04-04 06:06:38 +00:00
if ( ! empty ( $contact [ 'addr' ])) {
2020-08-06 10:31:05 +00:00
$fcontact = FContact :: getByURL ( $contact [ 'addr' ]);
2018-04-04 06:06:38 +00:00
if ( ! empty ( $fcontact )) {
2022-06-18 03:01:51 +00:00
$dest_url = ( $public_batch ? $fcontact [ 'batch' ] : $fcontact [ 'notify' ]);
2018-04-04 06:06:38 +00:00
}
2018-01-04 19:48:56 +00:00
}
2019-09-02 03:25:05 +00:00
if ( empty ( $dest_url )) {
2022-06-18 03:01:51 +00:00
$dest_url = ( $public_batch ? $contact [ 'batch' ] : $contact [ 'notify' ]);
2019-09-02 03:25:05 +00:00
}
2017-11-08 00:37:53 +00:00
if ( ! $dest_url ) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'No URL for contact: ' . $contact [ 'id' ] . ' batch mode =' . $public_batch );
2017-11-08 00:37:53 +00:00
return 0 ;
}
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'transmit: ' . $logid . '-' . $guid . ' ' . $dest_url );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
if ( ! intval ( DI :: config () -> get ( 'system' , 'diaspora_test' ))) {
$content_type = (( $public_batch ) ? 'application/magic-envelope+xml' : 'application/json' );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$postResult = DI :: httpClient () -> post ( $dest_url . '/' , $envelope , [ 'Content-Type' => $content_type ]);
2019-04-05 18:04:39 +00:00
$return_code = $postResult -> getReturnCode ();
} else {
2022-06-18 03:01:51 +00:00
Logger :: notice ( 'test_mode' );
2019-04-05 18:04:39 +00:00
return 200 ;
2017-11-08 00:37:53 +00:00
}
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'transmit: ' . $logid . '-' . $guid . ' to ' . $dest_url . ' returns: ' . $return_code );
2017-11-08 00:37:53 +00:00
2018-02-14 14:29:28 +00:00
return $return_code ? $return_code : - 1 ;
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Build the post xml
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param string $type The message type
* @ param array $message The message data
2017-11-08 00:37:53 +00:00
*
* @ return string The post XML
*/
2022-06-17 10:22:40 +00:00
public static function buildPostXml ( string $type , array $message ) : string
2017-11-08 22:02:50 +00:00
{
2018-01-15 13:05:12 +00:00
$data = [ $type => $message ];
2017-11-08 00:37:53 +00:00
2017-11-20 17:56:31 +00:00
return XML :: fromArray ( $data , $xml );
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Builds and transmit messages
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $owner the array of the item owner
* @ param array $contact Target of the communication
* @ param string $type The message type
* @ param array $message The message data
* @ param bool $public_batch Is it a public post ?
* @ param string $guid message guid
2017-11-08 00:37:53 +00:00
*
* @ return int Result of the transmission
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-18 03:01:51 +00:00
private static function buildAndTransmit ( array $owner , array $contact , string $type , array $message , bool $public_batch = false , string $guid = '' )
2017-11-08 22:02:50 +00:00
{
2017-11-23 19:01:58 +00:00
$msg = self :: buildPostXml ( $type , $message );
2017-11-08 00:37:53 +00:00
// Fallback if the private key wasn't transmitted in the expected field
2018-07-19 11:07:14 +00:00
if ( empty ( $owner [ 'uprvkey' ])) {
2017-11-08 00:37:53 +00:00
$owner [ 'uprvkey' ] = $owner [ 'prvkey' ];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2020-12-11 06:35:38 +00:00
// When sending content to Friendica contacts using the Diaspora protocol
// we have to fetch the public key from the fcontact.
// This is due to the fact that legacy DFRN had unique keys for every contact.
$pubkey = $contact [ 'pubkey' ];
if ( ! empty ( $contact [ 'addr' ])) {
$fcontact = FContact :: getByURL ( $contact [ 'addr' ]);
if ( ! empty ( $fcontact )) {
$pubkey = $fcontact [ 'pubkey' ];
}
2021-08-10 03:50:43 +00:00
} else {
// The "addr" field should always be filled.
// If this isn't the case, it will raise a notice some lines later.
// And in the log we will see where it came from and we can handle it there.
Logger :: notice ( 'Empty addr' , [ 'contact' => $contact ? ? [], 'callstack' => System :: callstack ( 20 )]);
2020-12-11 06:35:38 +00:00
}
2022-07-28 21:16:42 +00:00
$envelope = self :: buildMessage ( $msg , $owner , $contact , $owner [ 'uprvkey' ], $pubkey ? ? '' , $public_batch );
2017-11-08 00:37:53 +00:00
2019-06-01 06:54:47 +00:00
$return_code = self :: transmit ( $owner , $contact , $envelope , $public_batch , $guid );
2017-11-08 00:37:53 +00:00
2021-08-04 12:35:03 +00:00
Logger :: info ( 'Transmitted message' , [ 'owner' => $owner [ 'uid' ], 'target' => $contact [ 'addr' ], 'type' => $type , 'guid' => $guid , 'result' => $return_code ]);
2017-11-08 00:37:53 +00:00
return $return_code ;
}
2017-12-29 18:52:26 +00:00
/**
2020-01-19 06:05:23 +00:00
* sends a participation ( Used to get all further updates )
2017-12-29 18:52:26 +00:00
*
* @ param array $contact Target of the communication
2019-01-06 21:06:53 +00:00
* @ param array $item Item array
2017-12-29 18:52:26 +00:00
*
* @ return int The result of the transmission
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-12-29 18:52:26 +00:00
*/
2022-06-17 08:44:13 +00:00
private static function sendParticipation ( array $contact , array $item ) : int
2017-12-29 18:52:26 +00:00
{
// Don't send notifications for private postings
2020-03-02 07:57:23 +00:00
if ( $item [ 'private' ] == Item :: PRIVATE ) {
2022-06-18 03:01:51 +00:00
return 0 ;
2017-12-29 18:52:26 +00:00
}
2022-06-18 03:01:51 +00:00
$cachekey = 'diaspora:sendParticipation:' . $item [ 'guid' ];
2017-12-29 18:52:26 +00:00
2020-01-06 23:45:49 +00:00
$result = DI :: cache () -> get ( $cachekey );
2017-12-29 18:52:26 +00:00
if ( ! is_null ( $result )) {
2022-06-17 08:44:13 +00:00
return - 1 ;
2017-12-29 18:52:26 +00:00
}
2021-08-04 12:35:03 +00:00
// Fetch some user id to have a valid handle to transmit the participation.
// In fact it doesn't matter which user sends this - but it is needed by the protocol.
// If the item belongs to a user, we take this user id.
if ( $item [ 'uid' ] == 0 ) {
// @todo Possibly use an administrator account?
$condition = [ 'verified' => true , 'blocked' => false ,
'account_removed' => false , 'account_expired' => false , 'account-type' => User :: ACCOUNT_TYPE_PERSON ];
$first_user = DBA :: selectFirst ( 'user' , [ 'uid' ], $condition , [ 'order' => [ 'uid' ]]);
$owner = User :: getOwnerDataById ( $first_user [ 'uid' ]);
} else {
$owner = User :: getOwnerDataById ( $item [ 'uid' ]);
}
2017-12-29 18:52:26 +00:00
$author = self :: myHandle ( $owner );
2022-06-18 03:01:51 +00:00
$message = [
'author' => $author ,
'guid' => System :: createUUID (),
'parent_type' => 'Post' ,
'parent_guid' => $item [ 'guid' ]
];
2017-12-29 18:52:26 +00:00
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Send participation for ' . $item [ 'guid' ] . ' by ' . $author );
2017-12-29 18:52:26 +00:00
// It doesn't matter what we store, we only want to avoid sending repeated notifications for the same item
2022-06-18 03:01:51 +00:00
DI :: cache () -> set ( $cachekey , $item [ 'guid' ], Duration :: QUARTER_HOUR );
2017-12-29 18:52:26 +00:00
2022-06-18 03:01:51 +00:00
return self :: buildAndTransmit ( $owner , $contact , 'participation' , $message );
2017-12-29 18:52:26 +00:00
}
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* sends an account migration
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $owner the array of the item owner
2017-11-08 00:37:53 +00:00
* @ param array $contact Target of the communication
2019-01-06 21:06:53 +00:00
* @ param int $uid User ID
2017-11-08 00:37:53 +00:00
*
* @ return int The result of the transmission
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-17 08:44:13 +00:00
public static function sendAccountMigration ( array $owner , array $contact , int $uid ) : int
2017-11-08 22:02:50 +00:00
{
2020-01-18 15:50:57 +00:00
$old_handle = DI :: pConfig () -> get ( $uid , 'system' , 'previous_addr' );
2017-11-08 00:37:53 +00:00
$profile = self :: createProfileData ( $uid );
$signed_text = 'AccountMigration:' . $old_handle . ':' . $profile [ 'author' ];
2022-06-18 03:01:51 +00:00
$signature = base64_encode ( Crypto :: rsaSign ( $signed_text , $owner [ 'uprvkey' ], 'sha256' ));
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$message = [
'author' => $old_handle ,
'profile' => $profile ,
'signature' => $signature
];
2017-11-08 00:37:53 +00:00
2020-06-29 20:22:00 +00:00
Logger :: info ( 'Send account migration' , [ 'msg' => $message ]);
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
return self :: buildAndTransmit ( $owner , $contact , 'account_migration' , $message );
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Sends a " share " message
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $owner the array of the item owner
2017-11-08 00:37:53 +00:00
* @ param array $contact Target of the communication
*
* @ return int The result of the transmission
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
public static function sendShare ( array $owner , array $contact ) : int
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
/**
* @ todo support the different possible combinations of " following " and " sharing "
* Currently , Diaspora only interprets the " sharing " field
*
2017-11-23 19:01:58 +00:00
* Before switching this code productive , we have to check all " sendShare " calls if " rel " is set correctly
2017-11-08 00:37:53 +00:00
*/
/*
switch ( $contact [ " rel " ]) {
2018-07-25 02:53:46 +00:00
case Contact :: FRIEND :
2017-11-08 00:37:53 +00:00
$following = true ;
$sharing = true ;
2018-07-25 02:53:46 +00:00
case Contact :: SHARING :
2017-11-08 00:37:53 +00:00
$following = false ;
$sharing = true ;
2018-07-25 02:53:46 +00:00
case Contact :: FOLLOWER :
2017-11-08 00:37:53 +00:00
$following = true ;
$sharing = false ;
}
*/
2022-06-18 03:01:51 +00:00
$message = [
'author' => self :: myHandle ( $owner ),
'recipient' => $contact [ 'addr' ],
'following' => 'true' ,
'sharing' => 'true'
];
2017-11-08 00:37:53 +00:00
2020-06-29 20:22:00 +00:00
Logger :: info ( 'Send share' , [ 'msg' => $message ]);
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
return self :: buildAndTransmit ( $owner , $contact , 'contact' , $message );
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* sends an " unshare "
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $owner the array of the item owner
2017-11-08 00:37:53 +00:00
* @ param array $contact Target of the communication
*
* @ return int The result of the transmission
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
public static function sendUnshare ( array $owner , array $contact ) : int
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
$message = [
2022-07-19 23:06:05 +00:00
'author' => self :: myHandle ( $owner ),
2022-06-18 03:01:51 +00:00
'recipient' => $contact [ 'addr' ],
'following' => 'false' ,
2022-07-19 23:06:05 +00:00
'sharing' => 'false'
2022-06-18 03:01:51 +00:00
];
2017-11-08 00:37:53 +00:00
2020-06-29 20:22:00 +00:00
Logger :: info ( 'Send unshare' , [ 'msg' => $message ]);
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
return self :: buildAndTransmit ( $owner , $contact , 'contact' , $message );
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Checks a message body if it is a reshare
2017-11-08 00:37:53 +00:00
*
2022-10-25 06:37:23 +00:00
* @ param array $item The message body that is to be check
2017-11-08 00:37:53 +00:00
*
2022-10-25 06:37:23 +00:00
* @ return array Reshare details or " false " if no reshare
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-11-08 00:37:53 +00:00
*/
2022-10-25 06:37:23 +00:00
public static function isReshare ( array $item ) : array
2017-11-08 22:02:50 +00:00
{
2022-10-25 06:37:23 +00:00
$reshared = Item :: getShareArray ( $item );
2019-12-05 06:42:10 +00:00
if ( empty ( $reshared )) {
2022-10-25 06:37:23 +00:00
return [];
2017-11-08 00:37:53 +00:00
}
2022-10-25 06:37:23 +00:00
// Skip if it isn't a pure repeated messages or not a real reshare
if ( ! empty ( $reshared [ 'comment' ]) || empty ( $reshared [ 'guid' ])) {
return [];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-10-25 06:37:23 +00:00
$condition = [ 'guid' => $reshared [ 'guid' ], 'network' => [ Protocol :: DFRN , Protocol :: DIASPORA ]];
$item = Post :: selectFirst ([ 'author-addr' ], $condition );
if ( DBA :: isResult ( $item )) {
return [
'root_handle' => strtolower ( $item [ 'author-addr' ]),
'root_guid' => $reshared [ 'guid' ]
];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-10-25 06:37:23 +00:00
// We are resharing something that isn't a DFRN or Diaspora post.
return [];
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Create an event array
2017-11-08 00:37:53 +00:00
*
* @ param integer $event_id The id of the event
*
* @ return array with event data
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
private static function buildEvent ( string $event_id ) : array
2017-11-08 22:02:50 +00:00
{
2021-10-02 17:11:54 +00:00
$event = DBA :: selectFirst ( 'event' , [], [ 'id' => $event_id ]);
if ( ! DBA :: isResult ( $event )) {
2018-01-15 13:05:12 +00:00
return [];
2017-11-08 00:37:53 +00:00
}
2018-01-15 13:05:12 +00:00
$eventdata = [];
2017-11-08 00:37:53 +00:00
2021-10-02 17:11:54 +00:00
$owner = User :: getOwnerDataById ( $event [ 'uid' ]);
if ( ! $owner ) {
2018-01-15 13:05:12 +00:00
return [];
2017-11-08 00:37:53 +00:00
}
2017-11-23 19:01:58 +00:00
$eventdata [ 'author' ] = self :: myHandle ( $owner );
2017-11-08 00:37:53 +00:00
if ( $event [ 'guid' ]) {
$eventdata [ 'guid' ] = $event [ 'guid' ];
}
2018-01-27 02:38:34 +00:00
$mask = DateTimeFormat :: ATOM ;
2017-11-08 00:37:53 +00:00
/// @todo - establish "all day" events in Friendica
2022-06-18 03:01:51 +00:00
$eventdata [ 'all_day' ] = 'false' ;
2017-11-08 00:37:53 +00:00
2018-10-16 22:29:08 +00:00
$eventdata [ 'timezone' ] = 'UTC' ;
2017-11-08 00:37:53 +00:00
if ( $event [ 'start' ]) {
2021-10-03 17:21:17 +00:00
$eventdata [ 'start' ] = DateTimeFormat :: utc ( $event [ 'start' ], $mask );
2017-11-08 00:37:53 +00:00
}
if ( $event [ 'finish' ] && ! $event [ 'nofinish' ]) {
2021-10-03 17:21:17 +00:00
$eventdata [ 'end' ] = DateTimeFormat :: utc ( $event [ 'finish' ], $mask );
2017-11-08 00:37:53 +00:00
}
if ( $event [ 'summary' ]) {
2018-03-04 22:39:41 +00:00
$eventdata [ 'summary' ] = html_entity_decode ( BBCode :: toMarkdown ( $event [ 'summary' ]));
2017-11-08 00:37:53 +00:00
}
if ( $event [ 'desc' ]) {
2018-03-04 22:39:41 +00:00
$eventdata [ 'description' ] = html_entity_decode ( BBCode :: toMarkdown ( $event [ 'desc' ]));
2017-11-08 00:37:53 +00:00
}
if ( $event [ 'location' ]) {
2018-03-20 06:32:17 +00:00
$event [ 'location' ] = preg_replace ( " / \ [map \ ](.*?) \ [ \ /map \ ]/ism " , '$1' , $event [ 'location' ]);
$coord = Map :: getCoordinates ( $event [ 'location' ]);
2018-01-15 13:05:12 +00:00
$location = [];
2022-06-18 03:01:51 +00:00
$location [ 'address' ] = html_entity_decode ( BBCode :: toMarkdown ( $event [ 'location' ]));
2018-03-20 06:32:17 +00:00
if ( ! empty ( $coord [ 'lat' ]) && ! empty ( $coord [ 'lon' ])) {
2022-06-18 03:01:51 +00:00
$location [ 'lat' ] = $coord [ 'lat' ];
$location [ 'lng' ] = $coord [ 'lon' ];
2018-03-20 06:32:17 +00:00
} else {
2022-06-18 03:01:51 +00:00
$location [ 'lat' ] = 0 ;
$location [ 'lng' ] = 0 ;
2018-03-20 06:32:17 +00:00
}
2017-11-08 00:37:53 +00:00
$eventdata [ 'location' ] = $location ;
}
return $eventdata ;
}
/**
2020-01-19 06:05:23 +00:00
* Create a post ( status message or reshare )
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $item The item that will be exported
2017-11-08 00:37:53 +00:00
* @ param array $owner the array of the item owner
*
* @ return array
* 'type' -> Message type ( " status_message " or " reshare " )
* 'message' -> Array of XML elements of the status
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-11-08 00:37:53 +00:00
*/
2018-07-19 11:07:14 +00:00
public static function buildStatus ( array $item , array $owner )
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
$cachekey = 'diaspora:buildStatus:' . $item [ 'guid' ];
2017-11-08 00:37:53 +00:00
2020-01-06 23:45:49 +00:00
$result = DI :: cache () -> get ( $cachekey );
2017-11-08 00:37:53 +00:00
if ( ! is_null ( $result )) {
return $result ;
}
2017-11-23 19:01:58 +00:00
$myaddr = self :: myHandle ( $owner );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$public = ( $item [ 'private' ] == Item :: PRIVATE ? 'false' : 'true' );
2019-11-22 20:24:02 +00:00
$created = DateTimeFormat :: utc ( $item [ 'received' ], DateTimeFormat :: ATOM );
2022-06-18 03:01:51 +00:00
$edited = DateTimeFormat :: utc ( $item [ 'edited' ] ? ? $item [ 'created' ], DateTimeFormat :: ATOM );
2017-11-08 00:37:53 +00:00
// Detect a share element and do a reshare
2022-10-25 06:37:23 +00:00
if (( $item [ 'private' ] != Item :: PRIVATE ) && ( $ret = self :: isReshare ( $item ))) {
2022-06-18 03:01:51 +00:00
$message = [
2022-07-19 23:06:05 +00:00
'author' => $myaddr ,
'guid' => $item [ 'guid' ],
'created_at' => $created ,
'root_author' => $ret [ 'root_handle' ],
'root_guid' => $ret [ 'root_guid' ],
2022-06-18 03:01:51 +00:00
'provider_display_name' => $item [ 'app' ],
2022-07-19 23:06:05 +00:00
'public' => $public
2022-06-18 03:01:51 +00:00
];
$type = 'reshare' ;
2017-11-08 00:37:53 +00:00
} else {
2022-06-18 03:01:51 +00:00
$title = $item [ 'title' ];
2021-04-30 22:35:16 +00:00
$body = Post\Media :: addAttachmentsToBody ( $item [ 'uri-id' ], $item [ 'body' ]);
2017-11-08 00:37:53 +00:00
2019-11-18 18:52:00 +00:00
// Fetch the title from an attached link - if there is one
2022-06-18 03:01:51 +00:00
if ( empty ( $item [ 'title' ]) && DI :: pConfig () -> get ( $owner [ 'uid' ], 'system' , 'attach_link_title' )) {
2019-11-18 18:52:00 +00:00
$page_data = BBCode :: getAttachmentData ( $item [ 'body' ]);
if ( ! empty ( $page_data [ 'type' ]) && ! empty ( $page_data [ 'title' ]) && ( $page_data [ 'type' ] == 'link' )) {
$title = $page_data [ 'title' ];
}
}
2022-10-09 21:16:36 +00:00
// @todo Check if this is obsolete and if we are still using different owners. (Possibly a fragment from the forum functionality)
2018-04-15 19:01:19 +00:00
if ( $item [ 'author-link' ] != $item [ 'owner-link' ]) {
2022-10-09 21:16:36 +00:00
$body = DI :: contentItem () -> createSharedBlockByArray ( $item );
2018-04-15 19:01:19 +00:00
}
2017-11-08 00:37:53 +00:00
// convert to markdown
2018-03-04 22:39:41 +00:00
$body = html_entity_decode ( BBCode :: toMarkdown ( $body ));
2017-11-08 00:37:53 +00:00
// Adding the title
2017-11-08 22:02:50 +00:00
if ( strlen ( $title )) {
2022-06-18 03:01:51 +00:00
$body = '### ' . html_entity_decode ( $title ) . " \n \n " . $body ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2020-11-06 04:14:29 +00:00
$attachments = Post\Media :: getByURIId ( $item [ 'uri-id' ], [ Post\Media :: DOCUMENT , Post\Media :: TORRENT , Post\Media :: UNKNOWN ]);
if ( ! empty ( $attachments )) {
2021-12-08 13:32:20 +00:00
$body .= " \n [hr] \n " ;
2020-11-06 04:14:29 +00:00
foreach ( $attachments as $attachment ) {
$body .= " [ " . $attachment [ 'description' ] . " ]( " . $attachment [ 'url' ] . " ) \n " ;
2017-11-08 00:37:53 +00:00
}
}
2018-01-15 13:05:12 +00:00
$location = [];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
if ( $item [ 'location' ] != '' )
$location [ 'address' ] = $item [ 'location' ];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
if ( $item [ 'coord' ] != '' ) {
$coord = explode ( ' ' , $item [ 'coord' ]);
$location [ 'lat' ] = $coord [ 0 ];
$location [ 'lng' ] = $coord [ 1 ];
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
$message = [
'author' => $myaddr ,
'guid' => $item [ 'guid' ],
'created_at' => $created ,
'edited_at' => $edited ,
'public' => $public ,
'text' => $body ,
'provider_display_name' => $item [ 'app' ],
'location' => $location
];
2017-11-08 00:37:53 +00:00
// Diaspora rejects messages when they contain a location without "lat" or "lng"
2022-06-18 03:01:51 +00:00
if ( ! isset ( $location [ 'lat' ]) || ! isset ( $location [ 'lng' ])) {
unset ( $message [ 'location' ]);
2017-11-08 00:37:53 +00:00
}
if ( $item [ 'event-id' ] > 0 ) {
2017-11-23 19:01:58 +00:00
$event = self :: buildEvent ( $item [ 'event-id' ]);
2017-11-08 00:37:53 +00:00
if ( count ( $event )) {
$message [ 'event' ] = $event ;
2018-03-20 06:32:17 +00:00
if ( ! empty ( $event [ 'location' ][ 'address' ]) &&
! empty ( $event [ 'location' ][ 'lat' ]) &&
! empty ( $event [ 'location' ][ 'lng' ])) {
$message [ 'location' ] = $event [ 'location' ];
}
/// @todo Once Diaspora supports it, we will remove the body and the location hack above
2017-11-08 00:37:53 +00:00
// $message['text'] = '';
}
}
2022-06-18 03:01:51 +00:00
$type = 'status_message' ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
$msg = [
2022-07-19 23:06:05 +00:00
'type' => $type ,
2022-06-18 03:01:51 +00:00
'message' => $message
];
2017-11-08 00:37:53 +00:00
2020-01-18 14:41:19 +00:00
DI :: cache () -> set ( $cachekey , $msg , Duration :: QUARTER_HOUR );
2017-11-08 00:37:53 +00:00
return $msg ;
}
2022-06-17 08:44:13 +00:00
private static function prependParentAuthorMention ( string $body , string $profile_url ) : string
2019-02-09 04:10:36 +00:00
{
2022-02-15 07:08:02 +00:00
$profile = Contact :: getByURL ( $profile_url , false , [ 'addr' , 'name' ]);
2019-02-09 04:10:36 +00:00
if ( ! empty ( $profile [ 'addr' ])
&& ! strstr ( $body , $profile [ 'addr' ])
&& ! strstr ( $body , $profile_url )
) {
2019-02-23 04:38:59 +00:00
$body = '@[url=' . $profile_url . ']' . $profile [ 'name' ] . '[/url] ' . $body ;
2019-02-09 04:10:36 +00:00
}
return $body ;
}
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* Sends a post
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $item The item that will be exported
* @ param array $owner the array of the item owner
* @ param array $contact Target of the communication
* @ param bool $public_batch Is it a public post ?
2017-11-08 00:37:53 +00:00
*
* @ return int The result of the transmission
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-17 08:44:13 +00:00
public static function sendStatus ( array $item , array $owner , array $contact , bool $public_batch = false ) : int
2017-11-08 22:02:50 +00:00
{
2017-11-23 19:01:58 +00:00
$status = self :: buildStatus ( $item , $owner );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
return self :: buildAndTransmit ( $owner , $contact , $status [ 'type' ], $status [ 'message' ], $public_batch , $item [ 'guid' ]);
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Creates a " like " object
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $item The item that will be exported
2017-11-08 00:37:53 +00:00
* @ param array $owner the array of the item owner
*
2022-06-17 08:44:13 +00:00
* @ return array | bool The data for a " like " or false on error
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2017-11-08 00:37:53 +00:00
*/
2018-07-19 11:07:14 +00:00
private static function constructLike ( array $item , array $owner )
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
$parent = Post :: selectFirst ([ 'guid' , 'uri' , 'thr-parent' ], [ 'uri' => $item [ 'thr-parent' ]]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $parent )) {
2017-11-08 00:37:53 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$target_type = ( $parent [ 'uri' ] === $parent [ 'thr-parent' ] ? 'Post' : 'Comment' );
2018-02-14 04:58:46 +00:00
$positive = null ;
2019-10-23 22:25:43 +00:00
if ( $item [ 'verb' ] === Activity :: LIKE ) {
2022-06-18 03:01:51 +00:00
$positive = 'true' ;
2019-10-23 22:25:43 +00:00
} elseif ( $item [ 'verb' ] === Activity :: DISLIKE ) {
2022-06-18 03:01:51 +00:00
$positive = 'false' ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
return [
2022-07-19 23:06:05 +00:00
'author' => self :: myHandle ( $owner ),
'guid' => $item [ 'guid' ],
'parent_guid' => $parent [ 'guid' ],
'parent_type' => $target_type ,
'positive' => $positive ,
'author_signature' => '' ,
2022-06-18 03:01:51 +00:00
];
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Creates an " EventParticipation " object
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $item The item that will be exported
2017-11-08 00:37:53 +00:00
* @ param array $owner the array of the item owner
*
2022-06-17 08:44:13 +00:00
* @ return array | bool The data for an " EventParticipation " or false on error
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2018-07-19 11:07:14 +00:00
private static function constructAttend ( array $item , array $owner )
2017-11-23 19:01:58 +00:00
{
2021-01-16 04:14:58 +00:00
$parent = Post :: selectFirst ([ 'guid' ], [ 'uri' => $item [ 'thr-parent' ]]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $parent )) {
2017-11-08 00:37:53 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
switch ( $item [ 'verb' ]) {
2019-10-23 22:25:43 +00:00
case Activity :: ATTEND :
2017-11-08 00:37:53 +00:00
$attend_answer = 'accepted' ;
break ;
2019-10-23 22:25:43 +00:00
case Activity :: ATTENDNO :
2017-11-08 00:37:53 +00:00
$attend_answer = 'declined' ;
break ;
2019-10-23 22:25:43 +00:00
case Activity :: ATTENDMAYBE :
2017-11-08 00:37:53 +00:00
$attend_answer = 'tentative' ;
break ;
default :
2022-08-30 19:45:30 +00:00
Logger :: warning ( 'Unknown verb ' . $item [ 'verb' ] . ' in item ' . $item [ 'guid' ]);
2017-11-08 00:37:53 +00:00
return false ;
}
2022-06-18 03:01:51 +00:00
return [
'author' => self :: myHandle ( $owner ),
'guid' => $item [ 'guid' ],
'parent_guid' => $parent [ 'guid' ],
'status' => $attend_answer ,
'author_signature' => ''
];
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Creates the object for a comment
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $item The item that will be exported
2017-11-08 00:37:53 +00:00
* @ param array $owner the array of the item owner
*
2019-02-23 04:38:59 +00:00
* @ return array | false The data for a comment
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2017-11-08 00:37:53 +00:00
*/
2021-09-03 04:20:32 +00:00
private static function constructComment ( array $item , array $owner )
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
$cachekey = 'diaspora:constructComment:' . $item [ 'guid' ];
2017-11-08 00:37:53 +00:00
2020-01-06 23:45:49 +00:00
$result = DI :: cache () -> get ( $cachekey );
2017-11-08 00:37:53 +00:00
if ( ! is_null ( $result )) {
return $result ;
}
2021-02-16 07:20:41 +00:00
$toplevel_item = Post :: selectFirst ([ 'guid' , 'author-id' , 'author-link' , 'gravity' ], [ 'id' => $item [ 'parent' ], 'parent' => $item [ 'parent' ]]);
2019-02-23 04:38:59 +00:00
if ( ! DBA :: isResult ( $toplevel_item )) {
2020-05-27 12:19:06 +00:00
Logger :: error ( 'Missing parent conversation item' , [ 'parent' => $item [ 'parent' ]]);
2017-11-08 00:37:53 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2019-02-23 04:38:59 +00:00
$thread_parent_item = $toplevel_item ;
if ( $item [ 'thr-parent' ] != $item [ 'parent-uri' ]) {
2021-02-16 07:20:41 +00:00
$thread_parent_item = Post :: selectFirst ([ 'guid' , 'author-id' , 'author-link' , 'gravity' ], [ 'uri' => $item [ 'thr-parent' ], 'uid' => $item [ 'uid' ]]);
2019-02-23 04:38:59 +00:00
}
2021-04-30 22:35:16 +00:00
$body = Post\Media :: addAttachmentsToBody ( $item [ 'uri-id' ], $item [ 'body' ]);
2019-02-09 04:10:36 +00:00
2019-09-14 21:38:05 +00:00
// The replied to autor mention is prepended for clarity if:
// - Item replied isn't yours
// - Item is public or explicit mentions are disabled
// - Implicit mentions are enabled
if (
2019-09-15 11:05:47 +00:00
$item [ 'author-id' ] != $thread_parent_item [ 'author-id' ]
2022-09-12 21:12:11 +00:00
&& ( $thread_parent_item [ 'gravity' ] != Item :: GRAVITY_PARENT )
2019-09-14 21:38:05 +00:00
&& ( empty ( $item [ 'uid' ]) || ! Feature :: isEnabled ( $item [ 'uid' ], 'explicit_mentions' ))
2020-01-19 20:21:13 +00:00
&& ! DI :: config () -> get ( 'system' , 'disable_implicit_mentions' )
2019-02-23 04:38:59 +00:00
) {
$body = self :: prependParentAuthorMention ( $body , $thread_parent_item [ 'author-link' ]);
2019-02-09 04:10:36 +00:00
}
$text = html_entity_decode ( BBCode :: toMarkdown ( $body ));
2022-06-18 03:01:51 +00:00
$created = DateTimeFormat :: utc ( $item [ 'created' ], DateTimeFormat :: ATOM );
$edited = DateTimeFormat :: utc ( $item [ 'edited' ], DateTimeFormat :: ATOM );
2017-11-08 00:37:53 +00:00
2019-02-23 04:38:59 +00:00
$comment = [
2022-06-18 03:01:51 +00:00
'author' => self :: myHandle ( $owner ),
'guid' => $item [ 'guid' ],
'created_at' => $created ,
'edited_at' => $edited ,
'parent_guid' => $toplevel_item [ 'guid' ],
'text' => $text ,
2022-07-19 23:06:05 +00:00
'author_signature' => '' ,
2019-02-23 04:38:59 +00:00
];
2017-11-08 00:37:53 +00:00
// Send the thread parent guid only if it is a threaded comment
if ( $item [ 'thr-parent' ] != $item [ 'parent-uri' ]) {
2019-02-23 04:38:59 +00:00
$comment [ 'thread_parent_guid' ] = $thread_parent_item [ 'guid' ];
2017-11-08 00:37:53 +00:00
}
2020-01-18 14:41:19 +00:00
DI :: cache () -> set ( $cachekey , $comment , Duration :: QUARTER_HOUR );
2017-11-08 00:37:53 +00:00
2022-06-17 08:44:13 +00:00
return $comment ;
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Send a like or a comment
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $item The item that will be exported
* @ param array $owner the array of the item owner
* @ param array $contact Target of the communication
* @ param bool $public_batch Is it a public post ?
2017-11-08 00:37:53 +00:00
*
* @ return int The result of the transmission
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-17 08:44:13 +00:00
public static function sendFollowup ( array $item , array $owner , array $contact , bool $public_batch = false ) : int
2017-11-08 22:02:50 +00:00
{
2019-10-23 22:25:43 +00:00
if ( in_array ( $item [ 'verb' ], [ Activity :: ATTEND , Activity :: ATTENDNO , Activity :: ATTENDMAYBE ])) {
2017-11-23 19:01:58 +00:00
$message = self :: constructAttend ( $item , $owner );
2022-06-18 03:01:51 +00:00
$type = 'event_participation' ;
} elseif ( in_array ( $item [ 'verb' ], [ Activity :: LIKE , Activity :: DISLIKE ])) {
2017-11-23 19:01:58 +00:00
$message = self :: constructLike ( $item , $owner );
2022-06-18 03:01:51 +00:00
$type = 'like' ;
} elseif ( ! in_array ( $item [ 'verb' ], [ Activity :: FOLLOW , Activity :: TAG ])) {
2017-11-23 19:01:58 +00:00
$message = self :: constructComment ( $item , $owner );
2022-06-18 03:01:51 +00:00
$type = 'comment' ;
2017-11-08 00:37:53 +00:00
}
2018-12-24 14:50:21 +00:00
if ( empty ( $message )) {
2022-06-17 08:44:13 +00:00
return - 1 ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$message [ 'author_signature' ] = self :: signature ( $owner , $message );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
return self :: buildAndTransmit ( $owner , $contact , $type , $message , $public_batch , $item [ 'guid' ]);
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Relays messages ( like , comment , retraction ) to other servers if we are the thread owner
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $item The item that will be exported
* @ param array $owner the array of the item owner
* @ param array $contact Target of the communication
* @ param bool $public_batch Is it a public post ?
2017-11-08 00:37:53 +00:00
*
* @ return int The result of the transmission
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
public static function sendRelay ( array $item , array $owner , array $contact , bool $public_batch = false ) : int
2017-11-08 22:02:50 +00:00
{
2022-06-18 03:01:51 +00:00
if ( $item [ 'deleted' ]) {
2017-11-23 19:01:58 +00:00
return self :: sendRetraction ( $item , $owner , $contact , $public_batch , true );
2022-06-18 03:01:51 +00:00
} elseif ( in_array ( $item [ 'verb' ], [ Activity :: LIKE , Activity :: DISLIKE ])) {
$type = 'like' ;
2017-11-08 00:37:53 +00:00
} else {
2022-06-18 03:01:51 +00:00
$type = 'comment' ;
2017-11-08 00:37:53 +00:00
}
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Got relayable data ' . $type . ' for item ' . $item [ 'guid' ] . ' (' . $item [ 'id' ] . ')' );
2017-11-08 00:37:53 +00:00
2020-04-15 05:51:06 +00:00
$msg = json_decode ( $item [ 'signed_text' ], true );
$message = [];
if ( is_array ( $msg )) {
foreach ( $msg as $field => $data ) {
2022-06-18 03:01:51 +00:00
if ( ! $item [ 'deleted' ]) {
if ( $field == 'diaspora_handle' ) {
$field = 'author' ;
2020-04-15 05:51:06 +00:00
}
2022-06-18 03:01:51 +00:00
if ( $field == 'target_type' ) {
$field = 'parent_type' ;
2017-11-08 00:37:53 +00:00
}
}
2020-04-15 05:51:06 +00:00
$message [ $field ] = $data ;
2017-11-08 22:02:50 +00:00
}
2020-04-15 05:51:06 +00:00
} else {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Signature text for item ' . $item [ 'guid' ] . ' (' . $item [ 'id' ] . ') could not be extracted: ' . $item [ 'signed_text' ]);
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
$message [ 'parent_author_signature' ] = self :: signature ( $owner , $message );
2017-11-08 00:37:53 +00:00
2020-06-29 20:22:00 +00:00
Logger :: info ( 'Relayed data' , [ 'msg' => $message ]);
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
return self :: buildAndTransmit ( $owner , $contact , $type , $message , $public_batch , $item [ 'guid' ]);
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Sends a retraction ( deletion ) of a message , like or comment
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $item The item that will be exported
* @ param array $owner the array of the item owner
* @ param array $contact Target of the communication
* @ param bool $public_batch Is it a public post ?
* @ param bool $relay Is the retraction transmitted from a relay ?
2017-11-08 00:37:53 +00:00
*
* @ return int The result of the transmission
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
public static function sendRetraction ( array $item , array $owner , array $contact , bool $public_batch = false , bool $relay = false ) : int
2017-11-08 22:02:50 +00:00
{
2022-10-25 06:37:23 +00:00
$itemaddr = strtolower ( $item [ 'author-addr' ]);
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$msg_type = 'retraction' ;
2017-11-08 00:37:53 +00:00
2022-09-12 21:12:11 +00:00
if ( $item [ 'gravity' ] == Item :: GRAVITY_PARENT ) {
2022-06-18 03:01:51 +00:00
$target_type = 'Post' ;
} elseif ( in_array ( $item [ 'verb' ], [ Activity :: LIKE , Activity :: DISLIKE ])) {
$target_type = 'Like' ;
2017-11-08 00:37:53 +00:00
} else {
2022-06-18 03:01:51 +00:00
$target_type = 'Comment' ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
$message = [
'author' => $itemaddr ,
'target_guid' => $item [ 'guid' ],
'target_type' => $target_type
];
2017-11-08 00:37:53 +00:00
2020-06-29 20:22:00 +00:00
Logger :: info ( 'Got message' , [ 'msg' => $message ]);
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
return self :: buildAndTransmit ( $owner , $contact , $msg_type , $message , $public_batch , $item [ 'guid' ]);
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Sends a mail
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param array $item The item that will be exported
* @ param array $owner The owner
2017-11-08 00:37:53 +00:00
* @ param array $contact Target of the communication
*
* @ return int The result of the transmission
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-17 08:44:13 +00:00
public static function sendMail ( array $item , array $owner , array $contact ) : int
2017-11-08 22:02:50 +00:00
{
2017-11-23 19:01:58 +00:00
$myaddr = self :: myHandle ( $owner );
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$cnv = DBA :: selectFirst ( 'conv' , [], [ 'id' => $item [ 'convid' ], 'uid' => $item [ 'uid' ]]);
2018-08-19 12:46:11 +00:00
if ( ! DBA :: isResult ( $cnv )) {
2022-07-19 23:06:05 +00:00
Logger :: notice ( 'Conversation not found.' );
2022-06-17 08:44:13 +00:00
return - 1 ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
$body = BBCode :: toMarkdown ( $item [ 'body' ]);
$created = DateTimeFormat :: utc ( $item [ 'created' ], DateTimeFormat :: ATOM );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$msg = [
2022-06-18 03:01:51 +00:00
'author' => $myaddr ,
'guid' => $item [ 'guid' ],
'conversation_guid' => $cnv [ 'guid' ],
'text' => $body ,
'created_at' => $created ,
2018-01-15 13:05:12 +00:00
];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
if ( $item [ 'reply' ]) {
2017-11-08 00:37:53 +00:00
$message = $msg ;
2022-06-18 03:01:51 +00:00
$type = 'message' ;
2017-11-08 00:37:53 +00:00
} else {
2018-01-15 13:05:12 +00:00
$message = [
2022-06-18 03:01:51 +00:00
'author' => $cnv [ 'creator' ],
'guid' => $cnv [ 'guid' ],
'subject' => $cnv [ 'subject' ],
'created_at' => DateTimeFormat :: utc ( $cnv [ 'created' ], DateTimeFormat :: ATOM ),
'participants' => $cnv [ 'recips' ],
'message' => $msg
2019-01-07 17:09:10 +00:00
];
2017-11-08 00:37:53 +00:00
2022-06-18 03:01:51 +00:00
$type = 'conversation' ;
2017-11-08 00:37:53 +00:00
}
2022-06-18 03:01:51 +00:00
return self :: buildAndTransmit ( $owner , $contact , $type , $message , false , $item [ 'guid' ]);
2017-11-08 00:37:53 +00:00
}
2017-12-20 20:31:25 +00:00
/**
2020-01-19 06:05:23 +00:00
* Split a name into first name and last name
2017-12-20 20:31:25 +00:00
*
* @ param string $name The name
*
* @ return array The array with " first " and " last "
*/
2022-06-17 08:44:13 +00:00
public static function splitName ( string $name ) : array
{
2017-12-20 20:31:25 +00:00
$name = trim ( $name );
// Is the name longer than 64 characters? Then cut the rest of it.
if ( strlen ( $name ) > 64 ) {
if (( strpos ( $name , ' ' ) <= 64 ) && ( strpos ( $name , ' ' ) !== false )) {
$name = trim ( substr ( $name , 0 , strrpos ( substr ( $name , 0 , 65 ), ' ' )));
} else {
$name = substr ( $name , 0 , 64 );
}
}
// Take the first word as first name
$first = (( strpos ( $name , ' ' ) ? trim ( substr ( $name , 0 , strpos ( $name , ' ' ))) : $name ));
$last = (( $first === $name ) ? '' : trim ( substr ( $name , strlen ( $first ))));
if (( strlen ( $first ) < 32 ) && ( strlen ( $last ) < 32 )) {
return [ 'first' => $first , 'last' => $last ];
}
// Take the last word as last name
$first = (( strrpos ( $name , ' ' ) ? trim ( substr ( $name , 0 , strrpos ( $name , ' ' ))) : $name ));
$last = (( $first === $name ) ? '' : trim ( substr ( $name , strlen ( $first ))));
if (( strlen ( $first ) < 32 ) && ( strlen ( $last ) < 32 )) {
return [ 'first' => $first , 'last' => $last ];
}
// Take the first 32 characters if there is no space in the first 32 characters
if (( strpos ( $name , ' ' ) > 32 ) || ( strpos ( $name , ' ' ) === false )) {
$first = substr ( $name , 0 , 32 );
$last = substr ( $name , 32 );
return [ 'first' => $first , 'last' => $last ];
}
$first = trim ( substr ( $name , 0 , strrpos ( substr ( $name , 0 , 33 ), ' ' )));
$last = (( $first === $name ) ? '' : trim ( substr ( $name , strlen ( $first ))));
// Check if the last name is longer than 32 characters
if ( strlen ( $last ) > 32 ) {
if ( strpos ( $last , ' ' ) <= 32 ) {
$last = trim ( substr ( $last , 0 , strrpos ( substr ( $last , 0 , 33 ), ' ' )));
} else {
$last = substr ( $last , 0 , 32 );
}
}
return [ 'first' => $first , 'last' => $last ];
}
2017-11-08 00:37:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* Create profile data
2017-11-08 00:37:53 +00:00
*
* @ param int $uid The user id
*
* @ return array The profile data
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
private static function createProfileData ( int $uid ) : array
2017-11-08 22:02:50 +00:00
{
2020-04-24 12:24:10 +00:00
$profile = DBA :: selectFirst ( 'owner-view' , [ 'uid' , 'addr' , 'name' , 'location' , 'net-publish' , 'dob' , 'about' , 'pub_keywords' ], [ 'uid' => $uid ]);
2022-07-19 23:06:05 +00:00
2020-04-24 12:24:10 +00:00
if ( ! DBA :: isResult ( $profile )) {
2018-01-15 13:05:12 +00:00
return [];
2017-11-08 00:37:53 +00:00
}
2017-12-21 04:53:57 +00:00
$split_name = self :: splitName ( $profile [ 'name' ]);
2017-12-20 20:31:25 +00:00
2022-07-19 23:06:05 +00:00
$data = [
'author' => $profile [ 'addr' ],
'first_name' => $split_name [ 'first' ],
'last_name' => $split_name [ 'last' ],
'image_url' => DI :: baseUrl () . '/photo/custom/300/' . $profile [ 'uid' ] . '.jpg' ,
'image_url_medium' => DI :: baseUrl () . '/photo/custom/100/' . $profile [ 'uid' ] . '.jpg' ,
'image_url_small' => DI :: baseUrl () . '/photo/custom/50/' . $profile [ 'uid' ] . '.jpg' ,
'searchable' => ( $profile [ 'net-publish' ] ? 'true' : 'false' ),
'birthday' => null ,
'about' => null ,
'location' => null ,
'tag_string' => null ,
'nsfw' => 'false' ,
];
2017-11-08 00:37:53 +00:00
2022-07-19 23:06:05 +00:00
if ( $data [ 'searchable' ] === 'true' ) {
$data [ 'birthday' ] = '' ;
2017-11-08 00:37:53 +00:00
2018-01-23 22:51:30 +00:00
if ( $profile [ 'dob' ] && ( $profile [ 'dob' ] > '0000-00-00' )) {
2021-08-25 19:54:54 +00:00
[ $year , $month , $day ] = sscanf ( $profile [ 'dob' ], '%4d-%2d-%2d' );
2018-01-23 22:51:30 +00:00
if ( $year < 1004 ) {
$year = 1004 ;
}
2022-07-19 23:06:05 +00:00
$data [ 'birthday' ] = DateTimeFormat :: utc ( $year . '-' . $month . '-' . $day , 'Y-m-d' );
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2022-08-17 19:39:20 +00:00
$data [ 'about' ] = BBCode :: toMarkdown ( $profile [ 'about' ] ? ? '' );
2022-07-19 23:06:05 +00:00
$data [ 'location' ] = $profile [ 'location' ];
$data [ 'tag_string' ] = '' ;
2017-11-08 00:37:53 +00:00
if ( $profile [ 'pub_keywords' ]) {
2017-11-08 22:02:50 +00:00
$kw = str_replace ( ',' , ' ' , $profile [ 'pub_keywords' ]);
$kw = str_replace ( ' ' , ' ' , $kw );
2019-01-07 18:28:24 +00:00
$arr = explode ( ' ' , $kw );
2017-11-08 00:37:53 +00:00
if ( count ( $arr )) {
for ( $x = 0 ; $x < 5 ; $x ++ ) {
2018-08-01 05:29:58 +00:00
if ( ! empty ( $arr [ $x ])) {
2022-07-19 23:06:05 +00:00
$data [ 'tag_string' ] .= '#' . trim ( $arr [ $x ]) . ' ' ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
}
}
}
2022-07-19 23:06:05 +00:00
$data [ 'tag_string' ] = trim ( $data [ 'tag_string' ]);
2017-11-08 00:37:53 +00:00
}
2022-07-19 23:06:05 +00:00
return $data ;
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Sends profile data
2017-11-08 00:37:53 +00:00
*
2022-07-19 23:06:05 +00:00
* @ param int $uid The user id
* @ param array $recipients optional , default empty array
*
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-07-19 23:06:05 +00:00
public static function sendProfile ( int $uid , array $recipients = [])
2017-11-08 22:02:50 +00:00
{
if ( ! $uid ) {
2022-07-19 23:06:05 +00:00
Logger :: warning ( 'Parameter "uid" is empty' );
2017-11-08 00:37:53 +00:00
return ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2017-12-17 21:22:39 +00:00
$owner = User :: getOwnerDataById ( $uid );
2022-07-19 23:06:05 +00:00
if ( empty ( $owner )) {
Logger :: warning ( 'Cannot fetch User record' , [ 'uid' => $uid ]);
2017-12-17 21:10:44 +00:00
return ;
}
2022-07-20 09:47:43 +00:00
if ( empty ( $recipients )) {
2022-07-19 23:06:05 +00:00
Logger :: debug ( 'No recipients provided, fetching for user' , [ 'uid' => $uid ]);
$recipients = DBA :: selectToArray ( 'contact' , [], [ 'network' => Protocol :: DIASPORA , 'uid' => $uid , 'rel' => [ Contact :: FOLLOWER , Contact :: FRIEND ]]);
2017-11-08 22:02:50 +00:00
}
2022-07-20 09:47:43 +00:00
if ( empty ( $recipients )) {
2022-07-19 23:06:05 +00:00
Logger :: warning ( 'Cannot fetch recipients' , [ 'uid' => $uid ]);
2017-11-08 00:37:53 +00:00
return ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
$message = self :: createProfileData ( $uid );
2022-07-19 23:06:05 +00:00
// @todo Split this into single worker jobs
2022-07-20 09:47:43 +00:00
foreach ( $recipients as $recipient ) {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'Send updated profile data for user ' . $uid . ' to contact ' . $recipient [ 'id' ]);
self :: buildAndTransmit ( $owner , $recipient , 'profile' , $message );
2017-11-08 00:37:53 +00:00
}
}
/**
2020-01-19 06:05:23 +00:00
* Creates the signature for likes that are created on our system
2017-11-08 00:37:53 +00:00
*
2018-10-29 21:15:37 +00:00
* @ param integer $uid The user of that comment
* @ param array $item Item array
2017-11-08 00:37:53 +00:00
*
2022-06-17 08:44:13 +00:00
* @ return array | bool Signed content or false on error
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2022-06-17 08:44:13 +00:00
public static function createLikeSignature ( int $uid , array $item )
2017-11-08 22:02:50 +00:00
{
2018-10-29 21:15:37 +00:00
$owner = User :: getOwnerDataById ( $uid );
if ( empty ( $owner )) {
2022-07-19 23:06:05 +00:00
Logger :: info ( 'No owner post, so not storing signature' , [ 'uid' => $uid ]);
2017-11-08 00:37:53 +00:00
return false ;
}
2022-06-18 03:01:51 +00:00
if ( ! in_array ( $item [ 'verb' ], [ Activity :: LIKE , Activity :: DISLIKE ])) {
2022-07-19 23:06:05 +00:00
Logger :: warning ( 'Item is neither a like nor a dislike' , [ 'uid' => $uid , 'item[verb]' => $item [ 'verb' ]]);;
2017-11-08 00:37:53 +00:00
return false ;
}
2018-10-29 21:15:37 +00:00
$message = self :: constructLike ( $item , $owner );
2018-01-14 14:05:06 +00:00
if ( $message === false ) {
return false ;
}
2022-06-18 03:01:51 +00:00
$message [ 'author_signature' ] = self :: signature ( $owner , $message );
2017-11-08 00:37:53 +00:00
2018-10-27 11:09:23 +00:00
return $message ;
2017-11-08 00:37:53 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Creates the signature for Comments that are created on our system
2017-11-08 00:37:53 +00:00
*
2018-10-29 21:15:37 +00:00
* @ param array $item Item array
2017-11-08 00:37:53 +00:00
*
2022-06-17 08:44:13 +00:00
* @ return array | bool Signed content or false on error
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2021-08-05 08:42:46 +00:00
public static function createCommentSignature ( array $item )
2017-11-08 22:02:50 +00:00
{
2022-07-19 21:44:54 +00:00
$contact = [];
2021-08-05 08:51:39 +00:00
if ( ! empty ( $item [ 'author-link' ])) {
$url = $item [ 'author-link' ];
} else {
$contact = Contact :: getById ( $item [ 'author-id' ], [ 'url' ]);
if ( empty ( $contact [ 'url' ])) {
Logger :: warning ( 'Author Contact not found' , [ 'author-id' => $item [ 'author-id' ]]);
return false ;
}
$url = $contact [ 'url' ];
2021-08-05 08:42:46 +00:00
}
2021-08-05 08:51:39 +00:00
$uid = User :: getIdForURL ( $url );
2021-08-05 08:42:46 +00:00
if ( empty ( $uid )) {
2022-07-19 21:44:54 +00:00
Logger :: info ( 'No owner post, so not storing signature' , [ 'url' => $contact [ 'url' ] ? ? 'No contact loaded' ]);
2021-08-05 08:42:46 +00:00
return false ;
}
2018-10-29 21:15:37 +00:00
$owner = User :: getOwnerDataById ( $uid );
if ( empty ( $owner )) {
2020-06-29 20:22:00 +00:00
Logger :: info ( 'No owner post, so not storing signature' );
2017-11-08 00:37:53 +00:00
return false ;
}
2021-05-31 20:02:53 +00:00
// This is only needed for the automated tests
if ( empty ( $owner [ 'uprvkey' ])) {
return false ;
}
2022-10-03 11:04:57 +00:00
if ( ! self :: parentSupportDiaspora ( $item [ 'thr-parent-id' ])) {
2022-10-03 11:42:50 +00:00
Logger :: info ( 'One of the parents does not support Diaspora. A signature will not be created.' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ]]);
2022-10-03 10:40:16 +00:00
return false ;
}
2018-10-29 21:15:37 +00:00
$message = self :: constructComment ( $item , $owner );
2018-01-14 14:05:06 +00:00
if ( $message === false ) {
return false ;
}
2022-06-18 03:01:51 +00:00
$message [ 'author_signature' ] = self :: signature ( $owner , $message );
2017-11-08 00:37:53 +00:00
2018-10-27 14:35:22 +00:00
return $message ;
2017-11-08 00:37:53 +00:00
}
2021-12-02 06:33:19 +00:00
2022-10-03 11:04:57 +00:00
/**
* Check if the parent and their parents support Diaspora
*
* @ param integer $parent_id
* @ return boolean
*/
private static function parentSupportDiaspora ( int $parent_id ) : bool
{
$parent_post = Post :: selectFirstPost ([ 'gravity' , 'signed_text' , 'author-link' , 'thr-parent-id' ], [ 'uri-id' => $parent_id ]);
if ( empty ( $parent_post [ 'thr-parent-id' ])) {
Logger :: warning ( 'Parent post does not exist.' , [ 'parent-id' => $parent_id ]);
return false ;
}
if ( empty ( FContact :: getByURL ( $parent_post [ 'author-link' ], false ))) {
Logger :: info ( 'Parent author is no Diaspora contact.' , [ 'parent-id' => $parent_id ]);
return false ;
}
2022-09-12 21:12:11 +00:00
if (( $parent_post [ 'gravity' ] == Item :: GRAVITY_COMMENT ) && empty ( $parent_post [ 'signed_text' ])) {
2022-10-03 11:04:57 +00:00
Logger :: info ( 'Parent comment has got no Diaspora signature.' , [ 'parent-id' => $parent_id ]);
return false ;
}
2022-09-12 21:12:11 +00:00
if ( $parent_post [ 'gravity' ] == Item :: GRAVITY_COMMENT ) {
2022-10-03 11:04:57 +00:00
return self :: parentSupportDiaspora ( $parent_post [ 'thr-parent-id' ]);
}
return true ;
}
2022-06-17 08:44:13 +00:00
public static function performReshare ( int $UriId , int $uid ) : int
2021-12-02 06:33:19 +00:00
{
2022-10-09 21:16:36 +00:00
$post = DI :: contentItem () -> createSharedPostByUriId ( $UriId , $uid );
if ( empty ( $post )) {
2021-12-02 06:33:19 +00:00
return 0 ;
}
$owner = User :: getOwnerDataById ( $uid );
$author = Contact :: getPublicIdByUserId ( $uid );
$item = [
'uid' => $uid ,
'verb' => Activity :: POST ,
'contact-id' => $owner [ 'id' ],
'author-id' => $author ,
'owner-id' => $author ,
'body' => $post ,
2022-04-11 18:57:30 +00:00
'allow_cid' => $owner [ 'allow_cid' ] ? ? '' ,
'allow_gid' => $owner [ 'allow_gid' ] ? ? '' ,
'deny_cid' => $owner [ 'deny_cid' ] ? ? '' ,
'deny_gid' => $owner [ 'deny_gid' ] ? ? '' ,
2021-12-02 06:33:19 +00:00
];
if ( ! empty ( $item [ 'allow_cid' ] . $item [ 'allow_gid' ] . $item [ 'deny_cid' ] . $item [ 'deny_gid' ])) {
$item [ 'private' ] = Item :: PRIVATE ;
} elseif ( DI :: pConfig () -> get ( $uid , 'system' , 'unlisted' )) {
$item [ 'private' ] = Item :: UNLISTED ;
} else {
$item [ 'private' ] = Item :: PUBLIC ;
}
2021-12-20 21:16:00 +00:00
// Don't trigger the addons
$item [ 'api_source' ] = false ;
2021-12-02 06:33:19 +00:00
return Item :: insert ( $item , true );
}
2017-11-08 00:37:53 +00:00
}