2017-11-08 00:37:53 +00:00
< ? php
/**
2017-12-13 07:02:52 +00:00
* @ file src / Protocol / diaspora . php
2017-11-08 00:37:53 +00:00
* @ brief The implementation of the diaspora protocol
*
* The new protocol is described here : http :// diaspora . github . io / diaspora_federation / index . html
* This implementation here interprets the old and the new protocol and sends the new one .
2017-11-23 19:01:58 +00:00
* In the future we will remove most stuff from " validPosting " and interpret only the new protocol .
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 ;
2017-11-09 16:05:18 +00:00
use Friendica\Core\Cache ;
2017-11-08 00:37:53 +00:00
use Friendica\Core\Config ;
2018-01-22 21:17:21 +00:00
use Friendica\Core\L10n ;
2018-10-29 21:20:46 +00:00
use Friendica\Core\Logger ;
2017-11-08 00:37:53 +00:00
use Friendica\Core\PConfig ;
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 ;
2017-12-07 14:04:24 +00:00
use Friendica\Model\Contact ;
2018-08-05 10:23:57 +00:00
use Friendica\Model\Conversation ;
2017-12-07 14:09:28 +00:00
use Friendica\Model\GContact ;
2017-12-09 18:45:17 +00:00
use Friendica\Model\Group ;
2018-01-28 11:18:08 +00:00
use Friendica\Model\Item ;
2019-05-08 05:44:22 +00:00
use Friendica\Model\Mail ;
2017-12-07 13:57:35 +00:00
use Friendica\Model\Profile ;
2017-12-09 18:45:17 +00:00
use Friendica\Model\User ;
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 ;
2017-11-08 12:02:55 +00:00
use SimpleXMLElement ;
2017-11-08 00:37:53 +00:00
/**
* @ brief This class contain functions to create and send Diaspora XML files
*
*/
2017-11-08 22:02:50 +00:00
class Diaspora
{
2017-11-08 00:37:53 +00:00
/**
* @ brief Return a list of relay servers
*
2018-03-28 04:24:38 +00:00
* The list contains not only the official relays but also servers that we serve directly
2017-11-08 00:37:53 +00:00
*
2018-04-11 19:01:25 +00:00
* @ param integer $item_id The id of the item that is sent
* @ param array $contacts The previously fetched contacts
2017-11-08 00:37:53 +00:00
*
* @ return array of relay servers
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
public static function relayList ( $item_id , array $contacts = [])
2017-11-08 22:02:50 +00:00
{
2018-03-28 04:24:38 +00:00
$serverlist = [];
// Fetching relay servers
2017-11-08 00:37:53 +00:00
$serverdata = Config :: get ( " system " , " relay_server " );
2018-07-19 11:07:14 +00:00
if ( ! empty ( $serverdata )) {
2018-03-28 04:24:38 +00:00
$servers = explode ( " , " , $serverdata );
foreach ( $servers as $server ) {
$serverlist [ $server ] = trim ( $server );
}
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2018-03-28 04:24:38 +00:00
if ( Config :: get ( " system " , " relay_directly " , false )) {
2018-05-01 06:37:12 +00:00
// We distribute our stuff based on the parent to ensure that the thread will be complete
2018-06-17 17:05:17 +00:00
$parent = Item :: selectFirst ([ 'parent' ], [ 'id' => $item_id ]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $parent )) {
2018-05-01 06:37:12 +00:00
return ;
}
2018-03-28 04:24:38 +00:00
// Servers that want to get all content
2018-07-20 12:19:26 +00:00
$servers = DBA :: select ( 'gserver' , [ 'url' ], [ 'relay-subscribe' => true , 'relay-scope' => 'all' ]);
while ( $server = DBA :: fetch ( $servers )) {
2018-03-28 04:24:38 +00:00
$serverlist [ $server [ 'url' ]] = $server [ 'url' ];
}
2017-11-08 00:37:53 +00:00
2018-03-28 04:24:38 +00:00
// All tags of the current post
2018-05-01 06:37:12 +00:00
$condition = [ 'otype' => TERM_OBJ_POST , 'type' => TERM_HASHTAG , 'oid' => $parent [ 'parent' ]];
2018-07-20 12:19:26 +00:00
$tags = DBA :: select ( 'term' , [ 'term' ], $condition );
2018-03-28 04:24:38 +00:00
$taglist = [];
2018-07-20 12:19:26 +00:00
while ( $tag = DBA :: fetch ( $tags )) {
2018-03-28 04:24:38 +00:00
$taglist [] = $tag [ 'term' ];
}
2017-11-08 00:37:53 +00:00
2018-03-28 04:24:38 +00:00
// All servers who wants content with this tag
$tagserverlist = [];
2018-04-03 12:18:05 +00:00
if ( ! empty ( $taglist )) {
2018-07-20 12:19:26 +00:00
$tagserver = DBA :: select ( 'gserver-tag' , [ 'gserver-id' ], [ 'tag' => $taglist ]);
while ( $server = DBA :: fetch ( $tagserver )) {
2018-04-03 12:18:05 +00:00
$tagserverlist [] = $server [ 'gserver-id' ];
}
2018-03-28 04:24:38 +00:00
}
2017-11-08 00:37:53 +00:00
2018-03-28 04:24:38 +00:00
// All adresses with the given id
2018-04-03 12:18:05 +00:00
if ( ! empty ( $tagserverlist )) {
2018-07-20 12:19:26 +00:00
$servers = DBA :: select ( 'gserver' , [ 'url' ], [ 'relay-subscribe' => true , 'relay-scope' => 'tags' , 'id' => $tagserverlist ]);
while ( $server = DBA :: fetch ( $servers )) {
2018-04-03 12:18:05 +00:00
$serverlist [ $server [ 'url' ]] = $server [ 'url' ];
}
2018-03-28 04:24:38 +00:00
}
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2018-03-28 04:24:38 +00:00
// Now we are collecting all relay contacts
foreach ( $serverlist as $server_url ) {
// We don't send messages to ourselves
2018-11-08 15:46:50 +00:00
if ( Strings :: compareLink ( $server_url , System :: baseUrl ())) {
2018-04-11 19:01:25 +00:00
continue ;
}
$contact = self :: getRelayContact ( $server_url );
if ( is_bool ( $contact )) {
continue ;
}
2017-11-08 00:37:53 +00:00
2018-04-11 19:01:25 +00:00
$exists = false ;
foreach ( $contacts as $entry ) {
if ( $entry [ 'batch' ] == $contact [ 'batch' ]) {
$exists = true ;
2017-11-08 22:02:50 +00:00
}
}
2018-04-11 19:01:25 +00:00
if ( ! $exists ) {
$contacts [] = $contact ;
}
2017-11-08 00:37:53 +00:00
}
2018-03-28 04:24:38 +00:00
return $contacts ;
}
/**
* @ brief Return a contact for a given server address or creates a dummy entry
*
* @ param string $server_url The url of the server
* @ return array with the contact
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-03-28 04:24:38 +00:00
*/
2018-04-11 19:01:25 +00:00
private static function getRelayContact ( $server_url )
2018-03-28 04:24:38 +00:00
{
2019-05-29 04:30:01 +00:00
$fields = [ 'batch' , 'id' , 'name' , 'network' , 'protocol' , 'archive' , 'blocked' ];
2018-04-09 05:53:23 +00:00
// Fetch the relay contact
2018-11-08 16:28:29 +00:00
$condition = [ 'uid' => 0 , 'nurl' => Strings :: normaliseLink ( $server_url ),
2019-01-06 22:08:35 +00:00
'contact-type' => Contact :: TYPE_RELAY ];
2018-07-20 12:19:26 +00:00
$contact = DBA :: selectFirst ( 'contact' , $fields , $condition );
2018-04-08 09:46:47 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $contact )) {
2018-04-08 09:46:47 +00:00
if ( $contact [ 'archive' ] || $contact [ 'blocked' ]) {
return false ;
}
2018-03-28 04:24:38 +00:00
return $contact ;
} else {
2018-04-30 05:33:47 +00:00
self :: setRelayContact ( $server_url );
2018-07-20 12:19:26 +00:00
$contact = DBA :: selectFirst ( 'contact' , $fields , $condition );
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $contact )) {
2018-03-28 04:24:38 +00:00
return $contact ;
}
}
2017-11-08 00:37:53 +00:00
2018-03-28 04:24:38 +00:00
// It should never happen that we arrive here
return [];
2017-11-08 00:37:53 +00:00
}
2018-04-30 05:33:47 +00:00
/**
* @ brief Update or insert a relay contact
*
2019-01-06 21:06:53 +00:00
* @ param string $server_url The url of the server
* @ param array $network_fields Optional network specific fields
* @ throws \Exception
2018-04-30 05:33:47 +00:00
*/
2018-07-19 11:07:14 +00:00
public static function setRelayContact ( $server_url , array $network_fields = [])
2018-04-30 05:33:47 +00:00
{
$fields = [ 'created' => DateTimeFormat :: utcNow (),
'name' => 'relay' , 'nick' => 'relay' ,
2018-08-11 20:40:44 +00:00
'url' => $server_url , 'network' => Protocol :: DIASPORA ,
2018-04-30 05:33:47 +00:00
'batch' => $server_url . '/receive/public' ,
2018-07-25 02:53:46 +00:00
'rel' => Contact :: FOLLOWER , 'blocked' => false ,
2018-04-30 05:33:47 +00:00
'pending' => false , 'writable' => true ];
$fields = array_merge ( $fields , $network_fields );
2018-11-08 16:28:29 +00:00
$condition = [ 'uid' => 0 , 'nurl' => Strings :: normaliseLink ( $server_url ),
2019-01-06 22:08:35 +00:00
'contact-type' => Contact :: TYPE_RELAY ];
2018-04-30 05:33:47 +00:00
2018-07-20 12:19:26 +00:00
if ( DBA :: exists ( 'contact' , $condition )) {
2018-04-30 05:33:47 +00:00
unset ( $fields [ 'created' ]);
}
2018-07-20 12:19:26 +00:00
DBA :: update ( 'contact' , $fields , $condition , true );
2018-04-30 05:33:47 +00:00
}
2018-01-12 20:52:43 +00:00
/**
* @ brief Return a list of participating contacts for a thread
*
* This is used for the participation feature .
* One of the parameters is a contact array .
* This is done to avoid duplicates .
*
* @ param integer $thread The id of the thread
* @ param array $contacts The previously fetched contacts
*
* @ return array of relay servers
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-01-12 20:52:43 +00:00
*/
2018-07-19 11:07:14 +00:00
public static function participantsForThread ( $thread , array $contacts )
2018-01-12 20:52:43 +00:00
{
2019-05-11 05:58:22 +00:00
$r = DBA :: p ( " SELECT `contact`.`batch`, `contact`.`id`, `contact`.`name`, `contact`.`network`, `contact`.`protocol`,
2018-01-12 20:52:43 +00:00
`fcontact` . `batch` AS `fbatch` , `fcontact` . `network` AS `fnetwork` FROM `participation`
2018-01-12 23:20:19 +00:00
INNER JOIN `contact` ON `contact` . `id` = `participation` . `cid`
2018-01-14 22:53:00 +00:00
INNER JOIN `fcontact` ON `fcontact` . `id` = `participation` . `fid`
2018-01-12 23:20:19 +00:00
WHERE `participation` . `iid` = ? " , $thread );
2018-01-12 20:52:43 +00:00
2018-07-20 12:19:26 +00:00
while ( $contact = DBA :: fetch ( $r )) {
2018-01-12 20:52:43 +00:00
if ( ! empty ( $contact [ 'fnetwork' ])) {
$contact [ 'network' ] = $contact [ 'fnetwork' ];
}
unset ( $contact [ 'fnetwork' ]);
2019-05-11 05:58:22 +00:00
if ( empty ( $contact [ 'protocol' ])) {
$contact [ 'protocol' ] = $contact [ 'network' ];
}
2018-01-12 20:52:43 +00:00
if ( empty ( $contact [ 'batch' ]) && ! empty ( $contact [ 'fbatch' ])) {
$contact [ 'batch' ] = $contact [ 'fbatch' ];
}
unset ( $contact [ 'fbatch' ]);
$exists = false ;
foreach ( $contacts as $entry ) {
if ( $entry [ 'batch' ] == $contact [ 'batch' ]) {
$exists = true ;
}
}
if ( ! $exists ) {
$contacts [] = $contact ;
}
}
2018-07-20 12:19:26 +00:00
DBA :: close ( $r );
2018-01-12 20:52:43 +00:00
return $contacts ;
}
2017-11-08 00:37:53 +00:00
/**
* @ brief repairs a signature that was double encoded
*
* The function is unused at the moment . It was copied from the old implementation .
*
2017-11-08 22:02:50 +00:00
* @ param string $signature The signature
* @ param string $handle The handle of the signature owner
* @ param integer $level This value is only set inside this function to avoid endless loops
2017-11-08 00:37:53 +00:00
*
* @ return string the repaired signature
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function repairSignature ( $signature , $handle = " " , $level = 1 )
2017-11-08 22:02:50 +00:00
{
if ( $signature == " " ) {
2017-11-08 00:37:53 +00:00
return ( $signature );
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
if ( base64_encode ( base64_decode ( base64_decode ( $signature ))) == base64_decode ( $signature )) {
$signature = base64_decode ( $signature );
2018-10-30 13:58:45 +00:00
Logger :: log ( " Repaired double encoded signature from Diaspora/Hubzilla handle " . $handle . " - level " . $level , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
// Do a recursive call to be able to fix even multiple levels
2017-11-08 22:02:50 +00:00
if ( $level < 10 ) {
2017-11-23 19:01:58 +00:00
$signature = self :: repairSignature ( $signature , $handle , ++ $level );
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
}
return ( $signature );
}
/**
* @ brief verify the envelope and return the verified data
*
* @ param string $envelope The magic envelope
*
* @ return string verified data
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function verifyMagicEnvelope ( $envelope )
2017-11-08 22:02:50 +00:00
{
2018-01-27 16:13:41 +00:00
$basedom = XML :: parseString ( $envelope );
2017-11-08 00:37:53 +00:00
if ( ! is_object ( $basedom )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " Envelope is no XML file " );
2017-11-08 00:37:53 +00:00
return false ;
}
$children = $basedom -> children ( 'http://salmon-protocol.org/ns/magic-env' );
if ( sizeof ( $children ) == 0 ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " XML has no children " );
2017-11-08 00:37:53 +00:00
return false ;
}
$handle = " " ;
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 ];
2017-11-08 22:02:50 +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 );
2018-01-15 13:05:12 +00:00
$msg = str_replace ([ " \n " , " \r " , " " , " \t " ], [ " " , " " , " " , " " ], $b64url_data );
2017-11-08 00:37:53 +00:00
2018-11-08 15:37:08 +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 == '' ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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 == '' ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " 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 ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Message from ' . $handle . ' did not verify. Discarding.' );
2017-11-08 00:37:53 +00:00
return false ;
}
return $data ;
}
/**
* @ brief encrypts data via AES
*
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
*/
2017-11-23 19:01:58 +00:00
private static function aesEncrypt ( $key , $iv , $data )
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 " ));
}
/**
* @ brief decrypts data via AES
*
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
*/
2017-11-23 19:01:58 +00:00
private static function aesDecrypt ( $key , $iv , $encrypted )
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
}
/**
* @ brief : Decodes incoming Diaspora message in the new format
*
2018-09-06 09:20:45 +00:00
* @ param array $importer Array of the importer user
* @ param string $raw raw post message
* @ param boolean $no_exit Don ' t do an http exit on error
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
*/
2018-09-06 09:20:45 +00:00
public static function decodeRaw ( array $importer , $raw , $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 = '' ;
@ openssl_private_decrypt ( $encrypted_aes_key_bundle , $outer_key_bundle , $importer [ 'prvkey' ]);
$j_outer_key_bundle = json_decode ( $outer_key_bundle );
if ( ! is_object ( $j_outer_key_bundle )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Outer Salmon 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
}
$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 ;
}
2018-01-27 16:13:41 +00:00
$basedom = XML :: parseString ( $xml );
2017-11-08 00:37:53 +00:00
if ( ! is_object ( $basedom )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Received data does not seem to be an XML. Discarding. ' . $xml );
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
}
$base = $basedom -> children ( NAMESPACE_SALMON_ME );
// Not sure if this cleaning is needed
2018-01-15 13:05:12 +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 ;
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
// 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 == '' ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'No author could be decoded. Discarding. Message: ' . $xml );
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
}
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 == '' ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " Couldn't get a key for handle " . $author_addr . " . 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
}
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 ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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
}
2018-11-08 15:37:08 +00:00
return [ 'message' => ( string ) Strings :: base64UrlDecode ( $base -> data ),
2018-11-05 12:40:18 +00:00
'author' => XML :: unescape ( $author_addr ),
2018-01-15 13:05:12 +00:00
'key' => ( string ) $key ];
2017-11-08 00:37:53 +00:00
}
/**
* @ brief : Decodes incoming Diaspora message in the deprecated format
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param string $xml urldecoded Diaspora salmon
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
*/
2018-07-19 11:07:14 +00:00
public static function decode ( array $importer , $xml )
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 )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " XML is not parseable. " );
2017-11-08 00:37:53 +00:00
return false ;
}
$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
if ( ! $importer ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " This is no private post in the old format " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return false ;
}
$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 = '' ;
2017-11-08 22:02:50 +00:00
openssl_private_decrypt ( $encrypted_aes_key_bundle , $outer_key_bundle , $importer [ 'prvkey' ]);
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
2018-10-30 13:58:45 +00:00
Logger :: log ( 'decrypted: ' . $decrypted , Logger :: DEBUG );
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
}
$dom = $basedom -> children ( NAMESPACE_SALMON_ME );
// 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 ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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
2018-01-15 13:05:12 +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 ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Fetching key for ' . $author_link );
2017-11-08 00:37:53 +00:00
$key = self :: key ( $author_link );
if ( ! $key ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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 ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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
}
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Message verified.' );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
return [ 'message' => ( string ) $inner_decrypted ,
2018-11-05 12:40:18 +00:00
'author' => XML :: unescape ( $author_link ),
2018-01-15 13:05:12 +00:00
'key' => ( string ) $key ];
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Dispatches public messages and find the fitting receivers
*
* @ param array $msg The post that will be dispatched
*
* @ return int 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
*/
2017-11-23 19:01:58 +00:00
public static function dispatchPublic ( $msg )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$enabled = intval ( Config :: get ( " system " , " diaspora_enabled " ));
if ( ! $enabled ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " 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 ))) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " Invalid posting " );
2017-11-08 00:37:53 +00:00
return false ;
}
2019-01-06 17:37:48 +00:00
$importer = [ " uid " => 0 , " page-flags " => User :: PAGE_FLAGS_FREELOVE ];
2018-04-27 14:03:10 +00:00
$success = self :: dispatch ( $importer , $msg , $fields );
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
}
/**
* @ brief Dispatches the different message types to the different functions
*
2019-01-21 21:51:59 +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
2017-11-08 00:37:53 +00:00
*
* @ return int 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
*/
2019-01-21 21:51:59 +00:00
public static function dispatch ( array $importer , $msg , SimpleXMLElement $fields = null )
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")
$sender = $msg [ " author " ];
// 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 ))) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " 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 ();
2018-10-30 13:58:45 +00:00
Logger :: log ( " Received message type " . $type . " from " . $sender . " for user " . $importer [ " uid " ], Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
switch ( $type ) {
case " account_migration " :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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 );
case " account_deletion " :
2018-04-24 18:34:35 +00:00
return self :: receiveAccountDeletion ( $fields );
2017-11-08 00:37:53 +00:00
case " comment " :
2017-11-23 19:01:58 +00:00
return self :: receiveComment ( $importer , $sender , $fields , $msg [ " message " ]);
2017-11-08 00:37:53 +00:00
case " contact " :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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
case " conversation " :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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
case " like " :
2017-11-23 19:01:58 +00:00
return self :: receiveLike ( $importer , $sender , $fields );
2017-11-08 00:37:53 +00:00
case " message " :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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
2018-01-12 20:52:43 +00:00
case " participation " :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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 :: receiveParticipation ( $importer , $fields );
2017-11-08 00:37:53 +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
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
case " profile " :
2018-04-24 18:34:35 +00:00
if ( ! $private ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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
case " reshare " :
2017-11-23 19:01:58 +00:00
return self :: receiveReshare ( $importer , $fields , $msg [ " message " ]);
2017-11-08 00:37:53 +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
case " status_message " :
2017-11-23 19:01:58 +00:00
return self :: receiveStatusMessage ( $importer , $fields , $msg [ " message " ]);
2017-11-08 00:37:53 +00:00
default :
2018-10-29 21:20:46 +00:00
Logger :: log ( " Unknown message type " . $type );
2017-11-08 00:37:53 +00:00
return false ;
}
}
/**
* @ brief Checks if a posting is valid and fetches the data fields .
*
* 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
*/
2017-11-23 19:01:58 +00:00
private static function validPosting ( $msg )
2017-11-08 22:02:50 +00:00
{
2018-01-27 16:13:41 +00:00
$data = XML :: parseString ( $msg [ " message " ]);
2017-11-08 00:37:53 +00:00
if ( ! is_object ( $data )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " No valid XML " . $msg [ " message " ], Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return false ;
}
// Is this the new or the old version?
if ( $data -> getName () == " XML " ) {
$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 ;
2018-10-30 13:58:45 +00:00
Logger :: log ( " Got message type " . $type . " : " . $msg [ " message " ], Logger :: DATA );
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".
2018-01-15 13:05:12 +00:00
if ( in_array ( $type , [ " signed_retraction " , " relayable_retraction " ]))
2017-11-08 00:37:53 +00:00
$type = " retraction " ;
2017-11-08 22:02:50 +00:00
if ( $type == " request " ) {
2017-11-08 00:37:53 +00:00
$type = " contact " ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
$fields = new SimpleXMLElement ( " < " . $type . " /> " );
$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
if ( $fieldname == " diaspora_handle " ) {
$fieldname = " author " ;
}
if ( $fieldname == " participant_handles " ) {
$fieldname = " participants " ;
}
2018-01-15 13:05:12 +00:00
if ( in_array ( $type , [ " like " , " participation " ])) {
2017-11-08 00:37:53 +00:00
if ( $fieldname == " target_type " ) {
$fieldname = " parent_type " ;
}
}
if ( $fieldname == " sender_handle " ) {
$fieldname = " author " ;
}
if ( $fieldname == " recipient_handle " ) {
$fieldname = " recipient " ;
}
if ( $fieldname == " root_diaspora_id " ) {
$fieldname = " root_author " ;
}
if ( $type == " status_message " ) {
if ( $fieldname == " raw_message " ) {
$fieldname = " text " ;
}
}
if ( $type == " retraction " ) {
if ( $fieldname == " post_guid " ) {
$fieldname = " target_guid " ;
}
if ( $fieldname == " type " ) {
$fieldname = " target_type " ;
}
}
}
2017-11-08 22:02:50 +00:00
if (( $fieldname == " author_signature " ) && ( $entry != " " )) {
2017-11-08 00:37:53 +00:00
$author_signature = base64_decode ( $entry );
2017-11-08 22:02:50 +00:00
} elseif (( $fieldname == " parent_author_signature " ) && ( $entry != " " )) {
2017-11-08 00:37:53 +00:00
$parent_author_signature = base64_decode ( $entry );
2018-01-15 13:05:12 +00:00
} elseif ( ! in_array ( $fieldname , [ " author_signature " , " parent_author_signature " , " target_author_signature " ])) {
2017-11-08 00:37:53 +00:00
if ( $signed_data != " " ) {
$signed_data .= " ; " ;
}
$signed_data .= $entry ;
}
2018-01-15 13:05:12 +00:00
if ( ! in_array ( $fieldname , [ " parent_author_signature " , " target_author_signature " ])
2017-11-08 22:02:50 +00:00
|| ( $orig_type == " relayable_retraction " )
) {
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.
2018-01-15 13:05:12 +00:00
if ( in_array ( $type , [ " status_message " , " reshare " , " profile " ])) {
2017-11-08 00:37:53 +00:00
if ( $msg [ " author " ] != $fields -> author ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " Message handle is not the same as envelope sender. Quitting this message. " );
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.
2018-01-15 13:05:12 +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
}
// No author_signature? This is a must, so we quit.
if ( ! isset ( $author_signature )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " No author signature for type " . $type . " - Message: " . $msg [ " message " ], Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return false ;
}
if ( isset ( $parent_author_signature )) {
$key = self :: key ( $msg [ " author " ]);
2018-04-26 20:41:06 +00:00
if ( empty ( $key )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " No key found for parent author " . $msg [ " author " ], Logger :: DEBUG );
2018-04-26 20:41:06 +00:00
return false ;
}
2017-11-08 00:37:53 +00:00
2017-12-30 16:51:49 +00:00
if ( ! Crypto :: rsaVerify ( $signed_data , $parent_author_signature , $key , " sha256 " )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " No valid parent author signature for parent author " . $msg [ " author " ] . " in type " . $type . " - signed data: " . $signed_data . " - Message: " . $msg [ " message " ] . " - Signature " . $parent_author_signature , Logger :: DEBUG );
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 )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " No key found for author " . $fields -> author , Logger :: DEBUG );
2018-04-26 20:41:06 +00:00
return false ;
}
2017-11-08 00:37:53 +00:00
2017-12-30 16:51:49 +00:00
if ( ! Crypto :: rsaVerify ( $signed_data , $author_signature , $key , " sha256 " )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " No valid author signature for author " . $fields -> author . " in type " . $type . " - signed data: " . $signed_data . " - Message: " . $msg [ " message " ] . " - Signature " . $author_signature , Logger :: DEBUG );
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
}
}
/**
* @ brief Fetches the public key for a given handle
*
* @ 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
*/
2017-11-08 22:02:50 +00:00
private static function key ( $handle )
{
2017-11-08 00:37:53 +00:00
$handle = strval ( $handle );
2018-10-29 21:20:46 +00:00
Logger :: log ( " Fetching diaspora key for: " . $handle );
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
$r = self :: personByHandle ( $handle );
2017-11-08 22:02:50 +00:00
if ( $r ) {
2017-11-08 00:37:53 +00:00
return $r [ " pubkey " ];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
return " " ;
}
/**
* @ brief Fetches data for a given handle
*
* @ param string $handle The handle
*
* @ return array the queried data
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
public static function personByHandle ( $handle )
2017-11-08 22:02:50 +00:00
{
2018-02-14 04:58:46 +00:00
$update = false ;
2018-08-11 20:40:44 +00:00
$person = DBA :: selectFirst ( 'fcontact' , [], [ 'network' => Protocol :: DIASPORA , 'addr' => $handle ]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $person )) {
2019-02-23 04:00:16 +00:00
Logger :: debug ( " In cache " . print_r ( $person , true ));
2017-11-08 00:37:53 +00:00
// update record occasionally so it doesn't get stale
$d = strtotime ( $person [ " updated " ] . " +00:00 " );
2017-11-08 22:02:50 +00:00
if ( $d < strtotime ( " now - 14 days " )) {
2017-11-08 00:37:53 +00:00
$update = true ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2017-11-08 22:02:50 +00:00
if ( $person [ " guid " ] == " " ) {
2017-11-08 00:37:53 +00:00
$update = true ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
}
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $person ) || $update ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " create or refresh " , Logger :: DEBUG );
2018-08-11 20:40:44 +00:00
$r = Probe :: uri ( $handle , Protocol :: DIASPORA );
2017-11-08 00:37:53 +00:00
// Note that Friendica contacts will return a "Diaspora person"
// if Diaspora connectivity is enabled on their server
2018-08-11 20:40:44 +00:00
if ( $r && ( $r [ " network " ] === Protocol :: DIASPORA )) {
2018-05-10 12:49:28 +00:00
self :: updateFContact ( $r );
2018-02-22 09:59:21 +00:00
// Fetch the updated or added contact
2018-08-11 20:40:44 +00:00
$person = DBA :: selectFirst ( 'fcontact' , [], [ 'network' => Protocol :: DIASPORA , 'addr' => $handle ]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $person )) {
2018-02-22 09:59:21 +00:00
$person = $r ;
2018-10-16 22:28:27 +00:00
$person [ 'id' ] = 0 ;
2018-02-22 09:59:21 +00:00
}
2017-11-08 00:37:53 +00:00
}
}
2018-02-14 04:58:46 +00:00
2017-11-08 00:37:53 +00:00
return $person ;
}
/**
* @ brief Updates the fcontact table
*
2018-05-10 12:49:28 +00:00
* @ param array $arr The fcontact data
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2018-05-10 12:49:28 +00:00
private static function updateFContact ( $arr )
2017-11-08 22:02:50 +00:00
{
2018-05-10 12:49:28 +00:00
$fields = [ 'name' => $arr [ " name " ], 'photo' => $arr [ " photo " ],
'request' => $arr [ " request " ], 'nick' => $arr [ " nick " ],
'addr' => strtolower ( $arr [ " addr " ]), 'guid' => $arr [ " guid " ],
'batch' => $arr [ " batch " ], 'notify' => $arr [ " notify " ],
'poll' => $arr [ " poll " ], 'confirm' => $arr [ " confirm " ],
'alias' => $arr [ " alias " ], 'pubkey' => $arr [ " pubkey " ],
'updated' => DateTimeFormat :: utcNow ()];
$condition = [ 'url' => $arr [ " url " ], 'network' => $arr [ " network " ]];
2018-07-20 12:19:26 +00:00
DBA :: update ( 'fcontact' , $fields , $condition , true );
2017-11-08 00:37:53 +00:00
}
/**
2018-02-21 05:15:55 +00:00
* @ brief get a handle ( user @ domain . tld ) from a given contact id
2017-11-08 00:37:53 +00:00
*
2017-11-08 22:02:50 +00:00
* @ param int $contact_id The id in the contact table
2018-02-21 05:15:55 +00:00
* @ param int $pcontact_id The id in the contact table ( Used for the public contact )
2017-11-08 00:37:53 +00:00
*
* @ return string the handle
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2018-02-21 05:21:55 +00:00
private static function handleFromContact ( $contact_id , $pcontact_id = 0 )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$handle = false ;
2018-10-30 13:58:45 +00:00
Logger :: log ( " contact id is " . $contact_id . " - pcontact id is " . $pcontact_id , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2018-02-21 05:15:55 +00:00
if ( $pcontact_id != 0 ) {
2018-08-19 12:46:11 +00:00
$contact = DBA :: selectFirst ( 'contact' , [ 'addr' ], [ 'id' => $pcontact_id ]);
2017-11-08 00:37:53 +00:00
2018-08-19 12:46:11 +00:00
if ( DBA :: isResult ( $contact ) && ! empty ( $contact [ " addr " ])) {
return strtolower ( $contact [ " addr " ]);
2017-11-08 00:37:53 +00:00
}
}
2017-11-08 22:02:50 +00:00
$r = q (
" SELECT `network`, `addr`, `self`, `url`, `nick` FROM `contact` WHERE `id` = %d " ,
intval ( $contact_id )
);
2017-11-08 00:37:53 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $r )) {
2017-11-08 00:37:53 +00:00
$contact = $r [ 0 ];
2018-10-30 13:58:45 +00:00
Logger :: log ( " contact 'self' = " . $contact [ 'self' ] . " 'url' = " . $contact [ 'url' ], Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
if ( $contact [ 'addr' ] != " " ) {
$handle = $contact [ 'addr' ];
} else {
2017-11-08 22:02:50 +00:00
$baseurl_start = strpos ( $contact [ 'url' ], '://' ) + 3 ;
// allows installations in a subdirectory--not sure how Diaspora will handle
$baseurl_length = strpos ( $contact [ 'url' ], '/profile' ) - $baseurl_start ;
2017-11-08 00:37:53 +00:00
$baseurl = substr ( $contact [ 'url' ], $baseurl_start , $baseurl_length );
$handle = $contact [ 'nick' ] . '@' . $baseurl ;
}
}
return strtolower ( $handle );
}
/**
* @ brief get a url ( scheme :// domain . tld / u / user ) from a given Diaspora *
* fcontact guid
*
* @ param mixed $fcontact_guid Hexadecimal string guid
*
* @ return string the contact url or null
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
public static function urlFromContactGuid ( $fcontact_guid )
2017-11-08 22:02:50 +00:00
{
2018-10-30 13:58:45 +00:00
Logger :: log ( " fcontact guid is " . $fcontact_guid , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2017-11-08 22:02:50 +00:00
$r = q (
" SELECT `url` FROM `fcontact` WHERE `url` != '' AND `network` = '%s' AND `guid` = '%s' " ,
2018-08-11 20:40:44 +00:00
DBA :: escape ( Protocol :: DIASPORA ),
2018-07-21 13:10:13 +00:00
DBA :: escape ( $fcontact_guid )
2017-11-08 00:37:53 +00:00
);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $r )) {
2017-11-08 00:37:53 +00:00
return $r [ 0 ][ 'url' ];
}
return null ;
}
/**
* @ brief Get a contact id for a given handle
*
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
*/
2017-11-23 19:01:58 +00:00
private static function contactByHandle ( $uid , $handle )
2017-11-08 22:02:50 +00:00
{
2018-07-23 11:43:18 +00:00
$cid = Contact :: getIdForURL ( $handle , $uid );
if ( ! $cid ) {
$handle_parts = explode ( " @ " , $handle );
$nurl_sql = " %%:// " . $handle_parts [ 1 ] . " %%/profile/ " . $handle_parts [ 0 ];
$cid = Contact :: getIdForURL ( $nurl_sql , $uid );
}
2017-11-08 00:37:53 +00:00
2018-07-23 11:43:18 +00:00
if ( ! $cid ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Haven't found a contact for user " . $uid . " and handle " . $handle , Logger :: DEBUG );
2018-07-23 11:43:18 +00:00
return false ;
2017-11-08 00:37:53 +00:00
}
2018-08-23 13:51:58 +00:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $cid ]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $contact )) {
2018-07-23 11:43:18 +00:00
// This here shouldn't happen at all
2018-10-30 13:58:45 +00:00
Logger :: log ( " Haven't found a contact for user " . $uid . " and handle " . $handle , Logger :: DEBUG );
2018-07-23 11:43:18 +00:00
return false ;
2017-11-08 00:37:53 +00:00
}
2018-07-23 11:43:18 +00:00
return $contact ;
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Check if posting is allowed for this contact
*
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 ?
*/
2018-07-19 11:07:14 +00:00
private static function postAllow ( array $importer , array $contact , $is_comment = false )
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))) {
2018-08-23 13:51:58 +00:00
// DBA::update(
2017-12-07 20:10:09 +00:00
// 'contact',
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;
2018-10-29 21:20:46 +00:00
// Logger::log("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
} 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?
2018-07-25 02:53:46 +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 it a post to a community?
2019-01-06 17:37:48 +00:00
} elseif (( $contact [ " rel " ] == Contact :: FOLLOWER ) && in_array ( $importer [ " page-flags " ], [ User :: PAGE_FLAGS_COMMUNITY , User :: PAGE_FLAGS_PRVGROUP ])) {
2017-11-08 00:37:53 +00:00
// That's good
return true ;
2017-11-23 19:01:58 +00:00
// Is the message a global user or a comment?
2017-11-08 00:37:53 +00:00
} elseif (( $importer [ " uid " ] == 0 ) || $is_comment ) {
// Messages for the global users and comments are always accepted
return true ;
}
return false ;
}
/**
* @ brief Fetches the contact id for a handle and checks if posting is allowed
*
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
*
* @ return array The contact data
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 allowedContactByHandle ( array $importer , $handle , $is_comment = false )
2017-11-08 22:02:50 +00:00
{
2017-11-23 19:01:58 +00:00
$contact = self :: contactByHandle ( $importer [ " uid " ], $handle );
2017-11-08 00:37:53 +00:00
if ( ! $contact ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " 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
2018-07-23 11:43:18 +00:00
if ( $is_comment && ( $importer [ " uid " ] != 0 )) {
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 )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " The handle: " . $handle . " is not allowed to post to user " . $importer [ " uid " ]);
2017-11-08 00:37:53 +00:00
return false ;
}
return $contact ;
}
/**
* @ brief Does the message already exists on the system ?
*
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
*/
2017-11-23 19:01:58 +00:00
private static function messageExists ( $uid , $guid )
2017-11-08 22:02:50 +00:00
{
2018-06-17 17:05:17 +00:00
$item = Item :: selectFirst ([ 'id' ], [ 'uid' => $uid , 'guid' => $guid ]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $item )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " message " . $guid . " already exists for user " . $uid );
2018-06-16 22:32:57 +00:00
return $item [ " id " ];
2017-11-08 00:37:53 +00:00
}
return false ;
}
/**
* @ brief Checks for links to posts in a message
*
* @ 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
{
2017-11-08 00:37:53 +00:00
$expression = " =diaspora://.*?/post/([0-9A-Za-z \ -_@.:] { 15,254}[0-9A-Za-z])=ism " ;
2017-11-08 22:02:50 +00:00
preg_replace_callback (
$expression ,
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
},
$item [ " body " ]
);
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
},
$item [ " body " ]
);
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Checks for relative / people /* links in an item body to match local
* 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
*/
2017-11-23 19:01:58 +00:00
public static function replacePeopleGuid ( $body , $author_link )
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'
2017-11-23 19:01:58 +00:00
$handle = self :: urlFromContactGuid ( $match [ 1 ]);
2017-11-08 00:37:53 +00:00
if ( $handle ) {
$return = '@[url=' . $handle . ']' . $match [ 2 ] . '[/url]' ;
} else {
// No local match, restoring absolute remote URL from author scheme and host
$author_url = parse_url ( $author_link );
$return = '[url=' . $author_url [ 'scheme' ] . '://' . $author_url [ 'host' ] . '/people/' . $match [ 1 ] . ']' . $match [ 2 ] . '[/url]' ;
}
return $return ;
2017-11-08 22:02:50 +00:00
},
$body
);
2017-11-08 00:37:53 +00:00
return $return ;
}
/**
2017-11-23 19:01:58 +00:00
* @ brief 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
*/
2017-11-23 19:01:58 +00:00
private static function fetchGuidSub ( $match , $item )
2017-11-08 22:02:50 +00:00
{
2017-11-23 19:01:58 +00:00
if ( ! self :: storeByGuid ( $match [ 1 ], $item [ " author-link " ])) {
self :: storeByGuid ( $match [ 1 ], $item [ " owner-link " ]);
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Fetches an item with a given guid from a given server
*
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
2017-11-08 22:02:50 +00:00
* @ param int $uid The user id of the user
2017-11-08 00:37:53 +00:00
*
* @ return int 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
*/
2017-11-23 19:01:58 +00:00
private static function storeByGuid ( $guid , $server , $uid = 0 )
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
if ( empty ( $serverparts [ " host " ]) || empty ( $serverparts [ " scheme " ])) {
return false ;
}
2017-11-08 00:37:53 +00:00
$server = $serverparts [ " scheme " ] . " :// " . $serverparts [ " host " ];
2018-10-30 13:58:45 +00:00
Logger :: log ( " Trying to fetch item " . $guid . " from " . $server , Logger :: DEBUG );
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
2018-10-30 13:58:45 +00:00
Logger :: log ( " Successfully fetched item " . $guid . " from " . $server , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
// Now call the dispatcher
2017-11-23 19:01:58 +00:00
return self :: dispatchPublic ( $msg );
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Fetches a message from a server
*
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
*/
2017-11-08 22:02:50 +00:00
private static function message ( $guid , $server , $level = 0 )
{
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
$source_url = $server . " /fetch/post/ " . urlencode ( $guid );
2018-10-30 13:58:45 +00:00
Logger :: log ( " Fetch post from " . $source_url , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2018-01-27 16:13:41 +00:00
$envelope = Network :: fetchUrl ( $source_url );
2017-11-08 00:37:53 +00:00
if ( $envelope ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Envelope was fetched. " , Logger :: DEBUG );
2017-11-23 19:01:58 +00:00
$x = self :: verifyMagicEnvelope ( $envelope );
2017-11-08 22:02:50 +00:00
if ( ! $x ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Envelope could not be verified. " , Logger :: DEBUG );
2017-11-08 22:02:50 +00:00
} else {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Envelope was verified. " , Logger :: DEBUG );
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
2018-10-30 13:58:45 +00:00
Logger :: log ( " Message is a reshare " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return self :: message ( $source_xml -> post -> reshare -> root_guid , $server , ++ $level );
} elseif ( $source_xml -> getName () == " reshare " ) {
// Reshare of a reshare - new Diaspora version
2018-10-30 13:58:45 +00:00
Logger :: log ( " Message is a new reshare " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return self :: message ( $source_xml -> root_guid , $server , ++ $level );
}
$author = " " ;
// 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 ;
2017-11-08 22:02:50 +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 ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Message doesn't seem to be a status message " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return false ;
}
2018-01-15 13:05:12 +00:00
$msg = [ " message " => $x , " author " => $author ];
2017-11-08 00:37:53 +00:00
$msg [ " key " ] = self :: key ( $msg [ " author " ]);
return $msg ;
}
/**
* @ brief Fetches the item record of a given guid
*
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
*
* @ return array the item record
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 parentItem ( $uid , $guid , $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' ,
'author-name' , 'author-link' , 'author-avatar' ,
'owner-name' , 'owner-link' , 'owner-avatar' ];
$condition = [ 'uid' => $uid , 'guid' => $guid ];
2018-06-17 17:05:17 +00:00
$item = Item :: selectFirst ( $fields , $condition );
2017-11-08 00:37:53 +00:00
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2018-08-01 06:47:18 +00:00
$person = self :: personByHandle ( $author );
$result = self :: storeByGuid ( $guid , $person [ " url " ], $uid );
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
if ( ! $result && ! empty ( $contact [ " url " ])) {
$result = self :: storeByGuid ( $guid , $contact [ " url " ], $uid );
2017-11-08 00:37:53 +00:00
}
if ( $result ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Fetched missing item " . $guid . " - result: " . $result , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2018-06-17 17:05:17 +00:00
$item = Item :: selectFirst ( $fields , $condition );
2017-11-08 00:37:53 +00:00
}
}
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " parent item not found: parent: " . $guid . " - user: " . $uid );
2017-11-08 00:37:53 +00:00
return false ;
} else {
2018-10-29 21:20:46 +00:00
Logger :: log ( " 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
}
}
/**
* @ brief returns contact details
*
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
*/
2018-02-14 21:18:16 +00:00
private static function authorContactByUrl ( $def_contact , $person , $uid )
2017-11-08 22:02:50 +00:00
{
2018-11-08 16:28:29 +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 )) {
2017-11-08 00:37:53 +00:00
$cid = $contact [ " id " ];
2018-02-14 21:18:16 +00:00
$network = $contact [ " network " ];
} else {
$cid = $def_contact [ " id " ];
2018-08-11 20:40:44 +00:00
$network = Protocol :: DIASPORA ;
2017-11-08 00:37:53 +00:00
}
2018-01-15 13:05:12 +00:00
return [ " cid " => $cid , " network " => $network ];
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Is the profile a hubzilla profile ?
*
* @ param string $url The profile link
*
* @ return bool is it a hubzilla server ?
*/
2017-11-23 19:01:58 +00:00
public static function isRedmatrix ( $url )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
return ( strstr ( $url , " /channel/ " ));
}
/**
* @ brief Generate a post link with a given handle and message guid
*
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
*/
2017-11-08 22:02:50 +00:00
private static function plink ( $addr , $guid , $parent_guid = '' )
{
2018-04-19 06:07:23 +00:00
$contact = Contact :: getDetailsByAddr ( $addr );
2017-11-08 00:37:53 +00:00
// Fallback
2018-04-19 06:07:23 +00:00
if ( ! $contact ) {
2017-11-08 00:37:53 +00:00
if ( $parent_guid != '' ) {
2018-04-19 06:07:23 +00:00
return " https:// " . substr ( $addr , strpos ( $addr , " @ " ) + 1 ) . " /posts/ " . $parent_guid . " # " . $guid ;
2017-11-08 00:37:53 +00:00
} else {
2018-04-19 06:07:23 +00:00
return " https:// " . substr ( $addr , strpos ( $addr , " @ " ) + 1 ) . " /posts/ " . $guid ;
2017-11-08 00:37:53 +00:00
}
}
2018-08-11 20:40:44 +00:00
if ( $contact [ " network " ] == Protocol :: DFRN ) {
2018-04-19 06:07:23 +00:00
return str_replace ( " /profile/ " . $contact [ " nick " ] . " / " , " /display/ " . $guid , $contact [ " url " ] . " / " );
2017-11-08 00:37:53 +00:00
}
2018-04-19 06:07:23 +00:00
if ( self :: isRedmatrix ( $contact [ " url " ])) {
return $contact [ " url " ] . " /?f=&mid= " . $guid ;
2017-11-08 00:37:53 +00:00
}
if ( $parent_guid != '' ) {
2018-04-19 06:07:23 +00:00
return " https:// " . substr ( $addr , strpos ( $addr , " @ " ) + 1 ) . " /posts/ " . $parent_guid . " # " . $guid ;
2017-11-08 00:37:53 +00:00
} else {
2018-04-19 06:07:23 +00:00
return " https:// " . substr ( $addr , strpos ( $addr , " @ " ) + 1 ) . " /posts/ " . $guid ;
2017-11-08 00:37:53 +00:00
}
}
/**
* @ brief Receives account migration
*
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 Success
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-20 05:10:16 +00:00
private static function receiveAccountMigration ( array $importer , $data )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$old_handle = Strings :: escapeTags ( XML :: unescape ( $data -> author ));
$new_handle = Strings :: escapeTags ( XML :: unescape ( $data -> profile -> author ));
$signature = Strings :: escapeTags ( XML :: unescape ( $data -> signature ));
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
$contact = self :: contactByHandle ( $importer [ " uid " ], $old_handle );
2017-11-08 00:37:53 +00:00
if ( ! $contact ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " cannot find contact for sender: " . $old_handle . " and user " . $importer [ " uid " ]);
2017-11-08 00:37:53 +00:00
return false ;
}
2018-10-29 21:20:46 +00:00
Logger :: log ( " Got migration for " . $old_handle . " , to " . $new_handle . " with user " . $importer [ " uid " ]);
2017-11-08 00:37:53 +00:00
// Check signature
$signed_text = 'AccountMigration:' . $old_handle . ':' . $new_handle ;
$key = self :: key ( $old_handle );
2017-12-30 16:51:49 +00:00
if ( ! Crypto :: rsaVerify ( $signed_text , $signature , $key , " sha256 " )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( '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
// change the technical stuff in contact and gcontact
$data = Probe :: uri ( $new_handle );
2018-08-11 20:40:44 +00:00
if ( $data [ 'network' ] == Protocol :: PHANTOM ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Account for ' . $new_handle . " couldn't be probed. " );
2017-11-08 00:37:53 +00:00
return false ;
}
2018-11-08 16:28:29 +00:00
$fields = [ 'url' => $data [ 'url' ], 'nurl' => Strings :: normaliseLink ( $data [ 'url' ]),
2017-11-08 00:37:53 +00:00
'name' => $data [ 'name' ], 'nick' => $data [ 'nick' ],
'addr' => $data [ 'addr' ], 'batch' => $data [ 'batch' ],
'notify' => $data [ 'notify' ], 'poll' => $data [ 'poll' ],
2018-01-15 13:05:12 +00:00
'network' => $data [ 'network' ]];
2017-11-08 00:37:53 +00:00
2018-07-20 12:19:26 +00:00
DBA :: update ( 'contact' , $fields , [ 'addr' => $old_handle ]);
2017-11-08 00:37:53 +00:00
2018-11-08 16:28:29 +00:00
$fields = [ 'url' => $data [ 'url' ], 'nurl' => Strings :: normaliseLink ( $data [ 'url' ]),
2017-11-08 00:37:53 +00:00
'name' => $data [ 'name' ], 'nick' => $data [ 'nick' ],
'addr' => $data [ 'addr' ], 'connect' => $data [ 'addr' ],
'notify' => $data [ 'notify' ], 'photo' => $data [ 'photo' ],
2018-01-15 13:05:12 +00:00
'server_url' => $data [ 'baseurl' ], 'network' => $data [ 'network' ]];
2017-11-08 00:37:53 +00:00
2018-07-20 12:19:26 +00:00
DBA :: update ( 'gcontact' , $fields , [ 'addr' => $old_handle ]);
2017-11-08 00:37:53 +00:00
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Contacts are updated.' );
2017-11-08 00:37:53 +00:00
return true ;
}
/**
* @ brief Processes an account deletion
*
2019-01-06 21:06:53 +00:00
* @ param object $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
*/
2018-04-24 18:34:35 +00:00
private static function receiveAccountDeletion ( $data )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$author = Strings :: escapeTags ( 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 )) {
2018-04-24 18:34:35 +00:00
Contact :: remove ( $contact [ " id " ]);
2017-11-08 00:37:53 +00:00
}
2018-07-20 12:19:26 +00:00
DBA :: delete ( 'gcontact' , [ 'addr' => $author ]);
2018-04-24 18:34:35 +00:00
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Removed contacts for ' . $author );
2018-04-24 18:34:35 +00:00
2017-11-08 00:37:53 +00:00
return true ;
}
/**
* @ brief Fetch the uri from our database if we already have this item ( maybe from ourselves )
*
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
*
* @ return string The constructed uri or the one from our database
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function getUriFromGuid ( $author , $guid , $onlyfound = false )
2017-11-08 22:02:50 +00:00
{
2018-06-17 17:05:17 +00:00
$item = Item :: selectFirst ([ 'uri' ], [ 'guid' => $guid ]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $item )) {
2018-06-16 06:44:19 +00:00
return $item [ " uri " ];
2017-11-08 00:37:53 +00:00
} elseif ( ! $onlyfound ) {
2018-09-15 07:37:34 +00:00
$person = self :: personByHandle ( $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' ]);
$host_url = Network :: unparseURL ( $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
}
return " " ;
}
/**
* @ brief Fetch the guid from our database with a given uri
*
* @ param string $uri Message uri
2017-11-08 22:02:50 +00:00
* @ param string $uid Author handle
2017-11-08 00:37:53 +00:00
*
* @ return string The post guid
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function getGuidFromUri ( $uri , $uid )
2017-11-08 22:02:50 +00:00
{
2018-06-17 17:05:17 +00:00
$item = Item :: selectFirst ([ 'guid' ], [ 'uri' => $uri , 'uid' => $uid ]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $item )) {
2018-06-16 22:32:57 +00:00
return $item [ " guid " ];
2017-11-08 00:37:53 +00:00
} else {
return false ;
}
}
/**
* @ brief Find the best importer for a comment , like , ...
*
* @ param string $guid The guid of the item
*
* @ return array | boolean the origin owner of that post - or false
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
private static function importerForGuid ( $guid )
2017-11-08 22:02:50 +00:00
{
2018-06-17 17:05:17 +00:00
$item = Item :: selectFirst ([ 'uid' ], [ 'origin' => true , 'guid' => $guid ]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $item )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Found user " . $item [ 'uid' ] . " as owner of item " . $guid , Logger :: DEBUG );
2018-07-20 12:19:26 +00:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'self' => true , 'uid' => $item [ 'uid' ]]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $contact )) {
2017-11-08 00:37:53 +00:00
return $contact ;
}
}
return false ;
}
/**
* @ brief Processes an incoming comment
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param string $sender The sender of the message
* @ param object $data The message object
* @ param string $xml The original XML of the message
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
*/
2018-07-20 05:10:16 +00:00
private static function receiveComment ( array $importer , $sender , $data , $xml )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$author = Strings :: escapeTags ( XML :: unescape ( $data -> author ));
$guid = Strings :: escapeTags ( XML :: unescape ( $data -> guid ));
$parent_guid = Strings :: escapeTags ( 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 )) {
2018-11-09 18:29:42 +00:00
$created_at = DateTimeFormat :: utc ( Strings :: escapeTags ( 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 )) {
2018-11-09 18:29:42 +00:00
$thread_parent_guid = Strings :: escapeTags ( XML :: unescape ( $data -> thread_parent_guid ));
2017-11-23 19:01:58 +00:00
$thr_uri = self :: getUriFromGuid ( " " , $thread_parent_guid , true );
2017-11-08 00:37:53 +00:00
} else {
$thr_uri = " " ;
}
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 ;
}
2017-11-23 19:01:58 +00:00
$message_id = self :: messageExists ( $importer [ " uid " ], $guid );
2017-11-08 00:37:53 +00:00
if ( $message_id ) {
return true ;
}
2017-11-23 19:01:58 +00:00
$parent_item = self :: parentItem ( $importer [ " uid " ], $parent_guid , $author , $contact );
2017-11-08 00:37:53 +00:00
if ( ! $parent_item ) {
return false ;
}
2017-11-23 19:01:58 +00:00
$person = self :: personByHandle ( $author );
2017-11-08 00:37:53 +00:00
if ( ! is_array ( $person )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " unable to find author details " );
2017-11-08 00:37:53 +00:00
return false ;
}
// Fetch the contact id - if we know this contact
2017-11-23 19:01:58 +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
$datarray [ " uid " ] = $importer [ " uid " ];
$datarray [ " contact-id " ] = $author_contact [ " cid " ];
$datarray [ " network " ] = $author_contact [ " network " ];
$datarray [ " author-link " ] = $person [ " url " ];
2018-06-16 22:32:57 +00:00
$datarray [ " author-id " ] = Contact :: getIdForURL ( $person [ " url " ], 0 );
2017-11-08 00:37:53 +00:00
$datarray [ " owner-link " ] = $contact [ " url " ];
2018-06-16 22:32:57 +00:00
$datarray [ " owner-id " ] = Contact :: getIdForURL ( $contact [ " url " ], 0 );
2017-11-08 00:37:53 +00:00
$datarray [ " guid " ] = $guid ;
2017-11-23 19:01:58 +00:00
$datarray [ " uri " ] = self :: getUriFromGuid ( $author , $guid );
2017-11-08 00:37:53 +00:00
$datarray [ " verb " ] = ACTIVITY_POST ;
$datarray [ " gravity " ] = GRAVITY_COMMENT ;
if ( $thr_uri != " " ) {
$datarray [ " parent-uri " ] = $thr_uri ;
} else {
$datarray [ " parent-uri " ] = $parent_item [ " uri " ];
}
$datarray [ " object-type " ] = ACTIVITY_OBJ_COMMENT ;
2018-08-05 10:23:57 +00:00
$datarray [ " protocol " ] = Conversation :: PARCEL_DIASPORA ;
2017-11-08 00:37:53 +00:00
$datarray [ " source " ] = $xml ;
$datarray [ " changed " ] = $datarray [ " created " ] = $datarray [ " edited " ] = $created_at ;
$datarray [ " plink " ] = self :: plink ( $author , $guid , $parent_item [ 'guid' ]);
2018-03-04 22:39:41 +00:00
$body = Markdown :: toBBCode ( $text );
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
$datarray [ " body " ] = self :: replacePeopleGuid ( $body , $person [ " url " ]);
2017-11-08 00:37: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.
if ( $parent_item [ " origin " ]) {
$datarray [ 'diaspora_signed_text' ] = json_encode ( $data );
}
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 ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Stored comment " . $datarray [ " guid " ] . " with message id " . $message_id , Logger :: DEBUG );
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 ;
}
/**
* @ brief processes and stores private messages
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param array $contact The contact of the message
* @ param object $data The message object
* @ 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
2017-11-08 00:37:53 +00:00
*/
2018-07-20 05:10:16 +00:00
private static function receiveConversationMessage ( array $importer , array $contact , $data , $msg , $mesg , $conversation )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$author = Strings :: escapeTags ( XML :: unescape ( $data -> author ));
$guid = Strings :: escapeTags ( XML :: unescape ( $data -> guid ));
$subject = Strings :: escapeTags ( 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 ) {
2018-11-09 18:29:42 +00:00
$msg_author = Strings :: escapeTags ( XML :: unescape ( $mesg -> author ));
2017-11-08 00:37:53 +00:00
} elseif ( $mesg -> diaspora_handle ) {
2018-11-09 18:29:42 +00:00
$msg_author = Strings :: escapeTags ( XML :: unescape ( $mesg -> diaspora_handle ));
2017-11-08 00:37:53 +00:00
} else {
return false ;
}
2018-11-09 18:29:42 +00:00
$msg_guid = Strings :: escapeTags ( XML :: unescape ( $mesg -> guid ));
$msg_conversation_guid = Strings :: escapeTags ( XML :: unescape ( $mesg -> conversation_guid ));
2018-11-05 12:40:18 +00:00
$msg_text = XML :: unescape ( $mesg -> text );
2018-11-09 18:29:42 +00:00
$msg_created_at = DateTimeFormat :: utc ( Strings :: escapeTags ( XML :: unescape ( $mesg -> created_at )));
2017-11-08 00:37:53 +00:00
if ( $msg_conversation_guid != $guid ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " message conversation guid does not belong to the current conversation. " );
2017-11-08 00:37:53 +00:00
return false ;
}
2018-03-04 22:39:41 +00:00
$body = Markdown :: toBBCode ( $msg_text );
2017-11-08 00:37:53 +00:00
$message_uri = $msg_author . " : " . $msg_guid ;
2017-11-23 19:01:58 +00:00
$person = self :: personByHandle ( $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
}
/**
* @ brief Processes new private messages ( answers to private messages are processed elsewhere )
*
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
* @ param object $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
*/
2018-07-20 05:10:16 +00:00
private static function receiveConversation ( array $importer , $msg , $data )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$author = Strings :: escapeTags ( XML :: unescape ( $data -> author ));
$guid = Strings :: escapeTags ( XML :: unescape ( $data -> guid ));
$subject = Strings :: escapeTags ( XML :: unescape ( $data -> subject ));
$created_at = DateTimeFormat :: utc ( Strings :: escapeTags ( XML :: unescape ( $data -> created_at )));
$participants = Strings :: escapeTags ( XML :: unescape ( $data -> participants ));
2017-11-08 00:37:53 +00:00
$messages = $data -> message ;
if ( ! count ( $messages )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " empty conversation " );
2017-11-08 00:37:53 +00:00
return false ;
}
2017-11-23 19:01:58 +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
2018-08-19 12:46:11 +00:00
$conversation = DBA :: selectFirst ( 'conv' , [], [ 'uid' => $importer [ " uid " ], 'guid' => $guid ]);
if ( ! DBA :: isResult ( $conversation )) {
2017-11-08 22:02:50 +00:00
$r = q (
" INSERT INTO `conv` (`uid`, `guid`, `creator`, `created`, `updated`, `subject`, `recips`)
2017-11-08 00:37:53 +00:00
VALUES ( % d , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' ) " ,
intval ( $importer [ " uid " ]),
2018-07-21 13:10:13 +00:00
DBA :: escape ( $guid ),
DBA :: escape ( $author ),
DBA :: escape ( $created_at ),
DBA :: escape ( DateTimeFormat :: utcNow ()),
DBA :: escape ( $subject ),
DBA :: escape ( $participants )
2017-11-08 00:37:53 +00:00
);
2017-11-08 22:02:50 +00:00
if ( $r ) {
2018-08-19 12:46:11 +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 ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " 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 ;
}
/**
* @ brief Processes " like " messages
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param string $sender The sender of the message
* @ param object $data The message object
2017-11-08 00:37:53 +00:00
*
* @ return int The message id of the generated like 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
*/
2018-07-20 05:10:16 +00:00
private static function receiveLike ( array $importer , $sender , $data )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$author = Strings :: escapeTags ( XML :: unescape ( $data -> author ));
$guid = Strings :: escapeTags ( XML :: unescape ( $data -> guid ));
$parent_guid = Strings :: escapeTags ( XML :: unescape ( $data -> parent_guid ));
$parent_type = Strings :: escapeTags ( XML :: unescape ( $data -> parent_type ));
$positive = Strings :: escapeTags ( 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.
2018-01-15 13:05:12 +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
2017-11-23 19:01:58 +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
2017-11-23 19:01:58 +00:00
$parent_item = self :: parentItem ( $importer [ " uid " ], $parent_guid , $author , $contact );
2017-11-08 22:02:50 +00:00
if ( ! $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
2017-11-23 19:01:58 +00:00
$person = self :: personByHandle ( $author );
2017-11-08 00:37:53 +00:00
if ( ! is_array ( $person )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " unable to find author details " );
2017-11-08 00:37:53 +00:00
return false ;
}
// Fetch the contact id - if we know this contact
2017-11-23 19:01:58 +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.
2017-11-08 22:02:50 +00:00
if ( $positive == " true " ) {
2017-11-08 00:37:53 +00:00
$verb = ACTIVITY_LIKE ;
2017-11-08 22:02:50 +00:00
} else {
2017-11-08 00:37:53 +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
2018-08-05 10:23:57 +00:00
$datarray [ " protocol " ] = Conversation :: PARCEL_DIASPORA ;
2017-11-08 00:37:53 +00:00
$datarray [ " uid " ] = $importer [ " uid " ];
$datarray [ " contact-id " ] = $author_contact [ " cid " ];
$datarray [ " network " ] = $author_contact [ " network " ];
2018-10-06 08:51:52 +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
$datarray [ " guid " ] = $guid ;
2017-11-23 19:01:58 +00:00
$datarray [ " uri " ] = self :: getUriFromGuid ( $author , $guid );
2017-11-08 00:37:53 +00:00
$datarray [ " verb " ] = $verb ;
2018-06-27 18:09:33 +00:00
$datarray [ " gravity " ] = GRAVITY_ACTIVITY ;
2017-11-08 00:37:53 +00:00
$datarray [ " parent-uri " ] = $parent_item [ " uri " ];
$datarray [ " object-type " ] = ACTIVITY_OBJ_NOTE ;
2018-07-06 05:39:25 +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
$datarray [ " changed " ] = $datarray [ " created " ] = $datarray [ " edited " ] = DateTimeFormat :: utcNow ();
2018-05-15 04:33:28 +00:00
// like on comments have the comment as parent. So we need to fetch the toplevel parent
if ( $parent_item [ " id " ] != $parent_item [ " parent " ]) {
2018-06-17 17:05:17 +00:00
$toplevel = Item :: selectFirst ([ 'origin' ], [ 'id' => $parent_item [ " parent " ]]);
2018-05-15 04:33:28 +00:00
$origin = $toplevel [ " origin " ];
} else {
$origin = $parent_item [ " origin " ];
}
// 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 );
}
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 ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Stored like " . $datarray [ " guid " ] . " with message id " . $message_id , Logger :: DEBUG );
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 ;
}
/**
* @ brief Processes private messages
*
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 Success ?
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2018-07-20 05:10:16 +00:00
private static function receiveMessage ( array $importer , $data )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$author = Strings :: escapeTags ( XML :: unescape ( $data -> author ));
$guid = Strings :: escapeTags ( XML :: unescape ( $data -> guid ));
$conversation_guid = Strings :: escapeTags ( XML :: unescape ( $data -> conversation_guid ));
2018-11-05 12:40:18 +00:00
$text = XML :: unescape ( $data -> text );
2018-11-09 18:29:42 +00:00
$created_at = DateTimeFormat :: utc ( Strings :: escapeTags ( 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 ;
}
$conversation = null ;
2018-08-19 12:46:11 +00:00
$condition = [ 'uid' => $importer [ " uid " ], 'guid' => $conversation_guid ];
$conversation = DBA :: selectFirst ( 'conv' , [], $condition );
if ( ! DBA :: isResult ( $conversation )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " conversation not available. " );
2017-11-08 00:37:53 +00:00
return false ;
}
$message_uri = $author . " : " . $guid ;
2017-11-23 19:01:58 +00:00
$person = self :: personByHandle ( $author );
2017-11-08 00:37:53 +00:00
if ( ! $person ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " 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
2017-11-23 19:01:58 +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 ,
'parent-uri' => $author . " : " . $conversation [ 'guid' ],
'created' => $created_at
]);
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Processes participations - unsupported by now
*
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
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-20 05:10:16 +00:00
private static function receiveParticipation ( array $importer , $data )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$author = strtolower ( Strings :: escapeTags ( XML :: unescape ( $data -> author )));
$parent_guid = Strings :: escapeTags ( XML :: unescape ( $data -> parent_guid ));
2018-01-12 20:52:43 +00:00
$contact_id = Contact :: getIdForURL ( $author );
if ( ! $contact_id ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Contact not found: ' . $author );
2018-01-14 22:53:00 +00:00
return false ;
}
$person = self :: personByHandle ( $author );
if ( ! is_array ( $person )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " Person not found: " . $author );
2018-01-12 20:52:43 +00:00
return false ;
}
2018-06-17 17:05:17 +00:00
$item = Item :: selectFirst ([ 'id' ], [ 'guid' => $parent_guid , 'origin' => true , 'private' => false ]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Item not found, no origin or private: ' . $parent_guid );
2018-01-12 20:52:43 +00:00
return false ;
}
$author_parts = explode ( '@' , $author );
if ( isset ( $author_parts [ 1 ])) {
$server = $author_parts [ 1 ];
} else {
// Should never happen
$server = $author ;
}
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Received participation for ID: ' . $item [ 'id' ] . ' - Contact: ' . $contact_id . ' - Server: ' . $server , Logger :: DEBUG );
2018-03-08 21:04:11 +00:00
2018-07-20 12:19:26 +00:00
if ( ! DBA :: exists ( 'participation' , [ 'iid' => $item [ 'id' ], 'server' => $server ])) {
DBA :: insert ( 'participation' , [ 'iid' => $item [ 'id' ], 'cid' => $contact_id , 'fid' => $person [ 'id' ], 'server' => $server ]);
2018-03-08 21:04:11 +00:00
}
2018-01-14 22:53:00 +00:00
// Send all existing comments and likes to the requesting server
2018-07-08 09:37:05 +00:00
$comments = Item :: select ([ 'id' , 'parent' , 'verb' , 'self' ], [ 'parent' => $item [ 'id' ]]);
2018-06-21 15:14:01 +00:00
while ( $comment = Item :: fetch ( $comments )) {
2018-06-16 22:32:57 +00:00
if ( $comment [ 'id' ] == $comment [ 'parent' ]) {
continue ;
}
2018-01-14 22:53:00 +00:00
if ( $comment [ 'verb' ] == ACTIVITY_POST ) {
2019-06-06 04:26:02 +00:00
$cmd = $comment [ 'self' ] ? Delivery :: COMMENT : 'comment-import' ;
2018-01-14 22:53:00 +00:00
} else {
2019-06-06 04:26:02 +00:00
$cmd = $comment [ 'self' ] ? Delivery :: ACTIVITY : 'activity-import' ;
2018-01-14 22:53:00 +00:00
}
2018-10-30 13:58:45 +00:00
Logger :: log ( " Send " . $cmd . " for item " . $comment [ 'id' ] . " to contact " . $contact_id , Logger :: DEBUG );
2018-01-14 22:53:00 +00:00
Worker :: add ( PRIORITY_HIGH , 'Delivery' , $cmd , $comment [ 'id' ], $contact_id );
}
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 ;
}
/**
* @ brief Processes photos - unneeded
*
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 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 ;
}
/**
* @ brief Processes poll participations - unssupported
*
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 ;
}
/**
* @ brief Processes incoming profile updates
*
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 Success
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-20 05:10:16 +00:00
private static function receiveProfile ( array $importer , $data )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$author = strtolower ( Strings :: escapeTags ( XML :: unescape ( $data -> author )));
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +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
2018-11-05 12:40:18 +00:00
$name = XML :: unescape ( $data -> first_name ) . (( strlen ( $data -> last_name )) ? " " . XML :: unescape ( $data -> last_name ) : " " );
$image_url = XML :: unescape ( $data -> image_url );
$birthday = XML :: unescape ( $data -> birthday );
$gender = XML :: unescape ( $data -> gender );
$about = Markdown :: toBBCode ( XML :: unescape ( $data -> bio ));
$location = Markdown :: toBBCode ( XML :: unescape ( $data -> location ));
$searchable = ( XML :: unescape ( $data -> searchable ) == " true " );
$nsfw = ( XML :: unescape ( $data -> nsfw ) == " true " );
$tags = XML :: unescape ( $data -> tag_string );
2017-11-08 00:37:53 +00:00
$tags = explode ( " # " , $tags );
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 ));
2017-11-08 22:02:50 +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
}
$keywords = implode ( " , " , $keywords );
$handle_parts = explode ( " @ " , $author );
$nick = $handle_parts [ 0 ];
2017-11-08 22:02:50 +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
2017-11-08 22:02:50 +00:00
if ( preg_match ( " |^https?://| " , $image_url ) === 0 ) {
2017-11-08 00:37:53 +00:00
$image_url = " http:// " . $handle_parts [ 1 ] . $image_url ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2017-11-29 22:29:11 +00:00
Contact :: updateAvatar ( $image_url , $importer [ " uid " ], $contact [ " id " ]);
2017-11-08 00:37:53 +00:00
// Generic birthday. We don't know the timezone. The year is irrelevant.
$birthday = str_replace ( " 1000 " , " 1901 " , $birthday );
2017-11-08 22:02:50 +00:00
if ( $birthday != " " ) {
2018-01-27 02:38:34 +00:00
$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
2017-11-08 22:02:50 +00:00
if ( substr ( $birthday , 5 ) === substr ( $contact [ " bd " ], 5 )) {
2017-11-08 00:37:53 +00:00
$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 ,
'name-date' => DateTimeFormat :: utcNow (),
'about' => $about , 'gender' => $gender ,
'addr' => $author , 'nick' => $nick ,
'keywords' => $keywords ];
if ( ! empty ( $birthday )) {
$fields [ 'bd' ] = $birthday ;
}
2018-07-20 12:19:26 +00:00
DBA :: update ( 'contact' , $fields , [ 'id' => $contact [ 'id' ]]);
2017-11-08 00:37:53 +00:00
2018-08-11 20:40:44 +00:00
$gcontact = [ " url " => $contact [ " url " ], " network " => Protocol :: DIASPORA , " generation " => 2 ,
2017-11-08 00:37:53 +00:00
" photo " => $image_url , " name " => $name , " location " => $location ,
" about " => $about , " birthday " => $birthday , " gender " => $gender ,
" addr " => $author , " nick " => $nick , " keywords " => $keywords ,
2018-01-15 13:05:12 +00:00
" hide " => ! $searchable , " nsfw " => $nsfw ];
2017-11-08 00:37:53 +00:00
2017-12-07 14:09:28 +00:00
$gcid = GContact :: update ( $gcontact );
2017-11-08 00:37:53 +00:00
2017-12-07 14:09:28 +00:00
GContact :: link ( $gcid , $importer [ " uid " ], $contact [ " id " ]);
2017-11-08 00:37:53 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( " Profile of contact " . $contact [ " id " ] . " stored for user " . $importer [ " uid " ], Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return true ;
}
/**
* @ brief Processes incoming friend requests
*
* @ 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
{
2018-07-25 02:53:46 +00:00
if ( $contact [ " rel " ] == Contact :: SHARING ) {
2018-07-20 12:19:26 +00:00
DBA :: update (
2017-11-08 22:02:50 +00:00
'contact' ,
2018-07-25 02:53:46 +00:00
[ 'rel' => Contact :: FRIEND , 'writable' => true ],
2018-01-15 13:05:12 +00:00
[ 'id' => $contact [ " id " ], 'uid' => $importer [ " uid " ]]
2017-11-08 22:02:50 +00:00
);
2017-11-08 00:37:53 +00:00
}
}
/**
* @ brief Processes incoming sharing notification
*
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 Success
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2018-07-20 05:10:16 +00:00
private static function receiveContactRequest ( array $importer , $data )
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 )) {
2018-11-05 12:40:18 +00:00
$following = ( XML :: unescape ( $data -> following ) == " true " );
2017-11-08 00:37:53 +00:00
} else {
$following = true ;
}
if ( isset ( $data -> sharing )) {
2018-11-05 12:40:18 +00:00
$sharing = ( XML :: unescape ( $data -> sharing ) == " true " );
2017-11-08 00:37:53 +00:00
} else {
$sharing = true ;
}
2017-11-23 19:01:58 +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 ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Author " . $author . " (Contact " . $contact [ " id " ] . " ) wants to follow us. " , Logger :: DEBUG );
2017-11-23 19:01:58 +00:00
self :: receiveRequestMakeFriend ( $importer , $contact );
2017-11-08 00:37:53 +00:00
// refetch the contact array
2017-11-23 19:01:58 +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.
2018-07-25 02:53:46 +00:00
if ( in_array ( $contact [ " rel " ], [ Contact :: FRIEND ])) {
2018-08-19 12:46:11 +00:00
$user = DBA :: selectFirst ( 'user' , [], [ 'uid' => $importer [ " uid " ]]);
if ( DBA :: isResult ( $user )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Sending share message to author " . $author . " - Contact: " . $contact [ " id " ] . " - User: " . $importer [ " uid " ], Logger :: DEBUG );
2019-01-07 17:09:10 +00:00
self :: sendShare ( $user , $contact );
2017-11-08 00:37:53 +00:00
}
}
return true ;
} else {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Author " . $author . " doesn't want to follow us anymore. " , Logger :: DEBUG );
2018-01-28 17:26:39 +00:00
Contact :: removeFollower ( $importer , $contact );
2017-11-08 00:37:53 +00:00
return true ;
}
}
2019-01-06 17:37:48 +00:00
if ( ! $following && $sharing && in_array ( $importer [ " page-flags " ], [ User :: PAGE_FLAGS_SOAPBOX , User :: PAGE_FLAGS_NORMAL ])) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Author " . $author . " wants to share with us - but doesn't want to listen. Request is ignored. " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return false ;
} elseif ( ! $following && ! $sharing ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Author " . $author . " doesn't want anything - and we don't know the author. Request is ignored. " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return false ;
} elseif ( ! $following && $sharing ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Author " . $author . " wants to share with us. " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
} elseif ( $following && $sharing ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Author " . $author . " wants to have a bidirectional conection. " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
} elseif ( $following && ! $sharing ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Author " . $author . " wants to listen to us. " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
}
2017-11-23 19:01:58 +00:00
$ret = self :: personByHandle ( $author );
2017-11-08 00:37:53 +00:00
2018-08-11 20:40:44 +00:00
if ( ! $ret || ( $ret [ " network " ] != Protocol :: DIASPORA )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " Cannot resolve diaspora handle " . $author . " for " . $recipient );
2017-11-08 00:37:53 +00:00
return false ;
}
$batch = (( $ret [ " batch " ]) ? $ret [ " batch " ] : implode ( " / " , array_slice ( explode ( " / " , $ret [ " url " ]), 0 , 3 )) . " /receive/public " );
2019-01-07 17:09:10 +00:00
q (
2017-11-08 22:02:50 +00:00
" INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`)
2017-11-08 00:37:53 +00:00
VALUES ( % d , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , % d , % d ) " ,
intval ( $importer [ " uid " ]),
2018-07-21 13:10:13 +00:00
DBA :: escape ( $ret [ " network " ]),
DBA :: escape ( $ret [ " addr " ]),
2018-01-27 02:38:34 +00:00
DateTimeFormat :: utcNow (),
2018-07-21 13:10:13 +00:00
DBA :: escape ( $ret [ " url " ]),
2018-11-08 16:28:29 +00:00
DBA :: escape ( Strings :: normaliseLink ( $ret [ " url " ])),
2018-07-21 13:10:13 +00:00
DBA :: escape ( $batch ),
DBA :: escape ( $ret [ " name " ]),
DBA :: escape ( $ret [ " nick " ]),
DBA :: escape ( $ret [ " photo " ]),
DBA :: escape ( $ret [ " pubkey " ]),
DBA :: escape ( $ret [ " notify " ]),
DBA :: escape ( $ret [ " poll " ]),
2017-11-08 00:37:53 +00:00
1 ,
2
);
// find the contact record we just created
2017-11-23 19:01:58 +00:00
$contact_record = self :: contactByHandle ( $importer [ " uid " ], $author );
2017-11-08 00:37:53 +00:00
if ( ! $contact_record ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " unable to locate newly created contact record. " );
2017-11-08 00:37:53 +00:00
return ;
}
2018-10-30 13:58:45 +00:00
Logger :: log ( " Author " . $author . " was added as contact number " . $contact_record [ " id " ] . " . " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2017-12-09 18:45:17 +00:00
Group :: addMember ( User :: getDefaultGroup ( $importer [ 'uid' ], $ret [ " network " ]), $contact_record [ 'id' ]);
2017-11-08 00:37:53 +00:00
2017-11-29 22:29:11 +00:00
Contact :: updateAvatar ( $ret [ " photo " ], $importer [ 'uid' ], $contact_record [ " id " ], true );
2017-11-08 00:37:53 +00:00
2019-01-06 17:37:48 +00:00
if ( in_array ( $importer [ " page-flags " ], [ User :: PAGE_FLAGS_NORMAL , User :: PAGE_FLAGS_PRVGROUP ])) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Sending intra message for author " . $author . " . " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2018-11-08 13:45:46 +00:00
$hash = Strings :: getRandomHex () . ( string ) time (); // Generate a confirm_key
2017-11-08 00:37:53 +00:00
2019-01-07 17:09:10 +00:00
q (
2017-11-08 22:02:50 +00:00
" INSERT INTO `intro` (`uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime`)
2017-11-08 00:37:53 +00:00
VALUES ( % d , % d , % d , % d , '%s' , '%s' , '%s' ) " ,
intval ( $importer [ " uid " ]),
intval ( $contact_record [ " id " ]),
0 ,
0 ,
2018-07-21 13:10:13 +00:00
DBA :: escape ( L10n :: t ( " Sharing notification from Diaspora network " )),
DBA :: escape ( $hash ),
DBA :: escape ( DateTimeFormat :: utcNow ())
2017-11-08 00:37:53 +00:00
);
} else {
// automatic friend approval
2018-10-30 13:58:45 +00:00
Logger :: log ( " Does an automatic friend approval for author " . $author . " . " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2017-11-29 22:29:11 +00:00
Contact :: updateAvatar ( $contact_record [ " photo " ], $importer [ " uid " ], $contact_record [ " id " ]);
2017-11-08 00:37:53 +00:00
Cleanups: isResult() more used, readability improved (#5608)
* [diaspora]: Maybe SimpleXMLElement is the right type-hint?
* Changes proposed + pre-renaming:
- pre-renamed $db -> $connection
- added TODOs for not allowing bad method invocations (there is a
BadMethodCallException in SPL)
* If no record is found, below $r[0] will fail with a E_NOTICE and the code
doesn't behave as expected.
* Ops, one more left ...
* Continued:
- added documentation for Contact::updateSslPolicy() method
- added type-hint for $contact of same method
- empty lines added + TODO where the bug origins that $item has no element 'body'
* Added empty lines for better readability
* Cleaned up:
- no more x() (deprecated) usage but empty() instead
- fixed mixing of space/tab indending
- merged else/if block goether in elseif() (lesser nested code blocks)
* Re-fixed DBM -> DBA switch
* Fixes/rewrites:
- use empty()/isset() instead of deprecated x()
- merged 2 nested if() blocks into one
- avoided nested if() block inside else block by rewriting it to elseif()
- $contact_id is an integer, let's test on > 0 here
- added a lot spaces and some empty lines for better readability
* Rewrite:
- moved all CONTACT_* constants from boot.php to Contact class
* CR request:
- renamed Contact::CONTACT_IS_* -> Contact::* ;-)
* Rewrites:
- moved PAGE_* to Friendica\Model\Profile class
- fixed mixure with "Contact::* rewrite"
* Ops, one still there (return is no function)
* Rewrite to Proxy class:
- introduced new Friendica\Network\Proxy class for in exchange of proxy_*()
functions
- moved also all PROXY_* constants there as Proxy::*
- removed now no longer needed mod/proxy.php loading as composer's auto-load
will do this for us
- renamed those proxy_*() functions to better names:
+ proxy_init() -> Proxy::init() (public)
+ proxy_url() -> Proxy::proxifyUrl() (public)
+ proxy_parse_html() -> Proxy::proxifyHtml() (public)
+ proxy_is_local_image() -> Proxy::isLocalImage() (private)
+ proxy_parse_query() -> Proxy::parseQuery() (private)
+ proxy_img_cb() -> Proxy::replaceUrl() (private)
* CR request:
- moved all PAGE_* constants to Friendica\Model\Contact class
- fixed all references of both classes
* Ops, need to set $a here ...
* CR request:
- moved Proxy class to Friendica\Module
- extended BaseModule
* Ops, no need for own instance of $a when self::getApp() is around.
* Proxy-rewrite:
- proxy_url() and proxy_parse_html() are both non-module functions (now
methods)
- so they must be splitted into a seperate class
- also the SIZE_* and DEFAULT_TIME constants are both not relevant to module
* No instances from utility classes
* Fixed error:
- proxify*() is now located in `Friendica\Util\ProxyUtils`
* Moved back to original place, ops? How did they move here? Well, it was not
intended by me.
* Removed duplicate (left-over from split) constants and static array. Thank to
MrPetovan finding it.
* Renamed ProxyUtils -> Proxy and aliased it back to ProxyUtils.
* Rewrite:
- stopped using deprecated NETWORK_* constants, now Protocol::* should be used
- still left them intact for slow/lazy developers ...
* Ops, was added accidentally ...
* Ops, why these wrong moves?
* Ops, one to much (thanks to MrPetovan)
* Ops, wrong moving ...
* moved back to original place ...
* spaces added
* empty lines add for better readability.
* convertered spaces -> tab for code indenting.
* CR request: Add space between if and brace.
* CR requests fixed + move reverted
- ops, src/Module/*.php has been moved to src/Network/ accidentally
- reverted some parts in src/Database/DBA.php as pointed out by Annando
- removed internal TODO items
- added some spaces for better readability
2018-08-24 05:05:49 +00:00
/*
* technically they are sharing with us ( Contact :: SHARING ),
* but if our page - type is Profile :: PAGE_COMMUNITY or Profile :: PAGE_SOAPBOX
* we are going to change the relationship and make them a follower .
*/
2019-01-06 17:37:48 +00:00
if (( $importer [ " page-flags " ] == User :: PAGE_FLAGS_FREELOVE ) && $sharing && $following ) {
2018-07-25 02:53:46 +00:00
$new_relation = Contact :: FRIEND ;
2019-01-06 17:37:48 +00:00
} elseif (( $importer [ " page-flags " ] == User :: PAGE_FLAGS_FREELOVE ) && $sharing ) {
2018-07-25 02:53:46 +00:00
$new_relation = Contact :: SHARING ;
2017-11-08 22:02:50 +00:00
} else {
2018-07-25 02:53:46 +00:00
$new_relation = Contact :: FOLLOWER ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2019-01-07 17:09:10 +00:00
q (
2017-11-08 22:02:50 +00:00
" UPDATE `contact` SET `rel` = %d,
2017-11-08 00:37:53 +00:00
`name-date` = '%s' ,
`uri-date` = '%s' ,
`blocked` = 0 ,
`pending` = 0 ,
`writable` = 1
WHERE `id` = % d
" ,
intval ( $new_relation ),
2018-07-21 13:10:13 +00:00
DBA :: escape ( DateTimeFormat :: utcNow ()),
DBA :: escape ( DateTimeFormat :: utcNow ()),
2017-11-08 00:37:53 +00:00
intval ( $contact_record [ " id " ])
);
2018-08-19 12:46:11 +00:00
$user = DBA :: selectFirst ( 'user' , [], [ 'uid' => $importer [ " uid " ]]);
if ( DBA :: isResult ( $user )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Sending share message (Relation: " . $new_relation . " ) to author " . $author . " - Contact: " . $contact_record [ " id " ] . " - User: " . $importer [ " uid " ], Logger :: DEBUG );
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
2018-01-15 13:05:12 +00:00
self :: sendProfile ( $importer [ " uid " ], [ $contact_record ]);
2017-11-08 00:37:53 +00:00
}
}
return true ;
}
/**
* @ brief Fetches a message with a given guid
*
2017-11-08 22:02:50 +00:00
* @ param string $guid message guid
2017-11-08 00:37:53 +00:00
* @ param string $orig_author handle of the original post
* @ return array The fetched item
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-11-08 00:37:53 +00:00
*/
2018-03-08 19:47:18 +00:00
public static function originalItem ( $guid , $orig_author )
2017-11-08 22:02:50 +00:00
{
2018-04-27 14:03:10 +00:00
if ( empty ( $guid )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Empty guid. Quitting.' );
2018-04-27 14:03:10 +00:00
return false ;
}
2017-11-08 00:37:53 +00:00
// Do we already have this item?
2018-04-18 04:59:33 +00:00
$fields = [ 'body' , 'tag' , 'app' , 'created' , 'object-type' , 'uri' , 'guid' ,
'author-name' , 'author-link' , 'author-avatar' ];
2018-04-27 14:03:10 +00:00
$condition = [ 'guid' => $guid , 'visible' => true , 'deleted' => false , 'private' => false ];
2018-06-17 17:05:17 +00:00
$item = Item :: selectFirst ( $fields , $condition );
2017-11-08 00:37:53 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $item )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " reshared message " . $guid . " already exists on system. " );
2017-11-08 00:37:53 +00:00
// Maybe it is already a reshared item?
// Then refetch the content, if it is a reshare from a reshare.
// If it is a reshared post from another network then reformat to avoid display problems with two share elements
2018-04-18 04:59:33 +00:00
if ( self :: isReshare ( $item [ " body " ], true )) {
2018-04-27 14:03:10 +00:00
$item = [];
2018-04-18 04:59:33 +00:00
} elseif ( self :: isReshare ( $item [ " body " ], false ) || strstr ( $item [ " body " ], " [share " )) {
$item [ " body " ] = Markdown :: toBBCode ( BBCode :: toMarkdown ( $item [ " body " ]));
2017-11-08 00:37:53 +00:00
2018-04-18 04:59:33 +00:00
$item [ " body " ] = self :: replacePeopleGuid ( $item [ " body " ], $item [ " author-link " ]);
2017-11-08 00:37:53 +00:00
// Add OEmbed and other information to the body
2018-04-18 04:59:33 +00:00
$item [ " body " ] = add_page_info_to_body ( $item [ " body " ], false , true );
2017-11-08 00:37:53 +00:00
2018-04-18 04:59:33 +00:00
return $item ;
2017-11-08 00:37:53 +00:00
} else {
2018-04-18 04:59:33 +00:00
return $item ;
2017-11-08 00:37:53 +00:00
}
}
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2018-04-27 14:03:10 +00:00
if ( empty ( $orig_author )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Empty author for guid ' . $guid . '. Quitting.' );
2018-04-27 14:03:10 +00:00
return false ;
}
2017-11-08 00:37:53 +00:00
$server = " https:// " . substr ( $orig_author , strpos ( $orig_author , " @ " ) + 1 );
2018-10-29 21:20:46 +00:00
Logger :: log ( " 1st try: reshared message " . $guid . " will be fetched via SSL from the server " . $server );
2018-04-27 14:03:10 +00:00
$stored = self :: storeByGuid ( $guid , $server );
2017-11-08 00:37:53 +00:00
2018-04-27 14:03:10 +00:00
if ( ! $stored ) {
2017-11-08 00:37:53 +00:00
$server = " http:// " . substr ( $orig_author , strpos ( $orig_author , " @ " ) + 1 );
2018-10-29 21:20:46 +00:00
Logger :: log ( " 2nd try: reshared message " . $guid . " will be fetched without SSL from the server " . $server );
2018-04-27 14:03:10 +00:00
$stored = self :: storeByGuid ( $guid , $server );
2017-11-08 00:37:53 +00:00
}
2018-04-27 14:03:10 +00:00
if ( $stored ) {
2018-04-18 04:59:33 +00:00
$fields = [ 'body' , 'tag' , 'app' , 'created' , 'object-type' , 'uri' , 'guid' ,
'author-name' , 'author-link' , 'author-avatar' ];
2018-04-27 14:03:10 +00:00
$condition = [ 'guid' => $guid , 'visible' => true , 'deleted' => false , 'private' => false ];
2018-06-17 17:05:17 +00:00
$item = Item :: selectFirst ( $fields , $condition );
2017-11-08 00:37:53 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $item )) {
2017-11-08 00:37:53 +00:00
// If it is a reshared post from another network then reformat to avoid display problems with two share elements
2018-04-18 04:59:33 +00:00
if ( self :: isReshare ( $item [ " body " ], false )) {
$item [ " body " ] = Markdown :: toBBCode ( BBCode :: toMarkdown ( $item [ " body " ]));
$item [ " body " ] = self :: replacePeopleGuid ( $item [ " body " ], $item [ " author-link " ]);
2017-11-08 00:37:53 +00:00
}
2018-04-18 04:59:33 +00:00
return $item ;
2017-11-08 00:37:53 +00:00
}
}
}
return false ;
}
2019-04-01 22:07:23 +00:00
/**
* @ brief Stores a reshare activity
*
* @ 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
*/
private static function addReshareActivity ( $item , $parent_message_id , $guid , $author )
{
$parent = Item :: selectFirst ([ 'uri' , 'guid' ], [ 'id' => $parent_message_id ]);
$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' ]);
$datarray [ 'parent-uri' ] = $parent [ 'uri' ];
$datarray [ 'verb' ] = $datarray [ 'body' ] = ACTIVITY2_ANNOUNCE ;
$datarray [ 'gravity' ] = GRAVITY_ACTIVITY ;
$datarray [ 'object-type' ] = ACTIVITY_OBJ_NOTE ;
$datarray [ 'protocol' ] = $item [ 'protocol' ];
$datarray [ 'plink' ] = self :: plink ( $author , $datarray [ 'guid' ]);
$datarray [ 'private' ] = $item [ 'private' ];
$datarray [ 'changed' ] = $datarray [ 'created' ] = $datarray [ 'edited' ] = $item [ 'created' ];
$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
/**
* @ brief Processes a reshare message
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param object $data The message object
* @ param string $xml The original XML of the message
2017-11-08 00:37:53 +00:00
*
* @ return int the message id
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-20 05:10:16 +00:00
private static function receiveReshare ( array $importer , $data , $xml )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$author = Strings :: escapeTags ( XML :: unescape ( $data -> author ));
$guid = Strings :: escapeTags ( XML :: unescape ( $data -> guid ));
$created_at = DateTimeFormat :: utc ( Strings :: escapeTags ( XML :: unescape ( $data -> created_at )));
$root_author = Strings :: escapeTags ( XML :: unescape ( $data -> root_author ));
$root_guid = Strings :: escapeTags ( XML :: unescape ( $data -> root_guid ));
2017-11-08 00:37:53 +00:00
/// @todo handle unprocessed property "provider_display_name"
2018-11-09 18:29:42 +00:00
$public = Strings :: escapeTags ( 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 ;
}
2017-11-23 19:01:58 +00:00
$message_id = self :: messageExists ( $importer [ " uid " ], $guid );
2017-11-08 00:37:53 +00:00
if ( $message_id ) {
return true ;
}
2018-03-08 19:47:18 +00:00
$original_item = self :: originalItem ( $root_guid , $root_author );
2017-11-08 00:37:53 +00:00
if ( ! $original_item ) {
return false ;
}
$orig_url = System :: baseUrl () . " /display/ " . $original_item [ " guid " ];
2018-01-15 13:05:12 +00:00
$datarray = [];
2017-11-08 00:37:53 +00:00
$datarray [ " uid " ] = $importer [ " uid " ];
$datarray [ " contact-id " ] = $contact [ " id " ];
2018-08-11 20:40:44 +00:00
$datarray [ " network " ] = Protocol :: DIASPORA ;
2017-11-08 00:37:53 +00:00
$datarray [ " author-link " ] = $contact [ " url " ];
2018-06-16 22:32:57 +00:00
$datarray [ " author-id " ] = Contact :: getIdForURL ( $contact [ " url " ], 0 );
2017-11-08 00:37:53 +00:00
$datarray [ " owner-link " ] = $datarray [ " author-link " ];
2018-06-16 22:32:57 +00:00
$datarray [ " owner-id " ] = $datarray [ " author-id " ];
2017-11-08 00:37:53 +00:00
$datarray [ " guid " ] = $guid ;
2017-11-23 19:01:58 +00:00
$datarray [ " uri " ] = $datarray [ " parent-uri " ] = self :: getUriFromGuid ( $author , $guid );
2017-11-08 00:37:53 +00:00
$datarray [ " verb " ] = ACTIVITY_POST ;
$datarray [ " gravity " ] = GRAVITY_PARENT ;
2018-08-05 10:23:57 +00:00
$datarray [ " protocol " ] = Conversation :: PARCEL_DIASPORA ;
2017-11-08 00:37:53 +00:00
$datarray [ " source " ] = $xml ;
2017-11-08 22:02:50 +00:00
$prefix = share_header (
$original_item [ " author-name " ],
$original_item [ " author-link " ],
$original_item [ " author-avatar " ],
$original_item [ " guid " ],
$original_item [ " created " ],
$orig_url
);
2017-11-08 00:37:53 +00:00
$datarray [ " body " ] = $prefix . $original_item [ " body " ] . " [/share] " ;
$datarray [ " tag " ] = $original_item [ " tag " ];
$datarray [ " app " ] = $original_item [ " app " ];
$datarray [ " plink " ] = self :: plink ( $author , $guid );
$datarray [ " private " ] = (( $public == " false " ) ? 1 : 0 );
$datarray [ " changed " ] = $datarray [ " created " ] = $datarray [ " edited " ] = $created_at ;
$datarray [ " object-type " ] = $original_item [ " object-type " ];
2017-11-23 19:01:58 +00:00
self :: fetchGuid ( $datarray );
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 );
2019-04-01 22:07:23 +00:00
$root_message_id = self :: messageExists ( $importer [ " uid " ], $root_guid );
if ( $root_message_id ) {
self :: addReshareActivity ( $datarray , $root_message_id , $guid , $author );
}
2017-11-08 00:37:53 +00:00
if ( $message_id ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Stored reshare " . $datarray [ " guid " ] . " with message id " . $message_id , Logger :: DEBUG );
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 ;
}
}
/**
* @ brief Processes retractions
*
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
* @ param object $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
*/
2018-07-20 05:10:16 +00:00
private static function itemRetraction ( array $importer , array $contact , $data )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$author = Strings :: escapeTags ( XML :: unescape ( $data -> author ));
$target_guid = Strings :: escapeTags ( XML :: unescape ( $data -> target_guid ));
$target_type = Strings :: escapeTags ( XML :: unescape ( $data -> target_type ));
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
$person = self :: personByHandle ( $author );
2017-11-08 00:37:53 +00:00
if ( ! is_array ( $person )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " unable to find author detail for " . $author );
2017-11-08 00:37:53 +00:00
return false ;
}
if ( empty ( $contact [ " url " ])) {
$contact [ " url " ] = $person [ " url " ];
}
// Fetch items that are about to be deleted
2018-07-07 18:14:16 +00:00
$fields = [ 'uid' , 'id' , 'parent' , 'parent-uri' , 'author-link' , 'file' ];
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
2018-06-17 17:05:17 +00:00
$r = Item :: select ( $fields , $condition );
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $r )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " Target guid " . $target_guid . " was not found on this system for user " . $importer [ 'uid' ] . " . " );
2017-11-08 00:37:53 +00:00
return false ;
}
2018-06-21 15:14:01 +00:00
while ( $item = Item :: fetch ( $r )) {
2018-07-07 18:14:16 +00:00
if ( strstr ( $item [ 'file' ], '[' )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Target guid " . $target_guid . " for user " . $item [ 'uid' ] . " is filed. So it won't be deleted. " , Logger :: DEBUG );
2018-07-07 18:14:16 +00:00
continue ;
}
2017-11-08 00:37:53 +00:00
// Fetch the parent item
2018-06-17 17:05:17 +00:00
$parent = Item :: selectFirst ([ 'author-link' ], [ 'id' => $item [ " parent " ]]);
2017-11-08 00:37:53 +00:00
// Only delete it if the parent author really fits
2018-11-08 15:46:50 +00:00
if ( ! Strings :: compareLink ( $parent [ " author-link " ], $contact [ " url " ]) && ! Strings :: compareLink ( $item [ " author-link " ], $contact [ " url " ])) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Thread author " . $parent [ " author-link " ] . " and item author " . $item [ " author-link " ] . " don't fit to expected contact " . $contact [ " url " ], Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
continue ;
}
2018-05-29 19:00:26 +00:00
Item :: delete ([ 'id' => $item [ 'id' ]]);
2017-11-08 00:37:53 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( " Deleted target " . $target_guid . " ( " . $item [ " id " ] . " ) from user " . $item [ " uid " ] . " parent: " . $item [ " parent " ], Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
}
return true ;
}
/**
* @ brief Receives retraction messages
*
2017-11-08 22:02:50 +00:00
* @ param array $importer Array of the importer user
* @ param string $sender The sender of the message
* @ param object $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
*/
2018-07-20 05:10:16 +00:00
private static function receiveRetraction ( array $importer , $sender , $data )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$target_type = Strings :: escapeTags ( XML :: unescape ( $data -> target_type ));
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
$contact = self :: contactByHandle ( $importer [ " uid " ], $sender );
2018-01-15 13:05:12 +00:00
if ( ! $contact && ( in_array ( $target_type , [ " Contact " , " Person " ]))) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " 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 = [];
}
2018-10-30 13:58:45 +00:00
Logger :: log ( " Got retraction for " . $target_type . " , sender " . $sender . " and user " . $importer [ " uid " ], Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
switch ( $target_type ) {
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
2018-05-04 06:34:02 +00:00
case " PollParticipation " :
case " Photo " :
// Currently unsupported
break ;
2017-11-08 00:37:53 +00:00
default :
2018-10-29 21:20:46 +00:00
Logger :: log ( " Unknown target type " . $target_type );
2017-11-08 00:37:53 +00:00
return false ;
}
return true ;
}
/**
* @ brief Receives status messages
*
2019-01-06 21:06:53 +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
2017-11-08 00:37:53 +00:00
*
* @ return int The message id of the newly created item
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-11-08 00:37:53 +00:00
*/
Cleanups: isResult() more used, readability improved (#5608)
* [diaspora]: Maybe SimpleXMLElement is the right type-hint?
* Changes proposed + pre-renaming:
- pre-renamed $db -> $connection
- added TODOs for not allowing bad method invocations (there is a
BadMethodCallException in SPL)
* If no record is found, below $r[0] will fail with a E_NOTICE and the code
doesn't behave as expected.
* Ops, one more left ...
* Continued:
- added documentation for Contact::updateSslPolicy() method
- added type-hint for $contact of same method
- empty lines added + TODO where the bug origins that $item has no element 'body'
* Added empty lines for better readability
* Cleaned up:
- no more x() (deprecated) usage but empty() instead
- fixed mixing of space/tab indending
- merged else/if block goether in elseif() (lesser nested code blocks)
* Re-fixed DBM -> DBA switch
* Fixes/rewrites:
- use empty()/isset() instead of deprecated x()
- merged 2 nested if() blocks into one
- avoided nested if() block inside else block by rewriting it to elseif()
- $contact_id is an integer, let's test on > 0 here
- added a lot spaces and some empty lines for better readability
* Rewrite:
- moved all CONTACT_* constants from boot.php to Contact class
* CR request:
- renamed Contact::CONTACT_IS_* -> Contact::* ;-)
* Rewrites:
- moved PAGE_* to Friendica\Model\Profile class
- fixed mixure with "Contact::* rewrite"
* Ops, one still there (return is no function)
* Rewrite to Proxy class:
- introduced new Friendica\Network\Proxy class for in exchange of proxy_*()
functions
- moved also all PROXY_* constants there as Proxy::*
- removed now no longer needed mod/proxy.php loading as composer's auto-load
will do this for us
- renamed those proxy_*() functions to better names:
+ proxy_init() -> Proxy::init() (public)
+ proxy_url() -> Proxy::proxifyUrl() (public)
+ proxy_parse_html() -> Proxy::proxifyHtml() (public)
+ proxy_is_local_image() -> Proxy::isLocalImage() (private)
+ proxy_parse_query() -> Proxy::parseQuery() (private)
+ proxy_img_cb() -> Proxy::replaceUrl() (private)
* CR request:
- moved all PAGE_* constants to Friendica\Model\Contact class
- fixed all references of both classes
* Ops, need to set $a here ...
* CR request:
- moved Proxy class to Friendica\Module
- extended BaseModule
* Ops, no need for own instance of $a when self::getApp() is around.
* Proxy-rewrite:
- proxy_url() and proxy_parse_html() are both non-module functions (now
methods)
- so they must be splitted into a seperate class
- also the SIZE_* and DEFAULT_TIME constants are both not relevant to module
* No instances from utility classes
* Fixed error:
- proxify*() is now located in `Friendica\Util\ProxyUtils`
* Moved back to original place, ops? How did they move here? Well, it was not
intended by me.
* Removed duplicate (left-over from split) constants and static array. Thank to
MrPetovan finding it.
* Renamed ProxyUtils -> Proxy and aliased it back to ProxyUtils.
* Rewrite:
- stopped using deprecated NETWORK_* constants, now Protocol::* should be used
- still left them intact for slow/lazy developers ...
* Ops, was added accidentally ...
* Ops, why these wrong moves?
* Ops, one to much (thanks to MrPetovan)
* Ops, wrong moving ...
* moved back to original place ...
* spaces added
* empty lines add for better readability.
* convertered spaces -> tab for code indenting.
* CR request: Add space between if and brace.
* CR requests fixed + move reverted
- ops, src/Module/*.php has been moved to src/Network/ accidentally
- reverted some parts in src/Database/DBA.php as pointed out by Annando
- removed internal TODO items
- added some spaces for better readability
2018-08-24 05:05:49 +00:00
private static function receiveStatusMessage ( array $importer , SimpleXMLElement $data , $xml )
2017-11-08 22:02:50 +00:00
{
2018-11-09 18:29:42 +00:00
$author = Strings :: escapeTags ( XML :: unescape ( $data -> author ));
$guid = Strings :: escapeTags ( XML :: unescape ( $data -> guid ));
$created_at = DateTimeFormat :: utc ( Strings :: escapeTags ( XML :: unescape ( $data -> created_at )));
$public = Strings :: escapeTags ( XML :: unescape ( $data -> public ));
2018-11-05 12:40:18 +00:00
$text = XML :: unescape ( $data -> text );
2018-11-09 18:29:42 +00:00
$provider_display_name = Strings :: escapeTags ( 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 ;
}
2017-11-23 19:01:58 +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 ) {
2018-11-09 18:29:42 +00:00
$address [ $fieldname ] = Strings :: escapeTags ( XML :: unescape ( $data ));
2017-11-08 00:37:53 +00:00
}
}
2018-03-04 22:39:41 +00:00
$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
// Attach embedded pictures to the body
if ( $data -> photo ) {
2017-11-08 22:02:50 +00:00
foreach ( $data -> photo as $photo ) {
2018-11-05 12:40:18 +00:00
$body = " [img] " . XML :: unescape ( $photo -> remote_photo_path ) .
XML :: unescape ( $photo -> remote_photo_name ) . " [/img] \n " . $body ;
2017-11-08 00:37:53 +00:00
}
$datarray [ " object-type " ] = ACTIVITY_OBJ_IMAGE ;
} else {
$datarray [ " object-type " ] = ACTIVITY_OBJ_NOTE ;
// Add OEmbed and other information to the body
2017-11-23 19:01:58 +00:00
if ( ! self :: isRedmatrix ( $contact [ " url " ])) {
2017-11-08 00:37:53 +00:00
$body = add_page_info_to_body ( $body , false , true );
}
}
/// @todo enable support for polls
//if ($data->poll) {
// foreach ($data->poll AS $poll)
// print_r($poll);
// die("poll!\n");
//}
/// @todo enable support for events
$datarray [ " uid " ] = $importer [ " uid " ];
$datarray [ " contact-id " ] = $contact [ " id " ];
2018-08-11 20:40:44 +00:00
$datarray [ " network " ] = Protocol :: DIASPORA ;
2017-11-08 00:37:53 +00:00
$datarray [ " author-link " ] = $contact [ " url " ];
2018-06-16 22:32:57 +00:00
$datarray [ " author-id " ] = Contact :: getIdForURL ( $contact [ " url " ], 0 );
2017-11-08 00:37:53 +00:00
$datarray [ " owner-link " ] = $datarray [ " author-link " ];
2018-06-16 22:32:57 +00:00
$datarray [ " owner-id " ] = $datarray [ " author-id " ];
2017-11-08 00:37:53 +00:00
$datarray [ " guid " ] = $guid ;
2017-11-23 19:01:58 +00:00
$datarray [ " uri " ] = $datarray [ " parent-uri " ] = self :: getUriFromGuid ( $author , $guid );
2017-11-08 00:37:53 +00:00
$datarray [ " verb " ] = ACTIVITY_POST ;
$datarray [ " gravity " ] = GRAVITY_PARENT ;
2018-08-05 10:23:57 +00:00
$datarray [ " protocol " ] = Conversation :: PARCEL_DIASPORA ;
2017-11-08 00:37:53 +00:00
$datarray [ " source " ] = $xml ;
2017-11-23 19:01:58 +00:00
$datarray [ " body " ] = self :: replacePeopleGuid ( $body , $contact [ " url " ]);
2017-11-08 00:37:53 +00:00
if ( $provider_display_name != " " ) {
$datarray [ " app " ] = $provider_display_name ;
}
$datarray [ " plink " ] = self :: plink ( $author , $guid );
$datarray [ " private " ] = (( $public == " false " ) ? 1 : 0 );
$datarray [ " changed " ] = $datarray [ " created " ] = $datarray [ " edited " ] = $created_at ;
if ( isset ( $address [ " address " ])) {
$datarray [ " location " ] = $address [ " address " ];
}
if ( isset ( $address [ " lat " ]) && isset ( $address [ " lng " ])) {
$datarray [ " coord " ] = $address [ " lat " ] . " " . $address [ " lng " ];
}
2017-11-23 19:01:58 +00:00
self :: fetchGuid ( $datarray );
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 ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Stored item " . $datarray [ " guid " ] . " with message id " . $message_id , Logger :: DEBUG );
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 *
* ************************************************************************************** */
/**
* @ brief returnes the handle of a contact
*
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
*/
2018-07-19 11:07:14 +00:00
private static function myHandle ( array $contact )
2017-11-08 22:02:50 +00:00
{
2018-07-19 11:07:14 +00:00
if ( ! empty ( $contact [ " addr " ])) {
2017-11-08 00:37:53 +00:00
return $contact [ " addr " ];
}
// 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.
if ( $contact [ " nickname " ] != " " ) {
$nick = $contact [ " nickname " ];
} else {
$nick = $contact [ " nick " ];
}
2018-07-19 11:07:14 +00:00
return $nick . " @ " . substr ( System :: baseUrl (), strpos ( System :: baseUrl (), " :// " ) + 3 );
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Creates the data for a private message in the new format
*
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
*/
2018-07-19 11:07:14 +00:00
public static function encodePrivateData ( $msg , array $user , array $contact , $prvkey , $pubkey )
2017-11-08 22:02:50 +00:00
{
2018-10-30 13:58:45 +00:00
Logger :: log ( " Message: " . $msg , Logger :: DATA );
2017-11-08 00:37:53 +00:00
// without a public key nothing will work
if ( ! $pubkey ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " pubkey missing: contact id: " . $contact [ " id " ]);
2017-11-08 00:37:53 +00:00
return false ;
}
$aes_key = openssl_random_pseudo_bytes ( 32 );
$b_aes_key = base64_encode ( $aes_key );
$iv = openssl_random_pseudo_bytes ( 16 );
$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
2018-01-15 13:05:12 +00:00
$json = json_encode ([ " iv " => $b_iv , " key " => $b_aes_key ]);
2017-11-08 00:37:53 +00:00
$encrypted_key_bundle = " " ;
openssl_public_encrypt ( $json , $encrypted_key_bundle , $pubkey );
2017-11-08 22:02:50 +00:00
$json_object = json_encode (
2018-01-15 13:05:12 +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 ;
}
/**
* @ brief Creates the envelope for the " fetch " endpoint and for the new format
*
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
*/
2018-07-19 11:07:14 +00:00
public static function buildMagicEnvelope ( $msg , array $user )
2017-11-08 22:02:50 +00:00
{
2018-11-08 15:37:08 +00:00
$b64url_data = Strings :: base64UrlEncode ( $msg );
2018-01-15 13:05:12 +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 ));
2017-11-08 00:37:53 +00:00
$type = " application/xml " ;
$encoding = " base64url " ;
$alg = " RSA-SHA256 " ;
2018-11-08 15:37:08 +00:00
$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
2017-11-08 22:02:50 +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
2017-12-30 16:51:49 +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
2018-01-15 13:05:12 +00:00
$xmldata = [ " me:env " => [ " me:data " => $data ,
" @attributes " => [ " type " => $type ],
2017-11-08 00:37:53 +00:00
" me:encoding " => $encoding ,
" me:alg " => $alg ,
" me:sig " => $sig ,
2018-01-15 13:05:12 +00:00
" @attributes2 " => [ " key_id " => $key_id ]]];
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$namespaces = [ " me " => " http://salmon-protocol.org/ns/magic-env " ];
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
}
/**
* @ brief Create the envelope for a message
*
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
*/
2018-07-19 11:07:14 +00:00
public static function buildMessage ( $msg , array $user , array $contact , $prvkey , $pubkey , $public = false )
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 ;
}
/**
* @ brief Creates a signature for a message
*
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
*/
2017-11-08 22:02:50 +00:00
private static function signature ( $owner , $message )
{
2017-11-08 00:37:53 +00:00
$sigmsg = $message ;
unset ( $sigmsg [ " author_signature " ]);
unset ( $sigmsg [ " parent_author_signature " ]);
$signed_text = implode ( " ; " , $sigmsg );
2017-12-30 16:51:49 +00:00
return base64_encode ( Crypto :: rsaSign ( $signed_text , $owner [ " uprvkey " ], " sha256 " ));
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Transmit a message to a target server
*
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
*/
2019-06-01 06:54:47 +00:00
private static function transmit ( array $owner , array $contact , $envelope , $public_batch , $guid = " " )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$enabled = intval ( 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
$dest_url = ( $public_batch ? $contact [ " batch " ] : $contact [ " notify " ]);
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' ])) {
2018-01-04 19:48:56 +00:00
$fcontact = self :: personByHandle ( $contact [ 'addr' ]);
2018-04-04 06:06:38 +00:00
if ( ! empty ( $fcontact )) {
$dest_url = ( $public_batch ? $fcontact [ " batch " ] : $fcontact [ " notify " ]);
}
2018-01-04 19:48:56 +00:00
}
2017-11-08 00:37:53 +00:00
if ( ! $dest_url ) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " no url for contact: " . $contact [ " id " ] . " batch mode = " . $public_batch );
2017-11-08 00:37:53 +00:00
return 0 ;
}
2018-10-29 21:20:46 +00:00
Logger :: log ( " transmit: " . $logid . " - " . $guid . " " . $dest_url );
2017-11-08 00:37:53 +00:00
2019-04-05 18:04:39 +00:00
if ( ! intval ( Config :: get ( " system " , " diaspora_test " ))) {
$content_type = (( $public_batch ) ? " application/magic-envelope+xml " : " application/json " );
2017-11-08 00:37:53 +00:00
2019-04-05 18:04:39 +00:00
$postResult = Network :: post ( $dest_url . " / " , $envelope , [ " Content-Type: " . $content_type ]);
$return_code = $postResult -> getReturnCode ();
} else {
Logger :: log ( " test_mode " );
return 200 ;
2017-11-08 00:37:53 +00:00
}
2018-10-29 21:20:46 +00:00
Logger :: log ( " 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
}
/**
* @ brief Build the post xml
*
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
*/
2017-11-23 19:01:58 +00:00
public static function buildPostXml ( $type , $message )
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
}
/**
* @ brief Builds and transmit messages
*
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
*/
2019-06-01 06:54:47 +00:00
private static function buildAndTransmit ( array $owner , array $contact , $type , $message , $public_batch = false , $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
2018-10-30 13:58:45 +00:00
Logger :: log ( 'message: ' . $msg , Logger :: DATA );
Logger :: log ( 'send guid ' . $guid , Logger :: DEBUG );
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
2017-11-23 19:01:58 +00:00
$envelope = self :: buildMessage ( $msg , $owner , $contact , $owner [ 'uprvkey' ], $contact [ '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
2018-10-30 13:58:45 +00:00
Logger :: log ( " guid: " . $guid . " result " . $return_code , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return $return_code ;
}
2017-12-29 18:52:26 +00:00
/**
* @ brief sends a participation ( Used to get all further updates )
*
* @ 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
*/
2018-07-19 11:07:14 +00:00
private static function sendParticipation ( array $contact , array $item )
2017-12-29 18:52:26 +00:00
{
// Don't send notifications for private postings
if ( $item [ 'private' ]) {
return ;
}
$cachekey = " diaspora:sendParticipation: " . $item [ 'guid' ];
$result = Cache :: get ( $cachekey );
if ( ! is_null ( $result )) {
return ;
}
// 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.
2017-12-29 20:02:15 +00:00
// If the item belongs to a user, we take this user id.
if ( $item [ 'uid' ] == 0 ) {
$condition = [ 'verified' => true , 'blocked' => false , 'account_removed' => false , 'account_expired' => false ];
2018-07-20 12:19:26 +00:00
$first_user = DBA :: selectFirst ( 'user' , [ 'uid' ], $condition );
2017-12-29 20:02:15 +00:00
$owner = User :: getOwnerDataById ( $first_user [ 'uid' ]);
} else {
$owner = User :: getOwnerDataById ( $item [ 'uid' ]);
}
2017-12-29 18:52:26 +00:00
$author = self :: myHandle ( $owner );
2018-01-15 13:05:12 +00:00
$message = [ " author " => $author ,
2018-09-27 11:52:15 +00:00
" guid " => System :: createUUID (),
2017-12-29 19:18:25 +00:00
" parent_type " => " Post " ,
2018-01-15 13:05:12 +00:00
" parent_guid " => $item [ " guid " ]];
2017-12-29 18:52:26 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( " Send participation for " . $item [ " guid " ] . " by " . $author , Logger :: DEBUG );
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
2018-10-20 16:19:55 +00:00
Cache :: set ( $cachekey , $item [ " guid " ], Cache :: QUARTER_HOUR );
2017-12-29 18:52:26 +00:00
return self :: buildAndTransmit ( $owner , $contact , " participation " , $message );
}
2017-11-08 00:37:53 +00:00
/**
* @ brief sends an account migration
*
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
*/
2018-07-19 11:07:14 +00:00
public static function sendAccountMigration ( array $owner , array $contact , $uid )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$old_handle = PConfig :: get ( $uid , 'system' , 'previous_addr' );
$profile = self :: createProfileData ( $uid );
$signed_text = 'AccountMigration:' . $old_handle . ':' . $profile [ 'author' ];
2017-12-30 16:51:49 +00:00
$signature = base64_encode ( Crypto :: rsaSign ( $signed_text , $owner [ " uprvkey " ], " sha256 " ));
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$message = [ " author " => $old_handle ,
2017-11-08 00:37:53 +00:00
" profile " => $profile ,
2018-01-15 13:05:12 +00:00
" signature " => $signature ];
2017-11-08 00:37:53 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( " Send account migration " . print_r ( $message , true ), Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
return self :: buildAndTransmit ( $owner , $contact , " account_migration " , $message );
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Sends a " share " message
*
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
*/
2018-07-19 11:07:14 +00:00
public static function sendShare ( array $owner , array $contact )
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 ;
}
*/
2018-01-15 13:05:12 +00:00
$message = [ " author " => self :: myHandle ( $owner ),
2017-11-08 00:37:53 +00:00
" recipient " => $contact [ " addr " ],
" following " => " true " ,
2018-01-15 13:05:12 +00:00
" sharing " => " true " ];
2017-11-08 00:37:53 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( " Send share " . print_r ( $message , true ), Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
return self :: buildAndTransmit ( $owner , $contact , " contact " , $message );
2017-11-08 00:37:53 +00:00
}
/**
* @ brief sends an " unshare "
*
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
*/
2018-07-19 11:07:14 +00:00
public static function sendUnshare ( array $owner , array $contact )
2017-11-08 22:02:50 +00:00
{
2018-01-15 13:05:12 +00:00
$message = [ " author " => self :: myHandle ( $owner ),
2017-11-08 00:37:53 +00:00
" recipient " => $contact [ " addr " ],
" following " => " false " ,
2018-01-15 13:05:12 +00:00
" sharing " => " false " ];
2017-11-08 00:37:53 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( " Send unshare " . print_r ( $message , true ), Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
return self :: buildAndTransmit ( $owner , $contact , " contact " , $message );
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Checks a message body if it is a reshare
*
2017-11-08 22:02:50 +00:00
* @ param string $body The message body that is to be check
* @ param bool $complete Should it be a complete check or a simple check ?
2017-11-08 00:37:53 +00:00
*
* @ return array | bool 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
*/
2017-11-23 19:01:58 +00:00
public static function isReshare ( $body , $complete = true )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$body = trim ( $body );
// Skip if it isn't a pure repeated messages
// Does it start with a share?
2017-11-08 22:02:50 +00:00
if (( strpos ( $body , " [share " ) > 0 ) && $complete ) {
2018-03-08 19:47:18 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
// Does it end with a share?
2017-11-08 22:02:50 +00:00
if ( strlen ( $body ) > ( strrpos ( $body , " [/share] " ) + 8 )) {
2018-03-08 19:47:18 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2017-11-08 22:02:50 +00:00
$attributes = preg_replace ( " / \ [share(.*?) \ ] \ s?(.*?) \ s? \ [ \ /share \ ] \ s?/ism " , " $ 1 " , $body );
2017-11-08 00:37:53 +00:00
// Skip if there is no shared message in there
2017-11-08 22:02:50 +00:00
if ( $body == $attributes ) {
2018-03-08 19:47:18 +00:00
return false ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
// If we don't do the complete check we quit here
$guid = " " ;
preg_match ( " /guid='(.*?)'/ism " , $attributes , $matches );
2018-07-10 12:27:56 +00:00
if ( ! empty ( $matches [ 1 ])) {
2017-11-08 00:37:53 +00:00
$guid = $matches [ 1 ];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
preg_match ( '/guid="(.*?)"/ism' , $attributes , $matches );
2018-07-10 12:27:56 +00:00
if ( ! empty ( $matches [ 1 ])) {
2017-11-08 00:37:53 +00:00
$guid = $matches [ 1 ];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2018-03-08 19:47:18 +00:00
if (( $guid != " " ) && $complete ) {
2018-08-11 20:40:44 +00:00
$condition = [ 'guid' => $guid , 'network' => [ Protocol :: DFRN , Protocol :: DIASPORA ]];
2018-06-17 17:05:17 +00:00
$item = Item :: selectFirst ([ 'contact-id' ], $condition );
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $item )) {
2018-01-15 13:05:12 +00:00
$ret = [];
2018-04-18 04:59:33 +00:00
$ret [ " root_handle " ] = self :: handleFromContact ( $item [ " contact-id " ]);
2017-11-08 00:37:53 +00:00
$ret [ " root_guid " ] = $guid ;
2018-03-08 19:47:18 +00:00
return $ret ;
2018-05-13 18:31:01 +00:00
} elseif ( $complete ) {
// We are resharing something that isn't a DFRN or Diaspora post.
// So we have to return "false" on "$complete" to not trigger a reshare.
return false ;
2017-11-08 00:37:53 +00:00
}
2018-05-03 13:03:41 +00:00
} elseif (( $guid == " " ) && $complete ) {
return false ;
2017-11-08 00:37:53 +00:00
}
2018-05-03 13:03:41 +00:00
$ret [ " root_guid " ] = $guid ;
2017-11-08 00:37:53 +00:00
$profile = " " ;
preg_match ( " /profile='(.*?)'/ism " , $attributes , $matches );
2018-07-10 12:27:56 +00:00
if ( ! empty ( $matches [ 1 ])) {
2017-11-08 00:37:53 +00:00
$profile = $matches [ 1 ];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
preg_match ( '/profile="(.*?)"/ism' , $attributes , $matches );
2018-07-10 12:27:56 +00:00
if ( ! empty ( $matches [ 1 ])) {
2017-11-08 00:37:53 +00:00
$profile = $matches [ 1 ];
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$ret = [];
2017-11-08 00:37:53 +00:00
2018-03-08 19:47:18 +00:00
if ( $profile != " " ) {
if ( Contact :: getIdForURL ( $profile )) {
$author = Contact :: getDetailsByURL ( $profile );
$ret [ " root_handle " ] = $author [ 'addr' ];
}
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2018-03-08 19:47:18 +00:00
if ( empty ( $ret ) && ! $complete ) {
return true ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
2018-03-08 19:47:18 +00:00
return $ret ;
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Create an event array
*
* @ 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
*/
2017-11-23 19:01:58 +00:00
private static function buildEvent ( $event_id )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
$r = q ( " SELECT `guid`, `uid`, `start`, `finish`, `nofinish`, `summary`, `desc`, `location`, `adjust` FROM `event` WHERE `id` = %d " , intval ( $event_id ));
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $r )) {
2018-01-15 13:05:12 +00:00
return [];
2017-11-08 00:37:53 +00:00
}
$event = $r [ 0 ];
2018-01-15 13:05:12 +00:00
$eventdata = [];
2017-11-08 00:37:53 +00:00
$r = q ( " SELECT `timezone` FROM `user` WHERE `uid` = %d " , intval ( $event [ 'uid' ]));
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $r )) {
2018-01-15 13:05:12 +00:00
return [];
2017-11-08 00:37:53 +00:00
}
$user = $r [ 0 ];
$r = q ( " SELECT `addr`, `nick` FROM `contact` WHERE `uid` = %d AND `self` " , intval ( $event [ 'uid' ]));
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $r )) {
2018-01-15 13:05:12 +00:00
return [];
2017-11-08 00:37:53 +00:00
}
$owner = $r [ 0 ];
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
$eventdata [ " all_day " ] = " false " ;
2018-10-16 22:29:08 +00:00
$eventdata [ 'timezone' ] = 'UTC' ;
if ( ! $event [ 'adjust' ] && $user [ 'timezone' ]) {
2017-11-08 00:37:53 +00:00
$eventdata [ 'timezone' ] = $user [ 'timezone' ];
}
if ( $event [ 'start' ]) {
2018-01-27 02:38:34 +00:00
$eventdata [ 'start' ] = DateTimeFormat :: convert ( $event [ 'start' ], " UTC " , $eventdata [ 'timezone' ], $mask );
2017-11-08 00:37:53 +00:00
}
if ( $event [ 'finish' ] && ! $event [ 'nofinish' ]) {
2018-01-27 02:38:34 +00:00
$eventdata [ 'end' ] = DateTimeFormat :: convert ( $event [ 'finish' ], " UTC " , $eventdata [ 'timezone' ], $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 = [];
2018-03-04 22:39:41 +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' ])) {
$location [ " lat " ] = $coord [ 'lat' ];
$location [ " lng " ] = $coord [ 'lon' ];
} else {
$location [ " lat " ] = 0 ;
$location [ " lng " ] = 0 ;
}
2017-11-08 00:37:53 +00:00
$eventdata [ 'location' ] = $location ;
}
return $eventdata ;
}
/**
* @ brief Create a post ( status message or reshare )
*
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
{
2017-11-23 19:01:58 +00:00
$cachekey = " diaspora:buildStatus: " . $item [ 'guid' ];
2017-11-08 00:37:53 +00:00
$result = Cache :: get ( $cachekey );
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
2018-09-13 21:11:52 +00:00
$public = ( $item [ " private " ] ? " false " : " true " );
2017-11-08 00:37:53 +00:00
2018-01-27 02:38:34 +00:00
$created = DateTimeFormat :: utc ( $item [ " created " ], DateTimeFormat :: ATOM );
2019-05-30 18:56:31 +00:00
$edited = DateTimeFormat :: utc ( $item [ " edited " ], DateTimeFormat :: ATOM );
2017-11-08 00:37:53 +00:00
// Detect a share element and do a reshare
2017-11-23 19:01:58 +00:00
if ( ! $item [ 'private' ] && ( $ret = self :: isReshare ( $item [ " body " ]))) {
2018-01-15 13:05:12 +00:00
$message = [ " author " => $myaddr ,
2017-11-08 00:37:53 +00:00
" guid " => $item [ " guid " ],
" created_at " => $created ,
" root_author " => $ret [ " root_handle " ],
" root_guid " => $ret [ " root_guid " ],
" provider_display_name " => $item [ " app " ],
2018-01-15 13:05:12 +00:00
" public " => $public ];
2017-11-08 00:37:53 +00:00
$type = " reshare " ;
} else {
$title = $item [ " title " ];
$body = $item [ " body " ];
2018-04-15 19:01:19 +00:00
if ( $item [ 'author-link' ] != $item [ 'owner-link' ]) {
require_once 'mod/share.php' ;
$body = share_header ( $item [ 'author-name' ], $item [ 'author-link' ], $item [ 'author-avatar' ],
" " , $item [ 'created' ], $item [ 'plink' ]) . $body . '[/share]' ;
}
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 )) {
2017-11-08 00:37:53 +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
if ( $item [ " attach " ]) {
$cnt = preg_match_all ( '/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism' , $item [ " attach " ], $matches , PREG_SET_ORDER );
2018-07-31 16:39:23 +00:00
if ( $cnt ) {
2018-01-21 16:38:01 +00:00
$body .= " \n " . L10n :: t ( " Attachments: " ) . " \n " ;
2017-11-08 22:02:50 +00:00
foreach ( $matches as $mtch ) {
2017-11-08 00:37:53 +00:00
$body .= " [ " . $mtch [ 3 ] . " ]( " . $mtch [ 1 ] . " ) \n " ;
2017-11-08 22:02:50 +00:00
}
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
if ( $item [ " location " ] != " " )
$location [ " address " ] = $item [ " location " ];
if ( $item [ " coord " ] != " " ) {
$coord = explode ( " " , $item [ " coord " ]);
$location [ " lat " ] = $coord [ 0 ];
$location [ " lng " ] = $coord [ 1 ];
}
2018-01-15 13:05:12 +00:00
$message = [ " author " => $myaddr ,
2017-11-08 00:37:53 +00:00
" guid " => $item [ " guid " ],
" created_at " => $created ,
2019-05-30 18:56:31 +00:00
" edited_at " => $edited ,
2017-11-08 00:37:53 +00:00
" public " => $public ,
" text " => $body ,
" provider_display_name " => $item [ " app " ],
2018-01-15 13:05:12 +00:00
" location " => $location ];
2017-11-08 00:37:53 +00:00
// Diaspora rejects messages when they contain a location without "lat" or "lng"
if ( ! isset ( $location [ " lat " ]) || ! isset ( $location [ " lng " ])) {
unset ( $message [ " location " ]);
}
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'] = '';
}
}
$type = " status_message " ;
}
2018-01-15 13:05:12 +00:00
$msg = [ " type " => $type , " message " => $message ];
2017-11-08 00:37:53 +00:00
2018-10-20 16:19:55 +00:00
Cache :: set ( $cachekey , $msg , Cache :: QUARTER_HOUR );
2017-11-08 00:37:53 +00:00
return $msg ;
}
2019-02-09 04:10:36 +00:00
private static function prependParentAuthorMention ( $body , $profile_url )
{
$profile = Contact :: getDetailsByURL ( $profile_url );
if ( ! empty ( $profile [ 'addr' ])
&& $profile [ 'contact-type' ] != Contact :: TYPE_COMMUNITY
&& ! 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
/**
* @ brief Sends a post
*
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
*/
2018-07-19 11:07:14 +00:00
public static function sendStatus ( array $item , array $owner , array $contact , $public_batch = false )
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
2017-11-23 19:01:58 +00:00
return self :: buildAndTransmit ( $owner , $contact , $status [ " type " ], $status [ " message " ], $public_batch , $item [ " guid " ]);
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Creates a " like " object
*
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 The data for a " like "
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
{
2018-06-17 17:05:17 +00:00
$parent = Item :: selectFirst ([ 'guid' , 'uri' , 'parent-uri' ], [ '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
$target_type = ( $parent [ " uri " ] === $parent [ " parent-uri " ] ? " Post " : " Comment " );
2018-02-14 04:58:46 +00:00
$positive = null ;
2017-11-08 00:37:53 +00:00
if ( $item [ 'verb' ] === ACTIVITY_LIKE ) {
$positive = " true " ;
} elseif ( $item [ 'verb' ] === ACTIVITY_DISLIKE ) {
$positive = " false " ;
}
2018-01-15 13:05:12 +00:00
return ([ " author " => self :: myHandle ( $owner ),
2017-11-08 00:37:53 +00:00
" guid " => $item [ " guid " ],
" parent_guid " => $parent [ " guid " ],
" parent_type " => $target_type ,
" positive " => $positive ,
2018-01-15 13:05:12 +00:00
" author_signature " => " " ]);
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Creates an " EventParticipation " object
*
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 The data for an " EventParticipation "
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
{
2018-06-17 17:05:17 +00:00
$parent = Item :: selectFirst ([ 'guid' , 'uri' , 'parent-uri' ], [ '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' ]) {
case ACTIVITY_ATTEND :
$attend_answer = 'accepted' ;
break ;
case ACTIVITY_ATTENDNO :
$attend_answer = 'declined' ;
break ;
case ACTIVITY_ATTENDMAYBE :
$attend_answer = 'tentative' ;
break ;
default :
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Unknown verb ' . $item [ 'verb' ] . ' in item ' . $item [ 'guid' ]);
2017-11-08 00:37:53 +00:00
return false ;
}
2018-01-15 13:05:12 +00:00
return ([ " author " => self :: myHandle ( $owner ),
2017-11-08 00:37:53 +00:00
" guid " => $item [ " guid " ],
" parent_guid " => $parent [ " guid " ],
" status " => $attend_answer ,
2018-01-15 13:05:12 +00:00
" author_signature " => " " ]);
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Creates the object for a comment
*
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
*/
2018-07-19 11:07:14 +00:00
private static function constructComment ( array $item , array $owner )
2017-11-08 22:02:50 +00:00
{
2017-11-23 19:01:58 +00:00
$cachekey = " diaspora:constructComment: " . $item [ 'guid' ];
2017-11-08 00:37:53 +00:00
$result = Cache :: get ( $cachekey );
if ( ! is_null ( $result )) {
return $result ;
}
2019-02-23 04:38:59 +00:00
$toplevel_item = Item :: selectFirst ([ 'guid' , 'author-link' ], [ 'id' => $item [ " parent " ], 'parent' => $item [ " parent " ]]);
if ( ! DBA :: isResult ( $toplevel_item )) {
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' ]) {
$thread_parent_item = Item :: selectFirst ([ 'guid' , 'author-link' ], [ 'uri' => $item [ 'thr-parent' ], 'uid' => $item [ 'uid' ]]);
}
2019-02-09 04:10:36 +00:00
$body = $item [ " body " ];
2019-02-23 04:38:59 +00:00
if (( empty ( $item [ 'uid' ]) || ! Feature :: isEnabled ( $item [ 'uid' ], 'explicit_mentions' ))
&& ! Config :: get ( 'system' , 'disable_implicit_mentions' )
) {
$body = self :: prependParentAuthorMention ( $body , $thread_parent_item [ 'author-link' ]);
2019-02-09 04:10:36 +00:00
}
$text = html_entity_decode ( BBCode :: toMarkdown ( $body ));
2018-01-27 02:38:34 +00:00
$created = DateTimeFormat :: utc ( $item [ " created " ], DateTimeFormat :: ATOM );
2019-05-30 18:56:31 +00:00
$edited = DateTimeFormat :: utc ( $item [ " edited " ], DateTimeFormat :: ATOM );
2017-11-08 00:37:53 +00:00
2019-02-23 04:38:59 +00:00
$comment = [
" author " => self :: myHandle ( $owner ),
" guid " => $item [ " guid " ],
" created_at " => $created ,
2019-05-30 18:56:31 +00:00
" edited_at " => $edited ,
2019-02-23 04:38:59 +00:00
" parent_guid " => $toplevel_item [ " guid " ],
" text " => $text ,
" author_signature " => " "
];
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
}
2018-10-20 16:19:55 +00:00
Cache :: set ( $cachekey , $comment , Cache :: QUARTER_HOUR );
2017-11-08 00:37:53 +00:00
return ( $comment );
}
/**
* @ brief Send a like or a comment
*
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
*/
2018-07-19 11:07:14 +00:00
public static function sendFollowup ( array $item , array $owner , array $contact , $public_batch = false )
2017-11-08 22:02:50 +00:00
{
2018-01-15 13:05:12 +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 );
2017-11-08 00:37:53 +00:00
$type = " event_participation " ;
2018-01-15 13:05:12 +00:00
} elseif ( in_array ( $item [ " verb " ], [ ACTIVITY_LIKE , ACTIVITY_DISLIKE ])) {
2017-11-23 19:01:58 +00:00
$message = self :: constructLike ( $item , $owner );
2017-11-08 00:37:53 +00:00
$type = " like " ;
2019-05-25 19:33:58 +00:00
} elseif ( ! in_array ( $item [ " verb " ], [ ACTIVITY_FOLLOW , ACTIVITY_TAG ])) {
2017-11-23 19:01:58 +00:00
$message = self :: constructComment ( $item , $owner );
2017-11-08 00:37:53 +00:00
$type = " comment " ;
}
2018-12-24 14:50:21 +00:00
if ( empty ( $message )) {
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
$message [ " author_signature " ] = self :: signature ( $owner , $message );
2017-11-23 19:01:58 +00:00
return self :: buildAndTransmit ( $owner , $contact , $type , $message , $public_batch , $item [ " guid " ]);
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Creates a message from a signature record entry
*
2019-01-06 21:06:53 +00:00
* @ param array $item The item that will be exported
* @ return array The message
2017-11-08 00:37:53 +00:00
*/
2018-10-15 21:42:55 +00:00
private static function messageFromSignature ( array $item )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
// Split the signed text
2018-10-15 21:42:55 +00:00
$signed_parts = explode ( " ; " , $item [ 'signed_text' ]);
2017-11-08 00:37:53 +00:00
if ( $item [ " deleted " ]) {
2018-10-15 21:42:55 +00:00
$message = [ " author " => $item [ 'signer' ],
2017-11-08 00:37:53 +00:00
" target_guid " => $signed_parts [ 0 ],
2018-01-15 13:05:12 +00:00
" target_type " => $signed_parts [ 1 ]];
} elseif ( in_array ( $item [ " verb " ], [ ACTIVITY_LIKE , ACTIVITY_DISLIKE ])) {
$message = [ " author " => $signed_parts [ 4 ],
2017-11-08 00:37:53 +00:00
" guid " => $signed_parts [ 1 ],
" parent_guid " => $signed_parts [ 3 ],
" parent_type " => $signed_parts [ 2 ],
" positive " => $signed_parts [ 0 ],
2018-10-15 21:42:55 +00:00
" author_signature " => $item [ 'signature' ],
2018-01-15 13:05:12 +00:00
" parent_author_signature " => " " ];
2017-11-08 00:37:53 +00:00
} else {
// Remove the comment guid
$guid = array_shift ( $signed_parts );
// Remove the parent guid
$parent_guid = array_shift ( $signed_parts );
// Remove the handle
$handle = array_pop ( $signed_parts );
2019-01-07 17:09:10 +00:00
$message = [
" author " => $handle ,
" guid " => $guid ,
" parent_guid " => $parent_guid ,
" text " => implode ( " ; " , $signed_parts ),
" author_signature " => $item [ 'signature' ],
" parent_author_signature " => " "
];
2017-11-08 00:37:53 +00:00
}
return $message ;
}
/**
* @ brief Relays messages ( like , comment , retraction ) to other servers if we are the thread owner
*
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
*/
2018-07-19 11:07:14 +00:00
public static function sendRelay ( array $item , array $owner , array $contact , $public_batch = false )
2017-11-08 22:02:50 +00:00
{
2017-11-08 00:37:53 +00:00
if ( $item [ " deleted " ]) {
2017-11-23 19:01:58 +00:00
return self :: sendRetraction ( $item , $owner , $contact , $public_batch , true );
2018-01-15 13:05:12 +00:00
} elseif ( in_array ( $item [ " verb " ], [ ACTIVITY_LIKE , ACTIVITY_DISLIKE ])) {
2017-11-08 00:37:53 +00:00
$type = " like " ;
} else {
$type = " comment " ;
}
2018-10-30 13:58:45 +00:00
Logger :: log ( " Got relayable data " . $type . " for item " . $item [ " guid " ] . " ( " . $item [ " id " ] . " ) " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
// Old way - is used by the internal Friendica functions
/// @todo Change all signatur storing functions to the new format
2018-10-15 21:42:55 +00:00
if ( $item [ 'signed_text' ] && $item [ 'signature' ] && $item [ 'signer' ]) {
$message = self :: messageFromSignature ( $item );
2017-11-08 22:02:50 +00:00
} else { // New way
2018-10-15 21:42:55 +00:00
$msg = json_decode ( $item [ 'signed_text' ], true );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$message = [];
2017-11-08 00:37:53 +00:00
if ( is_array ( $msg )) {
2017-11-23 19:01:58 +00:00
foreach ( $msg as $field => $data ) {
2017-11-08 00:37:53 +00:00
if ( ! $item [ " deleted " ]) {
if ( $field == " diaspora_handle " ) {
$field = " author " ;
}
if ( $field == " target_type " ) {
$field = " parent_type " ;
}
}
$message [ $field ] = $data ;
}
2017-11-08 22:02:50 +00:00
} else {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Signature text for item " . $item [ " guid " ] . " ( " . $item [ " id " ] . " ) couldn't be extracted: " . $item [ 'signed_text' ], Logger :: DEBUG );
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
}
$message [ " parent_author_signature " ] = self :: signature ( $owner , $message );
2018-10-30 13:58:45 +00:00
Logger :: log ( " Relayed data " . print_r ( $message , true ), Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
return self :: buildAndTransmit ( $owner , $contact , $type , $message , $public_batch , $item [ " guid " ]);
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Sends a retraction ( deletion ) of a message , like or comment
*
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
*/
2018-07-19 11:07:14 +00:00
public static function sendRetraction ( array $item , array $owner , array $contact , $public_batch = false , $relay = false )
2017-11-08 22:02:50 +00:00
{
2018-02-21 05:15:55 +00:00
$itemaddr = self :: handleFromContact ( $item [ " contact-id " ], $item [ " author-id " ]);
2017-11-08 00:37:53 +00:00
$msg_type = " retraction " ;
if ( $item [ 'id' ] == $item [ 'parent' ]) {
$target_type = " Post " ;
2018-01-15 13:05:12 +00:00
} elseif ( in_array ( $item [ " verb " ], [ ACTIVITY_LIKE , ACTIVITY_DISLIKE ])) {
2017-11-08 00:37:53 +00:00
$target_type = " Like " ;
} else {
$target_type = " Comment " ;
}
2018-01-15 13:05:12 +00:00
$message = [ " author " => $itemaddr ,
2017-11-08 00:37:53 +00:00
" target_guid " => $item [ 'guid' ],
2018-01-15 13:05:12 +00:00
" target_type " => $target_type ];
2017-11-08 00:37:53 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( " Got message " . print_r ( $message , true ), Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
2017-11-23 19:01:58 +00:00
return self :: buildAndTransmit ( $owner , $contact , $msg_type , $message , $public_batch , $item [ " guid " ]);
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Sends a mail
*
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
*/
2018-07-19 11:07:14 +00:00
public static function sendMail ( array $item , array $owner , array $contact )
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
2018-08-19 12:46:11 +00:00
$cnv = DBA :: selectFirst ( 'conv' , [], [ 'id' => $item [ " convid " ], 'uid' => $item [ " uid " ]]);
if ( ! DBA :: isResult ( $cnv )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( " conversation not found. " );
2017-11-08 00:37:53 +00:00
return ;
}
2018-03-04 22:39:41 +00:00
$body = BBCode :: toMarkdown ( $item [ " body " ]);
2018-01-27 02:38:34 +00:00
$created = DateTimeFormat :: utc ( $item [ " created " ], DateTimeFormat :: ATOM );
2017-11-08 00:37:53 +00:00
2018-01-15 13:05:12 +00:00
$msg = [
2017-11-08 00:37:53 +00:00
" 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
if ( $item [ " reply " ]) {
$message = $msg ;
$type = " message " ;
} else {
2018-01-15 13:05:12 +00:00
$message = [
2019-01-07 17:09:10 +00:00
" author " => $cnv [ " creator " ],
" guid " => $cnv [ " guid " ],
" subject " => $cnv [ " subject " ],
" created_at " => DateTimeFormat :: utc ( $cnv [ 'created' ], DateTimeFormat :: ATOM ),
" participants " => $cnv [ " recips " ],
" message " => $msg
];
2017-11-08 00:37:53 +00:00
$type = " conversation " ;
}
2017-11-23 19:01:58 +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
/**
* @ brief Split a name into first name and last name
*
* @ param string $name The name
*
* @ return array The array with " first " and " last "
*/
2017-12-20 21:15:13 +00:00
public static function splitName ( $name ) {
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
/**
* @ brief Create profile data
*
* @ 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
*/
2017-11-08 22:02:50 +00:00
private static function createProfileData ( $uid )
{
$r = q (
" SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.*, `user`.`prvkey` AS `uprvkey`, `contact`.`addr`
2017-11-08 00:37:53 +00:00
FROM `profile`
INNER JOIN `user` ON `profile` . `uid` = `user` . `uid`
INNER JOIN `contact` ON `profile` . `uid` = `contact` . `uid`
WHERE `user` . `uid` = % d AND `profile` . `is-default` AND `contact` . `self` LIMIT 1 " ,
intval ( $uid )
);
if ( ! $r ) {
2018-01-15 13:05:12 +00:00
return [];
2017-11-08 00:37:53 +00:00
}
$profile = $r [ 0 ];
$handle = $profile [ " addr " ];
2017-12-20 20:31:25 +00:00
2017-12-21 04:53:57 +00:00
$split_name = self :: splitName ( $profile [ 'name' ]);
$first = $split_name [ 'first' ];
$last = $split_name [ 'last' ];
2017-12-20 20:31:25 +00:00
2017-11-08 00:37:53 +00:00
$large = System :: baseUrl () . '/photo/custom/300/' . $profile [ 'uid' ] . '.jpg' ;
$medium = System :: baseUrl () . '/photo/custom/100/' . $profile [ 'uid' ] . '.jpg' ;
$small = System :: baseUrl () . '/photo/custom/50/' . $profile [ 'uid' ] . '.jpg' ;
$searchable = (( $profile [ 'publish' ] && $profile [ 'net-publish' ]) ? 'true' : 'false' );
2018-02-14 04:58:46 +00:00
$dob = null ;
$about = null ;
$location = null ;
$tags = null ;
2017-11-08 00:37:53 +00:00
if ( $searchable === 'true' ) {
2018-01-23 22:51:30 +00:00
$dob = '' ;
2017-11-08 00:37:53 +00:00
2018-01-23 22:51:30 +00:00
if ( $profile [ 'dob' ] && ( $profile [ 'dob' ] > '0000-00-00' )) {
list ( $year , $month , $day ) = sscanf ( $profile [ 'dob' ], '%4d-%2d-%2d' );
if ( $year < 1004 ) {
$year = 1004 ;
}
2018-01-27 02:38:34 +00:00
$dob = DateTimeFormat :: utc ( $year . '-' . $month . '-' . $day , 'Y-m-d' );
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
$about = $profile [ 'about' ];
2018-02-15 02:33:55 +00:00
$about = strip_tags ( BBCode :: convert ( $about ));
2017-11-08 00:37:53 +00:00
2017-11-19 22:03:39 +00:00
$location = Profile :: formatLocation ( $profile );
2017-11-08 00:37:53 +00:00
$tags = '' ;
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 ])) {
2017-11-08 00:37:53 +00:00
$tags .= '#' . trim ( $arr [ $x ]) . ' ' ;
2017-11-08 22:02:50 +00:00
}
2017-11-08 00:37:53 +00:00
}
}
}
$tags = trim ( $tags );
}
2018-01-15 13:05:12 +00:00
return [ " author " => $handle ,
2017-11-08 00:37:53 +00:00
" first_name " => $first ,
" last_name " => $last ,
" image_url " => $large ,
" image_url_medium " => $medium ,
" image_url_small " => $small ,
" birthday " => $dob ,
" gender " => $profile [ 'gender' ],
" bio " => $about ,
" location " => $location ,
" searchable " => $searchable ,
" nsfw " => " false " ,
2018-01-15 13:05:12 +00:00
" tag_string " => $tags ];
2017-11-08 00:37:53 +00:00
}
/**
* @ brief Sends profile data
*
2017-11-23 19:01:58 +00:00
* @ param int $uid The user id
* @ param bool $recips optional , default false
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2017-11-23 19:01:58 +00:00
public static function sendProfile ( $uid , $recips = false )
2017-11-08 22:02:50 +00:00
{
if ( ! $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
2017-12-17 21:22:39 +00:00
$owner = User :: getOwnerDataById ( $uid );
if ( ! $owner ) {
2017-12-17 21:10:44 +00:00
return ;
}
2017-11-08 22:02:50 +00:00
if ( ! $recips ) {
$recips = q (
" SELECT `id`,`name`,`network`,`pubkey`,`notify` FROM `contact` WHERE `network` = '%s'
2017-11-08 00:37:53 +00:00
AND `uid` = % d AND `rel` != % d " ,
2018-08-11 20:40:44 +00:00
DBA :: escape ( Protocol :: DIASPORA ),
2017-11-08 00:37:53 +00:00
intval ( $uid ),
2018-07-25 02:53:46 +00:00
intval ( Contact :: SHARING )
2017-11-08 00:37:53 +00:00
);
2017-11-08 22:02:50 +00:00
}
if ( ! $recips ) {
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 );
2019-06-01 06:54:47 +00:00
// @ToDo Split this into single worker jobs
2017-11-08 00:37:53 +00:00
foreach ( $recips as $recip ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Send updated profile data for user " . $uid . " to contact " . $recip [ " id " ], Logger :: DEBUG );
2019-06-01 06:54:47 +00:00
self :: buildAndTransmit ( $owner , $recip , " profile " , $message );
2017-11-08 00:37:53 +00:00
}
}
/**
2018-10-27 11:09:23 +00:00
* @ brief 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
*
2018-10-27 11:09:23 +00:00
* @ return array Signed content
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2018-10-29 21:15:37 +00:00
public static function createLikeSignature ( $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 )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " No owner post, so not storing signature " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return false ;
}
2018-06-16 22:32:57 +00:00
if ( ! in_array ( $item [ " verb " ], [ ACTIVITY_LIKE , ACTIVITY_DISLIKE ])) {
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 ;
}
2018-10-29 21:15:37 +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
}
/**
2018-10-27 14:35:22 +00:00
* @ brief 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 integer $uid The user of that comment
* @ param array $item Item array
2017-11-08 00:37:53 +00:00
*
2018-10-27 14:35:22 +00:00
* @ return array Signed content
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-08 00:37:53 +00:00
*/
2018-10-29 21:15:37 +00:00
public static function createCommentSignature ( $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 )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " No owner post, so not storing signature " , Logger :: DEBUG );
2017-11-08 00:37:53 +00:00
return false ;
}
2018-10-29 21:15:37 +00:00
// This is a workaround for the behaviour of the "insert" function, see mod/item.php
$item [ 'thr-parent' ] = $item [ 'parent-uri' ];
$parent = Item :: selectFirst ([ 'parent-uri' ], [ 'uri' => $item [ 'parent-uri' ]]);
if ( ! DBA :: isResult ( $parent )) {
return ;
2018-10-27 14:35:22 +00:00
}
2018-10-29 21:15:37 +00:00
$item [ 'parent-uri' ] = $parent [ 'parent-uri' ];
2017-11-08 00:37:53 +00:00
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 ;
}
2018-10-29 21:15:37 +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
}
}