2010-07-01 23:48:07 +00:00
< ? php
2017-12-17 01:13:10 +00:00
2010-12-15 00:34:49 +00:00
/**
2016-11-28 00:13:47 +00:00
* @ file mod / dfrn_request . php
* @ brief Module : dfrn_request
2010-12-15 00:34:49 +00:00
*
* Purpose : Handles communication associated with the issuance of
* friend requests .
*
2016-11-28 00:13:47 +00:00
* @ see PDF with dfrn specs : https :// github . com / friendica / friendica / blob / master / spec / dfrn2 . pdf
2016-11-29 18:57:30 +00:00
* You also find a graphic which describes the confirmation process at
* https :// github . com / friendica / friendica / blob / master / spec / dfrn2_contact_request . png
2010-12-15 00:34:49 +00:00
*/
2018-01-25 02:08:45 +00:00
2017-04-30 04:07:00 +00:00
use Friendica\App ;
2017-11-07 02:22:52 +00:00
use Friendica\Core\Config ;
2018-01-21 18:33:59 +00:00
use Friendica\Core\L10n ;
2018-10-29 21:20:46 +00:00
use Friendica\Core\Logger ;
2018-08-11 20:40:44 +00:00
use Friendica\Core\Protocol ;
2018-10-31 14:35:50 +00:00
use Friendica\Core\Renderer ;
2017-08-26 06:04:21 +00:00
use Friendica\Core\System ;
2019-09-28 18:09:11 +00:00
use Friendica\Core\Session ;
2018-07-20 12:19:26 +00:00
use Friendica\Database\DBA ;
2019-12-15 23:28:31 +00:00
use Friendica\DI ;
2017-12-07 14:04:24 +00:00
use Friendica\Model\Contact ;
2017-12-09 18:45:17 +00:00
use Friendica\Model\Group ;
2018-01-15 02:22:39 +00:00
use Friendica\Model\Profile ;
2018-01-25 02:08:45 +00:00
use Friendica\Model\User ;
2019-12-27 21:19:28 +00:00
use Friendica\Module\Security\Login ;
2017-05-07 18:44:30 +00:00
use Friendica\Network\Probe ;
2019-10-23 22:25:43 +00:00
use Friendica\Protocol\Activity ;
2018-01-27 02:38:34 +00:00
use Friendica\Util\DateTimeFormat ;
2018-01-27 04:09:48 +00:00
use Friendica\Util\Network ;
2018-11-08 13:45:46 +00:00
use Friendica\Util\Strings ;
2017-05-07 18:45:19 +00:00
2017-11-29 17:17:12 +00:00
function dfrn_request_init ( App $a )
{
2018-01-15 02:22:39 +00:00
if ( $a -> argc > 1 ) {
2010-07-01 23:48:07 +00:00
$which = $a -> argv [ 1 ];
2019-01-07 17:51:48 +00:00
Profile :: load ( $a , $which );
2018-01-15 02:22:39 +00:00
}
2010-07-01 23:48:07 +00:00
return ;
2016-11-28 00:13:47 +00:00
}
2010-07-01 23:48:07 +00:00
2010-12-15 00:34:49 +00: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 06:07:42 +00:00
* @ param App $a
* @ throws ImagickException
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2010-12-15 00:34:49 +00:00
*/
2017-12-17 01:13:10 +00:00
function dfrn_request_post ( App $a )
{
if (( $a -> argc != 2 ) || ( ! count ( $a -> profile ))) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Wrong count of argc or profiles: argc=' . $a -> argc . ',profile()=' . count ( $a -> profile ));
2010-07-01 23:48:07 +00:00
return ;
2016-03-01 13:42:55 +00:00
}
2010-07-01 23:48:07 +00:00
2018-11-30 14:06:22 +00:00
if ( ! empty ( $_POST [ 'cancel' ])) {
2019-12-15 23:28:31 +00:00
DI :: baseUrl () -> redirect ();
2014-09-06 15:28:46 +00:00
}
2010-07-01 23:48:07 +00:00
2016-11-28 00:13:47 +00:00
/*
2010-12-15 00:34:49 +00:00
* Scenario 2 : We ' ve introduced ourself to another cell , then have been returned to our own cell
2014-09-06 15:28:46 +00:00
* to confirm the request , and then we ' ve clicked submit ( perhaps after logging in ) .
2010-12-15 00:34:49 +00:00
* That brings us here :
*/
2018-11-30 14:06:22 +00:00
if ( ! empty ( $_POST [ 'localconfirm' ]) && ( $_POST [ 'localconfirm' ] == 1 )) {
2017-12-17 01:13:10 +00:00
// Ensure this is a valid request
2018-11-30 14:06:22 +00: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 13:01:17 +00:00
$confirm_key = $_POST [ 'confirm_key' ] ? ? '' ;
2018-11-30 14:06:22 +00:00
$hidden = ( ! empty ( $_POST [ 'hidden-contact' ]) ? intval ( $_POST [ 'hidden-contact' ]) : 0 );
2010-07-22 09:13:39 +00:00
$contact_record = null ;
2018-11-30 14:06:22 +00:00
$blocked = 1 ;
$pending = 1 ;
2014-03-11 22:52:32 +00:00
2018-11-30 14:06:22 +00:00
if ( ! empty ( $dfrn_url )) {
2017-12-17 01:13:10 +00:00
// Lookup the contact based on their URL (which is the only unique thing we have at the moment)
2016-11-28 00:13:47 +00:00
$r = q ( " SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND NOT `self` LIMIT 1 " ,
2016-11-28 14:30:36 +00:00
intval ( local_user ()),
2018-11-08 16:28:29 +00:00
DBA :: escape ( Strings :: normaliseLink ( $dfrn_url ))
2010-07-06 04:39:55 +00:00
);
2014-03-11 22:52:32 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $r )) {
2017-12-17 01:13:10 +00:00
if ( strlen ( $r [ 0 ][ 'dfrn-id' ])) {
// We don't need to be here. It has already happened.
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( " This introduction has already been accepted. " ) . EOL );
2010-07-22 09:13:39 +00:00
return ;
2018-01-04 16:53:57 +00:00
} else {
2010-07-22 09:13:39 +00:00
$contact_record = $r [ 0 ];
2018-01-04 16:53:57 +00:00
}
2010-07-22 09:13:39 +00:00
}
2013-12-02 19:30:24 +00:00
2017-12-17 01:13:10 +00:00
if ( is_array ( $contact_record )) {
2018-01-04 16:53:57 +00: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 09:13:39 +00:00
);
2017-12-17 01:13:10 +00:00
} else {
// Scrape the other site's profile page to pick up the dfrn links, key, fn, and photo
2016-07-04 06:05:30 +00:00
$parms = Probe :: profile ( $dfrn_url );
2014-08-20 22:56:21 +00:00
2017-12-17 01:13:10 +00:00
if ( ! count ( $parms )) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Profile location is not valid or does not contain profile information.' ) . EOL );
2010-07-06 04:39:55 +00:00
return ;
2017-12-17 01:13:10 +00:00
} else {
2018-11-30 14:06:22 +00:00
if ( empty ( $parms [ 'fn' ])) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Warning: profile location has no identifiable owner name.' ) . EOL );
2016-12-20 16:43:46 +00:00
}
2018-11-30 14:06:22 +00:00
if ( empty ( $parms [ 'photo' ])) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Warning: profile location has no profile photo.' ) . EOL );
2016-12-20 16:43:46 +00:00
}
2017-05-08 14:19:10 +00:00
$invalid = Probe :: validDfrn ( $parms );
2016-12-20 16:43:46 +00:00
if ( $invalid ) {
2018-01-24 02:59:16 +00:00
notice ( 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 09:13:39 +00:00
return ;
}
}
2010-07-01 23:48:07 +00:00
2010-07-22 09:13:39 +00:00
$dfrn_request = $parms [ 'dfrn-request' ];
2010-07-06 04:39:55 +00:00
2016-06-25 11:56:55 +00:00
$photo = $parms [ " photo " ];
2016-11-28 00:13:47 +00:00
// Escape the entire array
2018-07-21 12:48:29 +00:00
DBA :: escapeArray ( $parms );
2010-12-15 00:34:49 +00:00
2017-12-17 01:13:10 +00:00
// Create a contact record on our site for the other person
2015-11-25 17:46:02 +00:00
$r = q ( " INSERT INTO `contact` ( `uid`, `created`,`url`, `nurl`, `addr`, `name`, `nick`, `photo`, `site-pubkey`,
2018-08-02 14:11:21 +00: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 21:34:59 +00:00
intval ( local_user ()),
2018-01-27 02:38:34 +00:00
DateTimeFormat :: utcNow (),
2018-07-21 13:10:13 +00:00
DBA :: escape ( $dfrn_url ),
2018-11-08 16:28:29 +00:00
DBA :: escape ( Strings :: normaliseLink ( $dfrn_url )),
2015-11-25 17:46:02 +00:00
$parms [ 'addr' ],
2010-07-22 09:13:39 +00:00
$parms [ 'fn' ],
2010-10-23 08:20:26 +00:00
$parms [ 'nick' ],
2010-07-22 09:13:39 +00:00
$parms [ 'photo' ],
$parms [ 'key' ],
$parms [ 'dfrn-request' ],
$parms [ 'dfrn-confirm' ],
$parms [ 'dfrn-notify' ],
$parms [ 'dfrn-poll' ],
2018-08-11 20:40:44 +00:00
DBA :: escape ( Protocol :: DFRN ),
2012-05-30 01:43:56 +00:00
intval ( $aes_allow ),
2016-11-29 02:08:46 +00:00
intval ( $hidden ),
intval ( $blocked ),
intval ( $pending )
2010-07-22 09:13:39 +00:00
);
}
2010-07-06 04:39:55 +00:00
2016-12-20 20:15:53 +00:00
if ( $r ) {
2018-01-22 21:59:31 +00:00
info ( L10n :: t ( " Introduction complete. " ) . EOL );
2010-07-22 09:13:39 +00:00
}
2016-03-06 12:15:27 +00:00
$r = q ( " SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `site-pubkey` = '%s' LIMIT 1 " ,
2012-06-12 08:13:09 +00:00
intval ( local_user ()),
2018-07-21 13:10:13 +00:00
DBA :: escape ( $dfrn_url ),
2019-10-15 13:01:17 +00:00
$parms [ 'key' ] ? ? '' // Potentially missing
2012-06-12 08:13:09 +00:00
);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $r )) {
2018-02-12 02:25:09 +00:00
Group :: addMember ( User :: getDefaultGroup ( local_user (), $r [ 0 ][ " network " ]), $r [ 0 ][ 'id' ]);
2016-04-13 20:21:23 +00:00
2017-12-09 18:42:02 +00:00
if ( isset ( $photo )) {
2017-11-29 22:29:11 +00:00
Contact :: updateAvatar ( $photo , local_user (), $r [ 0 ][ " id " ], true );
2017-12-09 18:42:02 +00:00
}
2016-06-25 11:56:55 +00:00
2018-10-19 21:56:54 +00:00
$forward_path = " contact/ " . $r [ 0 ][ 'id' ];
2016-12-20 09:44:27 +00:00
} else {
2018-10-19 21:56:54 +00:00
$forward_path = " contact " ;
2016-12-20 09:44:27 +00:00
}
2012-06-12 08:13:09 +00:00
2017-12-17 01:13:10 +00:00
// Allow the blocked remote notification to complete
2016-12-20 09:44:27 +00:00
if ( is_array ( $contact_record )) {
2010-07-22 09:13:39 +00:00
$dfrn_request = $contact_record [ 'request' ];
2016-12-20 09:44:27 +00:00
}
2010-07-01 23:48:07 +00:00
2019-01-07 17:51:48 +00:00
if ( ! empty ( $dfrn_request ) && strlen ( $confirm_key )) {
2019-01-07 06:23:49 +00:00
Network :: fetchUrl ( $dfrn_request . '?confirm_key=' . $confirm_key );
2016-12-20 09:44:27 +00:00
}
2014-08-20 22:56:21 +00:00
2010-12-15 00:34:49 +00:00
// (ignore reply, nothing we can do it failed)
2019-12-15 23:28:31 +00:00
DI :: baseUrl () -> redirect ( $forward_path );
2010-07-22 09:13:39 +00:00
return ; // NOTREACHED
}
2010-07-01 23:48:07 +00:00
}
2010-07-22 09:13:39 +00:00
2017-12-17 01:13:10 +00:00
// invalid/bogus request
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Unrecoverable protocol error.' ) . EOL );
2019-12-15 23:28:31 +00:00
DI :: baseUrl () -> redirect ();
2010-07-22 09:13:39 +00:00
return ; // NOTREACHED
2010-07-01 23:48:07 +00:00
}
2016-11-28 00:13:47 +00:00
/*
2010-12-15 00:34:49 +00:00
* Otherwise :
2014-09-06 15:28:46 +00:00
*
2010-12-15 00:34:49 +00:00
* Scenario 1 :
2014-09-06 15:28:46 +00: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 00:34:49 +00: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 15:28:46 +00: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 00:34:49 +00: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 .
*
*/
2017-12-17 01:13:10 +00:00
if ( ! ( is_array ( $a -> profile ) && count ( $a -> profile ))) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Profile unavailable.' ) . EOL );
2010-07-22 09:13:39 +00:00
return ;
}
2010-12-20 08:27:00 +00:00
$nickname = $a -> profile [ 'nickname' ];
$uid = $a -> profile [ 'uid' ];
$maxreq = intval ( $a -> profile [ 'maxreq' ]);
2010-07-06 04:39:55 +00:00
$contact_record = null ;
2010-12-20 08:27:00 +00:00
$failed = false ;
$parms = null ;
2016-11-29 14:52:12 +00:00
$blocked = 1 ;
$pending = 1 ;
2010-07-01 23:48:07 +00:00
2018-11-30 14:06:22 +00:00
if ( ! empty ( $_POST [ 'dfrn_url' ])) {
2017-12-17 01:13:10 +00:00
// Block friend request spam
if ( $maxreq ) {
2010-12-20 08:27:00 +00:00
$r = q ( " SELECT * FROM `intro` WHERE `datetime` > '%s' AND `uid` = %d " ,
2018-07-21 13:10:13 +00:00
DBA :: escape ( DateTimeFormat :: utc ( 'now - 24 hours' )),
2010-12-20 08:27:00 +00:00
intval ( $uid )
);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $r ) && count ( $r ) > $maxreq ) {
2018-01-24 02:59:16 +00:00
notice ( L10n :: t ( '%s has received too many connection requests today.' , $a -> profile [ 'name' ]) . EOL );
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Spam protection measures have been invoked.' ) . EOL );
notice ( L10n :: t ( 'Friends are advised to please try again in 24 hours.' ) . EOL );
2010-12-20 08:27:00 +00:00
return ;
2014-09-06 15:28:46 +00:00
}
2010-12-20 08:27:00 +00:00
}
2017-12-17 01:13:10 +00:00
/* Cleanup old introductions that remain blocked .
2011-01-07 08:24:08 +00:00
* Also remove the contact record , but only if there is no existing relationship
2011-12-12 07:33:56 +00:00
*/
2014-09-06 15:28:46 +00:00
$r = q ( " SELECT `intro`.*, `intro`.`id` AS `iid`, `contact`.`id` AS `cid`, `contact`.`rel`
2011-12-12 07:33:56 +00:00
FROM `intro` LEFT JOIN `contact` on `intro` . `contact-id` = `contact` . `id`
2014-09-06 15:28:46 +00:00
WHERE `intro` . `blocked` = 1 AND `contact` . `self` = 0
2017-12-03 09:19:58 +00:00
AND `intro` . `datetime` < UTC_TIMESTAMP () - INTERVAL 30 MINUTE "
2011-12-12 07:33:56 +00:00
);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $r )) {
2016-12-20 20:15:53 +00:00
foreach ( $r as $rr ) {
2017-12-17 01:13:10 +00:00
if ( ! $rr [ 'rel' ]) {
2018-07-20 12:19:26 +00:00
DBA :: delete ( 'contact' , [ 'id' => $rr [ 'cid' ], 'self' => false ]);
2011-12-12 07:33:56 +00:00
}
2018-07-20 12:19:26 +00:00
DBA :: delete ( 'intro' , [ 'id' => $rr [ 'iid' ]]);
2011-12-12 07:33:56 +00:00
}
}
2010-07-01 23:48:07 +00:00
$url = trim ( $_POST [ 'dfrn_url' ]);
2017-12-17 01:13:10 +00:00
if ( ! strlen ( $url )) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( " Invalid locator " ) . EOL );
2010-07-06 04:39:55 +00:00
return ;
}
2011-09-07 01:06:19 +00:00
$hcard = '' ;
2010-07-20 02:09:58 +00:00
2017-12-03 09:19:58 +00:00
// Detect the network
$data = Probe :: uri ( $url );
$network = $data [ " network " ];
2012-04-11 02:15:52 +00:00
2017-12-03 09:19:58 +00:00
// Canonicalise email-style profile locator
2017-12-17 01:13:10 +00:00
$url = Probe :: webfingerDfrn ( $url , $hcard );
2012-12-23 10:47:14 +00:00
2017-12-17 01:13:10 +00:00
if ( substr ( $url , 0 , 5 ) === 'stat:' ) {
2017-12-03 09:19:58 +00: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 20:40:44 +00:00
if ( $network != Protocol :: DIASPORA ) {
$network = Protocol :: OSTATUS ;
2012-04-11 02:15:52 +00:00
}
2017-12-17 01:13:10 +00:00
$url = substr ( $url , 5 );
2016-01-01 16:49:07 +00:00
} else {
2018-08-11 20:40:44 +00:00
$network = Protocol :: DFRN ;
2010-07-08 14:03:25 +00:00
}
2010-07-06 04:39:55 +00:00
2018-10-30 13:58:45 +00:00
Logger :: log ( 'dfrn_request: url: ' . $url . ',network=' . $network , Logger :: DEBUG );
2016-12-14 15:36:32 +00:00
2018-08-11 20:40:44 +00:00
if ( $network === Protocol :: DFRN ) {
2014-09-06 15:28:46 +00:00
$ret = q ( " SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `self` = 0 LIMIT 1 " ,
2010-10-13 02:32:15 +00:00
intval ( $uid ),
2018-07-21 13:10:13 +00:00
DBA :: escape ( $url )
2010-07-06 04:39:55 +00:00
);
2010-09-14 00:12:54 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $ret )) {
2017-12-17 01:13:10 +00:00
if ( strlen ( $ret [ 0 ][ 'issued-id' ])) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'You have already introduced yourself here.' ) . EOL );
2010-10-13 02:32:15 +00:00
return ;
2018-07-25 02:53:46 +00:00
} elseif ( $ret [ 0 ][ 'rel' ] == Contact :: FRIEND ) {
2018-01-24 02:59:16 +00:00
notice ( L10n :: t ( 'Apparently you are already friends with %s.' , $a -> profile [ 'name' ]) . EOL );
2010-10-18 03:04:17 +00:00
return ;
2017-12-17 01:13:10 +00:00
} else {
2010-10-13 02:32:15 +00:00
$contact_record = $ret [ 0 ];
2018-01-15 13:05:12 +00:00
$parms = [ 'dfrn-request' => $ret [ 0 ][ 'request' ]];
2010-10-13 02:32:15 +00:00
}
2010-09-14 00:12:54 +00:00
}
2010-10-18 03:04:17 +00:00
2018-11-08 13:45:46 +00:00
$issued_id = Strings :: getRandomHex ();
2010-10-13 02:32:15 +00:00
2017-12-17 01:13:10 +00:00
if ( is_array ( $contact_record )) {
2010-10-13 02:32:15 +00:00
// There is a contact record but no issued-id, so this
// is a reciprocal introduction from a known contact
2013-12-02 19:30:24 +00:00
$r = q ( " UPDATE `contact` SET `issued-id` = '%s' WHERE `id` = %d " ,
2018-07-21 13:10:13 +00:00
DBA :: escape ( $issued_id ),
2010-10-13 02:32:15 +00:00
intval ( $contact_record [ 'id' ])
);
2017-12-17 01:13:10 +00:00
} else {
2018-01-27 16:13:41 +00:00
$url = Network :: isUrlValid ( $url );
2017-12-17 01:13:10 +00:00
if ( ! $url ) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Invalid profile URL.' ) . EOL );
2019-12-15 23:28:31 +00:00
DI :: baseUrl () -> redirect ( $a -> cmd );
2010-10-13 02:32:15 +00:00
return ; // NOTREACHED
}
2010-07-01 23:48:07 +00:00
2018-01-27 16:13:41 +00:00
if ( ! Network :: isUrlAllowed ( $url )) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Disallowed profile URL.' ) . EOL );
2019-12-15 23:28:31 +00:00
DI :: baseUrl () -> redirect ( $a -> cmd );
2010-10-13 02:32:15 +00:00
return ; // NOTREACHED
2010-07-01 23:48:07 +00:00
}
2014-09-06 15:28:46 +00:00
2018-01-27 16:13:41 +00:00
if ( Network :: isUrlBlocked ( $url )) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Blocked domain' ) . EOL );
2019-12-15 23:28:31 +00:00
DI :: baseUrl () -> redirect ( $a -> cmd );
2017-04-26 02:45:56 +00:00
return ; // NOTREACHED
}
2010-07-01 23:48:07 +00:00
2016-07-04 06:05:30 +00:00
$parms = Probe :: profile (( $hcard ) ? $hcard : $url );
2010-10-13 02:32:15 +00:00
2017-12-17 01:13:10 +00:00
if ( ! count ( $parms )) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Profile location is not valid or does not contain profile information.' ) . EOL );
2019-12-15 23:28:31 +00:00
DI :: baseUrl () -> redirect ( $a -> cmd );
2017-12-17 01:13:10 +00:00
} else {
2018-11-30 14:06:22 +00:00
if ( empty ( $parms [ 'fn' ])) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Warning: profile location has no identifiable owner name.' ) . EOL );
2016-12-20 16:43:46 +00:00
}
2018-11-30 14:06:22 +00:00
if ( empty ( $parms [ 'photo' ])) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Warning: profile location has no profile photo.' ) . EOL );
2016-12-20 16:43:46 +00:00
}
2017-05-08 14:19:10 +00:00
$invalid = Probe :: validDfrn ( $parms );
2016-12-20 16:43:46 +00:00
if ( $invalid ) {
2018-01-24 02:59:16 +00:00
notice ( 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 15:28:46 +00:00
2010-10-13 02:32:15 +00:00
return ;
}
}
2010-07-01 23:48:07 +00:00
2010-10-13 02:32:15 +00:00
$parms [ 'url' ] = $url ;
$parms [ 'issued-id' ] = $issued_id ;
2016-06-25 11:56:55 +00:00
$photo = $parms [ " photo " ];
2010-07-06 04:39:55 +00:00
2018-07-21 12:48:29 +00:00
DBA :: escapeArray ( $parms );
2015-11-25 17:46:02 +00:00
$r = q ( " INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `name`, `nick`, `issued-id`, `photo`, `site-pubkey`,
2018-08-02 14:11:21 +00: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 04:39:55 +00:00
intval ( $uid ),
2018-07-21 13:10:13 +00:00
DBA :: escape ( DateTimeFormat :: utcNow ()),
2010-07-06 04:39:55 +00:00
$parms [ 'url' ],
2018-11-08 16:28:29 +00:00
DBA :: escape ( Strings :: normaliseLink ( $url )),
2015-11-25 17:46:02 +00:00
$parms [ 'addr' ],
2010-10-13 02:32:15 +00:00
$parms [ 'fn' ],
2010-10-23 08:20:26 +00:00
$parms [ 'nick' ],
2010-10-13 02:32:15 +00:00
$parms [ 'issued-id' ],
$parms [ 'photo' ],
$parms [ 'key' ],
$parms [ 'dfrn-request' ],
$parms [ 'dfrn-confirm' ],
$parms [ 'dfrn-notify' ],
2011-08-18 23:47:45 +00:00
$parms [ 'dfrn-poll' ],
2018-08-11 20:40:44 +00:00
DBA :: escape ( Protocol :: DFRN ),
2016-11-29 14:52:12 +00:00
intval ( $blocked ),
intval ( $pending )
2010-07-06 04:39:55 +00:00
);
2010-07-01 23:48:07 +00:00
2010-10-13 02:32:15 +00:00
// find the contact record we just created
2016-12-20 09:44:27 +00:00
if ( $r ) {
2014-03-11 22:52:32 +00:00
$r = q ( " SELECT `id` FROM `contact`
2010-10-13 02:32:15 +00:00
WHERE `uid` = % d AND `url` = '%s' AND `issued-id` = '%s' LIMIT 1 " ,
intval ( $uid ),
$parms [ 'url' ],
$parms [ 'issued-id' ]
);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $r )) {
2010-10-13 02:32:15 +00:00
$contact_record = $r [ 0 ];
2017-11-29 22:29:11 +00:00
Contact :: updateAvatar ( $photo , $uid , $contact_record [ " id " ], true );
2016-06-25 11:56:55 +00:00
}
2010-10-13 02:32:15 +00:00
}
}
2016-12-20 09:44:27 +00:00
if ( $r === false ) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Failed to update contact record.' ) . EOL );
2010-10-13 02:32:15 +00:00
return ;
}
2010-07-01 23:48:07 +00:00
2018-11-08 13:45:46 +00:00
$hash = Strings :: getRandomHex () . ( string ) time (); // Generate a confirm_key
2013-12-02 19:30:24 +00:00
2016-12-20 09:44:27 +00:00
if ( is_array ( $contact_record )) {
2019-01-07 06:23:49 +00:00
q ( " INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime`)
2010-10-13 02:32:15 +00:00
VALUES ( % d , % d , 1 , % d , '%s' , '%s' , '%s' ) " ,
intval ( $uid ),
intval ( $contact_record [ 'id' ]),
2018-11-30 14:06:22 +00:00
intval ( ! empty ( $_POST [ 'knowyou' ])),
2019-10-15 13:01:17 +00:00
DBA :: escape ( Strings :: escapeTags ( trim ( $_POST [ 'dfrn-request-message' ] ? ? '' ))),
2018-07-21 13:10:13 +00:00
DBA :: escape ( $hash ),
DBA :: escape ( DateTimeFormat :: utcNow ())
2010-10-13 02:32:15 +00:00
);
}
2014-08-20 22:56:21 +00:00
2010-10-18 03:04:17 +00:00
// This notice will only be seen by the requestor if the requestor and requestee are on the same server.
2017-12-17 01:13:10 +00:00
if ( ! $failed ) {
2018-01-22 21:59:31 +00:00
info ( L10n :: t ( 'Your introduction has been sent.' ) . EOL );
2016-12-20 09:44:27 +00:00
}
2010-07-01 23:48:07 +00:00
2010-10-13 02:32:15 +00:00
// "Homecoming" - send the requestor back to their site to record the introduction.
2018-10-19 18:11:27 +00:00
$dfrn_url = bin2hex ( $a -> getBaseURL () . '/profile/' . $nickname );
2010-10-13 02:32:15 +00:00
$aes_allow = (( function_exists ( 'openssl_encrypt' )) ? 1 : 0 );
2010-07-01 23:48:07 +00:00
2018-10-19 18:11:27 +00:00
System :: externalRedirect ( $parms [ 'dfrn-request' ] . " ?dfrn_url= $dfrn_url "
2014-09-06 15:28:46 +00:00
. '&dfrn_version=' . DFRN_PROTOCOL_VERSION
2017-12-17 01:13:10 +00:00
. '&confirm_key=' . $hash
2010-10-13 02:32:15 +00:00
. (( $aes_allow ) ? " &aes_allow=1 " : " " )
);
// NOTREACHED
2018-08-11 20:40:44 +00:00
// END $network === Protocol::DFRN
} elseif (( $network != Protocol :: PHANTOM ) && ( $url != " " )) {
2014-09-06 15:28:46 +00:00
2017-12-17 01:13:10 +00:00
/* Substitute our user ' s feed URL into $url template
2010-12-15 00:34:49 +00:00
* Send the subscriber home to subscribe
*/
2015-12-28 02:14:38 +00:00
// Diaspora needs the uri in the format user@domain.tld
// Diaspora will support the remote subscription in a future version
2018-08-11 20:40:44 +00:00
if ( $network == Protocol :: DIASPORA ) {
2018-10-09 17:58:58 +00:00
$uri = $nickname . '@' . $a -> getHostName ();
2015-12-28 02:14:38 +00:00
2018-10-09 23:18:47 +00:00
if ( $a -> getURLPath ()) {
$uri .= '/' . $a -> getURLPath ();
2017-12-17 01:13:10 +00:00
}
2015-12-28 02:14:38 +00:00
$uri = urlencode ( $uri );
2016-12-20 09:44:27 +00:00
} else {
2018-10-13 18:02:04 +00:00
$uri = 'profile/' . $nickname ;
2016-12-20 09:44:27 +00:00
}
2015-12-28 02:14:38 +00:00
$url = str_replace ( '{uri}' , $uri , $url );
2018-10-19 18:11:27 +00:00
System :: externalRedirect ( $url );
2010-10-13 02:32:15 +00:00
// NOTREACHED
2018-08-11 20:40:44 +00:00
// END $network != Protocol::PHANTOM
2015-12-28 02:14:38 +00:00
} else {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( " Remote subscription can't be done for your network. Please subscribe directly on your system. " ) . EOL );
2015-12-28 02:14:38 +00:00
return ;
2010-10-13 02:32:15 +00:00
}
2017-12-17 01:13:10 +00:00
} return ;
2016-11-28 00:13:47 +00:00
}
2010-07-22 09:13:39 +00:00
2017-12-17 01:13:10 +00:00
function dfrn_request_content ( App $a )
{
2019-05-29 01:14:21 +00:00
if ( $a -> argc != 2 || empty ( $a -> profile )) {
2010-07-01 23:48:07 +00:00
return " " ;
2016-12-20 09:44:27 +00:00
}
2010-07-01 23:48:07 +00: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 14:06:22 +00:00
if ( ! empty ( $_GET [ 'dfrn_url' ])) {
2017-12-17 01:13:10 +00:00
if ( ! local_user ()) {
2018-01-22 21:59:31 +00:00
info ( L10n :: t ( " Please login to confirm introduction. " ) . EOL );
2011-01-07 07:41:14 +00:00
/* setup the return URL to come back to this page if they use openid */
2017-12-17 16:40:59 +00:00
return Login :: form ();
2010-07-01 23:48:07 +00:00
}
2014-09-06 15:28:46 +00:00
// Edge case, but can easily happen in the wild. This person is authenticated,
2010-07-01 23:48:07 +00:00
// but not as the person who needs to deal with this request.
2010-07-22 09:13:39 +00:00
if ( $a -> user [ 'nickname' ] != $a -> argv [ 1 ]) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( " Incorrect identity currently logged in. Please login to <strong>this</strong> profile. " ) . EOL );
2017-12-17 16:40:59 +00:00
return Login :: form ();
2010-07-01 23:48:07 +00:00
}
2018-11-09 18:29:42 +00:00
$dfrn_url = Strings :: escapeTags ( trim ( hex2bin ( $_GET [ 'dfrn_url' ])));
2018-11-30 14:06:22 +00:00
$aes_allow = ! empty ( $_GET [ 'aes_allow' ]);
2019-10-15 13:01:17 +00:00
$confirm_key = $_GET [ 'confirm_key' ] ? ? '' ;
2015-04-08 22:10:21 +00:00
// Checking fastlane for validity
2018-11-30 14:06:22 +00:00
if ( ! empty ( $_SESSION [ 'fastlane' ]) && ( Strings :: normaliseLink ( $_SESSION [ " fastlane " ]) == Strings :: normaliseLink ( $dfrn_url ))) {
2015-04-08 22:10:21 +00:00
$_POST [ " dfrn_url " ] = $dfrn_url ;
$_POST [ " confirm_key " ] = $confirm_key ;
$_POST [ " localconfirm " ] = 1 ;
$_POST [ " hidden-contact " ] = 0 ;
2018-01-21 18:33:59 +00:00
$_POST [ " submit " ] = L10n :: t ( 'Confirm' );
2015-04-08 22:10:21 +00:00
dfrn_request_post ( $a );
2018-12-25 04:14:09 +00:00
exit ();
2015-04-08 22:10:21 +00:00
}
2018-10-31 14:44:06 +00:00
$tpl = Renderer :: getMarkupTemplate ( " dfrn_req_confirm.tpl " );
2018-10-31 14:35:50 +00:00
$o = Renderer :: replaceMacros ( $tpl , [
2010-07-01 23:48:07 +00:00
'$dfrn_url' => $dfrn_url ,
'$aes_allow' => (( $aes_allow ) ? '<input type="hidden" name="aes_allow" value="1" />' : " " ),
2018-01-21 18:33:59 +00:00
'$hidethem' => L10n :: t ( 'Hide this contact' ),
2010-07-01 23:48:07 +00:00
'$confirm_key' => $confirm_key ,
2018-01-24 02:59:16 +00:00
'$welcome' => L10n :: t ( 'Welcome home %s.' , $a -> user [ 'username' ]),
'$please' => L10n :: t ( 'Please confirm your introduction/connection request to %s.' , $dfrn_url ),
2018-01-21 18:33:59 +00:00
'$submit' => L10n :: t ( 'Confirm' ),
2010-07-01 23:48:07 +00:00
'$uid' => $_SESSION [ 'uid' ],
2010-07-21 03:48:08 +00:00
'$nickname' => $a -> user [ 'nickname' ],
2010-07-01 23:48:07 +00:00
'dfrn_rawurl' => $_GET [ 'dfrn_url' ]
2018-01-15 13:05:12 +00:00
]);
2010-07-01 23:48:07 +00:00
return $o ;
2018-11-30 14:06:22 +00:00
} elseif ( ! empty ( $_GET [ 'confirm_key' ])) {
2010-07-22 09:13:39 +00:00
// we are the requestee and it is now safe to send our user their introduction,
2014-09-06 15:28:46 +00: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 09:13:39 +00:00
$intro = q ( " SELECT * FROM `intro` WHERE `hash` = '%s' LIMIT 1 " ,
2018-07-21 13:10:13 +00:00
DBA :: escape ( $_GET [ 'confirm_key' ])
2010-07-22 09:13:39 +00:00
);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $intro )) {
2010-07-22 09:13:39 +00: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 03:04:17 +00:00
$auto_confirm = false ;
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $r )) {
2019-01-06 17:37:48 +00:00
if ( $r [ 0 ][ 'page-flags' ] != User :: PAGE_FLAGS_NORMAL && $r [ 0 ][ 'page-flags' ] != User :: PAGE_FLAGS_PRVGROUP ) {
2014-03-11 22:52:32 +00:00
$auto_confirm = true ;
2017-12-17 01:13:10 +00:00
}
2011-12-26 23:47:40 +00:00
2017-12-17 01:13:10 +00:00
if ( ! $auto_confirm ) {
2018-01-15 13:05:12 +00:00
notification ([
2011-12-26 23:47:40 +00:00
'type' => NOTIFY_INTRO ,
'notify_flags' => $r [ 0 ][ 'notify-flags' ],
'language' => $r [ 0 ][ 'language' ],
'to_name' => $r [ 0 ][ 'username' ],
'to_email' => $r [ 0 ][ 'email' ],
2012-02-18 10:57:42 +00:00
'uid' => $r [ 0 ][ 'uid' ],
2017-08-26 07:32:10 +00:00
'link' => System :: baseUrl () . '/notifications/intros' ,
2018-01-21 18:33:59 +00:00
'source_name' => (( strlen ( stripslashes ( $r [ 0 ][ 'name' ]))) ? stripslashes ( $r [ 0 ][ 'name' ]) : L10n :: t ( '[Name Withheld]' )),
2011-12-26 23:47:40 +00:00
'source_link' => $r [ 0 ][ 'url' ],
2012-01-04 04:26:20 +00:00
'source_photo' => $r [ 0 ][ 'photo' ],
2019-10-23 22:25:43 +00:00
'verb' => Activity :: REQ_FRIEND ,
2012-01-04 04:26:20 +00:00
'otype' => 'intro'
2018-01-15 13:05:12 +00:00
]);
2010-07-22 09:13:39 +00:00
}
2011-12-26 23:47:40 +00:00
2017-12-17 01:13:10 +00:00
if ( $auto_confirm ) {
2017-05-07 18:40:23 +00:00
require_once 'mod/dfrn_confirm.php' ;
2018-01-15 13:05:12 +00:00
$handsfree = [
2016-12-14 08:56:27 +00:00
'uid' => $r [ 0 ][ 'uid' ],
'node' => $r [ 0 ][ 'nickname' ],
'dfrn_id' => $r [ 0 ][ 'issued-id' ],
2010-10-18 03:04:17 +00:00
'intro_id' => $intro [ 0 ][ 'id' ],
2019-01-06 17:37:48 +00:00
'duplex' => (( $r [ 0 ][ 'page-flags' ] == User :: PAGE_FLAGS_FREELOVE ) ? 1 : 0 ),
2018-01-15 13:05:12 +00:00
];
2017-12-17 01:13:10 +00:00
dfrn_confirm_post ( $a , $handsfree );
2010-10-18 03:04:17 +00:00
}
2010-07-22 09:13:39 +00:00
}
2017-12-17 01:13:10 +00:00
if ( ! $auto_confirm ) {
2010-07-01 23:48:07 +00:00
2010-10-18 03:04:17 +00:00
// If we are auto_confirming, this record will have already been nuked
// in dfrn_confirm_post()
2019-01-07 06:23:49 +00:00
q ( " UPDATE `intro` SET `blocked` = 0 WHERE `hash` = '%s' " ,
2018-07-21 13:10:13 +00:00
DBA :: escape ( $_GET [ 'confirm_key' ])
2010-10-18 03:04:17 +00:00
);
}
2010-07-22 09:13:39 +00:00
}
2011-08-18 23:47:45 +00:00
2018-12-26 05:40:12 +00:00
exit ();
2017-12-17 01:13:10 +00:00
} else {
// Normal web request. Display our user's introduction form.
2019-09-28 18:09:11 +00:00
if ( Config :: get ( 'system' , 'block_public' ) && ! Session :: isAuthenticated ()) {
2017-12-17 01:13:10 +00:00
if ( ! Config :: get ( 'system' , 'local_block' )) {
2018-01-21 18:33:59 +00:00
notice ( L10n :: t ( 'Public access denied.' ) . EOL );
2012-09-07 03:17:50 +00:00
return ;
}
2011-04-22 02:12:22 +00:00
}
2017-12-17 01:13:10 +00:00
// Try to auto-fill the profile address
2014-08-20 22:56:21 +00:00
// At first look if an address was provided
// Otherwise take the local address
2018-11-30 14:06:22 +00:00
if ( ! empty ( $_GET [ 'addr' ])) {
2014-08-20 22:56:21 +00:00
$myaddr = hex2bin ( $_GET [ 'addr' ]);
2018-11-30 14:06:22 +00:00
} elseif ( ! empty ( $_GET [ 'address' ])) {
2014-08-20 22:56:21 +00:00
$myaddr = $_GET [ 'address' ];
2016-12-19 13:26:13 +00:00
} elseif ( local_user ()) {
2018-10-09 23:18:47 +00:00
if ( strlen ( $a -> getURLPath ())) {
2017-08-26 07:32:10 +00:00
$myaddr = System :: baseUrl () . '/profile/' . $a -> user [ 'nickname' ];
2016-12-19 13:26:13 +00:00
} else {
2017-12-17 01:13:10 +00:00
$myaddr = $a -> user [ 'nickname' ] . '@' . substr ( System :: baseUrl (), strpos ( System :: baseUrl (), '://' ) + 3 );
2011-01-01 21:12:31 +00:00
}
2016-12-19 13:26:13 +00:00
} else {
2016-12-20 16:43:46 +00:00
// last, try a zrl
2018-01-15 02:22:39 +00:00
$myaddr = Profile :: getMyURL ();
2016-12-20 16:43:46 +00:00
}
2012-04-27 23:23:25 +00:00
2017-12-17 01:13:10 +00:00
$target_addr = $a -> profile [ 'nickname' ] . '@' . substr ( System :: baseUrl (), strpos ( System :: baseUrl (), '://' ) + 3 );
2011-09-06 01:34:30 +00:00
2017-12-17 01:13:10 +00:00
/* The auto_request form only has the profile address
2014-09-06 15:28:46 +00:00
* because nobody is going to read the comments and
2011-01-01 21:12:31 +00:00
* it doesn ' t matter if they know you or not .
*/
2019-01-06 17:37:48 +00:00
if ( $a -> profile [ 'page-flags' ] == User :: PAGE_FLAGS_NORMAL ) {
2018-10-31 14:44:06 +00:00
$tpl = Renderer :: getMarkupTemplate ( 'dfrn_request.tpl' );
2016-12-19 13:26:13 +00:00
} else {
2018-10-31 14:44:06 +00:00
$tpl = Renderer :: getMarkupTemplate ( 'auto_request.tpl' );
2016-12-20 16:43:46 +00:00
}
2011-01-01 21:12:31 +00:00
2018-01-21 18:33:59 +00:00
$page_desc = L10n :: t ( " Please enter your 'Identity Address' from one of the following supported communications networks: " );
2012-03-13 22:40:16 +00:00
2018-12-28 01:13:36 +00:00
$invite_desc = 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 site and join us today</a>.' , get_server () . '/servers' );
2011-09-06 01:34:30 +00:00
2018-10-31 14:35:50 +00:00
$o = Renderer :: replaceMacros ( $tpl , [
2018-01-21 18:33:59 +00:00
'$header' => L10n :: t ( 'Friend/Connection Request' ),
'$desc' => L10n :: t ( 'Examples: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, testuser@gnusocial.de' ),
'$pls_answer' => L10n :: t ( 'Please answer the following:' ),
2018-01-24 02:59:16 +00:00
'$does_know_you' => [ 'knowyou' , L10n :: t ( 'Does %s know you?' , $a -> profile [ 'name' ]), false , '' , [ L10n :: t ( 'No' ), L10n :: t ( 'Yes' )]],
2018-01-21 18:33:59 +00:00
'$add_note' => L10n :: t ( 'Add a personal note:' ),
2011-09-06 01:34:30 +00:00
'$page_desc' => $page_desc ,
2018-01-21 18:33:59 +00:00
'$friendica' => L10n :: t ( 'Friendica' ),
2018-01-24 21:51:32 +00:00
'$statusnet' => L10n :: t ( " GNU Social \x28 Pleroma, Mastodon \x29 " ),
'$diaspora' => L10n :: t ( " Diaspora \x28 Socialhome, Hubzilla \x29 " ),
2018-01-24 02:59:16 +00:00
'$diasnote' => L10n :: t ( ' - please do not use this form. Instead, enter %s into your Diaspora search bar.' , $target_addr ),
2018-01-21 18:33:59 +00:00
'$your_address' => L10n :: t ( 'Your Identity Address:' ),
2012-03-13 22:40:16 +00:00
'$invite_desc' => $invite_desc ,
2018-01-21 18:33:59 +00:00
'$submit' => L10n :: t ( 'Submit Request' ),
'$cancel' => L10n :: t ( 'Cancel' ),
2010-10-26 04:52:30 +00:00
'$nickname' => $a -> argv [ 1 ],
'$name' => $a -> profile [ 'name' ],
'$myaddr' => $myaddr
2018-01-15 13:05:12 +00:00
]);
2010-07-22 09:13:39 +00:00
return $o ;
2010-07-01 23:48:07 +00:00
}
2016-11-28 00:13:47 +00:00
}