2017-11-15 09:47:28 -05:00
< ? php
/**
* @ file src / Protocol / PortableContact . php
*
* @ todo Move GNU Social URL schemata ( http :// server . tld / user / number ) to http :// server . tld / username
* @ todo Fetch profile data from profile page for Redmatrix users
* @ todo Detect if it is a forum
*/
namespace Friendica\Protocol ;
use Friendica\Core\Config ;
use Friendica\Core\Worker ;
use Friendica\Database\DBM ;
2017-12-07 09:09:28 -05:00
use Friendica\Model\GContact ;
2017-12-07 08:57:35 -05:00
use Friendica\Model\Profile ;
2017-11-15 09:47:28 -05:00
use Friendica\Network\Probe ;
2018-01-26 23:09:48 -05:00
use Friendica\Util\Network ;
2017-11-15 10:53:16 -05:00
use dba ;
use DOMDocument ;
2017-12-17 15:24:57 -05:00
use DOMXPath ;
2017-11-15 10:53:16 -05:00
use Exception ;
2017-11-15 09:47:28 -05:00
2017-12-17 15:24:57 -05:00
require_once 'include/dba.php' ;
2017-11-15 09:47:28 -05:00
require_once 'include/datetime.php' ;
require_once 'include/html2bbcode.php' ;
class PortableContact
{
/**
* @ brief Fetch POCO data
*
* @ param integer $cid Contact ID
* @ param integer $uid User ID
* @ param integer $zcid Global Contact ID
* @ param integer $url POCO address that should be polled
*
* Given a contact - id ( minimum ), load the PortableContacts friend list for that contact ,
* and add the entries to the gcontact ( Global Contact ) table , or update existing entries
* if anything ( name or photo ) has changed .
* We use normalised urls for comparison which ignore http vs https and www . domain vs domain
*
* Once the global contact is stored add ( if necessary ) the contact linkage which associates
* the given uid , cid to the global contact entry . There can be many uid / cid combinations
* pointing to the same global contact id .
*
*/
public static function loadWorker ( $cid , $uid = 0 , $zcid = 0 , $url = null )
{
// Call the function "load" via the worker
2017-11-18 06:02:46 -05:00
Worker :: add ( PRIORITY_LOW , " DiscoverPoCo " , " load " , ( int ) $cid , ( int ) $uid , ( int ) $zcid , $url );
2017-11-15 09:47:28 -05:00
}
/**
* @ brief Fetch POCO data from the worker
*
* @ param integer $cid Contact ID
* @ param integer $uid User ID
* @ param integer $zcid Global Contact ID
* @ param integer $url POCO address that should be polled
*
*/
public static function load ( $cid , $uid , $zcid , $url )
{
$a = get_app ();
if ( $cid ) {
2017-12-19 01:47:39 -05:00
if ( ! $url || ! $uid ) {
2018-01-11 03:26:30 -05:00
$contact = dba :: selectFirst ( 'contact' , [ 'poco' , 'uid' ], [ 'id' => $cid ]);
if ( DBM :: is_result ( $contact )) {
$url = $contact [ 'poco' ];
$uid = $contact [ 'uid' ];
2017-11-15 09:47:28 -05:00
}
}
2017-12-19 01:47:39 -05:00
if ( ! $uid ) {
2017-11-15 09:47:28 -05:00
return ;
}
}
2017-12-19 01:47:39 -05:00
if ( ! $url ) {
2017-11-15 09:47:28 -05:00
return ;
}
$url = $url . (( $uid ) ? '/@me/@all?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation' : '?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation' ) ;
logger ( 'load: ' . $url , LOGGER_DEBUG );
2018-01-27 11:13:41 -05:00
$s = Network :: fetchUrl ( $url );
2017-11-15 09:47:28 -05:00
logger ( 'load: returns ' . $s , LOGGER_DATA );
logger ( 'load: return code: ' . $a -> get_curl_code (), LOGGER_DEBUG );
if (( $a -> get_curl_code () > 299 ) || ( ! $s )) {
return ;
}
$j = json_decode ( $s );
logger ( 'load: json: ' . print_r ( $j , true ), LOGGER_DATA );
if ( ! isset ( $j -> entry )) {
return ;
}
$total = 0 ;
foreach ( $j -> entry as $entry ) {
$total ++ ;
$profile_url = '' ;
$profile_photo = '' ;
$connect_url = '' ;
$name = '' ;
$network = '' ;
$updated = NULL_DATE ;
$location = '' ;
$about = '' ;
$keywords = '' ;
$gender = '' ;
$contact_type = - 1 ;
$generation = 0 ;
$name = $entry -> displayName ;
if ( isset ( $entry -> urls )) {
foreach ( $entry -> urls as $url ) {
if ( $url -> type == 'profile' ) {
$profile_url = $url -> value ;
continue ;
}
if ( $url -> type == 'webfinger' ) {
$connect_url = str_replace ( 'acct:' , '' , $url -> value );
continue ;
}
}
}
if ( isset ( $entry -> photos )) {
foreach ( $entry -> photos as $photo ) {
if ( $photo -> type == 'profile' ) {
$profile_photo = $photo -> value ;
continue ;
}
}
}
if ( isset ( $entry -> updated )) {
$updated = date ( " Y-m-d H:i:s " , strtotime ( $entry -> updated ));
}
if ( isset ( $entry -> network )) {
$network = $entry -> network ;
}
if ( isset ( $entry -> currentLocation )) {
$location = $entry -> currentLocation ;
}
if ( isset ( $entry -> aboutMe )) {
$about = html2bbcode ( $entry -> aboutMe );
}
if ( isset ( $entry -> gender )) {
$gender = $entry -> gender ;
}
if ( isset ( $entry -> generation ) && ( $entry -> generation > 0 )) {
$generation = ++ $entry -> generation ;
}
if ( isset ( $entry -> tags )) {
foreach ( $entry -> tags as $tag ) {
$keywords = implode ( " , " , $tag );
}
}
if ( isset ( $entry -> contactType ) && ( $entry -> contactType >= 0 )) {
$contact_type = $entry -> contactType ;
}
2018-01-15 08:05:12 -05:00
$gcontact = [ " url " => $profile_url ,
2017-11-15 09:47:28 -05:00
" name " => $name ,
" network " => $network ,
" photo " => $profile_photo ,
" about " => $about ,
" location " => $location ,
" gender " => $gender ,
" keywords " => $keywords ,
" connect " => $connect_url ,
" updated " => $updated ,
" contact-type " => $contact_type ,
2018-01-15 08:05:12 -05:00
" generation " => $generation ];
2017-11-15 09:47:28 -05:00
try {
2017-12-07 09:09:28 -05:00
$gcontact = GContact :: sanitize ( $gcontact );
$gcid = GContact :: update ( $gcontact );
2017-11-15 09:47:28 -05:00
2017-12-07 09:09:28 -05:00
GContact :: link ( $gcid , $uid , $cid , $zcid );
2017-11-15 09:47:28 -05:00
} catch ( Exception $e ) {
logger ( $e -> getMessage (), LOGGER_DEBUG );
}
}
logger ( " load: loaded $total entries " , LOGGER_DEBUG );
2017-12-19 01:47:39 -05:00
$condition = [ " `cid` = ? AND `uid` = ? AND `zcid` = ? AND `updated` < UTC_TIMESTAMP - INTERVAL 2 DAY " , $cid , $uid , $zcid ];
dba :: delete ( 'glink' , $condition );
2017-11-15 09:47:28 -05:00
}
2017-11-15 10:53:16 -05:00
public static function reachable ( $profile , $server = " " , $network = " " , $force = false )
{
2017-11-15 09:47:28 -05:00
if ( $server == " " ) {
2017-11-15 10:53:16 -05:00
$server = self :: detectServer ( $profile );
2017-11-15 09:47:28 -05:00
}
if ( $server == " " ) {
return true ;
}
2017-11-15 10:53:16 -05:00
return self :: checkServer ( $server , $network , $force );
2017-11-15 09:47:28 -05:00
}
2017-11-15 10:53:16 -05:00
public static function detectServer ( $profile )
{
2017-11-15 09:47:28 -05:00
// Try to detect the server path based upon some known standard paths
$server_url = " " ;
if ( $server_url == " " ) {
$friendica = preg_replace ( " =(https?://)(.*)/profile/(.*)=ism " , " $ 1 $ 2 " , $profile );
if ( $friendica != $profile ) {
$server_url = $friendica ;
$network = NETWORK_DFRN ;
}
}
if ( $server_url == " " ) {
$diaspora = preg_replace ( " =(https?://)(.*)/u/(.*)=ism " , " $ 1 $ 2 " , $profile );
if ( $diaspora != $profile ) {
$server_url = $diaspora ;
$network = NETWORK_DIASPORA ;
}
}
if ( $server_url == " " ) {
$red = preg_replace ( " =(https?://)(.*)/channel/(.*)=ism " , " $ 1 $ 2 " , $profile );
if ( $red != $profile ) {
$server_url = $red ;
$network = NETWORK_DIASPORA ;
}
}
// Mastodon
if ( $server_url == " " ) {
$mastodon = preg_replace ( " =(https?://)(.*)/users/(.*)=ism " , " $ 1 $ 2 " , $profile );
if ( $mastodon != $profile ) {
$server_url = $mastodon ;
$network = NETWORK_OSTATUS ;
}
}
// Numeric OStatus variant
if ( $server_url == " " ) {
$ostatus = preg_replace ( " =(https?://)(.*)/user/(.*)=ism " , " $ 1 $ 2 " , $profile );
if ( $ostatus != $profile ) {
$server_url = $ostatus ;
$network = NETWORK_OSTATUS ;
}
}
// Wild guess
if ( $server_url == " " ) {
$base = preg_replace ( " =(https?://)(.*?)/(.*)=ism " , " $ 1 $ 2 " , $profile );
if ( $base != $profile ) {
$server_url = $base ;
$network = NETWORK_PHANTOM ;
}
}
if ( $server_url == " " ) {
return " " ;
}
2017-11-15 10:53:16 -05:00
$r = q (
" SELECT `id` FROM `gserver` WHERE `nurl` = '%s' AND `last_contact` > `last_failure` " ,
dbesc ( normalise_link ( $server_url ))
);
2017-11-15 09:47:28 -05:00
if ( DBM :: is_result ( $r )) {
return $server_url ;
}
// Fetch the host-meta to check if this really is a server
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /.well-known/host-meta " );
2017-11-15 09:47:28 -05:00
if ( ! $serverret [ " success " ]) {
return " " ;
}
return $server_url ;
}
2017-11-15 10:53:16 -05:00
public static function alternateOStatusUrl ( $url )
{
2017-11-15 09:47:28 -05:00
return ( preg_match ( " =https?://.+/user/ \ d+=ism " , $url , $matches ));
}
2017-11-15 10:53:16 -05:00
public static function lastUpdated ( $profile , $force = false )
{
$gcontacts = q (
" SELECT * FROM `gcontact` WHERE `nurl` = '%s' " ,
dbesc ( normalise_link ( $profile ))
);
2017-11-15 09:47:28 -05:00
if ( ! DBM :: is_result ( $gcontacts )) {
return false ;
}
2018-01-15 08:05:12 -05:00
$contact = [ " url " => $profile ];
2017-11-15 09:47:28 -05:00
if ( $gcontacts [ 0 ][ " created " ] <= NULL_DATE ) {
$contact [ 'created' ] = datetime_convert ();
}
if ( $force ) {
2017-11-15 10:53:16 -05:00
$server_url = normalise_link ( self :: detectServer ( $profile ));
2017-11-15 09:47:28 -05:00
}
if (( $server_url == '' ) && ( $gcontacts [ 0 ][ " server_url " ] != " " )) {
$server_url = $gcontacts [ 0 ][ " server_url " ];
}
if ( ! $force && (( $server_url == '' ) || ( $gcontacts [ 0 ][ " server_url " ] == $gcontacts [ 0 ][ " nurl " ]))) {
2017-11-15 10:53:16 -05:00
$server_url = normalise_link ( self :: detectServer ( $profile ));
2017-11-15 09:47:28 -05:00
}
2018-01-15 08:05:12 -05:00
if ( ! in_array ( $gcontacts [ 0 ][ " network " ], [ NETWORK_DFRN , NETWORK_DIASPORA , NETWORK_FEED , NETWORK_OSTATUS , " " ])) {
2017-11-15 09:47:28 -05:00
logger ( " Profile " . $profile . " : Network type " . $gcontacts [ 0 ][ " network " ] . " can't be checked " , LOGGER_DEBUG );
return false ;
}
if ( $server_url != " " ) {
2017-11-15 10:53:16 -05:00
if ( ! self :: checkServer ( $server_url , $gcontacts [ 0 ][ " network " ], $force )) {
2017-11-15 09:47:28 -05:00
if ( $force ) {
2017-12-19 01:47:39 -05:00
$fields = [ 'last_failure' => datetime_convert ()];
dba :: update ( 'gcontact' , $fields , [ 'nurl' => normalise_link ( $profile )]);
2017-11-15 09:47:28 -05:00
}
logger ( " Profile " . $profile . " : Server " . $server_url . " wasn't reachable. " , LOGGER_DEBUG );
return false ;
}
$contact [ 'server_url' ] = $server_url ;
}
2018-01-15 08:05:12 -05:00
if ( in_array ( $gcontacts [ 0 ][ " network " ], [ " " , NETWORK_FEED ])) {
2017-11-15 10:53:16 -05:00
$server = q (
" SELECT `network` FROM `gserver` WHERE `nurl` = '%s' AND `network` != '' " ,
dbesc ( normalise_link ( $server_url ))
);
2017-11-15 09:47:28 -05:00
if ( $server ) {
$contact [ 'network' ] = $server [ 0 ][ " network " ];
} else {
return false ;
}
}
// noscrape is really fast so we don't cache the call.
if (( $server_url != " " ) && ( $gcontacts [ 0 ][ " nick " ] != " " )) {
// Use noscrape if possible
$server = q ( " SELECT `noscrape`, `network` FROM `gserver` WHERE `nurl` = '%s' AND `noscrape` != '' " , dbesc ( normalise_link ( $server_url )));
if ( $server ) {
2018-01-27 11:13:41 -05:00
$noscraperet = Network :: curl ( $server [ 0 ][ " noscrape " ] . " / " . $gcontacts [ 0 ][ " nick " ]);
2017-11-15 09:47:28 -05:00
if ( $noscraperet [ " success " ] && ( $noscraperet [ " body " ] != " " )) {
$noscrape = json_decode ( $noscraperet [ " body " ], true );
if ( is_array ( $noscrape )) {
$contact [ " network " ] = $server [ 0 ][ " network " ];
if ( isset ( $noscrape [ " fn " ])) {
$contact [ " name " ] = $noscrape [ " fn " ];
}
if ( isset ( $noscrape [ " comm " ])) {
$contact [ " community " ] = $noscrape [ " comm " ];
}
if ( isset ( $noscrape [ " tags " ])) {
$keywords = implode ( " " , $noscrape [ " tags " ]);
if ( $keywords != " " ) {
$contact [ " keywords " ] = $keywords ;
}
}
2017-11-19 17:03:39 -05:00
$location = Profile :: formatLocation ( $noscrape );
2017-11-15 09:47:28 -05:00
if ( $location ) {
$contact [ " location " ] = $location ;
}
if ( isset ( $noscrape [ " dfrn-notify " ])) {
$contact [ " notify " ] = $noscrape [ " dfrn-notify " ];
}
// Remove all fields that are not present in the gcontact table
unset ( $noscrape [ " fn " ]);
unset ( $noscrape [ " key " ]);
unset ( $noscrape [ " homepage " ]);
unset ( $noscrape [ " comm " ]);
unset ( $noscrape [ " tags " ]);
unset ( $noscrape [ " locality " ]);
unset ( $noscrape [ " region " ]);
unset ( $noscrape [ " country-name " ]);
unset ( $noscrape [ " contacts " ]);
unset ( $noscrape [ " dfrn-request " ]);
unset ( $noscrape [ " dfrn-confirm " ]);
unset ( $noscrape [ " dfrn-notify " ]);
unset ( $noscrape [ " dfrn-poll " ]);
// Set the date of the last contact
/// @todo By now the function "update_gcontact" doesn't work with this field
//$contact["last_contact"] = datetime_convert();
$contact = array_merge ( $contact , $noscrape );
2017-12-07 09:09:28 -05:00
GContact :: update ( $contact );
2017-11-15 09:47:28 -05:00
if ( trim ( $noscrape [ " updated " ]) != " " ) {
2017-12-19 01:47:39 -05:00
$fields = [ 'last_contact' => datetime_convert ()];
dba :: update ( 'gcontact' , $fields , [ 'nurl' => normalise_link ( $profile )]);
2017-11-15 09:47:28 -05:00
logger ( " Profile " . $profile . " was last updated at " . $noscrape [ " updated " ] . " (noscrape) " , LOGGER_DEBUG );
return $noscrape [ " updated " ];
}
}
}
}
}
// If we only can poll the feed, then we only do this once a while
2017-11-15 10:53:16 -05:00
if ( ! $force && ! self :: updateNeeded ( $gcontacts [ 0 ][ " created " ], $gcontacts [ 0 ][ " updated " ], $gcontacts [ 0 ][ " last_failure " ], $gcontacts [ 0 ][ " last_contact " ])) {
2017-11-15 09:47:28 -05:00
logger ( " Profile " . $profile . " was last updated at " . $gcontacts [ 0 ][ " updated " ] . " (cached) " , LOGGER_DEBUG );
2017-12-07 09:09:28 -05:00
GContact :: update ( $contact );
2017-11-15 09:47:28 -05:00
return $gcontacts [ 0 ][ " updated " ];
}
$data = Probe :: uri ( $profile );
// Is the profile link the alternate OStatus link notation? (http://domain.tld/user/4711)
// Then check the other link and delete this one
2017-11-15 10:53:16 -05:00
if (( $data [ " network " ] == NETWORK_OSTATUS ) && self :: alternateOStatusUrl ( $profile )
2017-11-15 09:47:28 -05:00
&& ( normalise_link ( $profile ) == normalise_link ( $data [ " alias " ]))
&& ( normalise_link ( $profile ) != normalise_link ( $data [ " url " ]))
) {
// Delete the old entry
2017-12-19 01:47:39 -05:00
dba :: delete ( 'gcontact' , [ 'nurl' => normalise_link ( $profile )]);
2017-11-15 09:47:28 -05:00
$gcontact = array_merge ( $gcontacts [ 0 ], $data );
$gcontact [ " server_url " ] = $data [ " baseurl " ];
try {
2017-12-07 09:09:28 -05:00
$gcontact = GContact :: sanitize ( $gcontact );
GContact :: update ( $gcontact );
2017-11-15 09:47:28 -05:00
2017-11-15 10:53:16 -05:00
self :: lastUpdated ( $data [ " url " ], $force );
2017-11-15 09:47:28 -05:00
} catch ( Exception $e ) {
logger ( $e -> getMessage (), LOGGER_DEBUG );
}
logger ( " Profile " . $profile . " was deleted " , LOGGER_DEBUG );
return false ;
}
2018-01-15 08:05:12 -05:00
if (( $data [ " poll " ] == " " ) || ( in_array ( $data [ " network " ], [ NETWORK_FEED , NETWORK_PHANTOM ]))) {
2017-12-19 01:47:39 -05:00
$fields = [ 'last_failure' => datetime_convert ()];
dba :: update ( 'gcontact' , $fields , [ 'nurl' => normalise_link ( $profile )]);
2017-11-15 09:47:28 -05:00
logger ( " Profile " . $profile . " wasn't reachable (profile) " , LOGGER_DEBUG );
return false ;
}
$contact = array_merge ( $contact , $data );
$contact [ " server_url " ] = $data [ " baseurl " ];
2017-12-07 09:09:28 -05:00
GContact :: update ( $contact );
2017-11-15 09:47:28 -05:00
2018-01-27 11:13:41 -05:00
$feedret = Network :: curl ( $data [ " poll " ]);
2017-11-15 09:47:28 -05:00
if ( ! $feedret [ " success " ]) {
2017-12-19 01:47:39 -05:00
$fields = [ 'last_failure' => datetime_convert ()];
dba :: update ( 'gcontact' , $fields , [ 'nurl' => normalise_link ( $profile )]);
2017-11-15 09:47:28 -05:00
logger ( " Profile " . $profile . " wasn't reachable (no feed) " , LOGGER_DEBUG );
return false ;
}
$doc = new DOMDocument ();
@ $doc -> loadXML ( $feedret [ " body " ]);
2017-12-17 15:24:57 -05:00
$xpath = new DOMXPath ( $doc );
2017-11-15 09:47:28 -05:00
$xpath -> registerNamespace ( 'atom' , " http://www.w3.org/2005/Atom " );
$entries = $xpath -> query ( '/atom:feed/atom:entry' );
$last_updated = " " ;
foreach ( $entries as $entry ) {
$published = $xpath -> query ( 'atom:published/text()' , $entry ) -> item ( 0 ) -> nodeValue ;
$updated = $xpath -> query ( 'atom:updated/text()' , $entry ) -> item ( 0 ) -> nodeValue ;
if ( $last_updated < $published )
$last_updated = $published ;
if ( $last_updated < $updated )
$last_updated = $updated ;
}
// Maybe there aren't any entries. Then check if it is a valid feed
if ( $last_updated == " " ) {
if ( $xpath -> query ( '/atom:feed' ) -> length > 0 ) {
$last_updated = NULL_DATE ;
}
}
2017-12-19 01:47:39 -05:00
$fields = [ 'updated' => DBM :: date ( $last_updated ), 'last_contact' => DBM :: date ()];
dba :: update ( 'gcontact' , $fields , [ 'nurl' => normalise_link ( $profile )]);
2017-11-15 09:47:28 -05:00
if (( $gcontacts [ 0 ][ " generation " ] == 0 )) {
2017-12-19 01:47:39 -05:00
$fields = [ 'generation' => 9 ];
dba :: update ( 'gcontact' , $fields , [ 'nurl' => normalise_link ( $profile )]);
2017-11-15 09:47:28 -05:00
}
logger ( " Profile " . $profile . " was last updated at " . $last_updated , LOGGER_DEBUG );
return ( $last_updated );
}
2017-11-15 10:53:16 -05:00
public static function updateNeeded ( $created , $updated , $last_failure , $last_contact )
{
2017-11-15 09:47:28 -05:00
$now = strtotime ( datetime_convert ());
if ( $updated > $last_contact ) {
$contact_time = strtotime ( $updated );
} else {
$contact_time = strtotime ( $last_contact );
}
$failure_time = strtotime ( $last_failure );
$created_time = strtotime ( $created );
// If there is no "created" time then use the current time
if ( $created_time <= 0 ) {
$created_time = $now ;
}
// If the last contact was less than 24 hours then don't update
if (( $now - $contact_time ) < ( 60 * 60 * 24 )) {
return false ;
}
// If the last failure was less than 24 hours then don't update
if (( $now - $failure_time ) < ( 60 * 60 * 24 )) {
return false ;
}
// If the last contact was less than a week ago and the last failure is older than a week then don't update
//if ((($now - $contact_time) < (60 * 60 * 24 * 7)) && ($contact_time > $failure_time))
// return false;
// If the last contact time was more than a week ago and the contact was created more than a week ago, then only try once a week
if ((( $now - $contact_time ) > ( 60 * 60 * 24 * 7 )) && (( $now - $created_time ) > ( 60 * 60 * 24 * 7 )) && (( $now - $failure_time ) < ( 60 * 60 * 24 * 7 ))) {
return false ;
}
// If the last contact time was more than a month ago and the contact was created more than a month ago, then only try once a month
if ((( $now - $contact_time ) > ( 60 * 60 * 24 * 30 )) && (( $now - $created_time ) > ( 60 * 60 * 24 * 30 )) && (( $now - $failure_time ) < ( 60 * 60 * 24 * 30 ))) {
return false ;
}
return true ;
}
2017-12-21 02:27:20 -05:00
private static function toBoolean ( $val )
2017-11-15 10:53:16 -05:00
{
2017-11-15 09:47:28 -05:00
if (( $val == " true " ) || ( $val == 1 )) {
return true ;
} elseif (( $val == " false " ) || ( $val == 0 )) {
return false ;
}
return $val ;
}
/**
* @ brief Detect server type ( Hubzilla or Friendica ) via the poco data
*
* @ param object $data POCO data
* @ return array Server data
*/
2017-12-21 02:27:20 -05:00
private static function detectPocoData ( $data )
2017-11-15 10:53:16 -05:00
{
2017-11-15 09:47:28 -05:00
$server = false ;
if ( ! isset ( $data -> entry )) {
return false ;
}
if ( count ( $data -> entry ) == 0 ) {
return false ;
}
if ( ! isset ( $data -> entry [ 0 ] -> urls )) {
return false ;
}
if ( count ( $data -> entry [ 0 ] -> urls ) == 0 ) {
return false ;
}
foreach ( $data -> entry [ 0 ] -> urls as $url ) {
if ( $url -> type == 'zot' ) {
2018-01-15 08:05:12 -05:00
$server = [];
2017-11-15 09:47:28 -05:00
$server [ " platform " ] = 'Hubzilla' ;
$server [ " network " ] = NETWORK_DIASPORA ;
return $server ;
}
}
return false ;
}
/**
* @ brief Detect server type by using the nodeinfo data
*
* @ param string $server_url address of the server
* @ return array Server data
*/
2017-12-21 02:27:20 -05:00
private static function fetchNodeinfo ( $server_url )
2017-11-15 10:53:16 -05:00
{
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /.well-known/nodeinfo " );
2017-11-15 09:47:28 -05:00
if ( ! $serverret [ " success " ]) {
return false ;
}
$nodeinfo = json_decode ( $serverret [ 'body' ]);
if ( ! is_object ( $nodeinfo )) {
return false ;
}
if ( ! is_array ( $nodeinfo -> links )) {
return false ;
}
$nodeinfo_url = '' ;
foreach ( $nodeinfo -> links as $link ) {
if ( $link -> rel == 'http://nodeinfo.diaspora.software/ns/schema/1.0' ) {
$nodeinfo_url = $link -> href ;
}
}
if ( $nodeinfo_url == '' ) {
return false ;
}
2017-12-19 04:57:24 -05:00
// When the nodeinfo url isn't on the same host, then there is obviously something wrong
if ( parse_url ( $server_url , PHP_URL_HOST ) != parse_url ( $nodeinfo_url , PHP_URL_HOST )) {
return false ;
}
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $nodeinfo_url );
2017-11-15 09:47:28 -05:00
if ( ! $serverret [ " success " ]) {
return false ;
}
$nodeinfo = json_decode ( $serverret [ 'body' ]);
if ( ! is_object ( $nodeinfo )) {
return false ;
}
2018-01-15 08:05:12 -05:00
$server = [];
2017-11-15 09:47:28 -05:00
$server [ 'register_policy' ] = REGISTER_CLOSED ;
if ( is_bool ( $nodeinfo -> openRegistrations ) && $nodeinfo -> openRegistrations ) {
$server [ 'register_policy' ] = REGISTER_OPEN ;
}
if ( is_object ( $nodeinfo -> software )) {
if ( isset ( $nodeinfo -> software -> name )) {
$server [ 'platform' ] = $nodeinfo -> software -> name ;
}
if ( isset ( $nodeinfo -> software -> version )) {
$server [ 'version' ] = $nodeinfo -> software -> version ;
// Version numbers on Nodeinfo are presented with additional info, e.g.:
// 0.6.3.0-p1702cc1c, 0.6.99.0-p1b9ab160 or 3.4.3-2-1191.
$server [ 'version' ] = preg_replace ( " =(.+)-(. { 4,})=ism " , " $ 1 " , $server [ 'version' ]);
}
}
if ( is_object ( $nodeinfo -> metadata )) {
if ( isset ( $nodeinfo -> metadata -> nodeName )) {
$server [ 'site_name' ] = $nodeinfo -> metadata -> nodeName ;
}
}
2017-12-18 18:58:18 -05:00
if ( ! empty ( $nodeinfo -> usage -> users -> total )) {
$server [ 'registered-users' ] = $nodeinfo -> usage -> users -> total ;
}
2017-11-15 09:47:28 -05:00
$diaspora = false ;
$friendica = false ;
$gnusocial = false ;
if ( is_array ( $nodeinfo -> protocols -> inbound )) {
foreach ( $nodeinfo -> protocols -> inbound as $inbound ) {
if ( $inbound == 'diaspora' ) {
$diaspora = true ;
}
if ( $inbound == 'friendica' ) {
$friendica = true ;
}
if ( $inbound == 'gnusocial' ) {
$gnusocial = true ;
}
}
}
if ( $gnusocial ) {
$server [ 'network' ] = NETWORK_OSTATUS ;
}
if ( $diaspora ) {
$server [ 'network' ] = NETWORK_DIASPORA ;
}
if ( $friendica ) {
$server [ 'network' ] = NETWORK_DFRN ;
}
if ( ! $server ) {
return false ;
}
return $server ;
}
/**
* @ brief Detect server type ( Hubzilla or Friendica ) via the front page body
*
* @ param string $body Front page of the server
* @ return array Server data
*/
2017-12-21 02:27:20 -05:00
private static function detectServerType ( $body )
2017-11-15 10:53:16 -05:00
{
2017-11-15 09:47:28 -05:00
$server = false ;
$doc = new DOMDocument ();
@ $doc -> loadHTML ( $body );
2017-12-17 15:24:57 -05:00
$xpath = new DOMXPath ( $doc );
2017-11-15 09:47:28 -05:00
$list = $xpath -> query ( " //meta[@name] " );
foreach ( $list as $node ) {
2018-01-15 08:05:12 -05:00
$attr = [];
2017-11-15 09:47:28 -05:00
if ( $node -> attributes -> length ) {
foreach ( $node -> attributes as $attribute ) {
$attr [ $attribute -> name ] = $attribute -> value ;
}
}
if ( $attr [ 'name' ] == 'generator' ) {
$version_part = explode ( " " , $attr [ 'content' ]);
if ( count ( $version_part ) == 2 ) {
2018-01-15 08:05:12 -05:00
if ( in_array ( $version_part [ 0 ], [ " Friendika " , " Friendica " ])) {
$server = [];
2017-11-15 09:47:28 -05:00
$server [ " platform " ] = $version_part [ 0 ];
$server [ " version " ] = $version_part [ 1 ];
$server [ " network " ] = NETWORK_DFRN ;
}
}
}
}
if ( ! $server ) {
$list = $xpath -> query ( " //meta[@property] " );
foreach ( $list as $node ) {
2018-01-15 08:05:12 -05:00
$attr = [];
2017-11-15 09:47:28 -05:00
if ( $node -> attributes -> length ) {
foreach ( $node -> attributes as $attribute ) {
$attr [ $attribute -> name ] = $attribute -> value ;
}
}
2018-01-15 08:05:12 -05:00
if ( $attr [ 'property' ] == 'generator' && in_array ( $attr [ 'content' ], [ " hubzilla " , " BlaBlaNet " ])) {
$server = [];
2017-11-15 09:47:28 -05:00
$server [ " platform " ] = $attr [ 'content' ];
$server [ " version " ] = " " ;
$server [ " network " ] = NETWORK_DIASPORA ;
}
}
}
if ( ! $server ) {
return false ;
}
2017-12-17 16:22:58 -05:00
$server [ " site_name " ] = $xpath -> evaluate ( " //head/title/text() " ) -> item ( 0 ) -> nodeValue ;
2017-11-15 09:47:28 -05:00
return $server ;
}
2017-11-15 10:53:16 -05:00
public static function checkServer ( $server_url , $network = " " , $force = false )
{
2017-11-15 09:47:28 -05:00
// Unify the server address
$server_url = trim ( $server_url , " / " );
$server_url = str_replace ( " /index.php " , " " , $server_url );
if ( $server_url == " " ) {
return false ;
}
2018-01-11 03:26:30 -05:00
$gserver = dba :: selectFirst ( 'gserver' , [], [ 'nurl' => normalise_link ( $server_url )]);
if ( DBM :: is_result ( $gserver )) {
if ( $gserver [ " created " ] <= NULL_DATE ) {
2017-12-19 01:47:39 -05:00
$fields = [ 'created' => datetime_convert ()];
$condition = [ 'nurl' => normalise_link ( $server_url )];
dba :: update ( 'gserver' , $fields , $condition );
2017-11-15 09:47:28 -05:00
}
2018-01-11 03:26:30 -05:00
$poco = $gserver [ " poco " ];
$noscrape = $gserver [ " noscrape " ];
2017-11-15 09:47:28 -05:00
if ( $network == " " ) {
2018-01-11 03:26:30 -05:00
$network = $gserver [ " network " ];
2017-11-15 09:47:28 -05:00
}
2018-01-11 03:26:30 -05:00
$last_contact = $gserver [ " last_contact " ];
$last_failure = $gserver [ " last_failure " ];
$version = $gserver [ " version " ];
$platform = $gserver [ " platform " ];
$site_name = $gserver [ " site_name " ];
$info = $gserver [ " info " ];
$register_policy = $gserver [ " register_policy " ];
$registered_users = $gserver [ " registered-users " ];
2017-11-15 09:47:28 -05:00
2018-01-11 03:26:30 -05:00
if ( ! $force && ! self :: updateNeeded ( $gserver [ " created " ], " " , $last_failure , $last_contact )) {
2017-11-15 09:47:28 -05:00
logger ( " Use cached data for server " . $server_url , LOGGER_DEBUG );
return ( $last_contact >= $last_failure );
}
} else {
$poco = " " ;
$noscrape = " " ;
$version = " " ;
$platform = " " ;
$site_name = " " ;
$info = " " ;
$register_policy = - 1 ;
2017-12-18 18:58:18 -05:00
$registered_users = 0 ;
2017-11-15 09:47:28 -05:00
$last_contact = NULL_DATE ;
$last_failure = NULL_DATE ;
}
2018-01-11 03:26:30 -05:00
logger ( " Server " . $server_url . " is outdated or unknown. Start discovery. Force: " . $force . " Created: " . $gserver [ " created " ] . " Failure: " . $last_failure . " Contact: " . $last_contact , LOGGER_DEBUG );
2017-11-15 09:47:28 -05:00
$failure = false ;
$possible_failure = false ;
$orig_last_failure = $last_failure ;
$orig_last_contact = $last_contact ;
2017-12-19 06:54:02 -05:00
// Mastodon uses the "@" for user profiles.
// But this can be misunderstood.
if ( parse_url ( $server_url , PHP_URL_USER ) != '' ) {
2018-01-15 08:05:12 -05:00
dba :: update ( 'gserver' , [ 'last_failure' => datetime_convert ()], [ 'nurl' => normalise_link ( $server_url )]);
2017-12-19 06:54:02 -05:00
return false ;
}
2017-11-15 09:47:28 -05:00
// Check if the page is accessible via SSL.
$orig_server_url = $server_url ;
$server_url = str_replace ( " http:// " , " https:// " , $server_url );
// We set the timeout to 20 seconds since this operation should be done in no time if the server was vital
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /.well-known/host-meta " , false , $redirects , [ 'timeout' => 20 ]);
2017-11-15 09:47:28 -05:00
// Quit if there is a timeout.
// But we want to make sure to only quit if we are mostly sure that this server url fits.
2018-01-11 03:26:30 -05:00
if ( DBM :: is_result ( $gserver ) && ( $orig_server_url == $server_url ) &&
2017-11-15 09:47:28 -05:00
( $serverret [ 'errno' ] == CURLE_OPERATION_TIMEDOUT )) {
logger ( " Connection to server " . $server_url . " timed out. " , LOGGER_DEBUG );
2018-01-15 08:05:12 -05:00
dba :: update ( 'gserver' , [ 'last_failure' => datetime_convert ()], [ 'nurl' => normalise_link ( $server_url )]);
2017-11-15 09:47:28 -05:00
return false ;
}
// Maybe the page is unencrypted only?
2017-11-15 10:53:16 -05:00
$xmlobj = @ simplexml_load_string ( $serverret [ " body " ], 'SimpleXMLElement' , 0 , " http://docs.oasis-open.org/ns/xri/xrd-1.0 " );
2017-11-15 09:47:28 -05:00
if ( ! $serverret [ " success " ] || ( $serverret [ " body " ] == " " ) || ( @ sizeof ( $xmlobj ) == 0 ) || ! is_object ( $xmlobj )) {
$server_url = str_replace ( " https:// " , " http:// " , $server_url );
// We set the timeout to 20 seconds since this operation should be done in no time if the server was vital
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /.well-known/host-meta " , false , $redirects , [ 'timeout' => 20 ]);
2017-11-15 09:47:28 -05:00
// Quit if there is a timeout
if ( $serverret [ 'errno' ] == CURLE_OPERATION_TIMEDOUT ) {
logger ( " Connection to server " . $server_url . " timed out. " , LOGGER_DEBUG );
2018-01-15 08:05:12 -05:00
dba :: update ( 'gserver' , [ 'last_failure' => datetime_convert ()], [ 'nurl' => normalise_link ( $server_url )]);
2017-11-15 09:47:28 -05:00
return false ;
}
2017-11-15 10:53:16 -05:00
$xmlobj = @ simplexml_load_string ( $serverret [ " body " ], 'SimpleXMLElement' , 0 , " http://docs.oasis-open.org/ns/xri/xrd-1.0 " );
2017-11-15 09:47:28 -05:00
}
if ( ! $serverret [ " success " ] || ( $serverret [ " body " ] == " " ) || ( sizeof ( $xmlobj ) == 0 ) || ! is_object ( $xmlobj )) {
// Workaround for bad configured servers (known nginx problem)
2018-01-15 08:05:12 -05:00
if ( ! in_array ( $serverret [ " debug " ][ " http_code " ], [ " 403 " , " 404 " ])) {
2017-11-15 09:47:28 -05:00
$failure = true ;
}
$possible_failure = true ;
}
// If the server has no possible failure we reset the cached data
if ( ! $possible_failure ) {
$version = " " ;
$platform = " " ;
$site_name = " " ;
$info = " " ;
$register_policy = - 1 ;
}
2017-12-18 18:58:18 -05:00
if ( ! $failure ) {
// This will be too low, but better than no value at all.
$registered_users = dba :: count ( 'gcontact' , [ 'server_url' => normalise_link ( $server_url )]);
}
2017-11-15 09:47:28 -05:00
// Look for poco
if ( ! $failure ) {
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /poco " );
2017-11-15 09:47:28 -05:00
if ( $serverret [ " success " ]) {
$data = json_decode ( $serverret [ " body " ]);
if ( isset ( $data -> totalResults )) {
2017-12-18 18:58:18 -05:00
$registered_users = $data -> totalResults ;
2017-11-15 09:47:28 -05:00
$poco = $server_url . " /poco " ;
2017-11-15 10:53:16 -05:00
$server = self :: detectPocoData ( $data );
2017-11-15 09:47:28 -05:00
if ( $server ) {
$platform = $server [ 'platform' ];
$network = $server [ 'network' ];
$version = '' ;
$site_name = '' ;
}
}
2017-12-19 04:57:24 -05:00
// There are servers out there who don't return 404 on a failure
// We have to be sure that don't misunderstand this
if ( is_null ( $data )) {
$poco = " " ;
$noscrape = " " ;
$network = " " ;
}
2017-11-15 09:47:28 -05:00
}
}
if ( ! $failure ) {
// Test for Diaspora, Hubzilla, Mastodon or older Friendica servers
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url );
2017-11-15 09:47:28 -05:00
if ( ! $serverret [ " success " ] || ( $serverret [ " body " ] == " " )) {
$failure = true ;
} else {
2017-11-15 10:53:16 -05:00
$server = self :: detectServerType ( $serverret [ " body " ]);
2017-11-15 09:47:28 -05:00
if ( $server ) {
$platform = $server [ 'platform' ];
$network = $server [ 'network' ];
$version = $server [ 'version' ];
$site_name = $server [ 'site_name' ];
}
2017-11-15 10:53:16 -05:00
$lines = explode ( " \n " , $serverret [ " header " ]);
2017-11-15 09:47:28 -05:00
if ( count ( $lines )) {
2017-11-15 10:53:16 -05:00
foreach ( $lines as $line ) {
2017-11-15 09:47:28 -05:00
$line = trim ( $line );
2017-11-15 10:53:16 -05:00
if ( stristr ( $line , 'X-Diaspora-Version:' )) {
2017-11-15 09:47:28 -05:00
$platform = " Diaspora " ;
$version = trim ( str_replace ( " X-Diaspora-Version: " , " " , $line ));
$version = trim ( str_replace ( " x-diaspora-version: " , " " , $version ));
$network = NETWORK_DIASPORA ;
$versionparts = explode ( " - " , $version );
$version = $versionparts [ 0 ];
}
2017-11-15 10:53:16 -05:00
if ( stristr ( $line , 'Server: Mastodon' )) {
2017-11-15 09:47:28 -05:00
$platform = " Mastodon " ;
$network = NETWORK_OSTATUS ;
}
}
}
}
}
if ( ! $failure && ( $poco == " " )) {
// Test for Statusnet
// Will also return data for Friendica and GNU Social - but it will be overwritten later
// The "not implemented" is a special treatment for really, really old Friendica versions
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /api/statusnet/version.json " );
2017-11-15 09:47:28 -05:00
if ( $serverret [ " success " ] && ( $serverret [ " body " ] != '{"error":"not implemented"}' ) &&
( $serverret [ " body " ] != '' ) && ( strlen ( $serverret [ " body " ]) < 30 )) {
$platform = " StatusNet " ;
// Remove junk that some GNU Social servers return
$version = str_replace ( chr ( 239 ) . chr ( 187 ) . chr ( 191 ), " " , $serverret [ " body " ]);
$version = trim ( $version , '"' );
$network = NETWORK_OSTATUS ;
}
// Test for GNU Social
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /api/gnusocial/version.json " );
2017-11-15 09:47:28 -05:00
if ( $serverret [ " success " ] && ( $serverret [ " body " ] != '{"error":"not implemented"}' ) &&
( $serverret [ " body " ] != '' ) && ( strlen ( $serverret [ " body " ]) < 30 )) {
$platform = " GNU Social " ;
// Remove junk that some GNU Social servers return
$version = str_replace ( chr ( 239 ) . chr ( 187 ) . chr ( 191 ), " " , $serverret [ " body " ]);
$version = trim ( $version , '"' );
$network = NETWORK_OSTATUS ;
}
// Test for Mastodon
$orig_version = $version ;
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /api/v1/instance " );
2017-11-15 09:47:28 -05:00
if ( $serverret [ " success " ] && ( $serverret [ " body " ] != '' )) {
$data = json_decode ( $serverret [ " body " ]);
2017-12-19 04:57:24 -05:00
2017-11-15 09:47:28 -05:00
if ( isset ( $data -> version )) {
$platform = " Mastodon " ;
$version = $data -> version ;
$site_name = $data -> title ;
$info = $data -> description ;
$network = NETWORK_OSTATUS ;
}
2017-12-18 18:58:18 -05:00
if ( ! empty ( $data -> stats -> user_count )) {
$registered_users = $data -> stats -> user_count ;
}
2017-11-15 09:47:28 -05:00
}
if ( strstr ( $orig_version . $version , 'Pleroma' )) {
$platform = 'Pleroma' ;
$version = trim ( str_replace ( 'Pleroma' , '' , $version ));
}
}
if ( ! $failure ) {
// Test for Hubzilla and Red
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /siteinfo.json " );
2017-11-15 09:47:28 -05:00
if ( $serverret [ " success " ]) {
$data = json_decode ( $serverret [ " body " ]);
if ( isset ( $data -> url )) {
$platform = $data -> platform ;
$version = $data -> version ;
$network = NETWORK_DIASPORA ;
}
if ( ! empty ( $data -> site_name )) {
$site_name = $data -> site_name ;
}
2017-12-18 18:58:18 -05:00
if ( ! empty ( $data -> channels_total )) {
$registered_users = $data -> channels_total ;
}
2017-11-15 09:47:28 -05:00
switch ( $data -> register_policy ) {
case " REGISTER_OPEN " :
$register_policy = REGISTER_OPEN ;
break ;
case " REGISTER_APPROVE " :
$register_policy = REGISTER_APPROVE ;
break ;
case " REGISTER_CLOSED " :
default :
$register_policy = REGISTER_CLOSED ;
break ;
}
} else {
// Test for Hubzilla, Redmatrix or Friendica
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /api/statusnet/config.json " );
2017-11-15 09:47:28 -05:00
if ( $serverret [ " success " ]) {
$data = json_decode ( $serverret [ " body " ]);
if ( isset ( $data -> site -> server )) {
if ( isset ( $data -> site -> platform )) {
$platform = $data -> site -> platform -> PLATFORM_NAME ;
$version = $data -> site -> platform -> STD_VERSION ;
$network = NETWORK_DIASPORA ;
}
if ( isset ( $data -> site -> BlaBlaNet )) {
$platform = $data -> site -> BlaBlaNet -> PLATFORM_NAME ;
$version = $data -> site -> BlaBlaNet -> STD_VERSION ;
$network = NETWORK_DIASPORA ;
}
if ( isset ( $data -> site -> hubzilla )) {
$platform = $data -> site -> hubzilla -> PLATFORM_NAME ;
$version = $data -> site -> hubzilla -> RED_VERSION ;
$network = NETWORK_DIASPORA ;
}
if ( isset ( $data -> site -> redmatrix )) {
if ( isset ( $data -> site -> redmatrix -> PLATFORM_NAME )) {
$platform = $data -> site -> redmatrix -> PLATFORM_NAME ;
} elseif ( isset ( $data -> site -> redmatrix -> RED_PLATFORM )) {
$platform = $data -> site -> redmatrix -> RED_PLATFORM ;
}
$version = $data -> site -> redmatrix -> RED_VERSION ;
$network = NETWORK_DIASPORA ;
}
if ( isset ( $data -> site -> friendica )) {
$platform = $data -> site -> friendica -> FRIENDICA_PLATFORM ;
$version = $data -> site -> friendica -> FRIENDICA_VERSION ;
$network = NETWORK_DFRN ;
}
$site_name = $data -> site -> name ;
2017-11-15 10:53:16 -05:00
$data -> site -> closed = self :: toBoolean ( $data -> site -> closed );
$data -> site -> private = self :: toBoolean ( $data -> site -> private );
$data -> site -> inviteonly = self :: toBoolean ( $data -> site -> inviteonly );
2017-11-15 09:47:28 -05:00
if ( ! $data -> site -> closed && ! $data -> site -> private and $data -> site -> inviteonly ) {
$register_policy = REGISTER_APPROVE ;
} elseif ( ! $data -> site -> closed && ! $data -> site -> private ) {
$register_policy = REGISTER_OPEN ;
} else {
$register_policy = REGISTER_CLOSED ;
}
}
}
}
}
// Query statistics.json. Optional package for Diaspora, Friendica and Redmatrix
if ( ! $failure ) {
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /statistics.json " );
2017-11-15 09:47:28 -05:00
if ( $serverret [ " success " ]) {
$data = json_decode ( $serverret [ " body " ]);
2017-12-19 04:57:24 -05:00
2017-11-15 09:47:28 -05:00
if ( isset ( $data -> version )) {
$version = $data -> version ;
// Version numbers on statistics.json are presented with additional info, e.g.:
// 0.6.3.0-p1702cc1c, 0.6.99.0-p1b9ab160 or 3.4.3-2-1191.
$version = preg_replace ( " =(.+)-(. { 4,})=ism " , " $ 1 " , $version );
}
if ( ! empty ( $data -> name )) {
$site_name = $data -> name ;
}
if ( ! empty ( $data -> network )) {
$platform = $data -> network ;
}
if ( $platform == " Diaspora " ) {
$network = NETWORK_DIASPORA ;
}
if ( $data -> registrations_open ) {
$register_policy = REGISTER_OPEN ;
} else {
$register_policy = REGISTER_CLOSED ;
}
}
}
// Query nodeinfo. Working for (at least) Diaspora and Friendica.
if ( ! $failure ) {
2017-11-15 10:53:16 -05:00
$server = self :: fetchNodeinfo ( $server_url );
2017-11-15 09:47:28 -05:00
if ( $server ) {
$register_policy = $server [ 'register_policy' ];
if ( isset ( $server [ 'platform' ])) {
$platform = $server [ 'platform' ];
}
if ( isset ( $server [ 'network' ])) {
$network = $server [ 'network' ];
}
if ( isset ( $server [ 'version' ])) {
$version = $server [ 'version' ];
}
if ( isset ( $server [ 'site_name' ])) {
$site_name = $server [ 'site_name' ];
}
2017-12-18 18:58:18 -05:00
if ( isset ( $server [ 'registered-users' ])) {
$registered_users = $server [ 'registered-users' ];
}
2017-11-15 09:47:28 -05:00
}
}
// Check for noscrape
// Friendica servers could be detected as OStatus servers
2018-01-15 08:05:12 -05:00
if ( ! $failure && in_array ( $network , [ NETWORK_DFRN , NETWORK_OSTATUS ])) {
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /friendica/json " );
2017-11-15 09:47:28 -05:00
if ( ! $serverret [ " success " ]) {
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $server_url . " /friendika/json " );
2017-11-15 09:47:28 -05:00
}
if ( $serverret [ " success " ]) {
$data = json_decode ( $serverret [ " body " ]);
if ( isset ( $data -> version )) {
$network = NETWORK_DFRN ;
$noscrape = $data -> no_scrape_url ;
$version = $data -> version ;
$site_name = $data -> site_name ;
$info = $data -> info ;
$register_policy_str = $data -> register_policy ;
$platform = $data -> platform ;
switch ( $register_policy_str ) {
case " REGISTER_CLOSED " :
$register_policy = REGISTER_CLOSED ;
break ;
case " REGISTER_APPROVE " :
$register_policy = REGISTER_APPROVE ;
break ;
case " REGISTER_OPEN " :
$register_policy = REGISTER_OPEN ;
break ;
}
}
}
}
2017-12-19 06:54:02 -05:00
// Every server has got at least an admin account
if ( ! $failure && ( $registered_users == 0 )) {
$registered_users = 1 ;
}
2017-11-15 09:47:28 -05:00
if ( $possible_failure && ! $failure ) {
$failure = true ;
}
if ( $failure ) {
$last_contact = $orig_last_contact ;
$last_failure = datetime_convert ();
} else {
$last_contact = datetime_convert ();
$last_failure = $orig_last_failure ;
}
if (( $last_contact <= $last_failure ) && ! $failure ) {
logger ( " Server " . $server_url . " seems to be alive, but last contact wasn't set - could be a bug " , LOGGER_DEBUG );
} elseif (( $last_contact >= $last_failure ) && $failure ) {
logger ( " Server " . $server_url . " seems to be dead, but last failure wasn't set - could be a bug " , LOGGER_DEBUG );
}
// Check again if the server exists
2018-01-15 08:05:12 -05:00
$found = dba :: exists ( 'gserver' , [ 'nurl' => normalise_link ( $server_url )]);
2017-11-15 09:47:28 -05:00
$version = strip_tags ( $version );
$site_name = strip_tags ( $site_name );
$info = strip_tags ( $info );
$platform = strip_tags ( $platform );
2017-12-19 01:47:39 -05:00
$fields = [ 'url' => $server_url , 'version' => $version ,
'site_name' => $site_name , 'info' => $info , 'register_policy' => $register_policy ,
'poco' => $poco , 'noscrape' => $noscrape , 'network' => $network ,
'platform' => $platform , 'registered-users' => $registered_users ,
2017-12-19 04:18:45 -05:00
'last_contact' => $last_contact , 'last_failure' => $last_failure ];
2017-12-19 01:47:39 -05:00
if ( $found ) {
2017-12-18 18:58:18 -05:00
dba :: update ( 'gserver' , $fields , [ 'nurl' => normalise_link ( $server_url )]);
2017-11-15 09:47:28 -05:00
} elseif ( ! $failure ) {
2017-12-19 01:47:39 -05:00
$fields [ 'nurl' ] = normalise_link ( $server_url );
$fields [ 'created' ] = datetime_convert ();
2017-12-18 18:58:18 -05:00
dba :: insert ( 'gserver' , $fields );
2017-11-15 09:47:28 -05:00
}
logger ( " End discovery for server " . $server_url , LOGGER_DEBUG );
return ! $failure ;
}
/**
* @ brief Returns a list of all known servers
* @ return array List of server urls
*/
2017-11-15 10:53:16 -05:00
public static function serverlist ()
{
$r = q (
" SELECT `url`, `site_name` AS `displayName`, `network`, `platform`, `version` FROM `gserver`
2017-11-15 09:47:28 -05:00
WHERE `network` IN ( '%s' , '%s' , '%s' ) AND `last_contact` > `last_failure`
ORDER BY `last_contact`
LIMIT 1000 " ,
2017-11-15 10:53:16 -05:00
dbesc ( NETWORK_DFRN ),
dbesc ( NETWORK_DIASPORA ),
dbesc ( NETWORK_OSTATUS )
);
2017-11-15 09:47:28 -05:00
if ( ! DBM :: is_result ( $r )) {
return false ;
}
return $r ;
}
/**
* @ brief Fetch server list from remote servers and adds them when they are new .
*
* @ param string $poco URL to the POCO endpoint
*/
2017-12-21 02:27:20 -05:00
private static function fetchServerlist ( $poco )
2017-11-15 10:53:16 -05:00
{
2018-01-27 11:13:41 -05:00
$serverret = Network :: curl ( $poco . " /@server " );
2017-11-15 09:47:28 -05:00
if ( ! $serverret [ " success " ]) {
return ;
}
$serverlist = json_decode ( $serverret [ 'body' ]);
if ( ! is_array ( $serverlist )) {
return ;
}
foreach ( $serverlist as $server ) {
$server_url = str_replace ( " /index.php " , " " , $server -> url );
$r = q ( " SELECT `nurl` FROM `gserver` WHERE `nurl` = '%s' " , dbesc ( normalise_link ( $server_url )));
if ( ! DBM :: is_result ( $r )) {
logger ( " Call server check for server " . $server_url , LOGGER_DEBUG );
2017-11-18 06:02:46 -05:00
Worker :: add ( PRIORITY_LOW , " DiscoverPoCo " , " server " , $server_url );
2017-11-15 09:47:28 -05:00
}
}
}
2017-12-21 02:27:20 -05:00
private static function discoverFederation ()
2017-11-15 10:53:16 -05:00
{
$last = Config :: get ( 'poco' , 'last_federation_discovery' );
2017-11-15 09:47:28 -05:00
if ( $last ) {
$next = $last + ( 24 * 60 * 60 );
if ( $next > time ()) {
return ;
}
}
// Discover Friendica, Hubzilla and Diaspora servers
$serverdata = fetch_url ( " http://the-federation.info/pods.json " );
if ( $serverdata ) {
$servers = json_decode ( $serverdata );
foreach ( $servers -> pods as $server ) {
2017-11-18 06:02:46 -05:00
Worker :: add ( PRIORITY_LOW , " DiscoverPoCo " , " server " , " https:// " . $server -> host );
2017-11-15 09:47:28 -05:00
}
}
// Disvover Mastodon servers
2017-11-15 10:53:16 -05:00
if ( ! Config :: get ( 'system' , 'ostatus_disabled' )) {
2017-12-06 15:09:59 -05:00
$accesstoken = Config :: get ( 'system' , 'instances_social_key' );
if ( ! empty ( $accesstoken )) {
$api = 'https://instances.social/api/1.0/instances/list?count=0' ;
2018-01-15 08:05:12 -05:00
$header = [ 'Authorization: Bearer ' . $accesstoken ];
2018-01-27 11:13:41 -05:00
$serverdata = Network :: curl ( $api , false , $redirects , [ 'headers' => $header ]);
2017-12-06 15:09:59 -05:00
if ( $serverdata [ 'success' ]) {
$servers = json_decode ( $serverdata [ 'body' ]);
foreach ( $servers -> instances as $server ) {
$url = ( is_null ( $server -> https_score ) ? 'http' : 'https' ) . '://' . $server -> name ;
Worker :: add ( PRIORITY_LOW , " DiscoverPoCo " , " server " , $url );
}
2017-11-15 09:47:28 -05:00
}
}
}
// Currently disabled, since the service isn't available anymore.
// It is not removed since I hope that there will be a successor.
// Discover GNU Social Servers.
//if (!Config::get('system','ostatus_disabled')) {
// $serverdata = "http://gstools.org/api/get_open_instances/";
2018-01-27 11:13:41 -05:00
// $result = Network::curl($serverdata);
2017-11-15 09:47:28 -05:00
// if ($result["success"]) {
// $servers = json_decode($result["body"]);
// foreach($servers->data as $server)
2017-11-15 10:53:16 -05:00
// self::checkServer($server->instance_address);
2017-11-15 09:47:28 -05:00
// }
//}
2017-11-15 10:53:16 -05:00
Config :: set ( 'poco' , 'last_federation_discovery' , time ());
2017-11-15 09:47:28 -05:00
}
2017-11-15 10:53:16 -05:00
public static function discoverSingleServer ( $id )
{
2017-11-15 09:47:28 -05:00
$r = q ( " SELECT `poco`, `nurl`, `url`, `network` FROM `gserver` WHERE `id` = %d " , intval ( $id ));
if ( ! DBM :: is_result ( $r )) {
return false ;
}
$server = $r [ 0 ];
// Discover new servers out there (Works from Friendica version 3.5.2)
2017-11-15 10:53:16 -05:00
self :: fetchServerlist ( $server [ " poco " ]);
2017-11-15 09:47:28 -05:00
// Fetch all users from the other server
$url = $server [ " poco " ] . " /?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation " ;
logger ( " Fetch all users from the server " . $server [ " url " ], LOGGER_DEBUG );
2018-01-27 11:13:41 -05:00
$retdata = Network :: curl ( $url );
2017-11-15 09:47:28 -05:00
if ( $retdata [ " success " ]) {
$data = json_decode ( $retdata [ " body " ]);
2017-11-15 10:53:16 -05:00
self :: discoverServer ( $data , 2 );
2017-11-15 09:47:28 -05:00
2017-11-15 10:53:16 -05:00
if ( Config :: get ( 'system' , 'poco_discovery' ) > 1 ) {
$timeframe = Config :: get ( 'system' , 'poco_discovery_since' );
2017-11-15 09:47:28 -05:00
if ( $timeframe == 0 ) {
$timeframe = 30 ;
}
$updatedSince = date ( " Y-m-d H:i:s " , time () - $timeframe * 86400 );
// Fetch all global contacts from the other server (Not working with Redmatrix and Friendica versions before 3.3)
$url = $server [ " poco " ] . " /@global?updatedSince= " . $updatedSince . " &fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation " ;
$success = false ;
2018-01-27 11:13:41 -05:00
$retdata = Network :: curl ( $url );
2017-11-15 09:47:28 -05:00
if ( $retdata [ " success " ]) {
logger ( " Fetch all global contacts from the server " . $server [ " nurl " ], LOGGER_DEBUG );
2017-11-15 10:53:16 -05:00
$success = self :: discoverServer ( json_decode ( $retdata [ " body " ]));
2017-11-15 09:47:28 -05:00
}
2017-11-15 10:53:16 -05:00
if ( ! $success && ( Config :: get ( 'system' , 'poco_discovery' ) > 2 )) {
2017-11-15 09:47:28 -05:00
logger ( " Fetch contacts from users of the server " . $server [ " nurl " ], LOGGER_DEBUG );
2017-11-15 10:53:16 -05:00
self :: discoverServerUsers ( $data , $server );
2017-11-15 09:47:28 -05:00
}
}
2017-12-19 01:47:39 -05:00
$fields = [ 'last_poco_query' => datetime_convert ()];
dba :: update ( 'gserver' , $fields , [ 'nurl' => $server [ " nurl " ]]);
2017-11-15 09:47:28 -05:00
return true ;
} else {
// If the server hadn't replied correctly, then force a sanity check
2017-11-15 10:53:16 -05:00
self :: checkServer ( $server [ " url " ], $server [ " network " ], true );
2017-11-15 09:47:28 -05:00
// If we couldn't reach the server, we will try it some time later
2017-12-19 01:47:39 -05:00
$fields = [ 'last_poco_query' => datetime_convert ()];
dba :: update ( 'gserver' , $fields , [ 'nurl' => $server [ " nurl " ]]);
2017-11-15 09:47:28 -05:00
return false ;
}
}
2017-11-15 10:53:16 -05:00
public static function discover ( $complete = false )
{
2017-11-15 09:47:28 -05:00
// Update the server list
2017-11-15 10:53:16 -05:00
self :: discoverFederation ();
2017-11-15 09:47:28 -05:00
$no_of_queries = 5 ;
$requery_days = intval ( Config :: get ( " system " , " poco_requery_days " ));
if ( $requery_days == 0 ) {
$requery_days = 7 ;
}
$last_update = date ( " c " , time () - ( 60 * 60 * 24 * $requery_days ));
$r = q ( " SELECT `id`, `url`, `network` FROM `gserver` WHERE `last_contact` >= `last_failure` AND `poco` != '' AND `last_poco_query` < '%s' ORDER BY RAND() " , dbesc ( $last_update ));
if ( DBM :: is_result ( $r )) {
foreach ( $r as $server ) {
2017-11-15 10:53:16 -05:00
if ( ! self :: checkServer ( $server [ " url " ], $server [ " network " ])) {
2017-11-15 09:47:28 -05:00
// The server is not reachable? Okay, then we will try it later
2017-12-19 01:47:39 -05:00
$fields = [ 'last_poco_query' => datetime_convert ()];
dba :: update ( 'gserver' , $fields , [ 'nurl' => $server [ " nurl " ]]);
2017-11-15 09:47:28 -05:00
continue ;
}
logger ( 'Update directory from server ' . $server [ 'url' ] . ' with ID ' . $server [ 'id' ], LOGGER_DEBUG );
2017-11-18 06:02:46 -05:00
Worker :: add ( PRIORITY_LOW , " DiscoverPoCo " , " update_server_directory " , ( int ) $server [ 'id' ]);
2017-11-15 09:47:28 -05:00
if ( ! $complete && ( -- $no_of_queries == 0 )) {
break ;
}
}
}
}
2017-12-21 02:27:20 -05:00
private static function discoverServerUsers ( $data , $server )
2017-11-15 10:53:16 -05:00
{
2017-11-15 09:47:28 -05:00
if ( ! isset ( $data -> entry )) {
return ;
}
foreach ( $data -> entry as $entry ) {
$username = " " ;
if ( isset ( $entry -> urls )) {
foreach ( $entry -> urls as $url ) {
if ( $url -> type == 'profile' ) {
$profile_url = $url -> value ;
$urlparts = parse_url ( $profile_url );
$username = end ( explode ( " / " , $urlparts [ " path " ]));
}
}
}
if ( $username != " " ) {
logger ( " Fetch contacts for the user " . $username . " from the server " . $server [ " nurl " ], LOGGER_DEBUG );
// Fetch all contacts from a given user from the other server
$url = $server [ " poco " ] . " / " . $username . " /?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation " ;
2018-01-27 11:13:41 -05:00
$retdata = Network :: curl ( $url );
2017-11-15 09:47:28 -05:00
if ( $retdata [ " success " ]) {
2017-11-15 10:53:16 -05:00
self :: discoverServer ( json_decode ( $retdata [ " body " ]), 3 );
2017-11-15 09:47:28 -05:00
}
}
}
}
2017-12-21 02:27:20 -05:00
private static function discoverServer ( $data , $default_generation = 0 )
2017-11-15 10:53:16 -05:00
{
2017-11-15 09:47:28 -05:00
if ( ! isset ( $data -> entry ) || ! count ( $data -> entry )) {
return false ;
}
$success = false ;
foreach ( $data -> entry as $entry ) {
$profile_url = '' ;
$profile_photo = '' ;
$connect_url = '' ;
$name = '' ;
$network = '' ;
$updated = NULL_DATE ;
$location = '' ;
$about = '' ;
$keywords = '' ;
$gender = '' ;
$contact_type = - 1 ;
$generation = $default_generation ;
$name = $entry -> displayName ;
if ( isset ( $entry -> urls )) {
foreach ( $entry -> urls as $url ) {
if ( $url -> type == 'profile' ) {
$profile_url = $url -> value ;
continue ;
}
if ( $url -> type == 'webfinger' ) {
$connect_url = str_replace ( 'acct:' , '' , $url -> value );
continue ;
}
}
}
if ( isset ( $entry -> photos )) {
foreach ( $entry -> photos as $photo ) {
if ( $photo -> type == 'profile' ) {
$profile_photo = $photo -> value ;
continue ;
}
}
}
if ( isset ( $entry -> updated )) {
$updated = date ( " Y-m-d H:i:s " , strtotime ( $entry -> updated ));
}
if ( isset ( $entry -> network )) {
$network = $entry -> network ;
}
if ( isset ( $entry -> currentLocation )) {
$location = $entry -> currentLocation ;
}
if ( isset ( $entry -> aboutMe )) {
$about = html2bbcode ( $entry -> aboutMe );
}
if ( isset ( $entry -> gender )) {
$gender = $entry -> gender ;
}
2017-11-15 10:53:16 -05:00
if ( isset ( $entry -> generation ) && ( $entry -> generation > 0 )) {
2017-11-15 09:47:28 -05:00
$generation = ++ $entry -> generation ;
}
2017-11-15 10:53:16 -05:00
if ( isset ( $entry -> contactType ) && ( $entry -> contactType >= 0 )) {
2017-11-15 09:47:28 -05:00
$contact_type = $entry -> contactType ;
}
if ( isset ( $entry -> tags )) {
foreach ( $entry -> tags as $tag ) {
$keywords = implode ( " , " , $tag );
}
}
if ( $generation > 0 ) {
$success = true ;
logger ( " Store profile " . $profile_url , LOGGER_DEBUG );
2018-01-15 08:05:12 -05:00
$gcontact = [ " url " => $profile_url ,
2017-11-15 09:47:28 -05:00
" name " => $name ,
" network " => $network ,
" photo " => $profile_photo ,
" about " => $about ,
" location " => $location ,
" gender " => $gender ,
" keywords " => $keywords ,
" connect " => $connect_url ,
" updated " => $updated ,
" contact-type " => $contact_type ,
2018-01-15 08:05:12 -05:00
" generation " => $generation ];
2017-11-15 09:47:28 -05:00
try {
2017-12-07 09:09:28 -05:00
$gcontact = GContact :: sanitize ( $gcontact );
GContact :: update ( $gcontact );
2017-11-15 09:47:28 -05:00
} catch ( Exception $e ) {
logger ( $e -> getMessage (), LOGGER_DEBUG );
}
logger ( " Done for profile " . $profile_url , LOGGER_DEBUG );
}
}
return $success ;
}
}