2015-06-07 21:18:02 +00:00
< ? php
2016-03-30 22:14:51 +00:00
/**
2020-02-09 15:18:46 +00:00
* @ copyright Copyright ( C ) 2020 , Friendica
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*
2016-03-30 22:14:51 +00:00
*/
2020-02-09 15:18:46 +00:00
2017-11-16 04:09:11 +00:00
namespace Friendica\Protocol ;
2016-03-30 22:14:51 +00:00
2018-07-05 18:57:31 +00:00
use DOMDocument ;
use DOMXPath ;
2020-07-14 14:15:04 +00:00
use Friendica\Content\PageInfo ;
2018-01-27 01:01:32 +00:00
use Friendica\Content\Text\BBCode ;
2018-03-07 21:24:13 +00:00
use Friendica\Content\Text\HTML ;
2020-01-18 14:41:19 +00:00
use Friendica\Core\Cache\Duration ;
2019-10-24 07:06:22 +00:00
use Friendica\Core\Logger ;
2018-08-11 20:40:44 +00:00
use Friendica\Core\Protocol ;
2018-07-20 12:19:26 +00:00
use Friendica\Database\DBA ;
2019-12-15 23:47:24 +00:00
use Friendica\DI ;
2019-10-24 07:06:22 +00:00
use Friendica\Model\APContact ;
2017-12-07 14:04:24 +00:00
use Friendica\Model\Contact ;
2018-01-20 23:52:54 +00:00
use Friendica\Model\Conversation ;
2018-01-28 11:18:08 +00:00
use Friendica\Model\Item ;
2020-04-14 17:00:56 +00:00
use Friendica\Model\ItemURI ;
2020-10-31 13:26:08 +00:00
use Friendica\Model\Post ;
2020-04-17 06:35:20 +00:00
use Friendica\Model\Tag ;
2018-06-18 20:36:34 +00:00
use Friendica\Model\User ;
2017-05-07 18:44:30 +00:00
use Friendica\Network\Probe ;
2018-01-27 02:38:34 +00:00
use Friendica\Util\DateTimeFormat ;
2019-10-18 01:26:15 +00:00
use Friendica\Util\Images ;
2018-07-31 02:06:22 +00:00
use Friendica\Util\Proxy as ProxyUtils ;
2018-11-08 16:28:29 +00:00
use Friendica\Util\Strings ;
2017-11-10 12:45:33 +00:00
use Friendica\Util\XML ;
2017-05-07 18:40:23 +00:00
require_once 'mod/share.php' ;
require_once 'include/api.php' ;
2015-06-20 12:40:30 +00:00
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* This class contain functions for the OStatus protocol
2016-03-30 22:14:51 +00:00
*/
2017-11-16 04:09:11 +00:00
class OStatus
2017-11-09 16:05:18 +00:00
{
2017-09-10 07:23:14 +00:00
private static $itemlist ;
2018-01-15 13:05:12 +00:00
private static $conv_list = [];
2017-09-10 07:23:14 +00:00
2017-09-10 07:29:24 +00:00
/**
2020-01-19 06:05:23 +00:00
* Fetches author data
2017-09-10 07:29:24 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMXPath $xpath The xpath object
* @ param object $context The xml context of the author details
* @ param array $importer user record of the importing user
* @ param array $contact Called by reference , will contain the fetched contact
* @ param bool $onlyfetch Only fetch the header without updating the contact entries
2017-09-10 07:29:24 +00:00
*
* @ return array Array of author related entries for the item
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-09-10 07:29:24 +00:00
*/
2019-01-23 22:04:14 +00:00
private static function fetchAuthor ( DOMXPath $xpath , $context , array $importer , array & $contact = null , $onlyfetch )
2017-11-09 16:05:18 +00:00
{
2018-01-15 13:05:12 +00:00
$author = [];
2018-07-08 12:58:43 +00:00
$author [ " author-link " ] = XML :: getFirstNodeValue ( $xpath , 'atom:author/atom:uri/text()' , $context );
$author [ " author-name " ] = XML :: getFirstNodeValue ( $xpath , 'atom:author/atom:name/text()' , $context );
$addr = XML :: getFirstNodeValue ( $xpath , 'atom:author/atom:email/text()' , $context );
2017-09-10 07:29:24 +00:00
$aliaslink = $author [ " author-link " ];
2018-07-10 12:27:56 +00:00
$alternate_item = $xpath -> query ( " atom:author/atom:link[@rel='alternate'] " , $context ) -> item ( 0 );
if ( is_object ( $alternate_item )) {
foreach ( $alternate_item -> attributes as $attributes ) {
2017-09-10 07:29:24 +00:00
if (( $attributes -> name == " href " ) && ( $attributes -> textContent != " " )) {
$author [ " author-link " ] = $attributes -> textContent ;
}
}
}
2018-07-10 12:27:56 +00:00
$author [ " author-id " ] = Contact :: getIdForURL ( $author [ " author-link " ]);
2019-10-16 12:35:14 +00:00
$author [ 'contact-id' ] = ( $contact [ 'id' ] ? ? 0 ) ? : $author [ 'author-id' ];
2017-09-10 07:29:24 +00:00
2019-01-22 13:53:53 +00:00
$contact = [];
2018-09-06 09:20:45 +00:00
/*
This here would be better , but we would get problems with contacts from the statusnet addon
This is kept here as a reminder for the future
$cid = Contact :: getIdForURL ( $author [ " author-link " ], $importer [ " uid " ]);
if ( $cid ) {
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $cid ]);
}
*/
2017-10-10 09:13:37 +00:00
if ( $aliaslink != '' ) {
2018-03-12 10:45:40 +00:00
$condition = [ " `uid` = ? AND `alias` = ? AND `network` != ? AND `rel` IN (?, ?) " ,
2018-08-11 20:40:44 +00:00
$importer [ " uid " ], $aliaslink , Protocol :: STATUSNET ,
2018-07-25 02:53:46 +00:00
Contact :: SHARING , Contact :: FRIEND ];
2018-07-20 12:19:26 +00:00
$contact = DBA :: selectFirst ( 'contact' , [], $condition );
2017-10-10 09:13:37 +00:00
}
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $contact ) && $author [ " author-link " ] != '' ) {
2017-09-10 07:29:24 +00:00
if ( $aliaslink == " " ) {
$aliaslink = $author [ " author-link " ];
}
2018-03-12 10:45:40 +00:00
$condition = [ " `uid` = ? AND `nurl` IN (?, ?) AND `network` != ? AND `rel` IN (?, ?) " ,
2018-11-08 16:28:29 +00:00
$importer [ " uid " ], Strings :: normaliseLink ( $author [ " author-link " ]), Strings :: normaliseLink ( $aliaslink ),
2018-08-11 20:40:44 +00:00
Protocol :: STATUSNET , Contact :: SHARING , Contact :: FRIEND ];
2018-07-20 12:19:26 +00:00
$contact = DBA :: selectFirst ( 'contact' , [], $condition );
2017-09-17 08:01:22 +00:00
}
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $contact ) && ( $addr != '' )) {
2018-03-12 10:45:40 +00:00
$condition = [ " `uid` = ? AND `addr` = ? AND `network` != ? AND `rel` IN (?, ?) " ,
2018-08-11 20:40:44 +00:00
$importer [ " uid " ], $addr , Protocol :: STATUSNET ,
2018-07-25 02:53:46 +00:00
Contact :: SHARING , Contact :: FRIEND ];
2018-07-20 12:19:26 +00:00
$contact = DBA :: selectFirst ( 'contact' , [], $condition );
2018-02-14 04:58:46 +00:00
}
2017-09-10 07:29:24 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $contact )) {
2018-02-14 04:58:46 +00:00
if ( $contact [ 'blocked' ]) {
$contact [ 'id' ] = - 1 ;
2019-07-21 05:56:57 +00:00
} elseif ( ! empty ( APContact :: getByURL ( $contact [ 'url' ], false ))) {
ActivityPub\Receiver :: switchContact ( $contact [ 'id' ], $importer [ 'uid' ], $contact [ 'url' ]);
2017-09-10 07:29:24 +00:00
}
2018-02-14 04:58:46 +00:00
$author [ " contact-id " ] = $contact [ " id " ];
2017-09-10 07:29:24 +00:00
}
2018-01-15 13:05:12 +00:00
$avatarlist = [];
2017-09-10 07:29:24 +00:00
$avatars = $xpath -> query ( " atom:author/atom:link[@rel='avatar'] " , $context );
2017-11-09 16:05:18 +00:00
foreach ( $avatars as $avatar ) {
2017-09-10 07:29:24 +00:00
$href = " " ;
$width = 0 ;
2017-11-09 16:05:18 +00:00
foreach ( $avatar -> attributes as $attributes ) {
2017-09-10 07:29:24 +00:00
if ( $attributes -> name == " href " ) {
$href = $attributes -> textContent ;
}
if ( $attributes -> name == " width " ) {
$width = $attributes -> textContent ;
}
}
if ( $href != " " ) {
$avatarlist [ $width ] = $href ;
}
}
if ( count ( $avatarlist ) > 0 ) {
krsort ( $avatarlist );
$author [ " author-avatar " ] = Probe :: fixAvatar ( current ( $avatarlist ), $author [ " author-link " ]);
}
2018-07-08 12:58:43 +00:00
$displayname = XML :: getFirstNodeValue ( $xpath , 'atom:author/poco:displayName/text()' , $context );
2017-09-10 07:29:24 +00:00
if ( $displayname != " " ) {
$author [ " author-name " ] = $displayname ;
}
2018-07-10 12:27:56 +00:00
$author [ " owner-id " ] = $author [ " author-id " ];
2017-09-10 07:29:24 +00:00
// Only update the contacts if it is an OStatus contact
2018-08-11 20:40:44 +00:00
if ( DBA :: isResult ( $contact ) && ( $contact [ 'id' ] > 0 ) && ! $onlyfetch && ( $contact [ " network " ] == Protocol :: OSTATUS )) {
2017-10-15 19:34:15 +00:00
2017-09-10 07:29:24 +00:00
// Update contact data
2017-10-10 09:13:37 +00:00
$current = $contact ;
unset ( $current [ 'name-date' ]);
2017-09-10 07:29:24 +00:00
// This query doesn't seem to work
// $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue;
// if ($value != "")
// $contact["notify"] = $value;
// This query doesn't seem to work as well - I hate these queries
// $value = $xpath->query("atom:link[@rel='self' and @type='application/atom+xml']", $context)->item(0)->nodeValue;
// if ($value != "")
// $contact["poll"] = $value;
2017-10-10 09:13:37 +00:00
$contact [ 'url' ] = $author [ " author-link " ];
2018-11-08 16:28:29 +00:00
$contact [ 'nurl' ] = Strings :: normaliseLink ( $contact [ 'url' ]);
2017-10-10 09:13:37 +00:00
2018-07-08 12:58:43 +00:00
$value = XML :: getFirstNodeValue ( $xpath , 'atom:author/atom:uri/text()' , $context );
2017-11-10 05:00:50 +00:00
if ( $value != " " ) {
2017-09-10 07:29:24 +00:00
$contact [ " alias " ] = $value ;
2017-11-10 05:00:50 +00:00
}
2017-09-10 07:29:24 +00:00
2018-07-08 12:58:43 +00:00
$value = XML :: getFirstNodeValue ( $xpath , 'atom:author/poco:displayName/text()' , $context );
2017-11-10 05:00:50 +00:00
if ( $value != " " ) {
2017-09-10 07:29:24 +00:00
$contact [ " name " ] = $value ;
2017-11-10 05:00:50 +00:00
}
2017-09-10 07:29:24 +00:00
2018-07-08 12:58:43 +00:00
$value = XML :: getFirstNodeValue ( $xpath , 'atom:author/poco:preferredUsername/text()' , $context );
2017-11-10 05:00:50 +00:00
if ( $value != " " ) {
2017-09-10 07:29:24 +00:00
$contact [ " nick " ] = $value ;
2017-11-10 05:00:50 +00:00
}
2017-09-10 07:29:24 +00:00
2018-07-08 12:58:43 +00:00
$value = XML :: getFirstNodeValue ( $xpath , 'atom:author/poco:note/text()' , $context );
2017-11-10 05:00:50 +00:00
if ( $value != " " ) {
2018-03-07 21:24:13 +00:00
$contact [ " about " ] = HTML :: toBBCode ( $value );
2017-11-10 05:00:50 +00:00
}
2017-09-10 07:29:24 +00:00
2018-07-08 12:58:43 +00:00
$value = XML :: getFirstNodeValue ( $xpath , 'atom:author/poco:address/poco:formatted/text()' , $context );
2017-11-10 05:00:50 +00:00
if ( $value != " " ) {
2017-09-10 07:29:24 +00:00
$contact [ " location " ] = $value ;
2017-11-10 05:00:50 +00:00
}
2017-09-10 07:29:24 +00:00
2018-01-27 02:38:34 +00:00
$contact [ 'name-date' ] = DateTimeFormat :: utcNow ();
2017-09-10 07:29:24 +00:00
2018-07-20 12:19:26 +00:00
DBA :: update ( 'contact' , $contact , [ 'id' => $contact [ " id " ]], $current );
2017-09-10 07:29:24 +00:00
2017-10-10 09:13:37 +00:00
if ( ! empty ( $author [ " author-avatar " ]) && ( $author [ " author-avatar " ] != $current [ 'avatar' ])) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Update profile picture for contact " . $contact [ " id " ], Logger :: DEBUG );
2020-07-25 11:48:52 +00:00
Contact :: updateAvatar ( $contact [ " id " ], $author [ " author-avatar " ]);
2017-09-10 07:29:24 +00:00
}
// Ensure that we are having this contact (with uid=0)
2020-08-07 13:49:59 +00:00
$cid = Contact :: getIdForURL ( $aliaslink );
2017-09-10 07:29:24 +00:00
if ( $cid ) {
2018-01-10 03:20:33 +00:00
$fields = [ 'url' , 'nurl' , 'name' , 'nick' , 'alias' , 'about' , 'location' ];
2018-07-20 12:19:26 +00:00
$old_contact = DBA :: selectFirst ( 'contact' , $fields , [ 'id' => $cid ]);
2017-09-17 08:01:22 +00:00
2017-09-10 07:29:24 +00:00
// Update it with the current values
2018-01-15 13:05:12 +00:00
$fields = [ 'url' => $author [ " author-link " ], 'name' => $contact [ " name " ],
2018-11-08 16:28:29 +00:00
'nurl' => Strings :: normaliseLink ( $author [ " author-link " ]),
2017-09-17 08:01:22 +00:00
'nick' => $contact [ " nick " ], 'alias' => $contact [ " alias " ],
'about' => $contact [ " about " ], 'location' => $contact [ " location " ],
2018-01-27 02:38:34 +00:00
'success_update' => DateTimeFormat :: utcNow (), 'last-update' => DateTimeFormat :: utcNow ()];
2017-09-17 08:01:22 +00:00
2018-07-20 12:19:26 +00:00
DBA :: update ( 'contact' , $fields , [ 'id' => $cid ], $old_contact );
2017-09-10 07:29:24 +00:00
// Update the avatar
2018-07-10 12:27:56 +00:00
if ( ! empty ( $author [ " author-avatar " ])) {
2020-07-25 11:48:52 +00:00
Contact :: updateAvatar ( $cid , $author [ " author-avatar " ]);
2018-07-10 12:27:56 +00:00
}
2017-09-10 07:29:24 +00:00
}
2020-03-25 07:29:47 +00:00
} elseif ( empty ( $contact [ " network " ]) || ( $contact [ " network " ] != Protocol :: DFRN )) {
2019-01-22 13:53:53 +00:00
$contact = [];
2017-09-10 07:29:24 +00:00
}
return $author ;
}
/**
2020-01-19 06:05:23 +00:00
* Fetches author data from a given XML string
2017-09-10 07:29:24 +00:00
*
2017-11-09 16:05:18 +00:00
* @ param string $xml The XML
* @ param array $importer user record of the importing user
2017-09-10 07:29:24 +00:00
*
* @ return array Array of author related entries for the item
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-09-10 07:29:24 +00:00
*/
2018-07-22 16:35:20 +00:00
public static function salmonAuthor ( $xml , array $importer )
2017-11-09 16:05:18 +00:00
{
2017-11-10 05:00:50 +00:00
if ( $xml == " " ) {
2017-09-10 07:29:24 +00:00
return ;
2017-11-10 05:00:50 +00:00
}
2017-09-10 07:29:24 +00:00
$doc = new DOMDocument ();
@ $doc -> loadXML ( $xml );
2017-12-17 20:24:57 +00:00
$xpath = new DOMXPath ( $doc );
2019-10-24 22:32:35 +00:00
$xpath -> registerNamespace ( 'atom' , ActivityNamespace :: ATOM1 );
$xpath -> registerNamespace ( 'thr' , ActivityNamespace :: THREAD );
$xpath -> registerNamespace ( 'georss' , ActivityNamespace :: GEORSS );
$xpath -> registerNamespace ( 'activity' , ActivityNamespace :: ACTIVITY );
$xpath -> registerNamespace ( 'media' , ActivityNamespace :: MEDIA );
$xpath -> registerNamespace ( 'poco' , ActivityNamespace :: POCO );
$xpath -> registerNamespace ( 'ostatus' , ActivityNamespace :: OSTATUS );
$xpath -> registerNamespace ( 'statusnet' , ActivityNamespace :: STATUSNET );
2017-09-10 07:29:24 +00:00
2018-03-13 06:21:44 +00:00
$contact = [ " id " => 0 ];
2017-09-10 07:29:24 +00:00
2018-03-13 06:21:44 +00:00
// Fetch the first author
$authordata = $xpath -> query ( '//author' ) -> item ( 0 );
$author = self :: fetchAuthor ( $xpath , $authordata , $importer , $contact , true );
return $author ;
2017-09-10 07:29:24 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Read attributes from element
2017-09-10 07:29:24 +00:00
*
* @ param object $element Element object
*
* @ return array attributes
*/
2017-11-16 04:09:11 +00:00
private static function readAttributes ( $element )
2017-11-09 16:05:18 +00:00
{
2018-01-15 13:05:12 +00:00
$attribute = [];
2017-09-10 07:29:24 +00:00
2017-11-09 16:05:18 +00:00
foreach ( $element -> attributes as $attributes ) {
2017-09-10 07:29:24 +00:00
$attribute [ $attributes -> name ] = $attributes -> textContent ;
}
return $attribute ;
}
2017-09-10 07:23:14 +00:00
/**
2020-01-19 06:05:23 +00:00
* Imports an XML string containing OStatus elements
2017-09-10 07:23:14 +00:00
*
2017-11-09 16:05:18 +00:00
* @ param string $xml The XML
* @ param array $importer user record of the importing user
* @ param array $contact contact
* @ param string $hub Called by reference , returns the fetched hub data
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-09-10 07:23:14 +00:00
*/
2019-01-07 17:51:48 +00:00
public static function import ( $xml , array $importer , array & $contact , & $hub )
2017-11-09 16:05:18 +00:00
{
2017-09-10 07:21:23 +00:00
self :: process ( $xml , $importer , $contact , $hub );
}
2017-09-10 07:23:14 +00:00
/**
2020-01-19 06:05:23 +00:00
* Internal feed processing
2017-09-10 07:23:14 +00:00
*
2017-11-09 16:05:18 +00:00
* @ param string $xml The XML
* @ param array $importer user record of the importing user
2017-11-23 19:01:58 +00:00
* @ param array $contact contact
2017-11-09 16:05:18 +00:00
* @ param string $hub Called by reference , returns the fetched hub data
* @ param boolean $stored Is the post fresh imported or from the database ?
2017-09-10 07:52:07 +00:00
* @ param boolean $initialize Is it the leading post so that data has to be initialized ?
*
* @ return boolean Could the XML be processed ?
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-09-10 07:23:14 +00:00
*/
2019-01-23 22:04:14 +00:00
private static function process ( $xml , array $importer , array & $contact = null , & $hub , $stored = false , $initialize = true )
2017-11-09 16:05:18 +00:00
{
2017-09-10 07:21:23 +00:00
if ( $initialize ) {
2018-01-15 13:05:12 +00:00
self :: $itemlist = [];
self :: $conv_list = [];
2017-09-10 07:21:23 +00:00
}
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Import OStatus message for user ' . $importer [ 'uid' ], Logger :: DEBUG );
2017-09-10 07:21:23 +00:00
if ( $xml == " " ) {
return false ;
}
$doc = new DOMDocument ();
@ $doc -> loadXML ( $xml );
2017-12-17 20:24:57 +00:00
$xpath = new DOMXPath ( $doc );
2019-10-24 22:32:35 +00:00
$xpath -> registerNamespace ( 'atom' , ActivityNamespace :: ATOM1 );
$xpath -> registerNamespace ( 'thr' , ActivityNamespace :: THREAD );
$xpath -> registerNamespace ( 'georss' , ActivityNamespace :: GEORSS );
$xpath -> registerNamespace ( 'activity' , ActivityNamespace :: ACTIVITY );
$xpath -> registerNamespace ( 'media' , ActivityNamespace :: MEDIA );
$xpath -> registerNamespace ( 'poco' , ActivityNamespace :: POCO );
$xpath -> registerNamespace ( 'ostatus' , ActivityNamespace :: OSTATUS );
$xpath -> registerNamespace ( 'statusnet' , ActivityNamespace :: STATUSNET );
2017-09-10 07:21:23 +00:00
2017-09-10 07:23:14 +00:00
$hub = " " ;
2018-07-10 12:27:56 +00:00
$hub_items = $xpath -> query ( " /atom:feed/atom:link[@rel='hub'] " ) -> item ( 0 );
if ( is_object ( $hub_items )) {
$hub_attributes = $hub_items -> attributes ;
if ( is_object ( $hub_attributes )) {
foreach ( $hub_attributes as $hub_attribute ) {
if ( $hub_attribute -> name == " href " ) {
$hub = $hub_attribute -> textContent ;
2018-10-30 13:58:45 +00:00
Logger :: log ( " Found hub " . $hub , Logger :: DEBUG );
2018-07-10 12:27:56 +00:00
}
2017-09-10 07:23:14 +00:00
}
}
}
2017-09-10 07:21:23 +00:00
2018-01-15 13:05:12 +00:00
$header = [];
2017-09-10 07:21:23 +00:00
$header [ " uid " ] = $importer [ " uid " ];
2018-08-11 20:40:44 +00:00
$header [ " network " ] = Protocol :: OSTATUS ;
2017-09-10 07:21:23 +00:00
$header [ " wall " ] = 0 ;
$header [ " origin " ] = 0 ;
2018-07-03 04:58:34 +00:00
$header [ " gravity " ] = GRAVITY_COMMENT ;
2017-09-10 07:21:23 +00:00
2018-08-30 04:05:32 +00:00
if ( ! is_object ( $doc -> firstChild ) || empty ( $doc -> firstChild -> tagName )) {
2018-07-31 05:54:25 +00:00
return false ;
}
2017-09-10 07:21:23 +00:00
$first_child = $doc -> firstChild -> tagName ;
if ( $first_child == " feed " ) {
$entries = $xpath -> query ( '/atom:feed/atom:entry' );
} else {
$entries = $xpath -> query ( '/atom:entry' );
2017-09-10 21:56:05 +00:00
}
if ( $entries -> length == 1 ) {
2017-09-11 12:44:37 +00:00
// We reformat the XML to make it better readable
2017-09-10 21:56:05 +00:00
$doc2 = new DOMDocument ();
$doc2 -> loadXML ( $xml );
$doc2 -> preserveWhiteSpace = false ;
$doc2 -> formatOutput = true ;
$xml2 = $doc2 -> saveXML ();
2018-08-05 10:23:57 +00:00
$header [ " protocol " ] = Conversation :: PARCEL_SALMON ;
2017-09-10 21:56:05 +00:00
$header [ " source " ] = $xml2 ;
2017-09-11 12:44:37 +00:00
} elseif ( ! $initialize ) {
return false ;
2017-09-10 07:21:23 +00:00
}
// Fetch the first author
$authordata = $xpath -> query ( '//author' ) -> item ( 0 );
2017-11-16 04:09:11 +00:00
$author = self :: fetchAuthor ( $xpath , $authordata , $importer , $contact , $stored );
2017-09-10 07:21:23 +00:00
// Reverse the order of the entries
2018-01-15 13:05:12 +00:00
$entrylist = [];
2017-09-10 07:21:23 +00:00
2017-11-09 16:05:18 +00:00
foreach ( $entries as $entry ) {
2017-09-10 07:21:23 +00:00
$entrylist [] = $entry ;
}
2017-11-09 16:05:18 +00:00
foreach ( array_reverse ( $entrylist ) as $entry ) {
2017-09-10 07:21:23 +00:00
// fetch the author
$authorelement = $xpath -> query ( '/atom:entry/atom:author' , $entry );
2017-09-24 15:31:09 +00:00
if ( $authorelement -> length == 0 ) {
$authorelement = $xpath -> query ( 'atom:author' , $entry );
}
2017-09-10 07:21:23 +00:00
if ( $authorelement -> length > 0 ) {
2017-11-16 04:09:11 +00:00
$author = self :: fetchAuthor ( $xpath , $entry , $importer , $contact , $stored );
2017-09-10 07:21:23 +00:00
}
$item = array_merge ( $header , $author );
2018-07-08 12:58:43 +00:00
$item [ " uri " ] = XML :: getFirstNodeValue ( $xpath , 'atom:id/text()' , $entry );
2020-04-14 17:00:56 +00:00
$item [ 'uri-id' ] = ItemURI :: insert ([ 'uri' => $item [ 'uri' ]]);
2017-09-17 08:01:22 +00:00
2018-07-08 12:58:43 +00:00
$item [ " verb " ] = XML :: getFirstNodeValue ( $xpath , 'activity:verb/text()' , $entry );
2017-09-10 07:21:23 +00:00
2017-09-17 08:01:22 +00:00
// Delete a message
2019-10-23 22:25:43 +00:00
if ( in_array ( $item [ " verb " ], [ 'qvitter-delete-notice' , Activity :: DELETE , 'delete' ])) {
2017-09-17 08:01:22 +00:00
self :: deleteNotice ( $item );
continue ;
}
2019-10-24 22:10:20 +00:00
if ( in_array ( $item [ " verb " ], [ Activity :: O_UNFAVOURITE , Activity :: UNFAVORITE ])) {
2017-09-17 08:01:22 +00:00
// Ignore "Unfavorite" message
2018-10-30 13:58:45 +00:00
Logger :: log ( " Ignore unfavorite message " . print_r ( $item , true ), Logger :: DEBUG );
2017-09-10 07:21:23 +00:00
continue ;
}
2017-09-17 08:01:22 +00:00
// Deletions come with the same uri, so we check for duplicates after processing deletions
2018-06-27 19:37:13 +00:00
if ( Item :: exists ([ 'uid' => $importer [ " uid " ], 'uri' => $item [ " uri " ]])) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Post with URI ' . $item [ " uri " ] . ' already existed for user ' . $importer [ " uid " ] . '.' , Logger :: DEBUG );
2017-09-17 08:01:22 +00:00
continue ;
} else {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Processing post with URI ' . $item [ " uri " ] . ' for user ' . $importer [ " uid " ] . '.' , Logger :: DEBUG );
2017-09-17 08:01:22 +00:00
}
2019-10-23 22:25:43 +00:00
if ( $item [ " verb " ] == Activity :: JOIN ) {
2017-09-10 07:21:23 +00:00
// ignore "Join" messages
2018-10-30 13:58:45 +00:00
Logger :: log ( " Ignore join message " . print_r ( $item , true ), Logger :: DEBUG );
2017-09-17 08:01:22 +00:00
continue ;
}
if ( $item [ " verb " ] == " http://mastodon.social/schema/1.0/block " ) {
// ignore mastodon "block" messages
2018-10-30 13:58:45 +00:00
Logger :: log ( " Ignore block message " . print_r ( $item , true ), Logger :: DEBUG );
2017-09-10 07:21:23 +00:00
continue ;
}
2019-10-23 22:25:43 +00:00
if ( $item [ " verb " ] == Activity :: FOLLOW ) {
2019-05-19 22:43:19 +00:00
Contact :: addRelationship ( $importer , $contact , $item );
2017-09-10 07:21:23 +00:00
continue ;
}
2019-10-24 22:10:20 +00:00
if ( $item [ " verb " ] == Activity :: O_UNFOLLOW ) {
2018-07-10 12:27:56 +00:00
$dummy = null ;
2018-01-28 17:26:39 +00:00
Contact :: removeFollower ( $importer , $contact , $item , $dummy );
2017-09-10 07:21:23 +00:00
continue ;
}
2019-10-23 22:25:43 +00:00
if ( $item [ " verb " ] == Activity :: FAVORITE ) {
2017-09-10 07:21:23 +00:00
$orig_uri = $xpath -> query ( " activity:object/atom:id " , $entry ) -> item ( 0 ) -> nodeValue ;
2018-10-29 21:20:46 +00:00
Logger :: log ( " Favorite " . $orig_uri . " " . print_r ( $item , true ));
2017-09-10 07:21:23 +00:00
2019-10-23 22:25:43 +00:00
$item [ " verb " ] = Activity :: LIKE ;
2017-09-10 07:21:23 +00:00
$item [ " parent-uri " ] = $orig_uri ;
2018-06-27 18:09:33 +00:00
$item [ " gravity " ] = GRAVITY_ACTIVITY ;
2019-10-24 22:10:20 +00:00
$item [ " object-type " ] = Activity\ObjectType :: NOTE ;
2017-09-10 07:21:23 +00:00
}
// http://activitystrea.ms/schema/1.0/rsvp-yes
2019-10-23 22:25:43 +00:00
if ( ! in_array ( $item [ " verb " ], [ Activity :: POST , Activity :: LIKE , Activity :: SHARE ])) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Unhandled verb " . $item [ " verb " ] . " " . print_r ( $item , true ), Logger :: DEBUG );
2017-09-10 07:21:23 +00:00
}
self :: processPost ( $xpath , $entry , $item , $importer );
if ( $initialize && ( count ( self :: $itemlist ) > 0 )) {
2017-09-14 10:00:45 +00:00
if ( self :: $itemlist [ 0 ][ 'uri' ] == self :: $itemlist [ 0 ][ 'parent-uri' ]) {
// We will import it everytime, when it is started by our contacts
2019-07-21 05:56:57 +00:00
$valid = Contact :: isSharingByURL ( self :: $itemlist [ 0 ][ 'author-link' ], self :: $itemlist [ 0 ][ 'uid' ]);
2017-09-14 10:00:45 +00:00
if ( ! $valid ) {
// If not, then it depends on this setting
2020-01-18 15:50:57 +00:00
$valid = (( self :: $itemlist [ 0 ][ 'uid' ] == 0 ) || ! DI :: pConfig () -> get ( self :: $itemlist [ 0 ][ 'uid' ], 'system' , 'accept_only_sharer' , false ));
2018-02-18 16:43:18 +00:00
if ( $valid ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item with uri " . self :: $itemlist [ 0 ][ 'uri' ] . " will be imported due to the system settings. " , Logger :: DEBUG );
2018-02-18 16:43:18 +00:00
}
} else {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item with uri " . self :: $itemlist [ 0 ][ 'uri' ] . " belongs to a contact ( " . self :: $itemlist [ 0 ][ 'contact-id' ] . " ). It will be imported. " , Logger :: DEBUG );
2017-09-14 10:00:45 +00:00
}
if ( $valid ) {
// Never post a thread when the only interaction by our contact was a like
$valid = false ;
2019-10-23 22:25:43 +00:00
$verbs = [ Activity :: POST , Activity :: SHARE ];
2017-11-09 16:05:18 +00:00
foreach ( self :: $itemlist as $item ) {
2019-07-21 05:56:57 +00:00
if ( in_array ( $item [ 'verb' ], $verbs ) && Contact :: isSharingByURL ( $item [ 'author-link' ], $item [ 'uid' ])) {
2017-09-14 10:00:45 +00:00
$valid = true ;
}
2017-09-10 07:21:23 +00:00
}
2018-02-18 16:43:18 +00:00
if ( $valid ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item with uri " . self :: $itemlist [ 0 ][ 'uri' ] . " will be imported since the thread contains posts or shares. " , Logger :: DEBUG );
2018-02-18 16:43:18 +00:00
}
2017-09-10 07:21:23 +00:00
}
2017-09-14 10:00:45 +00:00
} else {
// But we will only import complete threads
2018-06-27 19:37:13 +00:00
$valid = Item :: exists ([ 'uid' => $importer [ " uid " ], 'uri' => self :: $itemlist [ 0 ][ 'parent-uri' ]]);
2018-02-18 16:43:18 +00:00
if ( $valid ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item with uri " . self :: $itemlist [ 0 ][ " uri " ] . " belongs to parent " . self :: $itemlist [ 0 ][ 'parent-uri' ] . " of user " . $importer [ " uid " ] . " . It will be imported. " , Logger :: DEBUG );
2018-02-18 16:43:18 +00:00
}
2017-09-10 07:21:23 +00:00
}
if ( $valid ) {
$default_contact = 0 ;
for ( $key = count ( self :: $itemlist ) - 1 ; $key >= 0 ; $key -- ) {
if ( empty ( self :: $itemlist [ $key ][ 'contact-id' ])) {
self :: $itemlist [ $key ][ 'contact-id' ] = $default_contact ;
} else {
$default_contact = $item [ 'contact-id' ];
}
}
2017-11-09 16:05:18 +00:00
foreach ( self :: $itemlist as $item ) {
2018-06-27 19:37:13 +00:00
$found = Item :: exists ([ 'uid' => $importer [ " uid " ], 'uri' => $item [ " uri " ]]);
2017-09-10 07:21:23 +00:00
if ( $found ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item with uri " . $item [ " uri " ] . " for user " . $importer [ " uid " ] . " already exists. " , Logger :: DEBUG );
2017-10-03 20:52:24 +00:00
} elseif ( $item [ 'contact-id' ] < 0 ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " Item with uri " . $item [ " uri " ] . " is from a blocked contact. " , Logger :: DEBUG );
2017-09-10 07:21:23 +00:00
} else {
2020-08-06 18:53:45 +00:00
$ret = Item :: insert ( $item );
Logger :: log ( " Item with uri " . $item [ " uri " ] . " for user " . $importer [ " uid " ] . ' stored. Return value: ' . $ret );
2017-09-10 07:21:23 +00:00
}
}
}
2018-01-15 13:05:12 +00:00
self :: $itemlist = [];
2017-09-10 07:21:23 +00:00
}
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Processing done for post with URI ' . $item [ " uri " ] . ' for user ' . $importer [ " uid " ] . '.' , Logger :: DEBUG );
2017-09-10 07:21:23 +00:00
}
return true ;
}
2017-11-23 19:01:58 +00:00
/**
2018-07-22 16:35:20 +00:00
* Removes notice item from database
2019-01-06 21:06:53 +00:00
*
2018-07-22 16:35:20 +00:00
* @ param array $item item
2017-11-23 19:01:58 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-11-23 19:01:58 +00:00
*/
2018-07-22 16:35:20 +00:00
private static function deleteNotice ( array $item )
2017-11-09 16:05:18 +00:00
{
2018-06-27 19:37:13 +00:00
$condition = [ 'uid' => $item [ 'uid' ], 'author-id' => $item [ 'author-id' ], 'uri' => $item [ 'uri' ]];
if ( ! Item :: exists ( $condition )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Item from ' . $item [ 'author-link' ] . ' with uri ' . $item [ 'uri' ] . ' for user ' . $item [ 'uid' ] . " wasn't found. We don't delete it. " );
2017-09-17 08:01:22 +00:00
return ;
}
2020-03-03 06:47:28 +00:00
Item :: markForDeletion ( $condition );
2017-09-17 08:01:22 +00:00
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Deleted item with uri ' . $item [ 'uri' ] . ' for user ' . $item [ 'uid' ]);
2017-09-17 08:01:22 +00:00
}
2017-09-10 07:52:07 +00:00
/**
2020-01-19 06:05:23 +00:00
* Processes the XML for a post
2017-09-10 07:52:07 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMXPath $xpath The xpath object
* @ param object $entry The xml entry that is processed
* @ param array $item The item array
* @ param array $importer user record of the importing user
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-09-10 07:52:07 +00:00
*/
2018-07-22 16:35:20 +00:00
private static function processPost ( DOMXPath $xpath , $entry , array & $item , array $importer )
2017-11-09 16:05:18 +00:00
{
2018-07-08 12:58:43 +00:00
$item [ " body " ] = HTML :: toBBCode ( XML :: getFirstNodeValue ( $xpath , 'atom:content/text()' , $entry ));
$item [ " object-type " ] = XML :: getFirstNodeValue ( $xpath , 'activity:object-type/text()' , $entry );
2019-10-24 22:10:20 +00:00
if (( $item [ " object-type " ] == Activity\ObjectType :: BOOKMARK ) || ( $item [ " object-type " ] == Activity\ObjectType :: EVENT )) {
2018-07-08 12:58:43 +00:00
$item [ " title " ] = XML :: getFirstNodeValue ( $xpath , 'atom:title/text()' , $entry );
$item [ " body " ] = XML :: getFirstNodeValue ( $xpath , 'atom:summary/text()' , $entry );
2019-10-24 22:10:20 +00:00
} elseif ( $item [ " object-type " ] == Activity\ObjectType :: QUESTION ) {
2018-07-08 12:58:43 +00:00
$item [ " title " ] = XML :: getFirstNodeValue ( $xpath , 'atom:title/text()' , $entry );
2017-09-10 07:21:23 +00:00
}
2018-07-08 12:58:43 +00:00
$item [ " created " ] = XML :: getFirstNodeValue ( $xpath , 'atom:published/text()' , $entry );
$item [ " edited " ] = XML :: getFirstNodeValue ( $xpath , 'atom:updated/text()' , $entry );
$item [ 'conversation-uri' ] = XML :: getFirstNodeValue ( $xpath , 'ostatus:conversation/text()' , $entry );
2017-09-10 07:21:23 +00:00
$conv = $xpath -> query ( 'ostatus:conversation' , $entry );
if ( is_object ( $conv -> item ( 0 ))) {
2017-11-09 16:05:18 +00:00
foreach ( $conv -> item ( 0 ) -> attributes as $attributes ) {
2017-09-10 07:21:23 +00:00
if ( $attributes -> name == " ref " ) {
$item [ 'conversation-uri' ] = $attributes -> textContent ;
}
if ( $attributes -> name == " href " ) {
$item [ 'conversation-href' ] = $attributes -> textContent ;
}
}
}
$related = " " ;
$inreplyto = $xpath -> query ( 'thr:in-reply-to' , $entry );
if ( is_object ( $inreplyto -> item ( 0 ))) {
2017-11-09 16:05:18 +00:00
foreach ( $inreplyto -> item ( 0 ) -> attributes as $attributes ) {
2017-09-10 07:21:23 +00:00
if ( $attributes -> name == " ref " ) {
$item [ " parent-uri " ] = $attributes -> textContent ;
}
if ( $attributes -> name == " href " ) {
$related = $attributes -> textContent ;
}
}
}
$georsspoint = $xpath -> query ( 'georss:point' , $entry );
if ( ! empty ( $georsspoint ) && ( $georsspoint -> length > 0 )) {
$item [ " coord " ] = $georsspoint -> item ( 0 ) -> nodeValue ;
}
$categories = $xpath -> query ( 'atom:category' , $entry );
if ( $categories ) {
2017-11-09 16:05:18 +00:00
foreach ( $categories as $category ) {
foreach ( $category -> attributes as $attributes ) {
2018-11-22 14:23:42 +00:00
if ( $attributes -> name == 'term' ) {
2020-04-14 17:00:56 +00:00
// Store the hashtag
2020-05-05 05:11:59 +00:00
Tag :: store ( $item [ 'uri-id' ], Tag :: HASHTAG , $attributes -> textContent );
2017-09-10 07:21:23 +00:00
}
}
}
}
$self = '' ;
$add_body = '' ;
$links = $xpath -> query ( 'atom:link' , $entry );
if ( $links ) {
$link_data = self :: processLinks ( $links , $item );
$self = $link_data [ 'self' ];
$add_body = $link_data [ 'add_body' ];
}
$repeat_of = " " ;
$notice_info = $xpath -> query ( 'statusnet:notice_info' , $entry );
if ( $notice_info && ( $notice_info -> length > 0 )) {
2017-11-09 16:05:18 +00:00
foreach ( $notice_info -> item ( 0 ) -> attributes as $attributes ) {
2017-09-10 07:21:23 +00:00
if ( $attributes -> name == " source " ) {
$item [ " app " ] = strip_tags ( $attributes -> textContent );
}
if ( $attributes -> name == " repeat_of " ) {
$repeat_of = $attributes -> textContent ;
}
}
}
// Is it a repeated post?
2019-10-23 22:25:43 +00:00
if (( $repeat_of != " " ) || ( $item [ " verb " ] == Activity :: SHARE )) {
2017-09-10 07:21:23 +00:00
$link_data = self :: processRepeatedItem ( $xpath , $entry , $item , $importer );
if ( ! empty ( $link_data [ 'add_body' ])) {
$add_body .= $link_data [ 'add_body' ];
}
}
$item [ " body " ] .= $add_body ;
// Only add additional data when there is no picture in the post
2017-11-09 16:05:18 +00:00
if ( ! strstr ( $item [ " body " ], '[/img]' )) {
2020-07-17 23:15:43 +00:00
$item [ " body " ] = PageInfo :: searchAndAppendToBody ( $item [ " body " ]);
2017-09-10 07:21:23 +00:00
}
2020-04-19 07:24:36 +00:00
Tag :: storeFromBody ( $item [ 'uri-id' ], $item [ 'body' ]);
2017-09-10 07:21:23 +00:00
// Mastodon Content Warning
2019-10-23 22:25:43 +00:00
if (( $item [ " verb " ] == Activity :: POST ) && $xpath -> evaluate ( 'boolean(atom:summary)' , $entry )) {
2018-07-08 12:58:43 +00:00
$clear_text = XML :: getFirstNodeValue ( $xpath , 'atom:summary/text()' , $entry );
2018-03-13 21:58:05 +00:00
if ( ! empty ( $clear_text )) {
2018-03-23 10:15:55 +00:00
$item [ 'content-warning' ] = HTML :: toBBCode ( $clear_text );
2018-03-13 21:58:05 +00:00
}
2017-09-10 07:21:23 +00:00
}
2017-09-11 12:44:37 +00:00
if (( $self != '' ) && empty ( $item [ 'protocol' ])) {
self :: fetchSelf ( $self , $item );
}
2017-09-10 21:56:05 +00:00
if ( ! empty ( $item [ " conversation-href " ])) {
self :: fetchConversation ( $item [ 'conversation-href' ], $item [ 'conversation-uri' ]);
}
2018-03-15 21:33:28 +00:00
if ( isset ( $item [ " parent-uri " ])) {
2018-06-27 19:37:13 +00:00
if ( ! Item :: exists ([ 'uid' => $importer [ " uid " ], 'uri' => $item [ 'parent-uri' ]])) {
2018-03-15 21:33:28 +00:00
if ( $related != '' ) {
self :: fetchRelated ( $related , $item [ " parent-uri " ], $importer );
}
2017-09-14 19:23:14 +00:00
} else {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Reply with URI ' . $item [ " uri " ] . ' already existed for user ' . $importer [ " uid " ] . '.' , Logger :: DEBUG );
2017-09-14 19:23:14 +00:00
}
2017-09-10 07:21:23 +00:00
} else {
$item [ " parent-uri " ] = $item [ " uri " ];
2018-07-03 04:58:34 +00:00
$item [ " gravity " ] = GRAVITY_PARENT ;
2017-09-10 07:21:23 +00:00
}
2017-09-11 12:44:37 +00:00
if (( $item [ 'author-link' ] != '' ) && ! empty ( $item [ 'protocol' ])) {
2018-01-20 23:52:54 +00:00
$item = Conversation :: insert ( $item );
2017-09-10 07:21:23 +00:00
}
self :: $itemlist [] = $item ;
}
2017-09-10 21:56:05 +00:00
/**
2020-01-19 06:05:23 +00:00
* Fetch the conversation for posts
2017-09-10 21:56:05 +00:00
*
2017-11-09 16:05:18 +00:00
* @ param string $conversation The link to the conversation
2017-09-10 21:56:05 +00:00
* @ param string $conversation_uri The conversation in " uri " format
2017-11-23 19:01:58 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2017-09-10 21:56:05 +00:00
*/
2017-11-09 16:05:18 +00:00
private static function fetchConversation ( $conversation , $conversation_uri )
{
2017-09-10 21:56:05 +00:00
// Ensure that we only store a conversation once in a process
if ( isset ( self :: $conv_list [ $conversation ])) {
return ;
}
self :: $conv_list [ $conversation ] = true ;
2020-10-10 19:07:17 +00:00
$curlResult = DI :: httpRequest () -> get ( $conversation , [ 'accept_content' => 'application/atom+xml, text/html' ]);
2017-09-10 21:56:05 +00:00
2018-10-10 19:08:43 +00:00
if ( ! $curlResult -> isSuccess ()) {
2017-09-10 21:56:05 +00:00
return ;
}
$xml = '' ;
2020-10-11 21:26:03 +00:00
if ( stristr ( $curlResult -> getHeader (), 'Content-Type: application/atom+xml' )) {
2018-10-10 19:08:43 +00:00
$xml = $curlResult -> getBody ();
2017-09-10 21:56:05 +00:00
}
if ( $xml == '' ) {
$doc = new DOMDocument ();
2018-10-10 19:08:43 +00:00
if ( !@ $doc -> loadHTML ( $curlResult -> getBody ())) {
2017-09-10 21:56:05 +00:00
return ;
}
2017-12-17 20:24:57 +00:00
$xpath = new DOMXPath ( $doc );
2017-09-10 21:56:05 +00:00
$links = $xpath -> query ( '//link' );
if ( $links ) {
2018-02-14 04:58:46 +00:00
$file = '' ;
2017-11-09 16:05:18 +00:00
foreach ( $links as $link ) {
2017-11-16 04:09:11 +00:00
$attribute = self :: readAttributes ( $link );
2017-09-10 21:56:05 +00:00
if (( $attribute [ 'rel' ] == 'alternate' ) && ( $attribute [ 'type' ] == 'application/atom+xml' )) {
$file = $attribute [ 'href' ];
}
}
if ( $file != '' ) {
2020-03-04 21:35:09 +00:00
$conversation_atom = DI :: httpRequest () -> get ( $attribute [ 'href' ]);
2017-09-10 21:56:05 +00:00
2018-10-10 19:08:43 +00:00
if ( $conversation_atom -> isSuccess ()) {
$xml = $conversation_atom -> getBody ();
2017-09-10 21:56:05 +00:00
}
}
}
}
if ( $xml == '' ) {
return ;
}
self :: storeConversation ( $xml , $conversation , $conversation_uri );
}
/**
2020-01-19 06:05:23 +00:00
* Store a feed in several conversation entries
2017-09-10 21:56:05 +00:00
*
2017-11-09 16:05:18 +00:00
* @ param string $xml The feed
* @ param string $conversation conversation
* @ param string $conversation_uri conversation uri
2017-11-23 19:01:58 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2017-09-10 21:56:05 +00:00
*/
2017-11-09 16:05:18 +00:00
private static function storeConversation ( $xml , $conversation = '' , $conversation_uri = '' )
{
2017-09-10 21:56:05 +00:00
$doc = new DOMDocument ();
@ $doc -> loadXML ( $xml );
2017-12-17 20:24:57 +00:00
$xpath = new DOMXPath ( $doc );
2019-10-24 22:32:35 +00:00
$xpath -> registerNamespace ( 'atom' , ActivityNamespace :: ATOM1 );
$xpath -> registerNamespace ( 'thr' , ActivityNamespace :: THREAD );
$xpath -> registerNamespace ( 'ostatus' , ActivityNamespace :: OSTATUS );
2017-09-10 21:56:05 +00:00
$entries = $xpath -> query ( '/atom:feed/atom:entry' );
// Now store the entries
2017-11-09 16:05:18 +00:00
foreach ( $entries as $entry ) {
2017-09-10 21:56:05 +00:00
$doc2 = new DOMDocument ();
$doc2 -> preserveWhiteSpace = false ;
$doc2 -> formatOutput = true ;
2018-01-15 13:05:12 +00:00
$conv_data = [];
2017-09-10 21:56:05 +00:00
2018-08-05 10:23:57 +00:00
$conv_data [ 'protocol' ] = Conversation :: PARCEL_SPLIT_CONVERSATION ;
2018-08-11 20:40:44 +00:00
$conv_data [ 'network' ] = Protocol :: OSTATUS ;
2018-07-08 12:58:43 +00:00
$conv_data [ 'uri' ] = XML :: getFirstNodeValue ( $xpath , 'atom:id/text()' , $entry );
2017-09-10 21:56:05 +00:00
$inreplyto = $xpath -> query ( 'thr:in-reply-to' , $entry );
if ( is_object ( $inreplyto -> item ( 0 ))) {
2017-11-09 16:05:18 +00:00
foreach ( $inreplyto -> item ( 0 ) -> attributes as $attributes ) {
2017-09-10 21:56:05 +00:00
if ( $attributes -> name == " ref " ) {
$conv_data [ 'reply-to-uri' ] = $attributes -> textContent ;
}
}
}
2018-07-08 12:58:43 +00:00
$conv_data [ 'conversation-uri' ] = XML :: getFirstNodeValue ( $xpath , 'ostatus:conversation/text()' , $entry );
2017-09-10 21:56:05 +00:00
$conv = $xpath -> query ( 'ostatus:conversation' , $entry );
if ( is_object ( $conv -> item ( 0 ))) {
2017-11-09 16:05:18 +00:00
foreach ( $conv -> item ( 0 ) -> attributes as $attributes ) {
2017-09-10 21:56:05 +00:00
if ( $attributes -> name == " ref " ) {
$conv_data [ 'conversation-uri' ] = $attributes -> textContent ;
}
if ( $attributes -> name == " href " ) {
$conv_data [ 'conversation-href' ] = $attributes -> textContent ;
}
}
}
if ( $conversation != '' ) {
$conv_data [ 'conversation-uri' ] = $conversation ;
}
if ( $conversation_uri != '' ) {
$conv_data [ 'conversation-uri' ] = $conversation_uri ;
}
$entry = $doc2 -> importNode ( $entry , true );
$doc2 -> appendChild ( $entry );
$conv_data [ 'source' ] = $doc2 -> saveXML ();
2018-08-05 10:23:57 +00:00
$condition = [ 'item-uri' => $conv_data [ 'uri' ], 'protocol' => Conversation :: PARCEL_FEED ];
2018-07-20 12:19:26 +00:00
if ( DBA :: exists ( 'conversation' , $condition )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Delete deprecated entry for URI ' . $conv_data [ 'uri' ], Logger :: DEBUG );
2018-07-20 12:19:26 +00:00
DBA :: delete ( 'conversation' , [ 'item-uri' => $conv_data [ 'uri' ]]);
2017-09-11 12:44:37 +00:00
}
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Store conversation data for uri ' . $conv_data [ 'uri' ], Logger :: DEBUG );
2018-01-20 23:52:54 +00:00
Conversation :: insert ( $conv_data );
2017-09-10 21:56:05 +00:00
}
}
2017-09-11 12:44:37 +00:00
/**
2020-01-19 06:05:23 +00:00
* Fetch the own post so that it can be stored later
2017-09-11 12:44:37 +00:00
*
* We want to store the original data for later processing .
* This function is meant for cases where we process a feed with multiple entries .
* In that case we need to fetch the single posts here .
*
* @ param string $self The link to the self item
2017-11-23 19:01:58 +00:00
* @ param array $item The item array
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2017-09-11 12:44:37 +00:00
*/
2018-07-22 16:35:20 +00:00
private static function fetchSelf ( $self , array & $item )
2017-11-09 16:05:18 +00:00
{
2018-08-05 10:23:57 +00:00
$condition = [ '`item-uri` = ? AND `protocol` IN (?, ?)' , $self , Conversation :: PARCEL_DFRN , Conversation :: PARCEL_SALMON ];
2018-07-20 12:19:26 +00:00
if ( DBA :: exists ( 'conversation' , $condition )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Conversation ' . $item [ 'uri' ] . ' is already stored.' , Logger :: DEBUG );
2017-09-11 12:44:37 +00:00
return ;
}
2020-03-04 21:35:09 +00:00
$curlResult = DI :: httpRequest () -> get ( $self );
2017-09-11 12:44:37 +00:00
2018-10-10 19:08:43 +00:00
if ( ! $curlResult -> isSuccess ()) {
2017-09-11 12:44:37 +00:00
return ;
}
// We reformat the XML to make it better readable
$doc = new DOMDocument ();
2018-10-10 19:08:43 +00:00
$doc -> loadXML ( $curlResult -> getBody ());
2017-09-11 12:44:37 +00:00
$doc -> preserveWhiteSpace = false ;
$doc -> formatOutput = true ;
$xml = $doc -> saveXML ();
2018-08-05 10:23:57 +00:00
$item [ " protocol " ] = Conversation :: PARCEL_SALMON ;
2017-09-11 12:44:37 +00:00
$item [ " source " ] = $xml ;
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Conversation ' . $item [ 'uri' ] . ' is now fetched.' , Logger :: DEBUG );
2017-09-11 12:44:37 +00:00
}
2017-09-10 07:52:07 +00:00
/**
2020-01-19 06:05:23 +00:00
* Fetch related posts and processes them
2017-09-10 07:52:07 +00:00
*
2017-11-09 16:05:18 +00:00
* @ param string $related The link to the related item
2017-09-10 07:52:07 +00:00
* @ param string $related_uri The related item in " uri " format
2017-11-09 16:05:18 +00:00
* @ param array $importer user record of the importing user
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-09-10 07:52:07 +00:00
*/
2017-11-09 16:05:18 +00:00
private static function fetchRelated ( $related , $related_uri , $importer )
{
2018-08-05 10:23:57 +00:00
$condition = [ '`item-uri` = ? AND `protocol` IN (?, ?)' , $related_uri , Conversation :: PARCEL_DFRN , Conversation :: PARCEL_SALMON ];
2018-07-20 12:19:26 +00:00
$conversation = DBA :: selectFirst ( 'conversation' , [ 'source' , 'protocol' ], $condition );
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $conversation )) {
2017-09-10 07:21:23 +00:00
$stored = true ;
$xml = $conversation [ 'source' ];
if ( self :: process ( $xml , $importer , $contact , $hub , $stored , false )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Got valid cached XML for URI ' . $related_uri , Logger :: DEBUG );
2017-09-10 07:21:23 +00:00
return ;
}
2018-08-05 10:23:57 +00:00
if ( $conversation [ 'protocol' ] == Conversation :: PARCEL_SALMON ) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Delete invalid cached XML for URI ' . $related_uri , Logger :: DEBUG );
2018-07-20 12:19:26 +00:00
DBA :: delete ( 'conversation' , [ 'item-uri' => $related_uri ]);
2017-09-10 07:21:23 +00:00
}
}
$stored = false ;
2020-10-10 19:07:17 +00:00
$curlResult = DI :: httpRequest () -> get ( $related , [ 'accept_content' => 'application/atom+xml, text/html' ]);
2017-09-10 07:21:23 +00:00
2018-10-10 19:08:43 +00:00
if ( ! $curlResult -> isSuccess ()) {
2017-09-10 07:21:23 +00:00
return ;
}
$xml = '' ;
2020-10-11 21:26:03 +00:00
if ( stristr ( $curlResult -> getHeader (), 'Content-Type: application/atom+xml' )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Directly fetched XML for URI ' . $related_uri , Logger :: DEBUG );
2018-10-10 19:08:43 +00:00
$xml = $curlResult -> getBody ();
2017-09-10 07:21:23 +00:00
}
if ( $xml == '' ) {
$doc = new DOMDocument ();
2018-10-10 19:08:43 +00:00
if ( !@ $doc -> loadHTML ( $curlResult -> getBody ())) {
2017-09-10 07:21:23 +00:00
return ;
}
2017-12-17 20:24:57 +00:00
$xpath = new DOMXPath ( $doc );
2017-09-10 07:21:23 +00:00
2017-09-10 08:27:24 +00:00
$atom_file = '' ;
2017-09-10 07:21:23 +00:00
$links = $xpath -> query ( '//link' );
if ( $links ) {
2017-11-09 16:05:18 +00:00
foreach ( $links as $link ) {
2017-11-16 04:09:11 +00:00
$attribute = self :: readAttributes ( $link );
2017-09-10 07:21:23 +00:00
if (( $attribute [ 'rel' ] == 'alternate' ) && ( $attribute [ 'type' ] == 'application/atom+xml' )) {
2017-09-10 08:27:24 +00:00
$atom_file = $attribute [ 'href' ];
}
}
if ( $atom_file != '' ) {
2020-03-04 21:35:09 +00:00
$curlResult = DI :: httpRequest () -> get ( $atom_file );
2017-09-10 07:21:23 +00:00
2018-10-10 19:08:43 +00:00
if ( $curlResult -> isSuccess ()) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Fetched XML for URI ' . $related_uri , Logger :: DEBUG );
2018-10-10 19:08:43 +00:00
$xml = $curlResult -> getBody ();
2017-09-10 07:21:23 +00:00
}
}
}
}
// Workaround for older GNU Social servers
if (( $xml == '' ) && strstr ( $related , '/notice/' )) {
2020-03-04 21:35:09 +00:00
$curlResult = DI :: httpRequest () -> get ( str_replace ( '/notice/' , '/api/statuses/show/' , $related ) . '.atom' );
2017-09-10 08:27:24 +00:00
2018-10-10 19:08:43 +00:00
if ( $curlResult -> isSuccess ()) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'GNU Social workaround to fetch XML for URI ' . $related_uri , Logger :: DEBUG );
2018-10-10 19:08:43 +00:00
$xml = $curlResult -> getBody ();
2017-09-10 08:27:24 +00:00
}
}
// Even more worse workaround for GNU Social ;-)
if ( $xml == '' ) {
2020-05-12 20:13:48 +00:00
$related_guess = self :: convertHref ( $related_uri );
2020-03-04 21:35:09 +00:00
$curlResult = DI :: httpRequest () -> get ( str_replace ( '/notice/' , '/api/statuses/show/' , $related_guess ) . '.atom' );
2017-09-10 07:21:23 +00:00
2018-10-10 19:08:43 +00:00
if ( $curlResult -> isSuccess ()) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'GNU Social workaround 2 to fetch XML for URI ' . $related_uri , Logger :: DEBUG );
2018-10-10 19:08:43 +00:00
$xml = $curlResult -> getBody ();
2017-09-10 07:21:23 +00:00
}
}
2017-09-10 21:56:05 +00:00
// Finally we take the data that we fetched from "ostatus:conversation"
if ( $xml == '' ) {
2018-08-05 10:23:57 +00:00
$condition = [ 'item-uri' => $related_uri , 'protocol' => Conversation :: PARCEL_SPLIT_CONVERSATION ];
2018-07-20 12:19:26 +00:00
$conversation = DBA :: selectFirst ( 'conversation' , [ 'source' ], $condition );
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $conversation )) {
2017-09-10 21:56:05 +00:00
$stored = true ;
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Got cached XML from conversation for URI ' . $related_uri , Logger :: DEBUG );
2017-09-10 21:56:05 +00:00
$xml = $conversation [ 'source' ];
}
}
2017-09-10 07:21:23 +00:00
if ( $xml != '' ) {
self :: process ( $xml , $importer , $contact , $hub , $stored , false );
2017-09-10 08:27:24 +00:00
} else {
2018-10-30 13:58:45 +00:00
Logger :: log ( " XML couldn't be fetched for URI: " . $related_uri . " - href: " . $related , Logger :: DEBUG );
2017-09-10 07:21:23 +00:00
}
return ;
}
2017-09-10 07:52:07 +00:00
/**
2020-01-19 06:05:23 +00:00
* Processes the XML for a repeated post
2017-09-10 07:52:07 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMXPath $xpath The xpath object
* @ param object $entry The xml entry that is processed
* @ param array $item The item array
* @ param array $importer user record of the importing user
2017-09-10 07:52:07 +00:00
*
* @ return array with data from links
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2017-09-10 07:52:07 +00:00
*/
2018-07-22 16:35:20 +00:00
private static function processRepeatedItem ( DOMXPath $xpath , $entry , array & $item , array $importer )
2017-11-09 16:05:18 +00:00
{
2018-08-04 19:39:54 +00:00
$activityobject = $xpath -> query ( 'activity:object' , $entry ) -> item ( 0 );
2017-09-10 07:21:23 +00:00
2018-08-04 19:39:54 +00:00
if ( ! is_object ( $activityobject )) {
2018-01-15 13:05:12 +00:00
return [];
2017-09-10 07:21:23 +00:00
}
2018-01-15 13:05:12 +00:00
$link_data = [];
2017-09-10 07:21:23 +00:00
2018-08-04 19:39:54 +00:00
$orig_uri = XML :: getFirstNodeValue ( $xpath , 'atom:id/text()' , $activityobject );
2017-09-10 07:21:23 +00:00
2018-08-04 19:39:54 +00:00
$links = $xpath -> query ( " atom:link " , $activityobject );
2017-09-10 07:21:23 +00:00
if ( $links ) {
$link_data = self :: processLinks ( $links , $item );
}
2018-08-04 19:39:54 +00:00
$orig_body = XML :: getFirstNodeValue ( $xpath , 'atom:content/text()' , $activityobject );
$orig_created = XML :: getFirstNodeValue ( $xpath , 'atom:published/text()' , $activityobject );
$orig_edited = XML :: getFirstNodeValue ( $xpath , 'atom:updated/text()' , $activityobject );
2017-09-10 07:21:23 +00:00
2018-08-04 19:39:54 +00:00
$orig_author = self :: fetchAuthor ( $xpath , $activityobject , $importer , $dummy , false );
2017-09-10 07:21:23 +00:00
$item [ " author-name " ] = $orig_author [ " author-name " ];
$item [ " author-link " ] = $orig_author [ " author-link " ];
2018-07-10 12:27:56 +00:00
$item [ " author-id " ] = $orig_author [ " author-id " ];
2017-09-10 07:21:23 +00:00
2018-03-07 21:24:13 +00:00
$item [ " body " ] = HTML :: toBBCode ( $orig_body );
2017-09-10 07:21:23 +00:00
$item [ " created " ] = $orig_created ;
$item [ " edited " ] = $orig_edited ;
$item [ " uri " ] = $orig_uri ;
2018-08-04 19:39:54 +00:00
$item [ " verb " ] = XML :: getFirstNodeValue ( $xpath , 'activity:verb/text()' , $activityobject );
2017-09-10 07:21:23 +00:00
2018-08-04 19:39:54 +00:00
$item [ " object-type " ] = XML :: getFirstNodeValue ( $xpath , 'activity:object-type/text()' , $activityobject );
2017-09-10 07:21:23 +00:00
2018-08-04 19:40:45 +00:00
// Mastodon Content Warning
2019-10-23 22:25:43 +00:00
if (( $item [ " verb " ] == Activity :: POST ) && $xpath -> evaluate ( 'boolean(atom:summary)' , $activityobject )) {
2018-08-04 19:40:45 +00:00
$clear_text = XML :: getFirstNodeValue ( $xpath , 'atom:summary/text()' , $activityobject );
if ( ! empty ( $clear_text )) {
$item [ 'content-warning' ] = HTML :: toBBCode ( $clear_text );
}
}
2018-08-04 19:39:54 +00:00
$inreplyto = $xpath -> query ( 'thr:in-reply-to' , $activityobject );
2017-09-10 07:21:23 +00:00
if ( is_object ( $inreplyto -> item ( 0 ))) {
2017-11-09 16:05:18 +00:00
foreach ( $inreplyto -> item ( 0 ) -> attributes as $attributes ) {
2017-09-10 07:21:23 +00:00
if ( $attributes -> name == " ref " ) {
$item [ " parent-uri " ] = $attributes -> textContent ;
}
}
}
return $link_data ;
}
2017-09-10 07:52:07 +00:00
/**
2020-01-19 06:05:23 +00:00
* Processes links in the XML
2017-09-10 07:52:07 +00:00
*
* @ param object $links The xml data that contain links
2017-11-09 16:05:18 +00:00
* @ param array $item The item array
2017-09-10 07:52:07 +00:00
*
* @ return array with data from the links
*/
2018-07-22 16:35:20 +00:00
private static function processLinks ( $links , array & $item )
2017-11-09 16:05:18 +00:00
{
2018-01-15 13:05:12 +00:00
$link_data = [ 'add_body' => '' , 'self' => '' ];
2017-09-10 07:21:23 +00:00
2017-11-09 16:05:18 +00:00
foreach ( $links as $link ) {
2017-11-16 04:09:11 +00:00
$attribute = self :: readAttributes ( $link );
2017-09-10 07:21:23 +00:00
2018-07-10 12:27:56 +00:00
if ( ! empty ( $attribute [ 'rel' ]) && ! empty ( $attribute [ 'href' ])) {
2017-09-10 07:21:23 +00:00
switch ( $attribute [ 'rel' ]) {
case " alternate " :
$item [ " plink " ] = $attribute [ 'href' ];
2019-10-24 22:10:20 +00:00
if (( $item [ " object-type " ] == Activity\ObjectType :: QUESTION )
|| ( $item [ " object-type " ] == Activity\ObjectType :: EVENT )
2017-11-09 16:05:18 +00:00
) {
2020-07-14 14:15:04 +00:00
$item [ " body " ] .= " \n " . PageInfo :: getFooterFromUrl ( $attribute [ 'href' ]);
2017-09-10 07:21:23 +00:00
}
break ;
case " ostatus:conversation " :
$link_data [ 'conversation' ] = $attribute [ 'href' ];
$item [ 'conversation-href' ] = $link_data [ 'conversation' ];
if ( ! isset ( $item [ 'conversation-uri' ])) {
$item [ 'conversation-uri' ] = $item [ 'conversation-href' ];
}
break ;
case " enclosure " :
2017-11-09 16:05:18 +00:00
$filetype = strtolower ( substr ( $attribute [ 'type' ], 0 , strpos ( $attribute [ 'type' ], '/' )));
2017-09-10 07:21:23 +00:00
if ( $filetype == 'image' ) {
$link_data [ 'add_body' ] .= " \n [img] " . $attribute [ 'href' ] . '[/img]' ;
} else {
2020-11-07 08:22:59 +00:00
Post\Media :: insert ([ 'uri-id' => $item [ 'uri-id' ], 'type' => Post\Media :: DOCUMENT ,
'url' => $attribute [ 'href' ], 'mimetype' => $attribute [ 'type' ],
'size' => $attribute [ 'length' ] ? ? null , 'description' => $attribute [ 'title' ] ? ? null ]);
2017-09-10 07:21:23 +00:00
}
break ;
case " related " :
2019-10-24 22:10:20 +00:00
if ( $item [ " object-type " ] != Activity\ObjectType :: BOOKMARK ) {
2017-09-10 07:21:23 +00:00
if ( ! isset ( $item [ " parent-uri " ])) {
$item [ " parent-uri " ] = $attribute [ 'href' ];
}
$link_data [ 'related' ] = $attribute [ 'href' ];
} else {
2020-07-14 14:15:04 +00:00
$item [ " body " ] .= " \n " . PageInfo :: getFooterFromUrl ( $attribute [ 'href' ]);
2017-09-10 07:21:23 +00:00
}
break ;
case " self " :
2018-07-10 12:27:56 +00:00
if ( empty ( $item [ " plink " ])) {
2017-09-10 07:21:23 +00:00
$item [ " plink " ] = $attribute [ 'href' ];
}
$link_data [ 'self' ] = $attribute [ 'href' ];
break ;
}
}
}
return $link_data ;
}
2017-11-09 16:05:18 +00:00
/**
2020-01-19 06:05:23 +00:00
* Create an url out of an uri
2017-09-10 07:58:14 +00:00
*
* @ param string $href URI in the format " parameter1:parameter1:... "
*
* @ return string URL in the format http ( s ) ://....
*/
2020-05-12 20:13:48 +00:00
private static function convertHref ( $href )
2017-11-09 16:05:18 +00:00
{
$elements = explode ( " : " , $href );
2017-09-10 07:58:14 +00:00
2017-11-10 05:00:50 +00:00
if (( count ( $elements ) <= 2 ) || ( $elements [ 0 ] != " tag " )) {
2017-09-10 07:58:14 +00:00
return $href ;
2017-11-10 05:00:50 +00:00
}
2017-09-10 07:58:14 +00:00
$server = explode ( " , " , $elements [ 1 ]);
$conversation = explode ( " = " , $elements [ 2 ]);
2017-11-10 05:00:50 +00:00
if (( count ( $elements ) == 4 ) && ( $elements [ 2 ] == " post " )) {
2017-09-10 07:58:14 +00:00
return " http:// " . $server [ 0 ] . " /notice/ " . $elements [ 3 ];
2017-11-10 05:00:50 +00:00
}
2017-09-10 07:58:14 +00:00
if (( count ( $conversation ) != 2 ) || ( $conversation [ 1 ] == " " )) {
return $href ;
}
if ( $elements [ 3 ] == " objectType=thread " ) {
return " http:// " . $server [ 0 ] . " /conversation/ " . $conversation [ 1 ];
} else {
return " http:// " . $server [ 0 ] . " /notice/ " . $conversation [ 1 ];
}
}
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Checks if the current post is a reshare
2016-03-30 22:14:51 +00:00
*
2016-03-31 05:34:13 +00:00
* @ param array $item The item array of thw post
2016-03-30 22:14:51 +00:00
*
2016-03-31 05:34:13 +00:00
* @ return string The guid if the post is a reshare
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
public static function getResharedGuid ( array $item )
2017-11-09 16:05:18 +00:00
{
2019-12-05 06:42:10 +00:00
$reshared = Item :: getShareArray ( $item );
if ( empty ( $reshared [ 'guid' ]) || ! empty ( $reshared [ 'comment' ])) {
return '' ;
2017-11-10 05:00:50 +00:00
}
2015-11-23 17:44:47 +00:00
2019-12-05 06:42:10 +00:00
return $reshared [ 'guid' ];
2015-11-23 17:44:47 +00:00
}
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Cleans the body of a post if it contains picture links
2016-03-30 22:14:51 +00:00
*
2016-03-31 05:34:13 +00:00
* @ param string $body The body
2016-03-30 22:14:51 +00:00
*
2016-03-31 05:34:13 +00:00
* @ return string The cleaned body
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
public static function formatPicturePost ( $body )
2017-11-09 16:05:18 +00:00
{
2018-01-27 01:01:32 +00:00
$siteinfo = BBCode :: getAttachedData ( $body );
2015-11-23 17:44:47 +00:00
2019-01-04 15:34:53 +00:00
if (( $siteinfo [ " type " ] == " photo " ) && ( ! empty ( $siteinfo [ " preview " ]) || ! empty ( $siteinfo [ " image " ]))) {
2017-11-09 16:05:18 +00:00
if ( isset ( $siteinfo [ " preview " ])) {
2016-03-30 21:25:20 +00:00
$preview = $siteinfo [ " preview " ];
2017-11-09 16:05:18 +00:00
} else {
2016-03-30 21:25:20 +00:00
$preview = $siteinfo [ " image " ];
2017-11-09 16:05:18 +00:00
}
2015-12-04 04:57:33 +00:00
2016-03-30 21:25:20 +00:00
// Is it a remote picture? Then make a smaller preview here
2018-07-31 02:06:22 +00:00
$preview = ProxyUtils :: proxifyUrl ( $preview , false , ProxyUtils :: SIZE_SMALL );
2015-12-04 05:50:23 +00:00
2016-03-30 21:25:20 +00:00
// Is it a local picture? Then make it smaller here
2018-01-15 13:05:12 +00:00
$preview = str_replace ([ " -0.jpg " , " -0.png " ], [ " -2.jpg " , " -2.png " ], $preview );
$preview = str_replace ([ " -1.jpg " , " -1.png " ], [ " -2.jpg " , " -2.png " ], $preview );
2015-12-04 05:50:23 +00:00
2017-11-09 16:05:18 +00:00
if ( isset ( $siteinfo [ " url " ])) {
2016-03-30 21:25:20 +00:00
$url = $siteinfo [ " url " ];
2017-11-09 16:05:18 +00:00
} else {
2016-03-30 21:25:20 +00:00
$url = $siteinfo [ " image " ];
2017-11-09 16:05:18 +00:00
}
2015-12-04 05:50:23 +00:00
2016-03-30 21:25:20 +00:00
$body = trim ( $siteinfo [ " text " ]) . " [url] " . $url . " [/url] \n [img] " . $preview . " [/img] " ;
}
2015-12-04 05:50:23 +00:00
2016-03-30 21:25:20 +00:00
return $body ;
2015-12-04 04:57:33 +00:00
}
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds the header elements to the XML document
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param array $owner Contact data of the poster
* @ param string $filter The related feed filter ( activity , posts or comments )
2016-03-30 22:14:51 +00:00
*
2016-03-31 20:01:56 +00:00
* @ return object header root element
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
private static function addHeader ( DOMDocument $doc , array $owner , $filter )
2017-11-09 16:05:18 +00:00
{
2019-10-24 22:32:35 +00:00
$root = $doc -> createElementNS ( ActivityNamespace :: ATOM1 , 'feed' );
2016-03-30 21:25:20 +00:00
$doc -> appendChild ( $root );
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
$root -> setAttribute ( " xmlns:thr " , ActivityNamespace :: THREAD );
$root -> setAttribute ( " xmlns:georss " , ActivityNamespace :: GEORSS );
$root -> setAttribute ( " xmlns:activity " , ActivityNamespace :: ACTIVITY );
$root -> setAttribute ( " xmlns:media " , ActivityNamespace :: MEDIA );
$root -> setAttribute ( " xmlns:poco " , ActivityNamespace :: POCO );
$root -> setAttribute ( " xmlns:ostatus " , ActivityNamespace :: OSTATUS );
$root -> setAttribute ( " xmlns:statusnet " , ActivityNamespace :: STATUSNET );
$root -> setAttribute ( " xmlns:mastodon " , ActivityNamespace :: MASTODON );
2015-11-23 17:44:47 +00:00
2018-02-14 04:58:46 +00:00
$title = '' ;
2018-11-16 22:29:26 +00:00
$selfUri = '/feed/' . $owner [ " nick " ] . '/' ;
2017-12-30 05:34:50 +00:00
switch ( $filter ) {
2018-11-16 22:29:26 +00:00
case 'activity' :
2020-01-18 19:52:34 +00:00
$title = DI :: l10n () -> t ( '%s\'s timeline' , $owner [ 'name' ]);
2018-11-16 22:29:26 +00:00
$selfUri .= $filter ;
break ;
case 'posts' :
2020-01-18 19:52:34 +00:00
$title = DI :: l10n () -> t ( '%s\'s posts' , $owner [ 'name' ]);
2018-11-16 22:29:26 +00:00
break ;
case 'comments' :
2020-01-18 19:52:34 +00:00
$title = DI :: l10n () -> t ( '%s\'s comments' , $owner [ 'name' ]);
2018-11-16 22:29:26 +00:00
$selfUri .= $filter ;
break ;
2017-12-30 05:19:16 +00:00
}
2020-07-17 04:40:20 +00:00
$selfUri = " /dfrn_poll/ " . $owner [ " nick " ];
2017-12-30 05:19:16 +00:00
2018-01-15 13:05:12 +00:00
$attributes = [ " uri " => " https://friendi.ca " , " version " => FRIENDICA_VERSION . " - " . DB_UPDATE_VERSION ];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " generator " , FRIENDICA_PLATFORM , $attributes );
2019-12-30 22:00:08 +00:00
XML :: addElement ( $doc , $root , " id " , DI :: baseUrl () . " /profile/ " . $owner [ " nick " ]);
2017-12-30 05:19:16 +00:00
XML :: addElement ( $doc , $root , " title " , $title );
2020-01-19 20:21:13 +00:00
XML :: addElement ( $doc , $root , " subtitle " , sprintf ( " Updates from %s on %s " , $owner [ " name " ], DI :: config () -> get ( 'config' , 'sitename' )));
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " logo " , $owner [ " photo " ]);
2018-01-27 02:38:34 +00:00
XML :: addElement ( $doc , $root , " updated " , DateTimeFormat :: utcNow ( DateTimeFormat :: ATOM ));
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
$author = self :: addAuthor ( $doc , $owner , true );
2016-03-30 21:25:20 +00:00
$root -> appendChild ( $author );
2015-11-23 17:44:47 +00:00
2018-01-15 13:05:12 +00:00
$attributes = [ " href " => $owner [ " url " ], " rel " => " alternate " , " type " => " text/html " ];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2015-11-23 17:44:47 +00:00
2016-03-30 21:25:20 +00:00
/// @TODO We have to find out what this is
2019-12-30 22:45:42 +00:00
/// $attributes = array("href" => DI::baseUrl()."/sup",
2016-03-30 21:25:20 +00:00
/// "rel" => "http://api.friendfeed.com/2008/03#sup",
/// "type" => "application/json");
2017-11-20 17:56:31 +00:00
/// XML::addElement($doc, $root, "link", "", $attributes);
2015-11-23 17:44:47 +00:00
2017-08-23 05:01:15 +00:00
self :: hublinks ( $doc , $root , $owner [ " nick " ]);
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
$attributes = [ " href " => DI :: baseUrl () . " /salmon/ " . $owner [ " nick " ], " rel " => " salmon " ];
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
$attributes = [ " href " => DI :: baseUrl () . " /salmon/ " . $owner [ " nick " ], " rel " => " http://salmon-protocol.org/ns/salmon-replies " ];
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
$attributes = [ " href " => DI :: baseUrl () . " /salmon/ " . $owner [ " nick " ], " rel " => " http://salmon-protocol.org/ns/salmon-mention " ];
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2015-11-23 17:44:47 +00:00
2019-12-30 22:00:08 +00:00
$attributes = [ " href " => DI :: baseUrl () . $selfUri , " rel " => " self " , " type " => " application/atom+xml " ];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2015-11-23 17:44:47 +00:00
2020-07-17 06:58:39 +00:00
if ( $owner [ 'contact-type' ] == Contact :: TYPE_COMMUNITY ) {
2018-03-18 10:31:12 +00:00
$condition = [ 'uid' => $owner [ 'uid' ], 'self' => false , 'pending' => false ,
'archive' => false , 'hidden' => false , 'blocked' => false ];
2018-07-20 12:19:26 +00:00
$members = DBA :: count ( 'contact' , $condition );
2018-03-18 10:31:12 +00:00
XML :: addElement ( $doc , $root , " statusnet:group_info " , " " , [ " member_count " => $members ]);
}
2016-03-30 21:25:20 +00:00
return $root ;
}
2015-11-23 17:44:47 +00:00
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Add the link to the push hubs to the XML document
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param object $root XML root element where the hub links are added
* @ param object $nick nick
2017-11-23 19:01:58 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2016-03-30 22:14:51 +00:00
*/
2018-07-22 16:35:20 +00:00
public static function hublinks ( DOMDocument $doc , $root , $nick )
2017-11-09 16:05:18 +00:00
{
2019-12-30 22:00:08 +00:00
$h = DI :: baseUrl () . '/pubsubhubbub/' . $nick ;
2018-01-15 13:05:12 +00:00
XML :: addElement ( $doc , $root , " link " , " " , [ " href " => $h , " rel " => " hub " ]);
2015-11-23 17:44:47 +00:00
}
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds attachment data to the XML document
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param object $root XML root element where the hub links are added
* @ param array $item Data of the item that is to be posted
2017-11-23 19:01:58 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
public static function getAttachment ( DOMDocument $doc , $root , $item )
2017-11-09 16:05:18 +00:00
{
2018-01-27 01:01:32 +00:00
$siteinfo = BBCode :: getAttachedData ( $item [ " body " ]);
2016-03-30 21:25:20 +00:00
2017-04-20 05:17:00 +00:00
switch ( $siteinfo [ " type " ]) {
2016-03-30 21:25:20 +00:00
case 'photo' :
2018-08-14 19:37:44 +00:00
if ( ! empty ( $siteinfo [ " image " ])) {
2019-10-18 01:26:15 +00:00
$imgdata = Images :: getInfoFromURLCached ( $siteinfo [ " image " ]);
2018-08-14 19:37:44 +00:00
if ( $imgdata ) {
$attributes = [ " rel " => " enclosure " ,
" href " => $siteinfo [ " image " ],
" type " => $imgdata [ " mime " ],
" length " => intval ( $imgdata [ " size " ])];
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
}
2018-03-25 00:03:55 +00:00
}
2016-03-30 21:25:20 +00:00
break ;
case 'video' :
2018-01-15 13:05:12 +00:00
$attributes = [ " rel " => " enclosure " ,
2016-03-30 21:25:20 +00:00
" href " => $siteinfo [ " url " ],
" type " => " text/html; charset=UTF-8 " ,
2020-07-11 18:54:08 +00:00
" length " => " 0 " ,
2019-10-16 12:35:14 +00:00
" title " => ( $siteinfo [ " title " ] ? ? '' ) ? : $siteinfo [ " url " ],
];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
2016-03-30 21:25:20 +00:00
break ;
default :
break ;
}
2015-11-23 17:44:47 +00:00
2020-01-19 20:21:13 +00:00
if ( ! DI :: config () -> get ( 'system' , 'ostatus_not_attach_preview' ) && ( $siteinfo [ " type " ] != " photo " ) && isset ( $siteinfo [ " image " ])) {
2019-10-18 01:26:15 +00:00
$imgdata = Images :: getInfoFromURLCached ( $siteinfo [ " image " ]);
2018-03-25 00:03:55 +00:00
if ( $imgdata ) {
$attributes = [ " rel " => " enclosure " ,
" href " => $siteinfo [ " image " ],
" type " => $imgdata [ " mime " ],
" length " => intval ( $imgdata [ " size " ])];
2015-11-27 22:16:54 +00:00
2018-03-25 00:03:55 +00:00
XML :: addElement ( $doc , $root , " link " , " " , $attributes );
}
2016-03-30 21:25:20 +00:00
}
2015-11-27 22:16:54 +00:00
2020-11-06 04:14:29 +00:00
foreach ( Post\Media :: getByURIId ( $item [ 'uri-id' ], [ Post\Media :: DOCUMENT , Post\Media :: TORRENT , Post\Media :: UNKNOWN ]) as $attachment ) {
$attributes = [ 'rel' => 'enclosure' ,
'href' => $attachment [ 'url' ],
'type' => $attachment [ 'mimetype' ]];
if ( ! empty ( $attachment [ 'size' ])) {
$attributes [ 'length' ] = intval ( $attachment [ 'size' ]);
}
if ( ! empty ( $attachment [ 'description' ])) {
$attributes [ 'title' ] = $attachment [ 'description' ];
2015-11-23 17:44:47 +00:00
}
2020-11-06 04:14:29 +00:00
XML :: addElement ( $doc , $root , 'link' , '' , $attributes );
2015-11-23 17:44:47 +00:00
}
}
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds the author element to the XML document
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param array $owner Contact data of the poster
* @ param bool $show_profile Whether to show profile
2016-03-30 22:14:51 +00:00
*
2019-01-21 21:51:59 +00:00
* @ return \DOMElement author element
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
private static function addAuthor ( DOMDocument $doc , array $owner , $show_profile = true )
2017-11-09 16:05:18 +00:00
{
2020-01-23 00:34:15 +00:00
$profile = DBA :: selectFirst ( 'profile' , [ 'homepage' , 'publish' ], [ 'uid' => $owner [ 'uid' ]]);
2016-03-30 21:25:20 +00:00
$author = $doc -> createElement ( " author " );
2020-07-17 04:40:20 +00:00
XML :: addElement ( $doc , $author , " id " , $owner [ " url " ]);
2020-07-17 06:58:39 +00:00
if ( $owner [ 'contact-type' ] == Contact :: TYPE_COMMUNITY ) {
2020-07-17 04:40:20 +00:00
XML :: addElement ( $doc , $author , " activity:object-type " , Activity\ObjectType :: GROUP );
} else {
XML :: addElement ( $doc , $author , " activity:object-type " , Activity\ObjectType :: PERSON );
2018-03-18 10:31:12 +00:00
}
2020-07-17 04:40:20 +00:00
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " uri " , $owner [ " url " ]);
XML :: addElement ( $doc , $author , " name " , $owner [ " nick " ]);
XML :: addElement ( $doc , $author , " email " , $owner [ " addr " ]);
2020-07-17 04:40:20 +00:00
if ( $show_profile ) {
2020-05-16 16:28:15 +00:00
XML :: addElement ( $doc , $author , " summary " , BBCode :: convert ( $owner [ " about " ], false , BBCode :: OSTATUS ));
2018-03-18 10:31:12 +00:00
}
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
$attributes = [ " rel " => " alternate " , " type " => " text/html " , " href " => $owner [ " url " ]];
XML :: addElement ( $doc , $author , " link " , " " , $attributes );
$attributes = [
" rel " => " avatar " ,
" type " => " image/jpeg " , // To-Do?
" media:width " => 300 ,
" media:height " => 300 ,
" href " => $owner [ " photo " ]];
XML :: addElement ( $doc , $author , " link " , " " , $attributes );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
if ( isset ( $owner [ " thumb " ])) {
2018-01-15 13:05:12 +00:00
$attributes = [
2016-03-30 21:25:20 +00:00
" rel " => " avatar " ,
" type " => " image/jpeg " , // To-Do?
2020-07-17 04:40:20 +00:00
" media:width " => 80 ,
" media:height " => 80 ,
" href " => $owner [ " thumb " ]];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $author , " link " , " " , $attributes );
2020-07-17 04:40:20 +00:00
}
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
XML :: addElement ( $doc , $author , " poco:preferredUsername " , $owner [ " nick " ]);
XML :: addElement ( $doc , $author , " poco:displayName " , $owner [ " name " ]);
if ( $show_profile ) {
XML :: addElement ( $doc , $author , " poco:note " , BBCode :: convert ( $owner [ " about " ], false , BBCode :: OSTATUS ));
if ( trim ( $owner [ " location " ]) != " " ) {
$element = $doc -> createElement ( " poco:address " );
XML :: addElement ( $doc , $element , " poco:formatted " , $owner [ " location " ]);
$author -> appendChild ( $element );
2018-03-18 10:31:12 +00:00
}
2020-07-17 04:40:20 +00:00
}
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
if ( DBA :: isResult ( $profile ) && ! $show_profile ) {
if ( trim ( $profile [ " homepage " ]) != " " ) {
$urls = $doc -> createElement ( " poco:urls " );
XML :: addElement ( $doc , $urls , " poco:type " , " homepage " );
XML :: addElement ( $doc , $urls , " poco:value " , $profile [ " homepage " ]);
XML :: addElement ( $doc , $urls , " poco:primary " , " true " );
$author -> appendChild ( $urls );
}
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
XML :: addElement ( $doc , $author , " followers " , " " , [ " url " => DI :: baseUrl () . " /profile/ " . $owner [ " nick " ] . " /contacts/followers " ]);
XML :: addElement ( $doc , $author , " statusnet:profile_info " , " " , [ " local_id " => $owner [ " uid " ]]);
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
if ( $profile [ " publish " ]) {
XML :: addElement ( $doc , $author , " mastodon:scope " , " public " );
2018-03-18 10:31:12 +00:00
}
2017-04-27 20:38:46 +00:00
}
2018-03-18 10:31:12 +00:00
2016-03-30 21:25:20 +00:00
return $author ;
2015-11-23 17:44:47 +00:00
}
2016-03-30 21:25:20 +00:00
/**
* @ TODO Picture attachments should look like this :
* < a href = " https://status.pirati.ca/attachment/572819 " title = " https://status.pirati.ca/file/heluecht-20151202T222602-rd3u49p.gif "
* class = " attachment thumbnail " id = " attachment-572819 " rel = " nofollow external " > https :// status . pirati . ca / attachment / 572819 </ a >
2017-11-09 16:05:18 +00:00
*/
2016-03-30 21:25:20 +00:00
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Returns the given activity if present - otherwise returns the " post " activity
2016-03-30 22:14:51 +00:00
*
2016-03-31 05:34:13 +00:00
* @ param array $item Data of the item that is to be posted
2016-03-30 22:14:51 +00:00
*
2016-03-31 20:01:56 +00:00
* @ return string activity
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
public static function constructVerb ( array $item )
2017-11-09 16:05:18 +00:00
{
2018-07-22 16:35:20 +00:00
if ( ! empty ( $item [ 'verb' ])) {
2016-03-30 21:25:20 +00:00
return $item [ 'verb' ];
2017-11-10 05:00:50 +00:00
}
2019-10-23 22:25:43 +00:00
return Activity :: POST ;
2015-11-23 17:44:47 +00:00
}
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Returns the given object type if present - otherwise returns the " note " object type
2016-03-30 22:14:51 +00:00
*
2016-03-31 05:34:13 +00:00
* @ param array $item Data of the item that is to be posted
2016-03-30 22:14:51 +00:00
*
2016-03-31 20:01:56 +00:00
* @ return string Object type
2016-03-30 22:14:51 +00:00
*/
2018-07-22 16:35:20 +00:00
private static function constructObjecttype ( array $item )
2017-11-09 16:05:18 +00:00
{
2019-10-24 22:10:20 +00:00
if ( ! empty ( $item [ 'object-type' ]) && in_array ( $item [ 'object-type' ], [ Activity\ObjectType :: NOTE , Activity\ObjectType :: COMMENT ])) {
2016-03-30 21:25:20 +00:00
return $item [ 'object-type' ];
2018-12-15 09:32:47 +00:00
}
2019-10-24 22:10:20 +00:00
return Activity\ObjectType :: NOTE ;
2015-12-12 09:41:42 +00:00
}
2015-11-23 17:44:47 +00:00
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds an entry element to the XML document
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param array $item Data of the item that is to be posted
* @ param array $owner Contact data of the poster
* @ param bool $toplevel optional default false
2016-03-30 22:14:51 +00:00
*
2019-01-21 21:51:59 +00:00
* @ return \DOMElement Entry element
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
private static function entry ( DOMDocument $doc , array $item , array $owner , $toplevel = false )
2017-11-09 16:05:18 +00:00
{
2018-02-14 04:58:46 +00:00
$xml = null ;
2017-11-16 04:09:11 +00:00
$repeated_guid = self :: getResharedGuid ( $item );
2017-11-10 05:00:50 +00:00
if ( $repeated_guid != " " ) {
2020-07-17 04:40:20 +00:00
$xml = self :: reshareEntry ( $doc , $item , $owner , $repeated_guid , $toplevel );
2017-11-10 05:00:50 +00:00
}
2015-11-23 17:44:47 +00:00
2017-11-10 05:00:50 +00:00
if ( $xml ) {
2016-03-30 21:25:20 +00:00
return $xml ;
2017-11-10 05:00:50 +00:00
}
2015-12-03 13:38:04 +00:00
2019-10-23 22:25:43 +00:00
if ( $item [ " verb " ] == Activity :: LIKE ) {
2017-11-16 04:09:11 +00:00
return self :: likeEntry ( $doc , $item , $owner , $toplevel );
2019-10-24 22:10:20 +00:00
} elseif ( in_array ( $item [ " verb " ], [ Activity :: FOLLOW , Activity :: O_UNFOLLOW ])) {
2017-11-16 04:09:11 +00:00
return self :: followEntry ( $doc , $item , $owner , $toplevel );
2016-12-18 17:10:38 +00:00
} else {
2020-07-17 04:40:20 +00:00
return self :: noteEntry ( $doc , $item , $owner , $toplevel );
2016-12-18 17:10:38 +00:00
}
2016-03-30 21:25:20 +00:00
}
2015-11-23 17:44:47 +00:00
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds a source entry to the XML document
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param array $contact Array of the contact that is added
2016-03-30 22:14:51 +00:00
*
2019-01-21 21:51:59 +00:00
* @ return \DOMElement Source element
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2016-03-30 22:14:51 +00:00
*/
2018-07-22 16:35:20 +00:00
private static function sourceEntry ( DOMDocument $doc , array $contact )
2017-11-09 16:05:18 +00:00
{
2016-03-30 21:25:20 +00:00
$source = $doc -> createElement ( " source " );
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $source , " id " , $contact [ " poll " ]);
XML :: addElement ( $doc , $source , " title " , $contact [ " name " ]);
2018-01-15 13:05:12 +00:00
XML :: addElement ( $doc , $source , " link " , " " , [ " rel " => " alternate " , " type " => " text/html " , " href " => $contact [ " alias " ]]);
XML :: addElement ( $doc , $source , " link " , " " , [ " rel " => " self " , " type " => " application/atom+xml " , " href " => $contact [ " poll " ]]);
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $source , " icon " , $contact [ " photo " ]);
2018-01-27 02:38:34 +00:00
XML :: addElement ( $doc , $source , " updated " , DateTimeFormat :: utc ( $contact [ " success_update " ] . " +00:00 " , DateTimeFormat :: ATOM ));
2016-03-30 21:25:20 +00:00
return $source ;
2016-02-05 20:31:11 +00:00
}
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds an entry element with reshared content
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param array $item Data of the item that is to be posted
* @ param array $owner Contact data of the poster
* @ param string $repeated_guid guid
* @ param bool $toplevel Is it for en entry element ( false ) or a feed entry ( true ) ?
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ return bool Entry element
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
private static function reshareEntry ( DOMDocument $doc , array $item , array $owner , $repeated_guid , $toplevel )
2017-11-09 16:05:18 +00:00
{
2020-05-27 12:19:06 +00:00
if (( $item [ 'gravity' ] != GRAVITY_PARENT ) && ( Strings :: normaliseLink ( $item [ " author-link " ]) != Strings :: normaliseLink ( $owner [ " url " ]))) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " OStatus entry is from author " . $owner [ " url " ] . " - not from " . $item [ " author-link " ] . " . Quitting. " , Logger :: DEBUG );
2016-03-30 21:25:20 +00:00
}
2019-01-22 04:15:03 +00:00
$entry = self :: entryHeader ( $doc , $owner , $item , $toplevel );
2016-03-30 21:25:20 +00:00
2020-03-02 07:57:23 +00:00
$condition = [ 'uid' => $owner [ " uid " ], 'guid' => $repeated_guid , 'private' => [ Item :: PUBLIC , Item :: UNLISTED ],
2018-08-11 20:40:44 +00:00
'network' => [ Protocol :: DFRN , Protocol :: DIASPORA , Protocol :: OSTATUS ]];
2018-06-18 20:36:34 +00:00
$repeated_item = Item :: selectFirst ([], $condition );
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $repeated_item )) {
2016-03-30 21:25:20 +00:00
return false ;
2017-08-03 05:42:44 +00:00
}
2015-12-13 13:10:18 +00:00
2020-07-16 19:22:38 +00:00
$contact = Contact :: getByURL ( $repeated_item [ 'author-link' ]) ? : $owner ;
2015-11-23 17:44:47 +00:00
2016-03-30 21:25:20 +00:00
$title = $owner [ " nick " ] . " repeated a notice by " . $contact [ " nick " ];
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
self :: entryContent ( $doc , $entry , $item , $owner , $title , Activity :: SHARE , false );
2015-12-13 13:10:18 +00:00
2020-07-17 04:40:20 +00:00
$as_object = $doc -> createElement ( " activity:object " );
2015-12-13 13:10:18 +00:00
2020-07-17 04:40:20 +00:00
XML :: addElement ( $doc , $as_object , " activity:object-type " , ActivityNamespace :: ACTIVITY_SCHEMA . " activity " );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
self :: entryContent ( $doc , $as_object , $repeated_item , $owner , " " , " " , false );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
$author = self :: addAuthor ( $doc , $contact , false );
$as_object -> appendChild ( $author );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
$as_object2 = $doc -> createElement ( " activity:object " );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
XML :: addElement ( $doc , $as_object2 , " activity:object-type " , self :: constructObjecttype ( $repeated_item ));
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
$title = sprintf ( " New comment by %s " , $contact [ " nick " ]);
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
self :: entryContent ( $doc , $as_object2 , $repeated_item , $owner , $title );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
$as_object -> appendChild ( $as_object2 );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
self :: entryFooter ( $doc , $as_object , $item , $owner , false );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
$source = self :: sourceEntry ( $doc , $contact );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
$as_object -> appendChild ( $source );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
$entry -> appendChild ( $as_object );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
self :: entryFooter ( $doc , $entry , $item , $owner , true );
2016-03-30 21:25:20 +00:00
return $entry ;
}
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds an entry element with a " like "
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param array $item Data of the item that is to be posted
* @ param array $owner Contact data of the poster
* @ param bool $toplevel Is it for en entry element ( false ) or a feed entry ( true ) ?
2016-03-30 22:14:51 +00:00
*
2019-01-21 21:51:59 +00:00
* @ return \DOMElement Entry element with " like "
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2016-03-30 22:14:51 +00:00
*/
2018-07-22 16:35:20 +00:00
private static function likeEntry ( DOMDocument $doc , array $item , array $owner , $toplevel )
2017-11-09 16:05:18 +00:00
{
2020-05-27 12:19:06 +00:00
if (( $item [ 'gravity' ] != GRAVITY_PARENT ) && ( Strings :: normaliseLink ( $item [ " author-link " ]) != Strings :: normaliseLink ( $owner [ " url " ]))) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " OStatus entry is from author " . $owner [ " url " ] . " - not from " . $item [ " author-link " ] . " . Quitting. " , Logger :: DEBUG );
2015-12-13 13:10:18 +00:00
}
2016-03-30 21:25:20 +00:00
2019-01-22 04:15:03 +00:00
$entry = self :: entryHeader ( $doc , $owner , $item , $toplevel );
2016-03-30 21:25:20 +00:00
2019-10-24 22:34:46 +00:00
$verb = ActivityNamespace :: ACTIVITY_SCHEMA . " favorite " ;
2017-11-16 04:09:11 +00:00
self :: entryContent ( $doc , $entry , $item , $owner , " Favorite " , $verb , false );
2016-03-30 21:25:20 +00:00
2018-06-18 20:36:34 +00:00
$parent = Item :: selectFirst ([], [ 'uri' => $item [ " thr-parent " ], 'uid' => $item [ " uid " ]]);
2019-02-24 18:36:37 +00:00
if ( DBA :: isResult ( $parent )) {
$as_object = $doc -> createElement ( " activity:object " );
2016-03-30 21:25:20 +00:00
2019-02-24 18:36:37 +00:00
XML :: addElement ( $doc , $as_object , " activity:object-type " , self :: constructObjecttype ( $parent ));
2016-03-30 21:25:20 +00:00
2019-02-24 18:36:37 +00:00
self :: entryContent ( $doc , $as_object , $parent , $owner , " New entry " );
2016-03-30 21:25:20 +00:00
2019-02-24 18:36:37 +00:00
$entry -> appendChild ( $as_object );
}
2016-03-30 21:25:20 +00:00
2017-11-16 04:09:11 +00:00
self :: entryFooter ( $doc , $entry , $item , $owner );
2016-03-30 21:25:20 +00:00
return $entry ;
2015-12-13 13:10:18 +00:00
}
2015-11-23 17:44:47 +00:00
2016-12-18 17:10:38 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds the person object element to the XML document
2016-12-18 17:10:38 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param array $owner Contact data of the poster
* @ param array $contact Contact data of the target
2016-12-18 17:10:38 +00:00
*
* @ return object author element
*/
2018-07-22 16:35:20 +00:00
private static function addPersonObject ( DOMDocument $doc , array $owner , array $contact )
2017-11-09 16:05:18 +00:00
{
2016-12-18 17:10:38 +00:00
$object = $doc -> createElement ( " activity:object " );
2019-10-24 22:10:20 +00:00
XML :: addElement ( $doc , $object , " activity:object-type " , Activity\ObjectType :: PERSON );
2016-12-18 17:10:38 +00:00
2018-08-11 20:40:44 +00:00
if ( $contact [ 'network' ] == Protocol :: PHANTOM ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $object , " id " , $contact [ 'url' ]);
2016-12-18 17:10:38 +00:00
return $object ;
}
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $object , " id " , $contact [ " alias " ]);
XML :: addElement ( $doc , $object , " title " , $contact [ " nick " ]);
2016-12-18 17:10:38 +00:00
2018-01-15 13:05:12 +00:00
$attributes = [ " rel " => " alternate " , " type " => " text/html " , " href " => $contact [ " url " ]];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $object , " link " , " " , $attributes );
2016-12-18 17:10:38 +00:00
2018-01-15 13:05:12 +00:00
$attributes = [
2016-12-18 17:10:38 +00:00
" rel " => " avatar " ,
" type " => " image/jpeg " , // To-Do?
2018-10-23 14:36:57 +00:00
" media:width " => 300 ,
" media:height " => 300 ,
2018-01-15 13:05:12 +00:00
" href " => $contact [ " photo " ]];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $object , " link " , " " , $attributes );
2016-12-18 17:10:38 +00:00
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $object , " poco:preferredUsername " , $contact [ " nick " ]);
XML :: addElement ( $doc , $object , " poco:displayName " , $contact [ " name " ]);
2016-12-18 17:10:38 +00:00
if ( trim ( $contact [ " location " ]) != " " ) {
$element = $doc -> createElement ( " poco:address " );
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $element , " poco:formatted " , $contact [ " location " ]);
2016-12-18 17:10:38 +00:00
$object -> appendChild ( $element );
}
return $object ;
}
/**
2020-01-19 06:05:23 +00:00
* Adds a follow / unfollow entry element
2016-12-18 17:10:38 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param array $item Data of the follow / unfollow message
* @ param array $owner Contact data of the poster
* @ param bool $toplevel Is it for en entry element ( false ) or a feed entry ( true ) ?
2016-12-18 17:10:38 +00:00
*
2019-01-21 21:51:59 +00:00
* @ return \DOMElement Entry element
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2016-12-18 17:10:38 +00:00
*/
2018-07-22 16:35:20 +00:00
private static function followEntry ( DOMDocument $doc , array $item , array $owner , $toplevel )
2017-11-10 05:00:50 +00:00
{
2020-05-27 12:19:06 +00:00
$item [ " id " ] = $item [ 'parent' ] = 0 ;
2016-12-18 20:02:13 +00:00
$item [ " created " ] = $item [ " edited " ] = date ( " c " );
2020-03-02 07:57:23 +00:00
$item [ " private " ] = Item :: PRIVATE ;
2016-12-18 17:10:38 +00:00
2020-07-16 19:22:38 +00:00
$contact = Contact :: getByURL ( $item [ 'follow' ]);
2020-06-25 00:57:47 +00:00
$item [ 'follow' ] = $contact [ 'url' ];
2016-12-18 17:10:38 +00:00
2020-06-25 00:57:47 +00:00
if ( $contact [ 'alias' ]) {
2016-12-18 17:10:38 +00:00
$item [ 'follow' ] = $contact [ 'alias' ];
2020-06-25 00:57:47 +00:00
} else {
$contact [ 'alias' ] = $contact [ 'url' ];
2016-12-18 17:10:38 +00:00
}
2018-11-08 16:28:29 +00:00
$condition = [ 'uid' => $owner [ 'uid' ], 'nurl' => Strings :: normaliseLink ( $contact [ " url " ])];
2018-08-19 12:46:11 +00:00
$user_contact = DBA :: selectFirst ( 'contact' , [ 'id' ], $condition );
2016-12-18 20:02:13 +00:00
2018-08-19 12:46:11 +00:00
if ( DBA :: isResult ( $user_contact )) {
$connect_id = $user_contact [ 'id' ];
2016-12-18 20:02:13 +00:00
} else {
$connect_id = 0 ;
}
2019-10-23 22:25:43 +00:00
if ( $item [ 'verb' ] == Activity :: FOLLOW ) {
2020-01-18 19:52:34 +00:00
$message = DI :: l10n () -> t ( '%s is now following %s.' );
$title = DI :: l10n () -> t ( 'following' );
2016-12-18 20:02:13 +00:00
$action = " subscription " ;
2016-12-18 17:10:38 +00:00
} else {
2020-01-18 19:52:34 +00:00
$message = DI :: l10n () -> t ( '%s stopped following %s.' );
$title = DI :: l10n () -> t ( 'stopped following' );
2016-12-18 20:02:13 +00:00
$action = " unfollow " ;
2016-12-18 17:10:38 +00:00
}
2017-11-09 16:05:18 +00:00
$item [ " uri " ] = $item [ 'parent-uri' ] = $item [ 'thr-parent' ]
2019-12-30 01:17:16 +00:00
= 'tag:' . DI :: baseUrl () -> getHostname () .
2016-12-18 20:02:13 +00:00
',' . date ( 'Y-m-d' ) . ':' . $action . ':' . $owner [ 'uid' ] .
':person:' . $connect_id . ':' . $item [ 'created' ];
2016-12-18 17:10:38 +00:00
2016-12-18 20:02:13 +00:00
$item [ " body " ] = sprintf ( $message , $owner [ " nick " ], $contact [ " nick " ]);
2016-12-18 17:10:38 +00:00
2019-01-22 04:15:03 +00:00
$entry = self :: entryHeader ( $doc , $owner , $item , $toplevel );
2016-12-18 20:02:13 +00:00
2017-11-16 04:09:11 +00:00
self :: entryContent ( $doc , $entry , $item , $owner , $title );
2016-12-18 17:10:38 +00:00
2017-11-16 04:09:11 +00:00
$object = self :: addPersonObject ( $doc , $owner , $contact );
2016-12-18 17:10:38 +00:00
$entry -> appendChild ( $object );
2017-11-16 04:09:11 +00:00
self :: entryFooter ( $doc , $entry , $item , $owner );
2016-12-18 17:10:38 +00:00
return $entry ;
}
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds a regular entry element
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param array $item Data of the item that is to be posted
* @ param array $owner Contact data of the poster
* @ param bool $toplevel Is it for en entry element ( false ) or a feed entry ( true ) ?
2016-03-30 22:14:51 +00:00
*
2019-01-21 21:51:59 +00:00
* @ return \DOMElement Entry element
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
private static function noteEntry ( DOMDocument $doc , array $item , array $owner , $toplevel )
2017-11-09 16:05:18 +00:00
{
2020-05-27 12:19:06 +00:00
if (( $item [ 'gravity' ] != GRAVITY_PARENT ) && ( Strings :: normaliseLink ( $item [ " author-link " ]) != Strings :: normaliseLink ( $owner [ " url " ]))) {
2018-10-30 13:58:45 +00:00
Logger :: log ( " OStatus entry is from author " . $owner [ " url " ] . " - not from " . $item [ " author-link " ] . " . Quitting. " , Logger :: DEBUG );
2016-03-30 21:25:20 +00:00
}
2019-01-22 04:15:03 +00:00
if ( ! $toplevel ) {
if ( ! empty ( $item [ 'title' ])) {
2020-05-16 16:28:15 +00:00
$title = BBCode :: convert ( $item [ 'title' ], false , BBCode :: OSTATUS );
2019-01-22 04:15:03 +00:00
} else {
$title = sprintf ( " New note by %s " , $owner [ " nick " ]);
}
} else {
$title = sprintf ( " New comment by %s " , $owner [ " nick " ]);
}
$entry = self :: entryHeader ( $doc , $owner , $item , $toplevel );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
XML :: addElement ( $doc , $entry , " activity:object-type " , Activity\ObjectType :: NOTE );
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
self :: entryContent ( $doc , $entry , $item , $owner , $title , '' , true );
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
self :: entryFooter ( $doc , $entry , $item , $owner , true );
2015-12-13 13:10:18 +00:00
2016-03-30 21:25:20 +00:00
return $entry ;
2015-12-13 13:10:18 +00:00
}
2016-03-30 21:25:20 +00:00
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds a header element to the XML document
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param array $owner Contact data of the poster
* @ param array $item
* @ param bool $toplevel Is it for en entry element ( false ) or a feed entry ( true ) ?
2016-03-30 22:14:51 +00:00
*
2019-01-22 04:15:03 +00:00
* @ return \DOMElement The entry element where the elements are added
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
public static function entryHeader ( DOMDocument $doc , array $owner , array $item , $toplevel )
2017-11-09 16:05:18 +00:00
{
2016-03-30 21:25:20 +00:00
if ( ! $toplevel ) {
$entry = $doc -> createElement ( " entry " );
2018-03-18 10:31:12 +00:00
2020-07-17 06:58:39 +00:00
if ( $owner [ 'contact-type' ] == Contact :: TYPE_COMMUNITY ) {
2020-07-16 19:22:38 +00:00
$contact = Contact :: getByURL ( $item [ 'author-link' ]) ? : $owner ;
2018-03-18 10:31:12 +00:00
$author = self :: addAuthor ( $doc , $contact , false );
$entry -> appendChild ( $author );
}
2016-03-30 21:25:20 +00:00
} else {
2019-10-24 22:32:35 +00:00
$entry = $doc -> createElementNS ( ActivityNamespace :: ATOM1 , " entry " );
$entry -> setAttribute ( " xmlns:thr " , ActivityNamespace :: THREAD );
$entry -> setAttribute ( " xmlns:georss " , ActivityNamespace :: GEORSS );
$entry -> setAttribute ( " xmlns:activity " , ActivityNamespace :: ACTIVITY );
$entry -> setAttribute ( " xmlns:media " , ActivityNamespace :: MEDIA );
$entry -> setAttribute ( " xmlns:poco " , ActivityNamespace :: POCO );
$entry -> setAttribute ( " xmlns:ostatus " , ActivityNamespace :: OSTATUS );
$entry -> setAttribute ( " xmlns:statusnet " , ActivityNamespace :: STATUSNET );
$entry -> setAttribute ( " xmlns:mastodon " , ActivityNamespace :: MASTODON );
2016-03-30 21:25:20 +00:00
2017-11-16 04:09:11 +00:00
$author = self :: addAuthor ( $doc , $owner );
2016-03-30 21:25:20 +00:00
$entry -> appendChild ( $author );
}
2019-01-22 04:15:03 +00:00
return $entry ;
2015-12-13 13:10:18 +00:00
}
2015-11-23 17:44:47 +00:00
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds elements to the XML document
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
2019-01-21 21:51:59 +00:00
* @ param \DOMElement $entry Entry element where the content is added
2019-01-06 21:06:53 +00:00
* @ param array $item Data of the item that is to be posted
* @ param array $owner Contact data of the poster
* @ param string $title Title for the post
* @ param string $verb The activity verb
* @ param bool $complete Add the " status_net " element ?
2017-11-23 19:01:58 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
private static function entryContent ( DOMDocument $doc , \DOMElement $entry , array $item , array $owner , $title , $verb = " " , $complete = true )
2017-11-09 16:05:18 +00:00
{
if ( $verb == " " ) {
2017-11-16 04:09:11 +00:00
$verb = self :: constructVerb ( $item );
2017-11-09 16:05:18 +00:00
}
2015-11-23 17:44:47 +00:00
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " id " , $item [ " uri " ]);
2018-11-17 00:30:53 +00:00
XML :: addElement ( $doc , $entry , " title " , html_entity_decode ( $title , ENT_QUOTES , 'UTF-8' ));
2015-11-23 17:44:47 +00:00
2017-11-16 04:09:11 +00:00
$body = self :: formatPicturePost ( $item [ 'body' ]);
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
if ( ! empty ( $item [ 'title' ])) {
2016-03-30 21:25:20 +00:00
$body = " [b] " . $item [ 'title' ] . " [/b] \n \n " . $body ;
2017-11-10 05:00:50 +00:00
}
2015-11-23 17:44:47 +00:00
2020-05-16 16:28:15 +00:00
$body = BBCode :: convert ( $body , false , BBCode :: OSTATUS );
2015-11-23 17:44:47 +00:00
2018-01-15 13:05:12 +00:00
XML :: addElement ( $doc , $entry , " content " , $body , [ " type " => " html " ]);
2015-12-07 20:52:42 +00:00
2018-01-15 13:05:12 +00:00
XML :: addElement ( $doc , $entry , " link " , " " , [ " rel " => " alternate " , " type " => " text/html " ,
2019-12-30 22:00:08 +00:00
" href " => DI :: baseUrl () . " /display/ " . $item [ " guid " ]]
2017-11-09 16:05:18 +00:00
);
2015-12-07 20:52:42 +00:00
2020-07-17 04:40:20 +00:00
if ( $complete && ( $item [ " id " ] > 0 )) {
2018-01-15 13:05:12 +00:00
XML :: addElement ( $doc , $entry , " status_net " , " " , [ " notice_id " => $item [ " id " ]]);
2017-11-09 16:05:18 +00:00
}
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
XML :: addElement ( $doc , $entry , " activity:verb " , $verb );
2015-11-23 17:44:47 +00:00
2018-01-27 02:38:34 +00:00
XML :: addElement ( $doc , $entry , " published " , DateTimeFormat :: utc ( $item [ " created " ] . " +00:00 " , DateTimeFormat :: ATOM ));
XML :: addElement ( $doc , $entry , " updated " , DateTimeFormat :: utc ( $item [ " edited " ] . " +00:00 " , DateTimeFormat :: ATOM ));
2015-11-23 17:44:47 +00:00
}
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Adds the elements at the foot of an entry to the XML document
2016-03-30 22:14:51 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param DOMDocument $doc XML document
* @ param object $entry The entry element where the elements are added
* @ param array $item Data of the item that is to be posted
* @ param array $owner Contact data of the poster
* @ param bool $complete default true
2017-11-23 19:01:58 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
private static function entryFooter ( DOMDocument $doc , $entry , array $item , array $owner , $complete = true )
2017-11-09 16:05:18 +00:00
{
2018-01-15 13:05:12 +00:00
$mentioned = [];
2016-03-30 21:25:20 +00:00
2020-05-28 16:02:36 +00:00
if ( $item [ 'gravity' ] != GRAVITY_PARENT ) {
2020-05-27 12:19:06 +00:00
$parent = Item :: selectFirst ([ 'guid' , 'author-link' , 'owner-link' ], [ 'id' => $item [ 'parent' ]]);
2016-03-30 21:25:20 +00:00
$parent_item = (( $item [ 'thr-parent' ]) ? $item [ 'thr-parent' ] : $item [ 'parent-uri' ]);
2018-06-19 13:26:03 +00:00
$thrparent = Item :: selectFirst ([ 'guid' , 'author-link' , 'owner-link' , 'plink' ], [ 'uid' => $owner [ " uid " ], 'uri' => $parent_item ]);
2018-06-18 20:36:34 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $thrparent )) {
2018-06-18 20:36:34 +00:00
$mentioned [ $thrparent [ " author-link " ]] = $thrparent [ " author-link " ];
$mentioned [ $thrparent [ " owner-link " ]] = $thrparent [ " owner-link " ];
$parent_plink = $thrparent [ " plink " ];
2017-04-27 20:38:46 +00:00
} else {
2018-06-18 20:36:34 +00:00
$mentioned [ $parent [ " author-link " ]] = $parent [ " author-link " ];
$mentioned [ $parent [ " owner-link " ]] = $parent [ " owner-link " ];
2019-12-30 22:00:08 +00:00
$parent_plink = DI :: baseUrl () . " /display/ " . $parent [ " guid " ];
2017-04-27 20:38:46 +00:00
}
2018-01-15 13:05:12 +00:00
$attributes = [
2016-03-30 21:25:20 +00:00
" ref " => $parent_item ,
2018-01-15 13:05:12 +00:00
" href " => $parent_plink ];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " thr:in-reply-to " , " " , $attributes );
2016-03-30 21:25:20 +00:00
2018-01-15 13:05:12 +00:00
$attributes = [
2016-03-30 21:25:20 +00:00
" rel " => " related " ,
2018-01-15 13:05:12 +00:00
" href " => $parent_plink ];
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " link " , " " , $attributes );
2017-04-27 20:38:46 +00:00
}
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
if ( intval ( $item [ 'parent' ]) > 0 ) {
2018-10-02 20:12:38 +00:00
$conversation_href = $conversation_uri = str_replace ( '/objects/' , '/context/' , $item [ 'parent-uri' ]);
2016-03-30 21:25:20 +00:00
2017-04-27 20:38:46 +00:00
if ( isset ( $parent_item )) {
2018-07-20 12:19:26 +00:00
$conversation = DBA :: selectFirst ( 'conversation' , [ 'conversation-uri' , 'conversation-href' ], [ 'item-uri' => $parent_item ]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $conversation )) {
2018-07-10 12:27:56 +00:00
if ( $conversation [ 'conversation-uri' ] != '' ) {
2018-06-19 21:33:07 +00:00
$conversation_uri = $conversation [ 'conversation-uri' ];
2017-04-27 20:38:46 +00:00
}
2018-07-10 12:27:56 +00:00
if ( $conversation [ 'conversation-href' ] != '' ) {
2018-06-19 21:33:07 +00:00
$conversation_href = $conversation [ 'conversation-href' ];
2017-04-27 20:38:46 +00:00
}
}
2016-03-30 21:25:20 +00:00
}
2020-07-17 04:40:20 +00:00
XML :: addElement ( $doc , $entry , " link " , " " , [ " rel " => " ostatus:conversation " , " href " => $conversation_href ]);
2017-04-19 21:37:00 +00:00
2020-07-17 04:40:20 +00:00
$attributes = [
" href " => $conversation_href ,
" local_id " => $item [ 'parent' ],
" ref " => $conversation_uri ];
2017-04-19 21:37:00 +00:00
2020-07-17 04:40:20 +00:00
XML :: addElement ( $doc , $entry , " ostatus:conversation " , $conversation_uri , $attributes );
2016-12-18 17:10:38 +00:00
}
2020-05-04 22:51:03 +00:00
// uri-id isn't present for follow entry pseudo-items
2020-05-05 21:49:48 +00:00
$tags = Tag :: getByURIId ( $item [ 'uri-id' ] ? ? 0 );
foreach ( $tags as $tag ) {
2020-05-04 22:51:03 +00:00
$mentioned [ $tag [ 'url' ]] = $tag [ 'url' ];
2017-11-09 16:05:18 +00:00
}
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
// Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS)
$newmentions = [];
foreach ( $mentioned as $mention ) {
$newmentions [ str_replace ( " http:// " , " https:// " , $mention )] = str_replace ( " http:// " , " https:// " , $mention );
$newmentions [ str_replace ( " https:// " , " http:// " , $mention )] = str_replace ( " https:// " , " http:// " , $mention );
}
$mentioned = $newmentions ;
2015-11-23 17:44:47 +00:00
2020-07-17 04:40:20 +00:00
foreach ( $mentioned as $mention ) {
2020-08-05 12:53:25 +00:00
$contact = Contact :: getByURL ( $mention , false , [ 'contact-type' ]);
2020-07-17 04:40:20 +00:00
if ( ! empty ( $contact ) && ( $contact [ 'contact-type' ] == Contact :: TYPE_COMMUNITY )) {
XML :: addElement ( $doc , $entry , " link " , " " ,
[
" rel " => " mentioned " ,
" ostatus:object-type " => Activity\ObjectType :: GROUP ,
" href " => $mention ]
);
} else {
XML :: addElement ( $doc , $entry , " link " , " " ,
[
" rel " => " mentioned " ,
" ostatus:object-type " => Activity\ObjectType :: PERSON ,
" href " => $mention ]
);
2020-07-11 19:11:35 +00:00
}
2020-07-17 04:40:20 +00:00
}
2018-03-18 11:18:25 +00:00
2020-07-17 06:58:39 +00:00
if ( $owner [ 'contact-type' ] == Contact :: TYPE_COMMUNITY ) {
2020-07-17 04:40:20 +00:00
XML :: addElement ( $doc , $entry , " link " , " " , [
" rel " => " mentioned " ,
" ostatus:object-type " => " http://activitystrea.ms/schema/1.0/group " ,
" href " => $owner [ 'url' ]
]);
}
if ( $item [ 'private' ] != Item :: PRIVATE ) {
XML :: addElement ( $doc , $entry , " link " , " " , [ " rel " => " ostatus:attention " ,
" href " => " http://activityschema.org/collection/public " ]);
XML :: addElement ( $doc , $entry , " link " , " " , [ " rel " => " mentioned " ,
" ostatus:object-type " => " http://activitystrea.ms/schema/1.0/collection " ,
" href " => " http://activityschema.org/collection/public " ]);
XML :: addElement ( $doc , $entry , " mastodon:scope " , " public " );
2016-03-30 21:25:20 +00:00
}
2020-05-05 21:49:48 +00:00
foreach ( $tags as $tag ) {
if ( $tag [ 'type' ] == Tag :: HASHTAG ) {
XML :: addElement ( $doc , $entry , " category " , " " , [ " term " => $tag [ 'name' ]]);
2017-11-09 16:05:18 +00:00
}
}
2016-03-30 21:25:20 +00:00
2017-11-16 04:09:11 +00:00
self :: getAttachment ( $doc , $entry , $item );
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
if ( $complete && ( $item [ " id " ] > 0 )) {
2016-03-30 21:25:20 +00:00
$app = $item [ " app " ];
2017-11-10 05:00:50 +00:00
if ( $app == " " ) {
2016-03-30 21:25:20 +00:00
$app = " web " ;
2017-11-10 05:00:50 +00:00
}
2016-03-30 21:25:20 +00:00
2018-01-15 13:05:12 +00:00
$attributes = [ " local_id " => $item [ " id " ], " source " => $app ];
2016-03-30 21:25:20 +00:00
2017-11-09 16:05:18 +00:00
if ( isset ( $parent [ " id " ])) {
2016-03-30 21:25:20 +00:00
$attributes [ " repeat_of " ] = $parent [ " id " ];
2017-11-09 16:05:18 +00:00
}
2016-03-30 21:25:20 +00:00
2017-11-09 16:05:18 +00:00
if ( $item [ " coord " ] != " " ) {
2017-11-20 17:56:31 +00:00
XML :: addElement ( $doc , $entry , " georss:point " , $item [ " coord " ]);
2017-11-09 16:05:18 +00:00
}
2016-03-30 21:25:20 +00:00
2020-07-11 19:11:35 +00:00
XML :: addElement ( $doc , $entry , " statusnet:notice_info " , " " , $attributes );
2016-03-30 21:25:20 +00:00
}
}
2015-11-23 17:44:47 +00:00
2016-03-30 22:14:51 +00:00
/**
2017-12-30 05:19:16 +00:00
* Creates the XML feed for a given nickname
*
* Supported filters :
* - activity ( default ) : all the public posts
* - posts : all the public top - level posts
* - comments : all the public replies
*
* Updates the provided last_update parameter if the result comes from the
* cache or it is empty
*
2017-11-09 16:05:18 +00:00
* @ param string $owner_nick Nickname of the feed owner
* @ param string $last_update Date of the last update
* @ param integer $max_items Number of maximum items to fetch
2017-12-30 05:34:50 +00:00
* @ param string $filter Feed items filter ( activity , posts or comments )
* @ param boolean $nocache Wether to bypass caching
2016-03-30 22:14:51 +00:00
*
2016-03-31 20:01:56 +00:00
* @ return string XML feed
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2016-03-30 22:14:51 +00:00
*/
2020-07-17 04:40:20 +00:00
public static function feed ( $owner_nick , & $last_update , $max_items = 300 , $filter = 'activity' , $nocache = false )
2017-11-09 16:05:18 +00:00
{
2017-08-03 05:42:44 +00:00
$stamp = microtime ( true );
2018-06-19 05:39:56 +00:00
$owner = User :: getOwnerDataByNick ( $owner_nick );
if ( ! $owner ) {
return ;
}
2017-12-30 05:34:50 +00:00
$cachekey = " ostatus:feed: " . $owner_nick . " : " . $filter . " : " . $last_update ;
2017-08-03 05:42:44 +00:00
$previous_created = $last_update ;
2018-06-19 05:39:56 +00:00
// Don't cache when the last item was posted less then 15 minutes ago (Cache duration)
if (( time () - strtotime ( $owner [ 'last-item' ])) < 15 * 60 ) {
2020-01-06 23:45:49 +00:00
$result = DI :: cache () -> get ( $cachekey );
2018-06-19 05:39:56 +00:00
if ( ! $nocache && ! is_null ( $result )) {
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Feed duration: ' . number_format ( microtime ( true ) - $stamp , 3 ) . ' - ' . $owner_nick . ' - ' . $filter . ' - ' . $previous_created . ' (cached)' , Logger :: DEBUG );
2018-06-19 05:39:56 +00:00
$last_update = $result [ 'last_update' ];
return $result [ 'feed' ];
}
2017-08-03 05:42:44 +00:00
}
2016-03-30 21:25:20 +00:00
2017-08-03 05:42:44 +00:00
if ( ! strlen ( $last_update )) {
2016-03-30 21:25:20 +00:00
$last_update = 'now -30 days' ;
2017-08-03 05:42:44 +00:00
}
2016-03-30 21:25:20 +00:00
2020-07-17 04:40:20 +00:00
$check_date = DateTimeFormat :: utc ( $last_update );
2020-08-07 13:49:59 +00:00
$authorid = Contact :: getIdForURL ( $owner [ " url " ]);
2016-10-21 18:25:21 +00:00
2019-07-07 21:30:33 +00:00
$condition = [ " `uid` = ? AND `received` > ? AND NOT `deleted`
2020-03-02 07:57:23 +00:00
AND `private` != ? AND `visible` AND `wall` AND `parent-network` IN ( ? , ? ) " ,
$owner [ " uid " ], $check_date , Item :: PRIVATE , Protocol :: OSTATUS , Protocol :: DFRN ];
2018-06-18 20:36:34 +00:00
2017-12-30 05:34:50 +00:00
if ( $filter === 'comments' ) {
2018-06-18 20:36:34 +00:00
$condition [ 0 ] .= " AND `object-type` = ? " ;
2019-10-24 22:10:20 +00:00
$condition [] = Activity\ObjectType :: COMMENT ;
2017-12-30 05:19:16 +00:00
}
2020-07-17 06:58:39 +00:00
if ( $owner [ 'contact-type' ] != Contact :: TYPE_COMMUNITY ) {
2018-06-18 20:36:34 +00:00
$condition [ 0 ] .= " AND `contact-id` = ? AND `author-id` = ? " ;
$condition [] = $owner [ " id " ];
$condition [] = $authorid ;
}
2019-07-07 21:30:33 +00:00
$params = [ 'order' => [ 'received' => true ], 'limit' => $max_items ];
2018-06-19 05:39:56 +00:00
if ( $filter === 'posts' ) {
$ret = Item :: selectThread ([], $condition , $params );
} else {
$ret = Item :: select ([], $condition , $params );
}
2018-06-21 15:14:01 +00:00
$items = Item :: inArray ( $ret );
2016-10-23 21:59:40 +00:00
2016-03-30 21:25:20 +00:00
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2020-07-17 04:40:20 +00:00
$root = self :: addHeader ( $doc , $owner , $filter );
2016-03-30 21:25:20 +00:00
2017-11-09 16:05:18 +00:00
foreach ( $items as $item ) {
2020-01-19 20:21:13 +00:00
if ( DI :: config () -> get ( 'system' , 'ostatus_debug' )) {
2017-05-05 19:41:41 +00:00
$item [ 'body' ] .= '🍼' ;
}
2018-11-15 13:21:58 +00:00
2020-07-11 18:54:08 +00:00
if ( in_array ( $item [ " verb " ], [ Activity :: FOLLOW , Activity :: O_UNFOLLOW , Activity :: LIKE ])) {
continue ;
}
2020-07-17 04:40:20 +00:00
$entry = self :: entry ( $doc , $item , $owner , false );
2016-03-30 21:25:20 +00:00
$root -> appendChild ( $entry );
2017-08-03 05:42:44 +00:00
if ( $last_update < $item [ 'created' ]) {
$last_update = $item [ 'created' ];
}
2016-03-30 21:25:20 +00:00
}
2017-08-03 05:42:44 +00:00
$feeddata = trim ( $doc -> saveXML ());
2018-01-15 13:05:12 +00:00
$msg = [ 'feed' => $feeddata , 'last_update' => $last_update ];
2020-01-18 14:41:19 +00:00
DI :: cache () -> set ( $cachekey , $msg , Duration :: QUARTER_HOUR );
2017-08-03 05:42:44 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( 'Feed duration: ' . number_format ( microtime ( true ) - $stamp , 3 ) . ' - ' . $owner_nick . ' - ' . $filter . ' - ' . $previous_created , Logger :: DEBUG );
2017-08-03 05:42:44 +00:00
return $feeddata ;
2016-03-30 21:25:20 +00:00
}
2016-03-30 22:14:51 +00:00
/**
2020-01-19 06:05:23 +00:00
* Creates the XML for a salmon message
2016-03-30 22:14:51 +00:00
*
2017-11-09 16:05:18 +00:00
* @ param array $item Data of the item that is to be posted
2016-03-31 05:34:13 +00:00
* @ param array $owner Contact data of the poster
2016-03-30 22:14:51 +00:00
*
2016-03-31 05:34:13 +00:00
* @ return string XML for the salmon
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2016-03-30 22:14:51 +00:00
*/
2018-07-22 16:35:20 +00:00
public static function salmon ( array $item , array $owner )
2017-11-09 16:05:18 +00:00
{
2016-03-30 21:25:20 +00:00
$doc = new DOMDocument ( '1.0' , 'utf-8' );
$doc -> formatOutput = true ;
2015-11-23 17:44:47 +00:00
2020-01-19 20:21:13 +00:00
if ( DI :: config () -> get ( 'system' , 'ostatus_debug' )) {
2017-05-05 19:41:41 +00:00
$item [ 'body' ] .= '🐟' ;
}
2016-03-30 21:25:20 +00:00
$entry = self :: entry ( $doc , $item , $owner , true );
2015-11-23 17:44:47 +00:00
2016-03-30 21:25:20 +00:00
$doc -> appendChild ( $entry );
2017-08-03 05:42:44 +00:00
return trim ( $doc -> saveXML ());
2016-03-30 21:25:20 +00:00
}
2019-07-27 11:09:12 +00:00
/**
* Checks if the given contact url does support OStatus
*
* @ param string $url profile url
* @ return boolean
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
*/
2020-08-06 18:53:45 +00:00
public static function isSupportedByContactUrl ( $url )
2019-07-27 11:09:12 +00:00
{
2020-08-06 18:53:45 +00:00
$probe = Probe :: uri ( $url , Protocol :: OSTATUS );
2019-07-27 11:09:12 +00:00
return $probe [ 'network' ] == Protocol :: OSTATUS ;
}
2015-11-23 17:44:47 +00:00
}