2010-07-02 01:48:07 +02:00
< ? php
2010-12-15 01:34:49 +01:00
/**
2020-02-09 16:18:46 +01:00
* @ copyright Copyright ( C ) 2020 , Friendica
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
2010-12-15 01:34:49 +01:00
*
2020-02-09 16:18:46 +01:00
* Handles communication associated with the issuance of friend requests .
2010-12-15 01:34:49 +01:00
*
2020-06-15 19:47:23 +02:00
* @ see PDF with dfrn specs : https :// github . com / friendica / friendica / blob / stable / spec / dfrn2 . pdf
2016-11-29 19:57:30 +01:00
* You also find a graphic which describes the confirmation process at
2020-06-15 19:47:23 +02:00
* https :// github . com / friendica / friendica / blob / stable / spec / dfrn2_contact_request . png
2010-12-15 01:34:49 +01:00
*/
2018-01-25 03:08:45 +01:00
2017-04-30 06:07:00 +02:00
use Friendica\App ;
2018-10-29 22:20:46 +01:00
use Friendica\Core\Logger ;
2018-08-11 22:40:44 +02:00
use Friendica\Core\Protocol ;
2018-10-31 15:35:50 +01:00
use Friendica\Core\Renderer ;
2020-01-04 23:59:20 +01:00
use Friendica\Core\Search ;
2019-09-28 20:09:11 +02:00
use Friendica\Core\Session ;
2020-03-04 22:07:05 +01:00
use Friendica\Core\System ;
2018-07-20 14:19:26 +02:00
use Friendica\Database\DBA ;
2019-12-16 00:28:31 +01:00
use Friendica\DI ;
2017-12-07 15:04:24 +01:00
use Friendica\Model\Contact ;
2017-12-09 19:45:17 +01:00
use Friendica\Model\Group ;
2020-02-04 22:03:45 +01:00
use Friendica\Model\Notify\Type ;
2018-01-15 03:22:39 +01:00
use Friendica\Model\Profile ;
2018-01-25 03:08:45 +01:00
use Friendica\Model\User ;
2019-12-27 22:19:28 +01:00
use Friendica\Module\Security\Login ;
2020-03-04 22:07:05 +01:00
use Friendica\Network\HTTPRequest ;
2017-05-07 20:44:30 +02:00
use Friendica\Network\Probe ;
2019-10-24 00:25:43 +02:00
use Friendica\Protocol\Activity ;
2018-01-27 03:38:34 +01:00
use Friendica\Util\DateTimeFormat ;
2018-01-27 05:09:48 +01:00
use Friendica\Util\Network ;
2018-11-08 14:45:46 +01:00
use Friendica\Util\Strings ;
2017-05-07 20:45:19 +02:00
2017-11-29 18:17:12 +01:00
function dfrn_request_init ( App $a )
{
2018-01-15 03:22:39 +01:00
if ( $a -> argc > 1 ) {
2010-07-02 01:48:07 +02:00
$which = $a -> argv [ 1 ];
2019-01-07 18:51:48 +01:00
Profile :: load ( $a , $which );
2018-01-15 03:22:39 +01:00
}
2010-07-02 01:48:07 +02:00
return ;
2016-11-28 01:13:47 +01:00
}
2010-07-02 01:48:07 +02:00
2010-12-15 01:34:49 +01:00
/**
* Function : dfrn_request_post
*
* Purpose :
* Handles multiple scenarios .
*
* Scenario 1 :
* Clicking 'submit' on a friend request page .
*
* Scenario 2 :
* Following Scenario 1 , we are brought back to our home site
* in order to link our friend request with our own server cell .
* After logging in , we click 'submit' to approve the linkage .
*
2019-01-07 07:07:42 +01:00
* @ param App $a
* @ throws ImagickException
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2010-12-15 01:34:49 +01:00
*/
2017-12-17 02:13:10 +01:00
function dfrn_request_post ( App $a )
{
2020-01-30 05:19:03 +01:00
if ( $a -> argc != 2 || empty ( $a -> profile )) {
Logger :: log ( 'Wrong count of argc or profiles: argc=' . $a -> argc . ', profile()=' . count ( $a -> profile ? ? []));
2010-07-02 01:48:07 +02:00
return ;
2016-03-01 14:42:55 +01:00
}
2010-07-02 01:48:07 +02:00
2018-11-30 15:06:22 +01:00
if ( ! empty ( $_POST [ 'cancel' ])) {
2019-12-16 00:28:31 +01:00
DI :: baseUrl () -> redirect ();
2014-09-06 17:28:46 +02:00
}
2010-07-02 01:48:07 +02:00
2016-11-28 01:13:47 +01:00
/*
2010-12-15 01:34:49 +01:00
* Scenario 2 : We ' ve introduced ourself to another cell , then have been returned to our own cell
2014-09-06 17:28:46 +02:00
* to confirm the request , and then we ' ve clicked submit ( perhaps after logging in ) .
2010-12-15 01:34:49 +01:00
* That brings us here :
*/
2018-11-30 15:06:22 +01:00
if ( ! empty ( $_POST [ 'localconfirm' ]) && ( $_POST [ 'localconfirm' ] == 1 )) {
2017-12-17 02:13:10 +01:00
// Ensure this is a valid request
2018-11-30 15:06:22 +01:00
if ( local_user () && ( $a -> user [ 'nickname' ] == $a -> argv [ 1 ]) && ! empty ( $_POST [ 'dfrn_url' ])) {
$dfrn_url = Strings :: escapeTags ( trim ( $_POST [ 'dfrn_url' ]));
$aes_allow = ! empty ( $_POST [ 'aes_allow' ]);
2019-10-15 15:01:17 +02:00
$confirm_key = $_POST [ 'confirm_key' ] ? ? '' ;
2018-11-30 15:06:22 +01:00
$hidden = ( ! empty ( $_POST [ 'hidden-contact' ]) ? intval ( $_POST [ 'hidden-contact' ]) : 0 );
2010-07-22 11:13:39 +02:00
$contact_record = null ;
2018-11-30 15:06:22 +01:00
$blocked = 1 ;
$pending = 1 ;
2014-03-11 23:52:32 +01:00
2018-11-30 15:06:22 +01:00
if ( ! empty ( $dfrn_url )) {
2017-12-17 02:13:10 +01:00
// Lookup the contact based on their URL (which is the only unique thing we have at the moment)
2016-11-28 01:13:47 +01:00
$r = q ( " SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND NOT `self` LIMIT 1 " ,
2016-11-28 15:30:36 +01:00
intval ( local_user ()),
2018-11-08 17:28:29 +01:00
DBA :: escape ( Strings :: normaliseLink ( $dfrn_url ))
2010-07-06 06:39:55 +02:00
);
2014-03-11 23:52:32 +01:00
2018-07-21 14:46:04 +02:00
if ( DBA :: isResult ( $r )) {
2017-12-17 02:13:10 +01:00
if ( strlen ( $r [ 0 ][ 'dfrn-id' ])) {
// We don't need to be here. It has already happened.
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( " This introduction has already been accepted. " ) . EOL );
2010-07-22 11:13:39 +02:00
return ;
2018-01-04 17:53:57 +01:00
} else {
2010-07-22 11:13:39 +02:00
$contact_record = $r [ 0 ];
2018-01-04 17:53:57 +01:00
}
2010-07-22 11:13:39 +02:00
}
2013-12-02 20:30:24 +01:00
2017-12-17 02:13:10 +01:00
if ( is_array ( $contact_record )) {
2018-01-04 17:53:57 +01:00
$r = q ( " UPDATE `contact` SET `ret-aes` = %d, hidden = %d WHERE `id` = %d " ,
intval ( $aes_allow ),
intval ( $hidden ),
intval ( $contact_record [ 'id' ])
2010-07-22 11:13:39 +02:00
);
2017-12-17 02:13:10 +01:00
} else {
// Scrape the other site's profile page to pick up the dfrn links, key, fn, and photo
2016-07-04 08:05:30 +02:00
$parms = Probe :: profile ( $dfrn_url );
2014-08-21 00:56:21 +02:00
2017-12-17 02:13:10 +01:00
if ( ! count ( $parms )) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Profile location is not valid or does not contain profile information.' ) . EOL );
2010-07-06 06:39:55 +02:00
return ;
2017-12-17 02:13:10 +01:00
} else {
2018-11-30 15:06:22 +01:00
if ( empty ( $parms [ 'fn' ])) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Warning: profile location has no identifiable owner name.' ) . EOL );
2016-12-20 17:43:46 +01:00
}
2018-11-30 15:06:22 +01:00
if ( empty ( $parms [ 'photo' ])) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Warning: profile location has no profile photo.' ) . EOL );
2016-12-20 17:43:46 +01:00
}
2017-05-08 16:19:10 +02:00
$invalid = Probe :: validDfrn ( $parms );
2016-12-20 17:43:46 +01:00
if ( $invalid ) {
2020-01-18 20:53:01 +01:00
notice ( DI :: l10n () -> tt ( " %d required parameter was not found at the given location " , " %d required parameters were not found at the given location " , $invalid ) . EOL );
2010-07-22 11:13:39 +02:00
return ;
}
}
2010-07-02 01:48:07 +02:00
2010-07-22 11:13:39 +02:00
$dfrn_request = $parms [ 'dfrn-request' ];
2010-07-06 06:39:55 +02:00
2016-06-25 13:56:55 +02:00
$photo = $parms [ " photo " ];
2016-11-28 01:13:47 +01:00
// Escape the entire array
2018-07-21 14:48:29 +02:00
DBA :: escapeArray ( $parms );
2010-12-15 01:34:49 +01:00
2017-12-17 02:13:10 +01:00
// Create a contact record on our site for the other person
2015-11-25 18:46:02 +01:00
$r = q ( " INSERT INTO `contact` ( `uid`, `created`,`url`, `nurl`, `addr`, `name`, `nick`, `photo`, `site-pubkey`,
2018-08-02 16:11:21 +02:00
`request` , `confirm` , `notify` , `poll` , `network` , `aes_allow` , `hidden` , `blocked` , `pending` )
VALUES ( % d , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , % d , % d , % d , % d ) " ,
2010-10-18 23:34:59 +02:00
intval ( local_user ()),
2018-01-27 03:38:34 +01:00
DateTimeFormat :: utcNow (),
2018-07-21 15:10:13 +02:00
DBA :: escape ( $dfrn_url ),
2018-11-08 17:28:29 +01:00
DBA :: escape ( Strings :: normaliseLink ( $dfrn_url )),
2015-11-25 18:46:02 +01:00
$parms [ 'addr' ],
2010-07-22 11:13:39 +02:00
$parms [ 'fn' ],
2010-10-23 10:20:26 +02:00
$parms [ 'nick' ],
2010-07-22 11:13:39 +02:00
$parms [ 'photo' ],
$parms [ 'key' ],
$parms [ 'dfrn-request' ],
$parms [ 'dfrn-confirm' ],
$parms [ 'dfrn-notify' ],
$parms [ 'dfrn-poll' ],
2018-08-11 22:40:44 +02:00
DBA :: escape ( Protocol :: DFRN ),
2012-05-30 03:43:56 +02:00
intval ( $aes_allow ),
2016-11-29 03:08:46 +01:00
intval ( $hidden ),
intval ( $blocked ),
intval ( $pending )
2010-07-22 11:13:39 +02:00
);
}
2010-07-06 06:39:55 +02:00
2016-12-20 21:15:53 +01:00
if ( $r ) {
2020-01-18 20:52:34 +01:00
info ( DI :: l10n () -> t ( " Introduction complete. " ) . EOL );
2010-07-22 11:13:39 +02:00
}
2016-03-06 13:15:27 +01:00
$r = q ( " SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `site-pubkey` = '%s' LIMIT 1 " ,
2012-06-12 10:13:09 +02:00
intval ( local_user ()),
2018-07-21 15:10:13 +02:00
DBA :: escape ( $dfrn_url ),
2019-10-15 15:01:17 +02:00
$parms [ 'key' ] ? ? '' // Potentially missing
2012-06-12 10:13:09 +02:00
);
2018-07-21 14:46:04 +02:00
if ( DBA :: isResult ( $r )) {
2018-02-12 03:25:09 +01:00
Group :: addMember ( User :: getDefaultGroup ( local_user (), $r [ 0 ][ " network " ]), $r [ 0 ][ 'id' ]);
2016-04-13 22:21:23 +02:00
2017-12-09 19:42:02 +01:00
if ( isset ( $photo )) {
2017-11-29 23:29:11 +01:00
Contact :: updateAvatar ( $photo , local_user (), $r [ 0 ][ " id " ], true );
2017-12-09 19:42:02 +01:00
}
2016-06-25 13:56:55 +02:00
2018-10-19 23:56:54 +02:00
$forward_path = " contact/ " . $r [ 0 ][ 'id' ];
2016-12-20 10:44:27 +01:00
} else {
2018-10-19 23:56:54 +02:00
$forward_path = " contact " ;
2016-12-20 10:44:27 +01:00
}
2012-06-12 10:13:09 +02:00
2017-12-17 02:13:10 +01:00
// Allow the blocked remote notification to complete
2016-12-20 10:44:27 +01:00
if ( is_array ( $contact_record )) {
2010-07-22 11:13:39 +02:00
$dfrn_request = $contact_record [ 'request' ];
2016-12-20 10:44:27 +01:00
}
2010-07-02 01:48:07 +02:00
2019-01-07 18:51:48 +01:00
if ( ! empty ( $dfrn_request ) && strlen ( $confirm_key )) {
2020-03-04 22:07:05 +01:00
HTTPRequest :: fetchUrl ( $dfrn_request . '?confirm_key=' . $confirm_key );
2016-12-20 10:44:27 +01:00
}
2014-08-21 00:56:21 +02:00
2010-12-15 01:34:49 +01:00
// (ignore reply, nothing we can do it failed)
2019-12-16 00:28:31 +01:00
DI :: baseUrl () -> redirect ( $forward_path );
2010-07-22 11:13:39 +02:00
return ; // NOTREACHED
}
2010-07-02 01:48:07 +02:00
}
2010-07-22 11:13:39 +02:00
2017-12-17 02:13:10 +01:00
// invalid/bogus request
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Unrecoverable protocol error.' ) . EOL );
2019-12-16 00:28:31 +01:00
DI :: baseUrl () -> redirect ();
2010-07-22 11:13:39 +02:00
return ; // NOTREACHED
2010-07-02 01:48:07 +02:00
}
2016-11-28 01:13:47 +01:00
/*
2010-12-15 01:34:49 +01:00
* Otherwise :
2014-09-06 17:28:46 +02:00
*
2010-12-15 01:34:49 +01:00
* Scenario 1 :
2014-09-06 17:28:46 +02:00
* We are the requestee . A person from a remote cell has made an introduction
* on our profile web page and clicked submit . We will use their DFRN - URL to
* figure out how to contact their cell .
2010-12-15 01:34:49 +01:00
*
* Scrape the originating DFRN - URL for everything we need . Create a contact record
* and an introduction to show our user next time he / she logs in .
* Finally redirect back to the requestor so that their site can record the request .
2014-09-06 17:28:46 +02:00
* If our user ( the requestee ) later confirms this request , a record of it will need
* to exist on the requestor ' s cell in order for the confirmation process to complete ..
2010-12-15 01:34:49 +01:00
*
* It ' s possible that neither the requestor or the requestee are logged in at the moment ,
* and the requestor does not yet have any credentials to the requestee profile .
*
* Who is the requestee ? We ' ve already loaded their profile which means their nickname should be
* in $a -> argv [ 1 ] and we should have their complete info in $a -> profile .
*
*/
2020-01-30 05:19:03 +01:00
if ( empty ( $a -> profile [ 'uid' ])) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Profile unavailable.' ) . EOL );
2010-07-22 11:13:39 +02:00
return ;
}
2010-12-20 09:27:00 +01:00
$nickname = $a -> profile [ 'nickname' ];
$uid = $a -> profile [ 'uid' ];
$maxreq = intval ( $a -> profile [ 'maxreq' ]);
2010-07-06 06:39:55 +02:00
$contact_record = null ;
2010-12-20 09:27:00 +01:00
$failed = false ;
$parms = null ;
2016-11-29 15:52:12 +01:00
$blocked = 1 ;
$pending = 1 ;
2010-07-02 01:48:07 +02:00
2018-11-30 15:06:22 +01:00
if ( ! empty ( $_POST [ 'dfrn_url' ])) {
2017-12-17 02:13:10 +01:00
// Block friend request spam
if ( $maxreq ) {
2010-12-20 09:27:00 +01:00
$r = q ( " SELECT * FROM `intro` WHERE `datetime` > '%s' AND `uid` = %d " ,
2018-07-21 15:10:13 +02:00
DBA :: escape ( DateTimeFormat :: utc ( 'now - 24 hours' )),
2010-12-20 09:27:00 +01:00
intval ( $uid )
);
2018-07-21 14:46:04 +02:00
if ( DBA :: isResult ( $r ) && count ( $r ) > $maxreq ) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( '%s has received too many connection requests today.' , $a -> profile [ 'name' ]) . EOL );
notice ( DI :: l10n () -> t ( 'Spam protection measures have been invoked.' ) . EOL );
notice ( DI :: l10n () -> t ( 'Friends are advised to please try again in 24 hours.' ) . EOL );
2010-12-20 09:27:00 +01:00
return ;
2014-09-06 17:28:46 +02:00
}
2010-12-20 09:27:00 +01:00
}
2017-12-17 02:13:10 +01:00
/* Cleanup old introductions that remain blocked .
2011-01-07 09:24:08 +01:00
* Also remove the contact record , but only if there is no existing relationship
2011-12-12 08:33:56 +01:00
*/
2014-09-06 17:28:46 +02:00
$r = q ( " SELECT `intro`.*, `intro`.`id` AS `iid`, `contact`.`id` AS `cid`, `contact`.`rel`
2011-12-12 08:33:56 +01:00
FROM `intro` LEFT JOIN `contact` on `intro` . `contact-id` = `contact` . `id`
2014-09-06 17:28:46 +02:00
WHERE `intro` . `blocked` = 1 AND `contact` . `self` = 0
2017-12-03 10:19:58 +01:00
AND `intro` . `datetime` < UTC_TIMESTAMP () - INTERVAL 30 MINUTE "
2011-12-12 08:33:56 +01:00
);
2018-07-21 14:46:04 +02:00
if ( DBA :: isResult ( $r )) {
2016-12-20 21:15:53 +01:00
foreach ( $r as $rr ) {
2017-12-17 02:13:10 +01:00
if ( ! $rr [ 'rel' ]) {
2018-07-20 14:19:26 +02:00
DBA :: delete ( 'contact' , [ 'id' => $rr [ 'cid' ], 'self' => false ]);
2011-12-12 08:33:56 +01:00
}
2018-07-20 14:19:26 +02:00
DBA :: delete ( 'intro' , [ 'id' => $rr [ 'iid' ]]);
2011-12-12 08:33:56 +01:00
}
}
2010-07-02 01:48:07 +02:00
$url = trim ( $_POST [ 'dfrn_url' ]);
2017-12-17 02:13:10 +01:00
if ( ! strlen ( $url )) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( " Invalid locator " ) . EOL );
2010-07-06 06:39:55 +02:00
return ;
}
2011-09-07 03:06:19 +02:00
$hcard = '' ;
2010-07-20 04:09:58 +02:00
2017-12-03 10:19:58 +01:00
// Detect the network
2020-07-16 12:22:14 +02:00
$data = Contact :: getByURL ( $url );
2017-12-03 10:19:58 +01:00
$network = $data [ " network " ];
2012-04-11 04:15:52 +02:00
2020-06-25 02:57:47 +02:00
// Canonicalize email-style profile locator
$url = Probe :: webfingerDfrn ( $data [ 'url' ], $hcard );
2012-12-23 11:47:14 +01:00
2017-12-17 02:13:10 +01:00
if ( substr ( $url , 0 , 5 ) === 'stat:' ) {
2017-12-03 10:19:58 +01:00
// Every time we detect the remote subscription we define this as OStatus.
// We do this even if it is not OStatus.
// we only need to pass this through another section of the code.
2018-08-11 22:40:44 +02:00
if ( $network != Protocol :: DIASPORA ) {
$network = Protocol :: OSTATUS ;
2012-04-11 04:15:52 +02:00
}
2017-12-17 02:13:10 +01:00
$url = substr ( $url , 5 );
2016-01-01 17:49:07 +01:00
} else {
2018-08-11 22:40:44 +02:00
$network = Protocol :: DFRN ;
2010-07-08 16:03:25 +02:00
}
2010-07-06 06:39:55 +02:00
2018-10-30 14:58:45 +01:00
Logger :: log ( 'dfrn_request: url: ' . $url . ',network=' . $network , Logger :: DEBUG );
2016-12-14 16:36:32 +01:00
2018-08-11 22:40:44 +02:00
if ( $network === Protocol :: DFRN ) {
2014-09-06 17:28:46 +02:00
$ret = q ( " SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `self` = 0 LIMIT 1 " ,
2010-10-13 04:32:15 +02:00
intval ( $uid ),
2018-07-21 15:10:13 +02:00
DBA :: escape ( $url )
2010-07-06 06:39:55 +02:00
);
2010-09-14 02:12:54 +02:00
2018-07-21 14:46:04 +02:00
if ( DBA :: isResult ( $ret )) {
2017-12-17 02:13:10 +01:00
if ( strlen ( $ret [ 0 ][ 'issued-id' ])) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'You have already introduced yourself here.' ) . EOL );
2010-10-13 04:32:15 +02:00
return ;
2018-07-25 04:53:46 +02:00
} elseif ( $ret [ 0 ][ 'rel' ] == Contact :: FRIEND ) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Apparently you are already friends with %s.' , $a -> profile [ 'name' ]) . EOL );
2010-10-18 05:04:17 +02:00
return ;
2017-12-17 02:13:10 +01:00
} else {
2010-10-13 04:32:15 +02:00
$contact_record = $ret [ 0 ];
2018-01-15 14:05:12 +01:00
$parms = [ 'dfrn-request' => $ret [ 0 ][ 'request' ]];
2010-10-13 04:32:15 +02:00
}
2010-09-14 02:12:54 +02:00
}
2010-10-18 05:04:17 +02:00
2018-11-08 14:45:46 +01:00
$issued_id = Strings :: getRandomHex ();
2010-10-13 04:32:15 +02:00
2017-12-17 02:13:10 +01:00
if ( is_array ( $contact_record )) {
2010-10-13 04:32:15 +02:00
// There is a contact record but no issued-id, so this
// is a reciprocal introduction from a known contact
2013-12-02 20:30:24 +01:00
$r = q ( " UPDATE `contact` SET `issued-id` = '%s' WHERE `id` = %d " ,
2018-07-21 15:10:13 +02:00
DBA :: escape ( $issued_id ),
2010-10-13 04:32:15 +02:00
intval ( $contact_record [ 'id' ])
);
2017-12-17 02:13:10 +01:00
} else {
2018-01-27 17:13:41 +01:00
$url = Network :: isUrlValid ( $url );
2017-12-17 02:13:10 +01:00
if ( ! $url ) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Invalid profile URL.' ) . EOL );
2019-12-16 01:33:13 +01:00
DI :: baseUrl () -> redirect ( DI :: args () -> getCommand ());
2010-10-13 04:32:15 +02:00
return ; // NOTREACHED
}
2010-07-02 01:48:07 +02:00
2018-01-27 17:13:41 +01:00
if ( ! Network :: isUrlAllowed ( $url )) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Disallowed profile URL.' ) . EOL );
2019-12-16 01:33:13 +01:00
DI :: baseUrl () -> redirect ( DI :: args () -> getCommand ());
2010-10-13 04:32:15 +02:00
return ; // NOTREACHED
2010-07-02 01:48:07 +02:00
}
2014-09-06 17:28:46 +02:00
2018-01-27 17:13:41 +01:00
if ( Network :: isUrlBlocked ( $url )) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Blocked domain' ) . EOL );
2019-12-16 01:33:13 +01:00
DI :: baseUrl () -> redirect ( DI :: args () -> getCommand ());
2017-04-26 04:45:56 +02:00
return ; // NOTREACHED
}
2010-07-02 01:48:07 +02:00
2016-07-04 08:05:30 +02:00
$parms = Probe :: profile (( $hcard ) ? $hcard : $url );
2010-10-13 04:32:15 +02:00
2017-12-17 02:13:10 +01:00
if ( ! count ( $parms )) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Profile location is not valid or does not contain profile information.' ) . EOL );
2019-12-16 01:33:13 +01:00
DI :: baseUrl () -> redirect ( DI :: args () -> getCommand ());
2017-12-17 02:13:10 +01:00
} else {
2018-11-30 15:06:22 +01:00
if ( empty ( $parms [ 'fn' ])) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Warning: profile location has no identifiable owner name.' ) . EOL );
2016-12-20 17:43:46 +01:00
}
2018-11-30 15:06:22 +01:00
if ( empty ( $parms [ 'photo' ])) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Warning: profile location has no profile photo.' ) . EOL );
2016-12-20 17:43:46 +01:00
}
2017-05-08 16:19:10 +02:00
$invalid = Probe :: validDfrn ( $parms );
2016-12-20 17:43:46 +01:00
if ( $invalid ) {
2020-01-18 20:53:01 +01:00
notice ( DI :: l10n () -> tt ( " %d required parameter was not found at the given location " , " %d required parameters were not found at the given location " , $invalid ) . EOL );
2014-09-06 17:28:46 +02:00
2010-10-13 04:32:15 +02:00
return ;
}
}
2010-07-02 01:48:07 +02:00
2010-10-13 04:32:15 +02:00
$parms [ 'url' ] = $url ;
$parms [ 'issued-id' ] = $issued_id ;
2016-06-25 13:56:55 +02:00
$photo = $parms [ " photo " ];
2010-07-06 06:39:55 +02:00
2018-07-21 14:48:29 +02:00
DBA :: escapeArray ( $parms );
2015-11-25 18:46:02 +01:00
$r = q ( " INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `name`, `nick`, `issued-id`, `photo`, `site-pubkey`,
2018-08-02 16:11:21 +02:00
`request` , `confirm` , `notify` , `poll` , `network` , `blocked` , `pending` )
VALUES ( % d , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , % d , % d ) " ,
2010-07-06 06:39:55 +02:00
intval ( $uid ),
2018-07-21 15:10:13 +02:00
DBA :: escape ( DateTimeFormat :: utcNow ()),
2010-07-06 06:39:55 +02:00
$parms [ 'url' ],
2018-11-08 17:28:29 +01:00
DBA :: escape ( Strings :: normaliseLink ( $url )),
2015-11-25 18:46:02 +01:00
$parms [ 'addr' ],
2010-10-13 04:32:15 +02:00
$parms [ 'fn' ],
2010-10-23 10:20:26 +02:00
$parms [ 'nick' ],
2010-10-13 04:32:15 +02:00
$parms [ 'issued-id' ],
$parms [ 'photo' ],
$parms [ 'key' ],
$parms [ 'dfrn-request' ],
$parms [ 'dfrn-confirm' ],
$parms [ 'dfrn-notify' ],
2011-08-19 01:47:45 +02:00
$parms [ 'dfrn-poll' ],
2018-08-11 22:40:44 +02:00
DBA :: escape ( Protocol :: DFRN ),
2016-11-29 15:52:12 +01:00
intval ( $blocked ),
intval ( $pending )
2010-07-06 06:39:55 +02:00
);
2010-07-02 01:48:07 +02:00
2010-10-13 04:32:15 +02:00
// find the contact record we just created
2016-12-20 10:44:27 +01:00
if ( $r ) {
2014-03-11 23:52:32 +01:00
$r = q ( " SELECT `id` FROM `contact`
2010-10-13 04:32:15 +02:00
WHERE `uid` = % d AND `url` = '%s' AND `issued-id` = '%s' LIMIT 1 " ,
intval ( $uid ),
$parms [ 'url' ],
$parms [ 'issued-id' ]
);
2018-07-21 14:46:04 +02:00
if ( DBA :: isResult ( $r )) {
2010-10-13 04:32:15 +02:00
$contact_record = $r [ 0 ];
2017-11-29 23:29:11 +01:00
Contact :: updateAvatar ( $photo , $uid , $contact_record [ " id " ], true );
2016-06-25 13:56:55 +02:00
}
2010-10-13 04:32:15 +02:00
}
}
2016-12-20 10:44:27 +01:00
if ( $r === false ) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Failed to update contact record.' ) . EOL );
2010-10-13 04:32:15 +02:00
return ;
}
2010-07-02 01:48:07 +02:00
2018-11-08 14:45:46 +01:00
$hash = Strings :: getRandomHex () . ( string ) time (); // Generate a confirm_key
2013-12-02 20:30:24 +01:00
2016-12-20 10:44:27 +01:00
if ( is_array ( $contact_record )) {
2019-01-07 07:23:49 +01:00
q ( " INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime`)
2010-10-13 04:32:15 +02:00
VALUES ( % d , % d , 1 , % d , '%s' , '%s' , '%s' ) " ,
intval ( $uid ),
intval ( $contact_record [ 'id' ]),
2018-11-30 15:06:22 +01:00
intval ( ! empty ( $_POST [ 'knowyou' ])),
2019-10-15 15:01:17 +02:00
DBA :: escape ( Strings :: escapeTags ( trim ( $_POST [ 'dfrn-request-message' ] ? ? '' ))),
2018-07-21 15:10:13 +02:00
DBA :: escape ( $hash ),
DBA :: escape ( DateTimeFormat :: utcNow ())
2010-10-13 04:32:15 +02:00
);
}
2014-08-21 00:56:21 +02:00
2010-10-18 05:04:17 +02:00
// This notice will only be seen by the requestor if the requestor and requestee are on the same server.
2017-12-17 02:13:10 +01:00
if ( ! $failed ) {
2020-01-18 20:52:34 +01:00
info ( DI :: l10n () -> t ( 'Your introduction has been sent.' ) . EOL );
2016-12-20 10:44:27 +01:00
}
2010-07-02 01:48:07 +02:00
2010-10-13 04:32:15 +02:00
// "Homecoming" - send the requestor back to their site to record the introduction.
2019-12-16 01:05:15 +01:00
$dfrn_url = bin2hex ( DI :: baseUrl () -> get () . '/profile/' . $nickname );
2010-10-13 04:32:15 +02:00
$aes_allow = (( function_exists ( 'openssl_encrypt' )) ? 1 : 0 );
2010-07-02 01:48:07 +02:00
2018-10-19 20:11:27 +02:00
System :: externalRedirect ( $parms [ 'dfrn-request' ] . " ?dfrn_url= $dfrn_url "
2014-09-06 17:28:46 +02:00
. '&dfrn_version=' . DFRN_PROTOCOL_VERSION
2017-12-17 02:13:10 +01:00
. '&confirm_key=' . $hash
2010-10-13 04:32:15 +02:00
. (( $aes_allow ) ? " &aes_allow=1 " : " " )
);
// NOTREACHED
2018-08-11 22:40:44 +02:00
// END $network === Protocol::DFRN
} elseif (( $network != Protocol :: PHANTOM ) && ( $url != " " )) {
2014-09-06 17:28:46 +02:00
2017-12-17 02:13:10 +01:00
/* Substitute our user ' s feed URL into $url template
2010-12-15 01:34:49 +01:00
* Send the subscriber home to subscribe
*/
2015-12-28 03:14:38 +01:00
// Diaspora needs the uri in the format user@domain.tld
// Diaspora will support the remote subscription in a future version
2018-08-11 22:40:44 +02:00
if ( $network == Protocol :: DIASPORA ) {
2020-02-06 23:45:22 +01:00
$uri = urlencode ( $a -> profile [ 'addr' ]);
2016-12-20 10:44:27 +01:00
} else {
2020-02-06 23:45:22 +01:00
$uri = urlencode ( $a -> profile [ 'url' ]);
2016-12-20 10:44:27 +01:00
}
2015-12-28 03:14:38 +01:00
$url = str_replace ( '{uri}' , $uri , $url );
2018-10-19 20:11:27 +02:00
System :: externalRedirect ( $url );
2010-10-13 04:32:15 +02:00
// NOTREACHED
2018-08-11 22:40:44 +02:00
// END $network != Protocol::PHANTOM
2015-12-28 03:14:38 +01:00
} else {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( " Remote subscription can't be done for your network. Please subscribe directly on your system. " ) . EOL );
2015-12-28 03:14:38 +01:00
return ;
2010-10-13 04:32:15 +02:00
}
2017-12-17 02:13:10 +01:00
} return ;
2016-11-28 01:13:47 +01:00
}
2010-07-22 11:13:39 +02:00
2017-12-17 02:13:10 +01:00
function dfrn_request_content ( App $a )
{
2019-05-29 03:14:21 +02:00
if ( $a -> argc != 2 || empty ( $a -> profile )) {
2010-07-02 01:48:07 +02:00
return " " ;
2016-12-20 10:44:27 +01:00
}
2010-07-02 01:48:07 +02:00
// "Homecoming". Make sure we're logged in to this site as the correct user. Then offer a confirm button
// to send us to the post section to record the introduction.
2018-11-30 15:06:22 +01:00
if ( ! empty ( $_GET [ 'dfrn_url' ])) {
2017-12-17 02:13:10 +01:00
if ( ! local_user ()) {
2020-01-18 20:52:34 +01:00
info ( DI :: l10n () -> t ( " Please login to confirm introduction. " ) . EOL );
2011-01-07 08:41:14 +01:00
/* setup the return URL to come back to this page if they use openid */
2017-12-17 17:40:59 +01:00
return Login :: form ();
2010-07-02 01:48:07 +02:00
}
2014-09-06 17:28:46 +02:00
// Edge case, but can easily happen in the wild. This person is authenticated,
2010-07-02 01:48:07 +02:00
// but not as the person who needs to deal with this request.
2010-07-22 11:13:39 +02:00
if ( $a -> user [ 'nickname' ] != $a -> argv [ 1 ]) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( " Incorrect identity currently logged in. Please login to <strong>this</strong> profile. " ) . EOL );
2017-12-17 17:40:59 +01:00
return Login :: form ();
2010-07-02 01:48:07 +02:00
}
2018-11-09 19:29:42 +01:00
$dfrn_url = Strings :: escapeTags ( trim ( hex2bin ( $_GET [ 'dfrn_url' ])));
2018-11-30 15:06:22 +01:00
$aes_allow = ! empty ( $_GET [ 'aes_allow' ]);
2019-10-15 15:01:17 +02:00
$confirm_key = $_GET [ 'confirm_key' ] ? ? '' ;
2015-04-09 00:10:21 +02:00
// Checking fastlane for validity
2018-11-30 15:06:22 +01:00
if ( ! empty ( $_SESSION [ 'fastlane' ]) && ( Strings :: normaliseLink ( $_SESSION [ " fastlane " ]) == Strings :: normaliseLink ( $dfrn_url ))) {
2015-04-09 00:10:21 +02:00
$_POST [ " dfrn_url " ] = $dfrn_url ;
$_POST [ " confirm_key " ] = $confirm_key ;
$_POST [ " localconfirm " ] = 1 ;
$_POST [ " hidden-contact " ] = 0 ;
2020-01-18 20:52:34 +01:00
$_POST [ " submit " ] = DI :: l10n () -> t ( 'Confirm' );
2015-04-09 00:10:21 +02:00
dfrn_request_post ( $a );
2018-12-25 05:14:09 +01:00
exit ();
2015-04-09 00:10:21 +02:00
}
2018-10-31 15:44:06 +01:00
$tpl = Renderer :: getMarkupTemplate ( " dfrn_req_confirm.tpl " );
2018-10-31 15:35:50 +01:00
$o = Renderer :: replaceMacros ( $tpl , [
2010-07-02 01:48:07 +02:00
'$dfrn_url' => $dfrn_url ,
'$aes_allow' => (( $aes_allow ) ? '<input type="hidden" name="aes_allow" value="1" />' : " " ),
2020-01-18 20:52:34 +01:00
'$hidethem' => DI :: l10n () -> t ( 'Hide this contact' ),
2010-07-02 01:48:07 +02:00
'$confirm_key' => $confirm_key ,
2020-01-18 20:52:34 +01:00
'$welcome' => DI :: l10n () -> t ( 'Welcome home %s.' , $a -> user [ 'username' ]),
'$please' => DI :: l10n () -> t ( 'Please confirm your introduction/connection request to %s.' , $dfrn_url ),
'$submit' => DI :: l10n () -> t ( 'Confirm' ),
2010-07-02 01:48:07 +02:00
'$uid' => $_SESSION [ 'uid' ],
2010-07-21 05:48:08 +02:00
'$nickname' => $a -> user [ 'nickname' ],
2010-07-02 01:48:07 +02:00
'dfrn_rawurl' => $_GET [ 'dfrn_url' ]
2018-01-15 14:05:12 +01:00
]);
2010-07-02 01:48:07 +02:00
return $o ;
2018-11-30 15:06:22 +01:00
} elseif ( ! empty ( $_GET [ 'confirm_key' ])) {
2010-07-22 11:13:39 +02:00
// we are the requestee and it is now safe to send our user their introduction,
2014-09-06 17:28:46 +02:00
// We could just unblock it, but first we have to jump through a few hoops to
// send an email, or even to find out if we need to send an email.
2010-07-22 11:13:39 +02:00
$intro = q ( " SELECT * FROM `intro` WHERE `hash` = '%s' LIMIT 1 " ,
2018-07-21 15:10:13 +02:00
DBA :: escape ( $_GET [ 'confirm_key' ])
2010-07-22 11:13:39 +02:00
);
2018-07-21 14:46:04 +02:00
if ( DBA :: isResult ( $intro )) {
2010-07-22 11:13:39 +02:00
$r = q ( " SELECT `contact`.*, `user`.* FROM `contact` LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
WHERE `contact` . `id` = % d LIMIT 1 " ,
intval ( $intro [ 0 ][ 'contact-id' ])
);
2010-10-18 05:04:17 +02:00
$auto_confirm = false ;
2018-07-21 14:46:04 +02:00
if ( DBA :: isResult ( $r )) {
2019-01-06 18:37:48 +01:00
if ( $r [ 0 ][ 'page-flags' ] != User :: PAGE_FLAGS_NORMAL && $r [ 0 ][ 'page-flags' ] != User :: PAGE_FLAGS_PRVGROUP ) {
2014-03-11 23:52:32 +01:00
$auto_confirm = true ;
2017-12-17 02:13:10 +01:00
}
2011-12-27 00:47:40 +01:00
2017-12-17 02:13:10 +01:00
if ( ! $auto_confirm ) {
2018-01-15 14:05:12 +01:00
notification ([
2020-02-04 22:03:45 +01:00
'type' => Type :: INTRO ,
2011-12-27 00:47:40 +01:00
'notify_flags' => $r [ 0 ][ 'notify-flags' ],
'language' => $r [ 0 ][ 'language' ],
'to_name' => $r [ 0 ][ 'username' ],
'to_email' => $r [ 0 ][ 'email' ],
2012-02-18 11:57:42 +01:00
'uid' => $r [ 0 ][ 'uid' ],
2019-12-30 23:00:08 +01:00
'link' => DI :: baseUrl () . '/notifications/intros' ,
2020-01-18 20:52:34 +01:00
'source_name' => (( strlen ( stripslashes ( $r [ 0 ][ 'name' ]))) ? stripslashes ( $r [ 0 ][ 'name' ]) : DI :: l10n () -> t ( '[Name Withheld]' )),
2011-12-27 00:47:40 +01:00
'source_link' => $r [ 0 ][ 'url' ],
2012-01-04 05:26:20 +01:00
'source_photo' => $r [ 0 ][ 'photo' ],
2019-10-24 00:25:43 +02:00
'verb' => Activity :: REQ_FRIEND ,
2012-01-04 05:26:20 +01:00
'otype' => 'intro'
2018-01-15 14:05:12 +01:00
]);
2010-07-22 11:13:39 +02:00
}
2011-12-27 00:47:40 +01:00
2017-12-17 02:13:10 +01:00
if ( $auto_confirm ) {
2017-05-07 20:40:23 +02:00
require_once 'mod/dfrn_confirm.php' ;
2018-01-15 14:05:12 +01:00
$handsfree = [
2016-12-14 09:56:27 +01:00
'uid' => $r [ 0 ][ 'uid' ],
'node' => $r [ 0 ][ 'nickname' ],
'dfrn_id' => $r [ 0 ][ 'issued-id' ],
2010-10-18 05:04:17 +02:00
'intro_id' => $intro [ 0 ][ 'id' ],
2019-01-06 18:37:48 +01:00
'duplex' => (( $r [ 0 ][ 'page-flags' ] == User :: PAGE_FLAGS_FREELOVE ) ? 1 : 0 ),
2018-01-15 14:05:12 +01:00
];
2017-12-17 02:13:10 +01:00
dfrn_confirm_post ( $a , $handsfree );
2010-10-18 05:04:17 +02:00
}
2010-07-22 11:13:39 +02:00
}
2017-12-17 02:13:10 +01:00
if ( ! $auto_confirm ) {
2010-07-02 01:48:07 +02:00
2010-10-18 05:04:17 +02:00
// If we are auto_confirming, this record will have already been nuked
// in dfrn_confirm_post()
2019-01-07 07:23:49 +01:00
q ( " UPDATE `intro` SET `blocked` = 0 WHERE `hash` = '%s' " ,
2018-07-21 15:10:13 +02:00
DBA :: escape ( $_GET [ 'confirm_key' ])
2010-10-18 05:04:17 +02:00
);
}
2010-07-22 11:13:39 +02:00
}
2011-08-19 01:47:45 +02:00
2018-12-26 06:40:12 +01:00
exit ();
2017-12-17 02:13:10 +01:00
} else {
// Normal web request. Display our user's introduction form.
2020-01-19 21:21:13 +01:00
if ( DI :: config () -> get ( 'system' , 'block_public' ) && ! Session :: isAuthenticated ()) {
if ( ! DI :: config () -> get ( 'system' , 'local_block' )) {
2020-01-18 20:52:34 +01:00
notice ( DI :: l10n () -> t ( 'Public access denied.' ) . EOL );
2012-09-07 05:17:50 +02:00
return ;
}
2011-04-22 04:12:22 +02:00
}
2017-12-17 02:13:10 +01:00
// Try to auto-fill the profile address
2014-08-21 00:56:21 +02:00
// At first look if an address was provided
// Otherwise take the local address
2018-11-30 15:06:22 +01:00
if ( ! empty ( $_GET [ 'addr' ])) {
2014-08-21 00:56:21 +02:00
$myaddr = hex2bin ( $_GET [ 'addr' ]);
2018-11-30 15:06:22 +01:00
} elseif ( ! empty ( $_GET [ 'address' ])) {
2014-08-21 00:56:21 +02:00
$myaddr = $_GET [ 'address' ];
2016-12-19 14:26:13 +01:00
} elseif ( local_user ()) {
2019-12-16 00:39:54 +01:00
if ( strlen ( DI :: baseUrl () -> getUrlPath ())) {
2019-12-30 23:00:08 +01:00
$myaddr = DI :: baseUrl () . '/profile/' . $a -> user [ 'nickname' ];
2016-12-19 14:26:13 +01:00
} else {
2019-12-30 23:00:08 +01:00
$myaddr = $a -> user [ 'nickname' ] . '@' . substr ( DI :: baseUrl (), strpos ( DI :: baseUrl (), '://' ) + 3 );
2011-01-01 22:12:31 +01:00
}
2016-12-19 14:26:13 +01:00
} else {
2016-12-20 17:43:46 +01:00
// last, try a zrl
2018-01-15 03:22:39 +01:00
$myaddr = Profile :: getMyURL ();
2016-12-20 17:43:46 +01:00
}
2012-04-28 01:23:25 +02:00
2019-12-30 23:00:08 +01:00
$target_addr = $a -> profile [ 'nickname' ] . '@' . substr ( DI :: baseUrl (), strpos ( DI :: baseUrl (), '://' ) + 3 );
2011-09-06 03:34:30 +02:00
2017-12-17 02:13:10 +01:00
/* The auto_request form only has the profile address
2014-09-06 17:28:46 +02:00
* because nobody is going to read the comments and
2011-01-01 22:12:31 +01:00
* it doesn ' t matter if they know you or not .
*/
2019-01-06 18:37:48 +01:00
if ( $a -> profile [ 'page-flags' ] == User :: PAGE_FLAGS_NORMAL ) {
2018-10-31 15:44:06 +01:00
$tpl = Renderer :: getMarkupTemplate ( 'dfrn_request.tpl' );
2016-12-19 14:26:13 +01:00
} else {
2018-10-31 15:44:06 +01:00
$tpl = Renderer :: getMarkupTemplate ( 'auto_request.tpl' );
2016-12-20 17:43:46 +01:00
}
2011-01-01 22:12:31 +01:00
2018-10-31 15:35:50 +01:00
$o = Renderer :: replaceMacros ( $tpl , [
2020-02-06 04:41:04 +01:00
'$header' => DI :: l10n () -> t ( 'Friend/Connection Request' ),
'$page_desc' => DI :: l10n () -> t ( 'Enter your Webfinger address (user@domain.tld) or profile URL here. If this isn\'t supported by your system (for example it doesn\'t work with Diaspora), you have to subscribe to <strong>%s</strong> directly on your system' , $target_addr ),
'$invite_desc' => DI :: l10n () -> t ( 'If you are not yet a member of the free social web, <a href="%s">follow this link to find a public Friendica node and join us today</a>.' , Search :: getGlobalDirectory () . '/servers' ),
'$your_address' => DI :: l10n () -> t ( 'Your Webfinger address or profile URL:' ),
'$pls_answer' => DI :: l10n () -> t ( 'Please answer the following:' ),
'$submit' => DI :: l10n () -> t ( 'Submit Request' ),
'$cancel' => DI :: l10n () -> t ( 'Cancel' ),
'$request' => 'dfrn_request/' . $a -> argv [ 1 ],
'$name' => $a -> profile [ 'name' ],
'$myaddr' => $myaddr ,
2020-02-09 07:01:11 +01:00
'$does_know_you' => [ 'knowyou' , DI :: l10n () -> t ( '%s knows you' , $a -> profile [ 'name' ])],
2020-02-06 04:41:04 +01:00
'$addnote_field' => [ 'dfrn-request-message' , DI :: l10n () -> t ( 'Add a personal note:' )],
2018-01-15 14:05:12 +01:00
]);
2010-07-22 11:13:39 +02:00
return $o ;
2010-07-02 01:48:07 +02:00
}
2016-11-28 01:13:47 +01:00
}