2017-11-15 15:47:28 +01:00
< ? php
2018-01-25 03:08:45 +01:00
2017-11-15 15:47:28 +01:00
/**
* @ file src / Model / GlobalContact . php
* @ brief This file includes the GlobalContact class with directory related functions
*/
namespace Friendica\Model ;
use Friendica\Core\Config ;
use Friendica\Core\System ;
2017-11-15 17:51:29 +01:00
use Friendica\Core\Worker ;
2017-11-15 15:47:28 +01:00
use Friendica\Database\DBM ;
2017-12-07 15:04:24 +01:00
use Friendica\Model\Contact ;
2017-12-07 15:05:35 +01:00
use Friendica\Model\Profile ;
use Friendica\Network\Probe ;
2017-11-15 15:47:28 +01:00
use Friendica\Protocol\PortableContact ;
2018-01-27 03:38:34 +01:00
use Friendica\Util\DateTimeFormat ;
2018-01-27 05:09:48 +01:00
use Friendica\Util\Network ;
2017-11-15 15:47:28 +01:00
use dba ;
use Exception ;
2017-12-17 21:24:57 +01:00
require_once 'include/dba.php' ;
2017-11-15 15:47:28 +01:00
/**
* @ brief This class handles GlobalContact related functions
*/
2017-12-07 15:09:28 +01:00
class GContact
2017-11-15 15:47:28 +01:00
{
/**
* @ brief Search global contact table by nick or name
*
* @ param string $search Name or nick
* @ param string $mode Search mode ( e . g . " community " )
*
* @ return array with search results
*/
public static function searchByName ( $search , $mode = '' )
{
2018-03-17 08:50:49 +01:00
if ( empty ( $search )) {
return [];
}
2017-11-15 15:47:28 +01:00
2018-03-17 08:50:49 +01:00
// check supported networks
if ( Config :: get ( 'system' , 'diaspora_enabled' )) {
$diaspora = NETWORK_DIASPORA ;
} else {
$diaspora = NETWORK_DFRN ;
}
2017-11-15 15:47:28 +01:00
2018-03-17 08:50:49 +01:00
if ( ! Config :: get ( 'system' , 'ostatus_disabled' )) {
$ostatus = NETWORK_OSTATUS ;
} else {
$ostatus = NETWORK_DFRN ;
}
2017-11-15 15:47:28 +01:00
2018-03-17 08:50:49 +01:00
// check if we search only communities or every contact
if ( $mode === " community " ) {
$extra_sql = " AND `community` " ;
} else {
$extra_sql = " " ;
}
$search .= " % " ;
$results = dba :: p ( " SELECT `nurl` FROM `gcontact`
WHERE NOT `hide` AND `network` IN ( ? , ? , ? ) AND
(( `last_contact` >= `last_failure` ) OR ( `updated` >= `last_failure` )) AND
( `addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ? ) $extra_sql
GROUP BY `nurl` ORDER BY `nurl` DESC LIMIT 1000 " ,
NETWORK_DFRN , $ostatus , $diaspora , $search , $search , $search
);
$gcontacts = [];
while ( $result = dba :: fetch ( $results )) {
$urlparts = parse_url ( $result [ " nurl " ]);
2017-11-15 15:47:28 +01:00
2018-03-17 08:50:49 +01:00
// Ignore results that look strange.
// For historic reasons the gcontact table does contain some garbage.
if ( ! empty ( $urlparts [ 'query' ]) || ! empty ( $urlparts [ 'fragment' ])) {
continue ;
}
2017-11-15 15:47:28 +01:00
2018-03-17 08:50:49 +01:00
$gcontacts [] = Contact :: getDetailsByURL ( $result [ " nurl " ], local_user ());
2017-11-15 15:47:28 +01:00
}
2018-03-17 08:50:49 +01:00
return $gcontacts ;
2017-11-15 15:47:28 +01:00
}
/**
* @ brief Link the gcontact entry with user , contact and global contact
*
* @ param integer $gcid Global contact ID
* @ param integer $uid User ID
* @ param integer $cid Contact ID
* @ param integer $zcid Global Contact ID
2017-11-19 23:04:40 +01:00
* @ return void
2017-11-15 15:47:28 +01:00
*/
public static function link ( $gcid , $uid = 0 , $cid = 0 , $zcid = 0 )
{
if ( $gcid <= 0 ) {
return ;
}
$r = q (
" SELECT * FROM `glink` WHERE `cid` = %d AND `uid` = %d AND `gcid` = %d AND `zcid` = %d LIMIT 1 " ,
intval ( $cid ),
intval ( $uid ),
intval ( $gcid ),
intval ( $zcid )
);
if ( ! DBM :: is_result ( $r )) {
q (
" INSERT INTO `glink` (`cid`, `uid`, `gcid`, `zcid`, `updated`) VALUES (%d, %d, %d, %d, '%s') " ,
intval ( $cid ),
intval ( $uid ),
intval ( $gcid ),
intval ( $zcid ),
2018-01-27 03:38:34 +01:00
dbesc ( DateTimeFormat :: utcNow ())
2017-11-15 15:47:28 +01:00
);
} else {
q (
" UPDATE `glink` SET `updated` = '%s' WHERE `cid` = %d AND `uid` = %d AND `gcid` = %d AND `zcid` = %d " ,
2018-01-27 03:38:34 +01:00
dbesc ( DateTimeFormat :: utcNow ()),
2017-11-15 15:47:28 +01:00
intval ( $cid ),
intval ( $uid ),
intval ( $gcid ),
intval ( $zcid )
);
}
}
/**
* @ brief Sanitize the given gcontact data
*
* @ param array $gcontact array with gcontact data
* @ throw Exception
*
* Generation :
* 0 : No definition
* 1 : Profiles on this server
* 2 : Contacts of profiles on this server
* 3 : Contacts of contacts of profiles on this server
* 4 : ...
2017-11-19 23:04:40 +01:00
* @ return array $gcontact
2017-11-15 15:47:28 +01:00
*/
public static function sanitize ( $gcontact )
{
if ( $gcontact [ 'url' ] == " " ) {
throw new Exception ( 'URL is empty' );
}
$urlparts = parse_url ( $gcontact [ 'url' ]);
if ( ! isset ( $urlparts [ " scheme " ])) {
throw new Exception ( " This ( " . $gcontact [ 'url' ] . " ) doesn't seem to be an url. " );
}
2018-01-15 14:05:12 +01:00
if ( in_array ( $urlparts [ " host " ], [ " www.facebook.com " , " facebook.com " , " twitter.com " , " identi.ca " , " alpha.app.net " ])) {
2017-11-15 15:47:28 +01:00
throw new Exception ( 'Contact from a non federated network ignored. (' . $gcontact [ 'url' ] . ')' );
}
// Don't store the statusnet connector as network
// We can't simply set this to NETWORK_OSTATUS since the connector could have fetched posts from friendica as well
if ( $gcontact [ 'network' ] == NETWORK_STATUSNET ) {
$gcontact [ 'network' ] = " " ;
}
// Assure that there are no parameter fragments in the profile url
2018-01-15 14:05:12 +01:00
if ( in_array ( $gcontact [ 'network' ], [ NETWORK_DFRN , NETWORK_DIASPORA , NETWORK_OSTATUS , " " ])) {
2017-11-15 15:47:28 +01:00
$gcontact [ 'url' ] = self :: cleanContactUrl ( $gcontact [ 'url' ]);
}
2017-11-15 16:53:16 +01:00
$alternate = PortableContact :: alternateOStatusUrl ( $gcontact [ 'url' ]);
2017-11-15 15:47:28 +01:00
// The global contacts should contain the original picture, not the cached one
if (( $gcontact [ 'generation' ] != 1 ) && stristr ( normalise_link ( $gcontact [ 'photo' ]), normalise_link ( System :: baseUrl () . " /photo/ " ))) {
$gcontact [ 'photo' ] = " " ;
}
if ( ! isset ( $gcontact [ 'network' ])) {
$r = q (
" SELECT `network` FROM `contact` WHERE `uid` = 0 AND `nurl` = '%s' AND `network` != '' AND `network` != '%s' LIMIT 1 " ,
dbesc ( normalise_link ( $gcontact [ 'url' ])),
dbesc ( NETWORK_STATUSNET )
);
if ( DBM :: is_result ( $r )) {
$gcontact [ 'network' ] = $r [ 0 ][ " network " ];
}
if (( $gcontact [ 'network' ] == " " ) || ( $gcontact [ 'network' ] == NETWORK_OSTATUS )) {
$r = q (
" SELECT `network`, `url` FROM `contact` WHERE `uid` = 0 AND `alias` IN ('%s', '%s') AND `network` != '' AND `network` != '%s' LIMIT 1 " ,
dbesc ( $gcontact [ 'url' ]),
dbesc ( normalise_link ( $gcontact [ 'url' ])),
dbesc ( NETWORK_STATUSNET )
);
if ( DBM :: is_result ( $r )) {
$gcontact [ 'network' ] = $r [ 0 ][ " network " ];
}
}
}
$gcontact [ 'server_url' ] = '' ;
$gcontact [ 'network' ] = '' ;
$x = q (
" SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1 " ,
dbesc ( normalise_link ( $gcontact [ 'url' ]))
);
if ( DBM :: is_result ( $x )) {
if ( ! isset ( $gcontact [ 'network' ]) && ( $x [ 0 ][ " network " ] != NETWORK_STATUSNET )) {
$gcontact [ 'network' ] = $x [ 0 ][ " network " ];
}
if ( $gcontact [ 'updated' ] <= NULL_DATE ) {
$gcontact [ 'updated' ] = $x [ 0 ][ " updated " ];
}
if ( ! isset ( $gcontact [ 'server_url' ]) && ( normalise_link ( $x [ 0 ][ " server_url " ]) != normalise_link ( $x [ 0 ][ " url " ]))) {
$gcontact [ 'server_url' ] = $x [ 0 ][ " server_url " ];
}
if ( ! isset ( $gcontact [ 'addr' ])) {
$gcontact [ 'addr' ] = $x [ 0 ][ " addr " ];
}
}
if (( ! isset ( $gcontact [ 'network' ]) || ! isset ( $gcontact [ 'name' ]) || ! isset ( $gcontact [ 'addr' ]) || ! isset ( $gcontact [ 'photo' ]) || ! isset ( $gcontact [ 'server_url' ]) || $alternate )
2017-11-15 16:53:16 +01:00
&& PortableContact :: reachable ( $gcontact [ 'url' ], $gcontact [ 'server_url' ], $gcontact [ 'network' ], false )
2017-11-15 15:47:28 +01:00
) {
$data = Probe :: uri ( $gcontact [ 'url' ]);
if ( $data [ " network " ] == NETWORK_PHANTOM ) {
throw new Exception ( 'Probing for URL ' . $gcontact [ 'url' ] . ' failed' );
}
$orig_profile = $gcontact [ 'url' ];
$gcontact [ " server_url " ] = $data [ " baseurl " ];
$gcontact = array_merge ( $gcontact , $data );
if ( $alternate && ( $gcontact [ 'network' ] == NETWORK_OSTATUS )) {
// Delete the old entry - if it exists
2018-05-10 22:14:51 +02:00
if ( dba :: exists ( 'gcontact' , [ 'nurl' => normalise_link ( $orig_profile )])) {
2018-05-02 21:26:15 +02:00
dba :: delete ( 'gcontact' , [ 'nurl' => normalise_link ( $orig_profile )]);
2017-11-15 15:47:28 +01:00
}
}
}
if ( ! isset ( $gcontact [ 'name' ]) || ! isset ( $gcontact [ 'photo' ])) {
throw new Exception ( 'No name and photo for URL ' . $gcontact [ 'url' ]);
}
2018-01-15 14:05:12 +01:00
if ( ! in_array ( $gcontact [ 'network' ], [ NETWORK_DFRN , NETWORK_OSTATUS , NETWORK_DIASPORA ])) {
2017-11-15 15:47:28 +01:00
throw new Exception ( 'No federated network (' . $gcontact [ 'network' ] . ') detected for URL ' . $gcontact [ 'url' ]);
}
if ( ! isset ( $gcontact [ 'server_url' ])) {
// We check the server url to be sure that it is a real one
2017-11-15 16:53:16 +01:00
$server_url = PortableContact :: detectServer ( $gcontact [ 'url' ]);
2017-11-15 15:47:28 +01:00
// We are now sure that it is a correct URL. So we use it in the future
if ( $server_url != " " ) {
$gcontact [ 'server_url' ] = $server_url ;
}
}
// The server URL doesn't seem to be valid, so we don't store it.
2017-11-15 16:53:16 +01:00
if ( ! PortableContact :: checkServer ( $gcontact [ 'server_url' ], $gcontact [ 'network' ])) {
2017-11-15 15:47:28 +01:00
$gcontact [ 'server_url' ] = " " ;
}
return $gcontact ;
}
2017-11-19 23:04:40 +01:00
/**
* @ param integer $uid id
* @ param integer $cid id
* @ return integer
*/
2017-11-15 15:47:28 +01:00
public static function countCommonFriends ( $uid , $cid )
{
$r = q (
" SELECT count(*) as `total`
FROM `glink` INNER JOIN `gcontact` on `glink` . `gcid` = `gcontact` . `id`
WHERE `glink` . `cid` = % d AND `glink` . `uid` = % d AND
(( `gcontact` . `last_contact` >= `gcontact` . `last_failure` ) OR
( `gcontact` . `updated` >= `gcontact` . `last_failure` ))
AND `gcontact` . `nurl` IN ( select nurl from contact where uid = % d and self = 0 and blocked = 0 and hidden = 0 and id != % d ) " ,
intval ( $cid ),
intval ( $uid ),
intval ( $uid ),
intval ( $cid )
);
// logger("countCommonFriends: $uid $cid {$r[0]['total']}");
if ( DBM :: is_result ( $r )) {
return $r [ 0 ][ 'total' ];
}
return 0 ;
}
2017-11-19 23:04:40 +01:00
/**
* @ param integer $uid id
* @ param integer $zcid zcid
* @ return integer
*/
2017-11-15 15:47:28 +01:00
public static function countCommonFriendsZcid ( $uid , $zcid )
{
$r = q (
" SELECT count(*) as `total`
FROM `glink` INNER JOIN `gcontact` on `glink` . `gcid` = `gcontact` . `id`
where `glink` . `zcid` = % d
and `gcontact` . `nurl` in ( select nurl from contact where uid = % d and self = 0 and blocked = 0 and hidden = 0 ) " ,
intval ( $zcid ),
intval ( $uid )
);
if ( DBM :: is_result ( $r )) {
return $r [ 0 ][ 'total' ];
}
return 0 ;
}
2017-11-19 23:04:40 +01:00
/**
2018-01-05 01:42:48 +01:00
* @ param integer $uid user
* @ param integer $cid cid
2017-11-19 23:04:40 +01:00
* @ param integer $start optional , default 0
* @ param integer $limit optional , default 9999
* @ param boolean $shuffle optional , default false
* @ return object
*/
2017-11-15 15:47:28 +01:00
public static function commonFriends ( $uid , $cid , $start = 0 , $limit = 9999 , $shuffle = false )
{
if ( $shuffle ) {
$sql_extra = " order by rand() " ;
} else {
$sql_extra = " order by `gcontact`.`name` asc " ;
}
$r = q (
" SELECT `gcontact`.*, `contact`.`id` AS `cid`
FROM `glink`
INNER JOIN `gcontact` ON `glink` . `gcid` = `gcontact` . `id`
INNER JOIN `contact` ON `gcontact` . `nurl` = `contact` . `nurl`
WHERE `glink` . `cid` = % d and `glink` . `uid` = % d
AND `contact` . `uid` = % d AND `contact` . `self` = 0 AND `contact` . `blocked` = 0
AND `contact` . `hidden` = 0 AND `contact` . `id` != % d
AND (( `gcontact` . `last_contact` >= `gcontact` . `last_failure` ) OR ( `gcontact` . `updated` >= `gcontact` . `last_failure` ))
$sql_extra LIMIT % d , % d " ,
intval ( $cid ),
intval ( $uid ),
intval ( $uid ),
intval ( $cid ),
intval ( $start ),
intval ( $limit )
);
/// @TODO Check all calling-findings of this function if they properly use DBM::is_result()
return $r ;
}
2017-11-19 23:04:40 +01:00
/**
2018-01-05 01:42:48 +01:00
* @ param integer $uid user
* @ param integer $zcid zcid
2017-11-19 23:04:40 +01:00
* @ param integer $start optional , default 0
* @ param integer $limit optional , default 9999
* @ param boolean $shuffle optional , default false
* @ return object
*/
public static function commonFriendsZcid ( $uid , $zcid , $start = 0 , $limit = 9999 , $shuffle = false )
2017-11-15 15:47:28 +01:00
{
if ( $shuffle ) {
$sql_extra = " order by rand() " ;
} else {
$sql_extra = " order by `gcontact`.`name` asc " ;
}
$r = q (
" SELECT `gcontact`.*
FROM `glink` INNER JOIN `gcontact` on `glink` . `gcid` = `gcontact` . `id`
where `glink` . `zcid` = % d
and `gcontact` . `nurl` in ( select nurl from contact where uid = % d and self = 0 and blocked = 0 and hidden = 0 )
$sql_extra limit % d , % d " ,
intval ( $zcid ),
intval ( $uid ),
intval ( $start ),
intval ( $limit )
);
/// @TODO Check all calling-findings of this function if they properly use DBM::is_result()
return $r ;
}
2017-11-19 23:04:40 +01:00
/**
2018-01-05 01:42:48 +01:00
* @ param integer $uid user
* @ param integer $cid cid
2017-11-19 23:04:40 +01:00
* @ return integer
*/
2017-11-15 15:47:28 +01:00
public static function countAllFriends ( $uid , $cid )
{
$r = q (
" SELECT count(*) as `total`
FROM `glink` INNER JOIN `gcontact` on `glink` . `gcid` = `gcontact` . `id`
where `glink` . `cid` = % d and `glink` . `uid` = % d AND
(( `gcontact` . `last_contact` >= `gcontact` . `last_failure` ) OR ( `gcontact` . `updated` >= `gcontact` . `last_failure` )) " ,
intval ( $cid ),
intval ( $uid )
);
if ( DBM :: is_result ( $r )) {
return $r [ 0 ][ 'total' ];
}
2017-11-19 23:06:18 +01:00
2017-11-15 15:47:28 +01:00
return 0 ;
}
2017-11-19 23:04:40 +01:00
/**
2018-01-05 01:42:48 +01:00
* @ param integer $uid user
* @ param integer $cid cid
2017-11-19 23:04:40 +01:00
* @ param integer $start optional , default 0
* @ param integer $limit optional , default 80
2018-01-05 01:42:48 +01:00
* @ return array
2017-11-19 23:04:40 +01:00
*/
2017-11-15 15:47:28 +01:00
public static function allFriends ( $uid , $cid , $start = 0 , $limit = 80 )
{
$r = q (
" SELECT `gcontact`.*, `contact`.`id` AS `cid`
FROM `glink`
INNER JOIN `gcontact` on `glink` . `gcid` = `gcontact` . `id`
LEFT JOIN `contact` ON `contact` . `nurl` = `gcontact` . `nurl` AND `contact` . `uid` = % d
WHERE `glink` . `cid` = % d AND `glink` . `uid` = % d AND
(( `gcontact` . `last_contact` >= `gcontact` . `last_failure` ) OR ( `gcontact` . `updated` >= `gcontact` . `last_failure` ))
ORDER BY `gcontact` . `name` ASC LIMIT % d , % d " ,
intval ( $uid ),
intval ( $cid ),
intval ( $uid ),
intval ( $start ),
intval ( $limit )
);
/// @TODO Check all calling-findings of this function if they properly use DBM::is_result()
return $r ;
}
2017-11-19 23:04:40 +01:00
/**
* @ param object $uid user
* @ param integer $start optional , default 0
* @ param integer $limit optional , default 80
* @ return array
*/
2017-11-15 15:47:28 +01:00
public static function suggestionQuery ( $uid , $start = 0 , $limit = 80 )
{
if ( ! $uid ) {
2018-01-15 14:05:12 +01:00
return [];
2017-11-15 15:47:28 +01:00
}
/*
* Uncommented because the result of the queries are to big to store it in the cache .
* We need to decide if we want to change the db column type or if we want to delete it .
*/
//$list = Cache::get("suggestion_query:".$uid.":".$start.":".$limit);
//if (!is_null($list)) {
// return $list;
//}
2018-01-15 14:05:12 +01:00
$network = [ NETWORK_DFRN ];
2017-11-15 15:47:28 +01:00
if ( Config :: get ( 'system' , 'diaspora_enabled' )) {
$network [] = NETWORK_DIASPORA ;
}
if ( ! Config :: get ( 'system' , 'ostatus_disabled' )) {
$network [] = NETWORK_OSTATUS ;
}
$sql_network = implode ( " ', ' " , $network );
$sql_network = " ' " . $sql_network . " ' " ;
/// @todo This query is really slow
// By now we cache the data for five minutes
$r = q (
" SELECT count(glink.gcid) as `total`, gcontact.* from gcontact
INNER JOIN `glink` ON `glink` . `gcid` = `gcontact` . `id`
where uid = % d and not gcontact . nurl in ( select nurl from contact where uid = % d )
AND NOT `gcontact` . `name` IN ( SELECT `name` FROM `contact` WHERE `uid` = % d )
AND NOT `gcontact` . `id` IN ( SELECT `gcid` FROM `gcign` WHERE `uid` = % d )
AND `gcontact` . `updated` >= '%s'
AND `gcontact` . `last_contact` >= `gcontact` . `last_failure`
AND `gcontact` . `network` IN ( % s )
GROUP BY `glink` . `gcid` ORDER BY `gcontact` . `updated` DESC , `total` DESC LIMIT % d , % d " ,
intval ( $uid ),
intval ( $uid ),
intval ( $uid ),
intval ( $uid ),
dbesc ( NULL_DATE ),
$sql_network ,
intval ( $start ),
intval ( $limit )
);
if ( DBM :: is_result ( $r ) && count ( $r ) >= ( $limit - 1 )) {
/*
* Uncommented because the result of the queries are to big to store it in the cache .
* We need to decide if we want to change the db column type or if we want to delete it .
*/
//Cache::set("suggestion_query:".$uid.":".$start.":".$limit, $r, CACHE_FIVE_MINUTES);
return $r ;
}
$r2 = q (
" SELECT gcontact.* FROM gcontact
INNER JOIN `glink` ON `glink` . `gcid` = `gcontact` . `id`
WHERE `glink` . `uid` = 0 AND `glink` . `cid` = 0 AND `glink` . `zcid` = 0 AND NOT `gcontact` . `nurl` IN ( SELECT `nurl` FROM `contact` WHERE `uid` = % d )
AND NOT `gcontact` . `name` IN ( SELECT `name` FROM `contact` WHERE `uid` = % d )
AND NOT `gcontact` . `id` IN ( SELECT `gcid` FROM `gcign` WHERE `uid` = % d )
AND `gcontact` . `updated` >= '%s'
AND `gcontact` . `last_contact` >= `gcontact` . `last_failure`
AND `gcontact` . `network` IN ( % s )
ORDER BY rand () LIMIT % d , % d " ,
intval ( $uid ),
intval ( $uid ),
intval ( $uid ),
dbesc ( NULL_DATE ),
$sql_network ,
intval ( $start ),
intval ( $limit )
);
2018-01-15 14:05:12 +01:00
$list = [];
2017-11-15 15:47:28 +01:00
foreach ( $r2 as $suggestion ) {
$list [ $suggestion [ " nurl " ]] = $suggestion ;
}
foreach ( $r as $suggestion ) {
$list [ $suggestion [ " nurl " ]] = $suggestion ;
}
while ( sizeof ( $list ) > ( $limit )) {
array_pop ( $list );
}
/*
* Uncommented because the result of the queries are to big to store it in the cache .
* We need to decide if we want to change the db column type or if we want to delete it .
*/
//Cache::set("suggestion_query:".$uid.":".$start.":".$limit, $list, CACHE_FIVE_MINUTES);
return $list ;
}
2017-11-19 23:04:40 +01:00
/**
* @ return void
*/
2017-11-15 15:47:28 +01:00
public static function updateSuggestions ()
{
$a = get_app ();
2018-01-15 14:05:12 +01:00
$done = [];
2017-11-15 15:47:28 +01:00
/// @TODO Check if it is really neccessary to poll the own server
PortableContact :: loadWorker ( 0 , 0 , 0 , System :: baseUrl () . '/poco' );
$done [] = System :: baseUrl () . '/poco' ;
if ( strlen ( Config :: get ( 'system' , 'directory' ))) {
2018-01-27 17:13:41 +01:00
$x = Network :: fetchUrl ( get_server () . " /pubsites " );
2017-11-15 15:47:28 +01:00
if ( $x ) {
$j = json_decode ( $x );
if ( $j -> entries ) {
foreach ( $j -> entries as $entry ) {
2017-11-15 16:53:16 +01:00
PortableContact :: checkServer ( $entry -> url );
2017-11-15 15:47:28 +01:00
$url = $entry -> url . '/poco' ;
if ( ! in_array ( $url , $done )) {
PortableContact :: loadWorker ( 0 , 0 , 0 , $entry -> url . '/poco' );
}
}
}
}
}
// Query your contacts from Friendica and Redmatrix/Hubzilla for their contacts
$r = q (
" SELECT DISTINCT(`poco`) AS `poco` FROM `contact` WHERE `network` IN ('%s', '%s') " ,
dbesc ( NETWORK_DFRN ),
dbesc ( NETWORK_DIASPORA )
);
if ( DBM :: is_result ( $r )) {
foreach ( $r as $rr ) {
$base = substr ( $rr [ 'poco' ], 0 , strrpos ( $rr [ 'poco' ], '/' ));
if ( ! in_array ( $base , $done )) {
PortableContact :: loadWorker ( 0 , 0 , 0 , $base );
}
}
}
}
/**
* @ brief Removes unwanted parts from a contact url
*
* @ param string $url Contact url
*
* @ return string Contact url with the wanted parts
*/
public static function cleanContactUrl ( $url )
{
$parts = parse_url ( $url );
if ( ! isset ( $parts [ " scheme " ]) || ! isset ( $parts [ " host " ])) {
return $url ;
}
$new_url = $parts [ " scheme " ] . " :// " . $parts [ " host " ];
if ( isset ( $parts [ " port " ])) {
$new_url .= " : " . $parts [ " port " ];
}
if ( isset ( $parts [ " path " ])) {
$new_url .= $parts [ " path " ];
}
if ( $new_url != $url ) {
logger ( " Cleaned contact url " . $url . " to " . $new_url . " - Called by: " . System :: callstack (), LOGGER_DEBUG );
}
return $new_url ;
}
/**
* @ brief Replace alternate OStatus user format with the primary one
*
2017-12-17 21:27:50 +01:00
* @ param array $contact contact array ( called by reference )
2017-11-19 23:04:40 +01:00
* @ return void
2017-11-15 15:47:28 +01:00
*/
public static function fixAlternateContactAddress ( & $contact )
{
2017-11-15 16:53:16 +01:00
if (( $contact [ " network " ] == NETWORK_OSTATUS ) && PortableContact :: alternateOStatusUrl ( $contact [ " url " ])) {
2017-11-15 15:47:28 +01:00
$data = Probe :: uri ( $contact [ " url " ]);
if ( $contact [ " network " ] == NETWORK_OSTATUS ) {
logger ( " Fix primary url from " . $contact [ " url " ] . " to " . $data [ " url " ] . " - Called by: " . System :: callstack (), LOGGER_DEBUG );
$contact [ " url " ] = $data [ " url " ];
$contact [ " addr " ] = $data [ " addr " ];
$contact [ " alias " ] = $data [ " alias " ];
$contact [ " server_url " ] = $data [ " baseurl " ];
}
}
}
/**
* @ brief Fetch the gcontact id , add an entry if not existed
*
2017-12-17 21:27:50 +01:00
* @ param array $contact contact array
2017-11-15 15:47:28 +01:00
*
* @ return bool | int Returns false if not found , integer if contact was found
*/
public static function getId ( $contact )
{
$gcontact_id = 0 ;
$doprobing = false ;
2018-02-14 06:05:00 +01:00
$last_failure_str = '' ;
$last_contact_str = '' ;
2017-11-15 15:47:28 +01:00
2018-01-15 14:05:12 +01:00
if ( in_array ( $contact [ " network " ], [ NETWORK_PHANTOM ])) {
2017-11-15 15:47:28 +01:00
logger ( " Invalid network for contact url " . $contact [ " url " ] . " - Called by: " . System :: callstack (), LOGGER_DEBUG );
return false ;
}
if ( $contact [ " network " ] == NETWORK_STATUSNET ) {
$contact [ " network " ] = NETWORK_OSTATUS ;
}
// All new contacts are hidden by default
if ( ! isset ( $contact [ " hide " ])) {
$contact [ " hide " ] = true ;
}
// Replace alternate OStatus user format with the primary one
self :: fixAlternateContactAddress ( $contact );
// Remove unwanted parts from the contact url (e.g. "?zrl=...")
2018-01-15 14:05:12 +01:00
if ( in_array ( $contact [ " network " ], [ NETWORK_DFRN , NETWORK_DIASPORA , NETWORK_OSTATUS ])) {
2017-11-15 15:47:28 +01:00
$contact [ " url " ] = self :: cleanContactUrl ( $contact [ " url " ]);
}
dba :: lock ( 'gcontact' );
$r = q (
" SELECT `id`, `last_contact`, `last_failure`, `network` FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1 " ,
dbesc ( normalise_link ( $contact [ " url " ]))
);
if ( DBM :: is_result ( $r )) {
$gcontact_id = $r [ 0 ][ " id " ];
// Update every 90 days
2018-01-15 14:05:12 +01:00
if ( in_array ( $r [ 0 ][ " network " ], [ NETWORK_DFRN , NETWORK_DIASPORA , NETWORK_OSTATUS , " " ])) {
2017-11-15 15:47:28 +01:00
$last_failure_str = $r [ 0 ][ " last_failure " ];
$last_failure = strtotime ( $r [ 0 ][ " last_failure " ]);
$last_contact_str = $r [ 0 ][ " last_contact " ];
$last_contact = strtotime ( $r [ 0 ][ " last_contact " ]);
$doprobing = ((( time () - $last_contact ) > ( 90 * 86400 )) && (( time () - $last_failure ) > ( 90 * 86400 )));
}
} else {
q (
" INSERT INTO `gcontact` (`name`, `nick`, `addr` , `network`, `url`, `nurl`, `photo`, `created`, `updated`, `location`, `about`, `hide`, `generation`)
VALUES ( '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , % d , % d ) " ,
dbesc ( $contact [ " name " ]),
dbesc ( $contact [ " nick " ]),
dbesc ( $contact [ " addr " ]),
dbesc ( $contact [ " network " ]),
dbesc ( $contact [ " url " ]),
dbesc ( normalise_link ( $contact [ " url " ])),
dbesc ( $contact [ " photo " ]),
2018-01-27 03:38:34 +01:00
dbesc ( DateTimeFormat :: utcNow ()),
dbesc ( DateTimeFormat :: utcNow ()),
2017-11-15 15:47:28 +01:00
dbesc ( $contact [ " location " ]),
dbesc ( $contact [ " about " ]),
intval ( $contact [ " hide " ]),
intval ( $contact [ " generation " ])
);
$r = q (
" SELECT `id`, `network` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 2 " ,
dbesc ( normalise_link ( $contact [ " url " ]))
);
if ( DBM :: is_result ( $r )) {
$gcontact_id = $r [ 0 ][ " id " ];
2018-01-15 14:05:12 +01:00
$doprobing = in_array ( $r [ 0 ][ " network " ], [ NETWORK_DFRN , NETWORK_DIASPORA , NETWORK_OSTATUS , " " ]);
2017-11-15 15:47:28 +01:00
}
}
dba :: unlock ();
if ( $doprobing ) {
logger ( " Last Contact: " . $last_contact_str . " - Last Failure: " . $last_failure_str . " - Checking: " . $contact [ " url " ], LOGGER_DEBUG );
2017-11-19 17:25:13 +01:00
Worker :: add ( PRIORITY_LOW , 'GProbe' , $contact [ " url " ]);
2017-11-15 15:47:28 +01:00
}
return $gcontact_id ;
}
/**
* @ brief Updates the gcontact table from a given array
*
2017-12-17 21:27:50 +01:00
* @ param array $contact contact array
2017-11-15 15:47:28 +01:00
*
* @ return bool | int Returns false if not found , integer if contact was found
*/
public static function update ( $contact )
{
// Check for invalid "contact-type" value
if ( isset ( $contact [ 'contact-type' ]) && ( intval ( $contact [ 'contact-type' ]) < 0 )) {
$contact [ 'contact-type' ] = 0 ;
}
/// @todo update contact table as well
$gcontact_id = self :: getId ( $contact );
if ( ! $gcontact_id ) {
return false ;
}
2018-01-11 09:26:30 +01:00
$public_contact = q (
2017-11-15 15:47:28 +01:00
" SELECT `name`, `nick`, `photo`, `location`, `about`, `addr`, `generation`, `birthday`, `gender`, `keywords`,
`contact-type` , `hide` , `nsfw` , `network` , `alias` , `notify` , `server_url` , `connect` , `updated` , `url`
FROM `gcontact` WHERE `id` = % d LIMIT 1 " ,
intval ( $gcontact_id )
);
// Get all field names
2018-01-15 14:05:12 +01:00
$fields = [];
2018-01-11 09:26:30 +01:00
foreach ( $public_contact [ 0 ] as $field => $data ) {
2017-11-15 15:47:28 +01:00
$fields [ $field ] = $data ;
}
unset ( $fields [ " url " ]);
unset ( $fields [ " updated " ]);
unset ( $fields [ " hide " ]);
// Bugfix: We had an error in the storing of keywords which lead to the "0"
// This value is still transmitted via poco.
if ( $contact [ " keywords " ] == " 0 " ) {
unset ( $contact [ " keywords " ]);
}
2018-01-11 09:26:30 +01:00
if ( $public_contact [ 0 ][ " keywords " ] == " 0 " ) {
$public_contact [ 0 ][ " keywords " ] = " " ;
2017-11-15 15:47:28 +01:00
}
// assign all unassigned fields from the database entry
foreach ( $fields as $field => $data ) {
if ( ! isset ( $contact [ $field ]) || ( $contact [ $field ] == " " )) {
2018-01-11 09:26:30 +01:00
$contact [ $field ] = $public_contact [ 0 ][ $field ];
2017-11-15 15:47:28 +01:00
}
}
if ( ! isset ( $contact [ " hide " ])) {
2018-01-11 09:26:30 +01:00
$contact [ " hide " ] = $public_contact [ 0 ][ " hide " ];
2017-11-15 15:47:28 +01:00
}
2018-01-11 09:26:30 +01:00
$fields [ " hide " ] = $public_contact [ 0 ][ " hide " ];
2017-11-15 15:47:28 +01:00
if ( $contact [ " network " ] == NETWORK_STATUSNET ) {
$contact [ " network " ] = NETWORK_OSTATUS ;
}
// Replace alternate OStatus user format with the primary one
self :: fixAlternateContactAddress ( $contact );
if ( ! isset ( $contact [ " updated " ])) {
$contact [ " updated " ] = DBM :: date ();
}
if ( $contact [ " network " ] == NETWORK_TWITTER ) {
$contact [ " server_url " ] = 'http://twitter.com' ;
}
if ( $contact [ " server_url " ] == " " ) {
$data = Probe :: uri ( $contact [ " url " ]);
if ( $data [ " network " ] != NETWORK_PHANTOM ) {
$contact [ " server_url " ] = $data [ 'baseurl' ];
}
} else {
$contact [ " server_url " ] = normalise_link ( $contact [ " server_url " ]);
}
if (( $contact [ " addr " ] == " " ) && ( $contact [ " server_url " ] != " " ) && ( $contact [ " nick " ] != " " )) {
$hostname = str_replace ( " http:// " , " " , $contact [ " server_url " ]);
$contact [ " addr " ] = $contact [ " nick " ] . " @ " . $hostname ;
}
// Check if any field changed
$update = false ;
unset ( $fields [ " generation " ]);
2018-01-11 09:26:30 +01:00
if ((( $contact [ " generation " ] > 0 ) && ( $contact [ " generation " ] <= $public_contact [ 0 ][ " generation " ])) || ( $public_contact [ 0 ][ " generation " ] == 0 )) {
2017-11-15 15:47:28 +01:00
foreach ( $fields as $field => $data ) {
2018-01-11 09:26:30 +01:00
if ( $contact [ $field ] != $public_contact [ 0 ][ $field ]) {
logger ( " Difference for contact " . $contact [ " url " ] . " in field ' " . $field . " '. New value: ' " . $contact [ $field ] . " ', old value ' " . $public_contact [ 0 ][ $field ] . " ' " , LOGGER_DEBUG );
2017-11-15 15:47:28 +01:00
$update = true ;
}
}
2018-01-11 09:26:30 +01:00
if ( $contact [ " generation " ] < $public_contact [ 0 ][ " generation " ]) {
logger ( " Difference for contact " . $contact [ " url " ] . " in field 'generation'. new value: ' " . $contact [ " generation " ] . " ', old value ' " . $public_contact [ 0 ][ " generation " ] . " ' " , LOGGER_DEBUG );
2017-11-15 15:47:28 +01:00
$update = true ;
}
}
if ( $update ) {
logger ( " Update gcontact for " . $contact [ " url " ], LOGGER_DEBUG );
2018-01-15 14:05:12 +01:00
$condition = [ '`nurl` = ? AND (`generation` = 0 OR `generation` >= ?)' ,
normalise_link ( $contact [ " url " ]), $contact [ " generation " ]];
2017-11-15 15:47:28 +01:00
$contact [ " updated " ] = DBM :: date ( $contact [ " updated " ]);
2018-01-15 14:05:12 +01:00
$updated = [ 'photo' => $contact [ 'photo' ], 'name' => $contact [ 'name' ],
2017-11-15 15:47:28 +01:00
'nick' => $contact [ 'nick' ], 'addr' => $contact [ 'addr' ],
'network' => $contact [ 'network' ], 'birthday' => $contact [ 'birthday' ],
'gender' => $contact [ 'gender' ], 'keywords' => $contact [ 'keywords' ],
'hide' => $contact [ 'hide' ], 'nsfw' => $contact [ 'nsfw' ],
'contact-type' => $contact [ 'contact-type' ], 'alias' => $contact [ 'alias' ],
'notify' => $contact [ 'notify' ], 'url' => $contact [ 'url' ],
'location' => $contact [ 'location' ], 'about' => $contact [ 'about' ],
'generation' => $contact [ 'generation' ], 'updated' => $contact [ 'updated' ],
2018-01-15 14:05:12 +01:00
'server_url' => $contact [ 'server_url' ], 'connect' => $contact [ 'connect' ]];
2017-11-15 15:47:28 +01:00
dba :: update ( 'gcontact' , $updated , $condition , $fields );
// Now update the contact entry with the user id "0" as well.
// This is used for the shadow copies of public items.
2018-03-24 23:27:04 +01:00
/// @todo Check if we really should do this.
// The quality of the gcontact table is mostly lower than the public contact
2018-01-11 09:26:30 +01:00
$public_contact = dba :: selectFirst ( 'contact' , [ 'id' ], [ 'nurl' => normalise_link ( $contact [ " url " ]), 'uid' => 0 ]);
if ( DBM :: is_result ( $public_contact )) {
logger ( " Update public contact " . $public_contact [ " id " ], LOGGER_DEBUG );
2017-11-15 15:47:28 +01:00
2018-01-11 09:26:30 +01:00
Contact :: updateAvatar ( $contact [ " photo " ], 0 , $public_contact [ " id " ]);
2017-11-15 15:47:28 +01:00
2018-01-15 14:05:12 +01:00
$fields = [ 'name' , 'nick' , 'addr' ,
2017-11-15 15:47:28 +01:00
'network' , 'bd' , 'gender' ,
'keywords' , 'alias' , 'contact-type' ,
2018-01-15 14:05:12 +01:00
'url' , 'location' , 'about' ];
2018-01-11 09:26:30 +01:00
$old_contact = dba :: selectFirst ( 'contact' , $fields , [ 'id' => $public_contact [ " id " ]]);
2017-11-15 15:47:28 +01:00
// Update it with the current values
2018-01-15 14:05:12 +01:00
$fields = [ 'name' => $contact [ 'name' ], 'nick' => $contact [ 'nick' ],
2017-11-15 15:47:28 +01:00
'addr' => $contact [ 'addr' ], 'network' => $contact [ 'network' ],
'bd' => $contact [ 'birthday' ], 'gender' => $contact [ 'gender' ],
'keywords' => $contact [ 'keywords' ], 'alias' => $contact [ 'alias' ],
'contact-type' => $contact [ 'contact-type' ], 'url' => $contact [ 'url' ],
2018-01-15 14:05:12 +01:00
'location' => $contact [ 'location' ], 'about' => $contact [ 'about' ]];
2017-11-15 15:47:28 +01:00
2018-03-24 23:27:04 +01:00
// Don't update the birthday field if not set or invalid
if ( empty ( $contact [ 'birthday' ]) || ( $contact [ 'birthday' ] < '0001-01-01' )) {
unset ( $fields [ 'bd' ]);
}
2018-01-15 14:05:12 +01:00
dba :: update ( 'contact' , $fields , [ 'id' => $public_contact [ " id " ]], $old_contact );
2017-11-15 15:47:28 +01:00
}
}
return $gcontact_id ;
}
/**
* @ brief Updates the gcontact entry from probe
*
2017-12-17 21:27:50 +01:00
* @ param string $url profile link
2017-11-19 23:04:40 +01:00
* @ return void
2017-11-15 15:47:28 +01:00
*/
public static function updateFromProbe ( $url )
{
$data = Probe :: uri ( $url );
2018-01-15 14:05:12 +01:00
if ( in_array ( $data [ " network " ], [ NETWORK_PHANTOM ])) {
2017-11-15 15:47:28 +01:00
logger ( " Invalid network for contact url " . $data [ " url " ] . " - Called by: " . System :: callstack (), LOGGER_DEBUG );
return ;
}
$data [ " server_url " ] = $data [ " baseurl " ];
self :: update ( $data );
}
/**
* @ brief Update the gcontact entry for a given user id
*
* @ param int $uid User ID
2017-11-19 23:04:40 +01:00
* @ return void
2017-11-15 15:47:28 +01:00
*/
public static function updateForUser ( $uid )
{
$r = q (
" SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
`profile` . `name` , `profile` . `about` , `profile` . `gender` ,
`profile` . `pub_keywords` , `profile` . `dob` , `profile` . `photo` ,
`profile` . `net-publish` , `user` . `nickname` , `user` . `hidewall` ,
`contact` . `notify` , `contact` . `url` , `contact` . `addr`
FROM `profile`
INNER JOIN `user` ON `user` . `uid` = `profile` . `uid`
INNER JOIN `contact` ON `contact` . `uid` = `profile` . `uid`
WHERE `profile` . `uid` = % d AND `profile` . `is-default` AND `contact` . `self` " ,
intval ( $uid )
);
2017-11-19 23:03:39 +01:00
$location = Profile :: formatLocation (
2018-01-15 14:05:12 +01:00
[ " locality " => $r [ 0 ][ " locality " ], " region " => $r [ 0 ][ " region " ], " country-name " => $r [ 0 ][ " country-name " ]]
2017-11-15 15:47:28 +01:00
);
// The "addr" field was added in 3.4.3 so it can be empty for older users
if ( $r [ 0 ][ " addr " ] != " " ) {
2018-01-15 14:05:12 +01:00
$addr = $r [ 0 ][ " nickname " ] . '@' . str_replace ([ " http:// " , " https:// " ], " " , System :: baseUrl ());
2017-11-15 15:47:28 +01:00
} else {
$addr = $r [ 0 ][ " addr " ];
}
2018-01-15 14:05:12 +01:00
$gcontact = [ " name " => $r [ 0 ][ " name " ], " location " => $location , " about " => $r [ 0 ][ " about " ],
2017-11-15 15:47:28 +01:00
" gender " => $r [ 0 ][ " gender " ], " keywords " => $r [ 0 ][ " pub_keywords " ],
" birthday " => $r [ 0 ][ " dob " ], " photo " => $r [ 0 ][ " photo " ],
" notify " => $r [ 0 ][ " notify " ], " url " => $r [ 0 ][ " url " ],
" hide " => ( $r [ 0 ][ " hidewall " ] || ! $r [ 0 ][ " net-publish " ]),
" nick " => $r [ 0 ][ " nickname " ], " addr " => $addr ,
" connect " => $addr , " server_url " => System :: baseUrl (),
2018-01-15 14:05:12 +01:00
" generation " => 1 , " network " => NETWORK_DFRN ];
2017-11-15 15:47:28 +01:00
self :: update ( $gcontact );
}
/**
* @ brief Fetches users of given GNU Social server
*
2018-01-17 20:22:38 +01:00
* If the " Statistics " addon is enabled ( See http :// gstools . org / for details ) we query user data with this .
2017-11-15 15:47:28 +01:00
*
2017-12-17 21:27:50 +01:00
* @ param string $server Server address
2017-11-19 23:04:40 +01:00
* @ return void
2017-11-15 15:47:28 +01:00
*/
2017-11-15 18:02:08 +01:00
public static function fetchGsUsers ( $server )
2017-11-15 15:47:28 +01:00
{
logger ( " Fetching users from GNU Social server " . $server , LOGGER_DEBUG );
$url = $server . " /main/statistics " ;
2018-01-27 17:13:41 +01:00
$result = Network :: curl ( $url );
2017-11-15 15:47:28 +01:00
if ( ! $result [ " success " ]) {
return false ;
}
$statistics = json_decode ( $result [ " body " ]);
if ( is_object ( $statistics -> config )) {
if ( $statistics -> config -> instance_with_ssl ) {
$server = " https:// " ;
} else {
$server = " http:// " ;
}
$server .= $statistics -> config -> instance_address ;
$hostname = $statistics -> config -> instance_address ;
} else {
/// @TODO is_object() above means here no object, still $statistics is being used as object
if ( $statistics -> instance_with_ssl ) {
$server = " https:// " ;
} else {
$server = " http:// " ;
}
$server .= $statistics -> instance_address ;
$hostname = $statistics -> instance_address ;
}
if ( is_object ( $statistics -> users )) {
foreach ( $statistics -> users as $nick => $user ) {
$profile_url = $server . " / " . $user -> nickname ;
2018-01-15 14:05:12 +01:00
$contact = [ " url " => $profile_url ,
2017-11-15 15:47:28 +01:00
" name " => $user -> fullname ,
" addr " => $user -> nickname . " @ " . $hostname ,
" nick " => $user -> nickname ,
" about " => $user -> bio ,
" network " => NETWORK_OSTATUS ,
2018-01-15 14:05:12 +01:00
" photo " => System :: baseUrl () . " /images/person-175.jpg " ];
2017-11-15 15:47:28 +01:00
self :: getId ( $contact );
}
}
}
/**
* @ brief Asking GNU Social server on a regular base for their user data
2017-11-19 23:04:40 +01:00
* @ return void
2017-11-15 15:47:28 +01:00
*/
2017-11-15 18:02:08 +01:00
public static function discoverGsUsers ()
2017-11-15 15:47:28 +01:00
{
$requery_days = intval ( Config :: get ( " system " , " poco_requery_days " ));
$last_update = date ( " c " , time () - ( 60 * 60 * 24 * $requery_days ));
$r = q (
" SELECT `nurl`, `url` FROM `gserver` WHERE `last_contact` >= `last_failure` AND `network` = '%s' AND `last_poco_query` < '%s' ORDER BY RAND() LIMIT 5 " ,
dbesc ( NETWORK_OSTATUS ),
dbesc ( $last_update )
);
if ( ! DBM :: is_result ( $r )) {
return ;
}
foreach ( $r as $server ) {
2017-11-15 18:02:08 +01:00
self :: fetchGsUsers ( $server [ " url " ]);
2018-01-27 03:38:34 +01:00
q ( " UPDATE `gserver` SET `last_poco_query` = '%s' WHERE `nurl` = '%s' " , dbesc ( DateTimeFormat :: utcNow ()), dbesc ( $server [ " nurl " ]));
2017-11-15 15:47:28 +01:00
}
}
2017-11-19 23:03:39 +01:00
2017-11-20 17:14:35 +01:00
/**
* @ return string
*/
public static function getRandomUrl ()
{
$r = q (
" SELECT `url` FROM `gcontact` WHERE `network` = '%s'
2017-11-19 23:03:39 +01:00
AND `last_contact` >= `last_failure`
AND `updated` > UTC_TIMESTAMP - INTERVAL 1 MONTH
ORDER BY rand () LIMIT 1 " ,
2017-11-20 17:14:35 +01:00
dbesc ( NETWORK_DFRN )
);
2017-11-19 23:03:39 +01:00
2017-11-20 17:14:35 +01:00
if ( DBM :: is_result ( $r )) {
2017-11-19 23:03:39 +01:00
return dirname ( $r [ 0 ][ 'url' ]);
2017-11-20 17:14:35 +01:00
}
2017-11-19 23:03:39 +01:00
return '' ;
}
2017-11-15 15:47:28 +01:00
}