2017-11-19 21:55:28 +00:00
< ? php
/**
2020-02-09 14:45:36 +00:00
* @ copyright Copyright ( C ) 2020 , Friendica
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*
2017-11-19 21:55:28 +00:00
*/
2019-10-10 23:21:41 +00:00
2017-11-19 21:55:28 +00:00
namespace Friendica\Model ;
2020-07-21 06:27:05 +00:00
use DivineOmega\DOFileCachePSR6\CacheItemPool ;
2018-10-19 15:26:48 +00:00
use DivineOmega\PasswordExposed ;
2020-07-21 06:27:05 +00:00
use ErrorException ;
2018-07-20 02:15:21 +00:00
use Exception ;
2020-02-25 21:16:27 +00:00
use Friendica\Content\Pager ;
2018-11-25 01:56:38 +00:00
use Friendica\Core\Hook ;
2020-02-21 21:57:17 +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 ;
2017-11-19 21:55:28 +00:00
use Friendica\Core\System ;
use Friendica\Core\Worker ;
2018-07-20 12:19:26 +00:00
use Friendica\Database\DBA ;
2019-12-15 23:47:24 +00:00
use Friendica\DI ;
2019-07-22 11:56:36 +00:00
use Friendica\Model\TwoFactor\AppSpecificPassword ;
2020-07-21 06:27:05 +00:00
use Friendica\Network\HTTPException ;
2017-12-07 13:56:11 +00:00
use Friendica\Object\Image ;
2017-12-30 16:51:49 +00:00
use Friendica\Util\Crypto ;
2018-01-27 02:38:34 +00:00
use Friendica\Util\DateTimeFormat ;
2019-10-18 01:26:15 +00:00
use Friendica\Util\Images ;
2018-01-27 04:09:48 +00:00
use Friendica\Util\Network ;
2018-11-08 15:14:37 +00:00
use Friendica\Util\Strings ;
2019-06-10 14:19:24 +00:00
use Friendica\Worker\Delivery ;
2020-07-21 06:27:05 +00:00
use ImagickException ;
2018-01-25 02:08:45 +00:00
use LightOpenID ;
2017-11-19 21:55:28 +00:00
/**
2020-01-19 06:05:23 +00:00
* This class handles User related functions
2017-11-19 21:55:28 +00:00
*/
class User
{
2019-01-06 17:37:48 +00:00
/**
2019-01-14 13:45:37 +00:00
* Page / profile types
2019-01-06 17:37:48 +00:00
*
2019-01-14 13:45:37 +00:00
* PAGE_FLAGS_NORMAL is a typical personal profile account
* PAGE_FLAGS_SOAPBOX automatically approves all friend requests as Contact :: SHARING , ( readonly )
* PAGE_FLAGS_COMMUNITY automatically approves all friend requests as Contact :: SHARING , but with
2019-01-06 17:37:48 +00:00
* write access to wall and comments ( no email and not included in page owner ' s ACL lists )
2019-01-14 13:45:37 +00:00
* PAGE_FLAGS_FREELOVE automatically approves all friend requests as full friends ( Contact :: FRIEND ) .
2019-01-06 17:37:48 +00:00
*
* @ {
*/
const PAGE_FLAGS_NORMAL = 0 ;
const PAGE_FLAGS_SOAPBOX = 1 ;
const PAGE_FLAGS_COMMUNITY = 2 ;
const PAGE_FLAGS_FREELOVE = 3 ;
const PAGE_FLAGS_BLOG = 4 ;
const PAGE_FLAGS_PRVGROUP = 5 ;
/**
* @ }
*/
2019-01-06 22:08:35 +00:00
/**
* Account types
*
* ACCOUNT_TYPE_PERSON - the account belongs to a person
* Associated page types : PAGE_FLAGS_NORMAL , PAGE_FLAGS_SOAPBOX , PAGE_FLAGS_FREELOVE
*
* ACCOUNT_TYPE_ORGANISATION - the account belongs to an organisation
* Associated page type : PAGE_FLAGS_SOAPBOX
*
* ACCOUNT_TYPE_NEWS - the account is a news reflector
* Associated page type : PAGE_FLAGS_SOAPBOX
*
* ACCOUNT_TYPE_COMMUNITY - the account is community forum
* Associated page types : PAGE_COMMUNITY , PAGE_FLAGS_PRVGROUP
*
* ACCOUNT_TYPE_RELAY - the account is a relay
* This will only be assigned to contacts , not to user accounts
* @ {
*/
const ACCOUNT_TYPE_PERSON = 0 ;
const ACCOUNT_TYPE_ORGANISATION = 1 ;
const ACCOUNT_TYPE_NEWS = 2 ;
const ACCOUNT_TYPE_COMMUNITY = 3 ;
const ACCOUNT_TYPE_RELAY = 4 ;
/**
* @ }
*/
2020-08-16 11:57:56 +00:00
private static $owner ;
2020-10-09 19:08:50 +00:00
/**
* Returns the numeric account type by their string
*
* @ param string $accounttype as string constant
2020-10-10 07:14:43 +00:00
* @ return int | null Numeric account type - or null when not set
2020-10-09 19:08:50 +00:00
*/
public static function getAccountTypeByString ( string $accounttype )
{
switch ( $accounttype ) {
case 'person' :
return User :: ACCOUNT_TYPE_PERSON ;
case 'organisation' :
return User :: ACCOUNT_TYPE_ORGANISATION ;
case 'news' :
return User :: ACCOUNT_TYPE_NEWS ;
case 'community' :
return User :: ACCOUNT_TYPE_COMMUNITY ;
default :
return null ;
break ;
}
}
2020-08-22 14:48:09 +00:00
/**
* Fetch the system account
*
2020-08-22 18:52:37 +00:00
* @ return array system account
2020-08-22 14:48:09 +00:00
*/
public static function getSystemAccount ()
{
$system = Contact :: selectFirst ([], [ 'self' => true , 'uid' => 0 ]);
if ( ! DBA :: isResult ( $system )) {
self :: createSystemAccount ();
$system = Contact :: selectFirst ([], [ 'self' => true , 'uid' => 0 ]);
if ( ! DBA :: isResult ( $system )) {
return [];
}
}
2020-08-23 07:29:56 +00:00
$system [ 'sprvkey' ] = $system [ 'uprvkey' ] = $system [ 'prvkey' ];
$system [ 'spubkey' ] = $system [ 'upubkey' ] = $system [ 'pubkey' ];
2020-08-22 14:48:09 +00:00
$system [ 'nickname' ] = $system [ 'nick' ];
return $system ;
}
/**
* Create the system account
*
* @ return void
*/
private static function createSystemAccount ()
{
2020-08-22 18:52:37 +00:00
$system_actor_name = self :: getActorName ();
2020-08-22 14:48:09 +00:00
if ( empty ( $system_actor_name )) {
2020-08-22 18:52:37 +00:00
return ;
2020-08-22 14:48:09 +00:00
}
$keys = Crypto :: newKeypair ( 4096 );
if ( $keys === false ) {
throw new Exception ( DI :: l10n () -> t ( 'SERIOUS ERROR: Generation of security keys failed.' ));
}
$system = [];
$system [ 'uid' ] = 0 ;
$system [ 'created' ] = DateTimeFormat :: utcNow ();
$system [ 'self' ] = true ;
$system [ 'network' ] = Protocol :: ACTIVITYPUB ;
$system [ 'name' ] = 'System Account' ;
$system [ 'addr' ] = $system_actor_name . '@' . DI :: baseUrl () -> getHostname ();
$system [ 'nick' ] = $system_actor_name ;
$system [ 'avatar' ] = DI :: baseUrl () . Contact :: DEFAULT_AVATAR_PHOTO ;
$system [ 'photo' ] = DI :: baseUrl () . Contact :: DEFAULT_AVATAR_PHOTO ;
$system [ 'thumb' ] = DI :: baseUrl () . Contact :: DEFAULT_AVATAR_THUMB ;
$system [ 'micro' ] = DI :: baseUrl () . Contact :: DEFAULT_AVATAR_MICRO ;
$system [ 'url' ] = DI :: baseUrl () . '/friendica' ;
$system [ 'nurl' ] = Strings :: normaliseLink ( $system [ 'url' ]);
$system [ 'pubkey' ] = $keys [ 'pubkey' ];
$system [ 'prvkey' ] = $keys [ 'prvkey' ];
$system [ 'blocked' ] = 0 ;
$system [ 'pending' ] = 0 ;
$system [ 'contact-type' ] = Contact :: TYPE_RELAY ; // In AP this is translated to 'Application'
$system [ 'name-date' ] = DateTimeFormat :: utcNow ();
$system [ 'uri-date' ] = DateTimeFormat :: utcNow ();
$system [ 'avatar-date' ] = DateTimeFormat :: utcNow ();
$system [ 'closeness' ] = 0 ;
$system [ 'baseurl' ] = DI :: baseUrl ();
$system [ 'gsid' ] = GServer :: getID ( $system [ 'baseurl' ]);
DBA :: insert ( 'contact' , $system );
}
/**
* Detect a usable actor name
*
* @ return string actor account name
*/
public static function getActorName ()
{
2020-08-22 18:52:37 +00:00
$system_actor_name = DI :: config () -> get ( 'system' , 'actor_name' );
if ( ! empty ( $system_actor_name )) {
2020-08-22 19:40:31 +00:00
$self = Contact :: selectFirst ([ 'nick' ], [ 'uid' => 0 , 'self' => true ]);
if ( ! empty ( $self [ 'nick' ])) {
if ( $self [ 'nick' ] != $system_actor_name ) {
// Reset the actor name to the already used name
DI :: config () -> set ( 'system' , 'actor_name' , $self [ 'nick' ]);
$system_actor_name = $self [ 'nick' ];
}
}
2020-08-22 18:52:37 +00:00
return $system_actor_name ;
}
2020-08-22 14:48:09 +00:00
// List of possible actor names
$possible_accounts = [ 'friendica' , 'actor' , 'system' , 'internal' ];
foreach ( $possible_accounts as $name ) {
if ( ! DBA :: exists ( 'user' , [ 'nickname' => $name , 'account_removed' => false , 'expire' ]) &&
! DBA :: exists ( 'userd' , [ 'username' => $name ])) {
DI :: config () -> set ( 'system' , 'actor_name' , $name );
return $name ;
}
}
return '' ;
}
2018-10-14 15:34:34 +00:00
/**
* Returns true if a user record exists with the provided id
*
* @ param integer $uid
* @ return boolean
2019-01-06 21:06:53 +00:00
* @ throws Exception
2018-10-14 15:34:34 +00:00
*/
public static function exists ( $uid )
{
return DBA :: exists ( 'user' , [ 'uid' => $uid ]);
}
2018-09-28 03:56:41 +00:00
/**
2018-10-15 15:58:52 +00:00
* @ param integer $uid
2019-04-30 22:14:06 +00:00
* @ param array $fields
2018-10-15 15:58:52 +00:00
* @ return array | boolean User record if it exists , false otherwise
2019-01-06 21:06:53 +00:00
* @ throws Exception
2018-10-15 15:58:52 +00:00
*/
2019-04-30 22:14:06 +00:00
public static function getById ( $uid , array $fields = [])
2018-10-15 15:58:52 +00:00
{
2019-04-30 22:14:06 +00:00
return DBA :: selectFirst ( 'user' , $fields , [ 'uid' => $uid ]);
}
2019-10-20 11:00:08 +00:00
/**
* Returns a user record based on it ' s GUID
*
* @ param string $guid The guid of the user
* @ param array $fields The fields to retrieve
* @ param bool $active True , if only active records are searched
*
* @ return array | boolean User record if it exists , false otherwise
* @ throws Exception
*/
public static function getByGuid ( string $guid , array $fields = [], bool $active = true )
{
if ( $active ) {
$cond = [ 'guid' => $guid , 'account_expired' => false , 'account_removed' => false ];
} else {
$cond = [ 'guid' => $guid ];
}
return DBA :: selectFirst ( 'user' , $fields , $cond );
}
2019-04-30 22:14:06 +00:00
/**
* @ param string $nickname
* @ param array $fields
* @ return array | boolean User record if it exists , false otherwise
* @ throws Exception
*/
public static function getByNickname ( $nickname , array $fields = [])
{
return DBA :: selectFirst ( 'user' , $fields , [ 'nickname' => $nickname ]);
2018-10-15 15:58:52 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Returns the user id of a given profile URL
2018-09-28 03:56:41 +00:00
*
2018-10-15 15:58:52 +00:00
* @ param string $url
2018-09-28 03:56:41 +00:00
*
* @ return integer user id
2019-01-06 21:06:53 +00:00
* @ throws Exception
2018-09-28 03:56:41 +00:00
*/
2020-08-18 19:45:01 +00:00
public static function getIdForURL ( string $url )
2018-09-28 03:56:41 +00:00
{
2020-08-19 04:45:31 +00:00
// Avoid any database requests when the hostname isn't even part of the url.
if ( ! strpos ( $url , DI :: baseUrl () -> getHostname ())) {
return 0 ;
}
2020-08-18 19:45:01 +00:00
$self = Contact :: selectFirst ([ 'uid' ], [ 'self' => true , 'nurl' => Strings :: normaliseLink ( $url )]);
if ( ! empty ( $self [ 'uid' ])) {
return $self [ 'uid' ];
}
$self = Contact :: selectFirst ([ 'uid' ], [ 'self' => true , 'addr' => $url ]);
if ( ! empty ( $self [ 'uid' ])) {
2018-09-28 03:56:41 +00:00
return $self [ 'uid' ];
}
2020-08-18 19:45:01 +00:00
$self = Contact :: selectFirst ([ 'uid' ], [ 'self' => true , 'alias' => [ $url , Strings :: normaliseLink ( $url )]]);
if ( ! empty ( $self [ 'uid' ])) {
return $self [ 'uid' ];
}
return 0 ;
2018-09-28 03:56:41 +00:00
}
2019-05-04 08:05:21 +00:00
/**
2019-05-05 08:00:28 +00:00
* Get a user based on its email
2019-05-04 08:05:21 +00:00
*
* @ param string $email
* @ param array $fields
*
* @ return array | boolean User record if it exists , false otherwise
*
* @ throws Exception
*/
public static function getByEmail ( $email , array $fields = [])
{
return DBA :: selectFirst ( 'user' , $fields , [ 'email' => $email ]);
}
2020-07-29 05:12:16 +00:00
/**
* Fetch the user array of the administrator . The first one if there are several .
*
* @ param array $fields
* @ return array user
*/
public static function getFirstAdmin ( array $fields = [])
{
if ( ! empty ( DI :: config () -> get ( 'config' , 'admin_nickname' ))) {
2020-07-29 14:59:55 +00:00
return self :: getByNickname ( DI :: config () -> get ( 'config' , 'admin_nickname' ), $fields );
2020-07-29 14:55:55 +00:00
} elseif ( ! empty ( DI :: config () -> get ( 'config' , 'admin_email' ))) {
2020-07-29 05:12:16 +00:00
$adminList = explode ( ',' , str_replace ( ' ' , '' , DI :: config () -> get ( 'config' , 'admin_email' )));
2020-07-29 14:59:55 +00:00
return self :: getByEmail ( $adminList [ 0 ], $fields );
} else {
return [];
2020-07-29 05:12:16 +00:00
}
}
2017-12-17 21:22:39 +00:00
/**
2020-01-19 06:05:23 +00:00
* Get owner data by user id
2017-12-17 21:22:39 +00:00
*
* @ param int $uid
2019-06-19 17:05:29 +00:00
* @ param boolean $check_valid Test if data is invalid and correct it
2017-12-17 21:22:39 +00:00
* @ return boolean | array
2019-01-06 21:06:53 +00:00
* @ throws Exception
2017-12-17 21:22:39 +00:00
*/
2020-08-16 12:51:15 +00:00
public static function getOwnerDataById ( int $uid , bool $check_valid = true )
2019-10-10 23:21:41 +00:00
{
2020-08-23 07:29:56 +00:00
if ( $uid == 0 ) {
return self :: getSystemAccount ();
}
2020-08-16 12:51:15 +00:00
if ( ! empty ( self :: $owner [ $uid ])) {
return self :: $owner [ $uid ];
2020-08-16 11:57:56 +00:00
}
2020-04-24 11:04:50 +00:00
$owner = DBA :: selectFirst ( 'owner-view' , [], [ 'uid' => $uid ]);
if ( ! DBA :: isResult ( $owner )) {
2020-04-25 07:29:38 +00:00
if ( ! DBA :: exists ( 'user' , [ 'uid' => $uid ]) || ! $check_valid ) {
return false ;
}
Contact :: createSelfFromUserId ( $uid );
$owner = self :: getOwnerDataById ( $uid , false );
2017-12-17 21:22:39 +00:00
}
2018-12-22 20:12:32 +00:00
2020-04-24 11:04:50 +00:00
if ( empty ( $owner [ 'nickname' ])) {
2018-12-22 20:12:32 +00:00
return false ;
}
2019-06-19 17:05:29 +00:00
if ( ! $check_valid ) {
2020-04-24 11:04:50 +00:00
return $owner ;
2019-06-19 17:05:29 +00:00
}
2018-12-22 20:12:32 +00:00
// Check if the returned data is valid, otherwise fix it. See issue #6122
2019-06-19 17:05:29 +00:00
// Check for correct url and normalised nurl
2020-04-24 11:04:50 +00:00
$url = DI :: baseUrl () . '/profile/' . $owner [ 'nickname' ];
$repair = ( $owner [ 'url' ] != $url ) || ( $owner [ 'nurl' ] != Strings :: normaliseLink ( $owner [ 'url' ]));
2019-06-19 17:05:29 +00:00
if ( ! $repair ) {
// Check if "addr" is present and correct
2020-04-24 11:04:50 +00:00
$addr = $owner [ 'nickname' ] . '@' . substr ( DI :: baseUrl (), strpos ( DI :: baseUrl (), '://' ) + 3 );
2020-09-26 20:59:28 +00:00
$repair = ( $addr != $owner [ 'addr' ]) || empty ( $owner [ 'prvkey' ]) || empty ( $owner [ 'pubkey' ]);
2019-06-19 17:05:29 +00:00
}
if ( ! $repair ) {
// Check if the avatar field is filled and the photo directs to the correct path
$avatar = Photo :: selectFirst ([ 'resource-id' ], [ 'uid' => $uid , 'profile' => true ]);
if ( DBA :: isResult ( $avatar )) {
2020-04-24 11:04:50 +00:00
$repair = empty ( $owner [ 'avatar' ]) || ! strpos ( $owner [ 'photo' ], $avatar [ 'resource-id' ]);
2019-06-19 17:05:29 +00:00
}
}
2018-12-22 20:12:32 +00:00
2019-06-19 17:05:29 +00:00
if ( $repair ) {
2018-12-22 20:12:32 +00:00
Contact :: updateSelfFromUserID ( $uid );
2019-06-19 17:05:29 +00:00
// Return the corrected data and avoid a loop
2020-04-24 11:04:50 +00:00
$owner = self :: getOwnerDataById ( $uid , false );
2018-12-22 20:12:32 +00:00
}
2020-08-16 12:51:15 +00:00
self :: $owner [ $uid ] = $owner ;
2020-04-24 11:04:50 +00:00
return $owner ;
2017-12-17 21:22:39 +00:00
}
2018-06-18 20:36:34 +00:00
/**
2020-01-19 06:05:23 +00:00
* Get owner data by nick name
2018-06-18 20:36:34 +00:00
*
* @ param int $nick
* @ return boolean | array
2019-01-06 21:06:53 +00:00
* @ throws Exception
2018-06-18 20:36:34 +00:00
*/
public static function getOwnerDataByNick ( $nick )
{
2018-07-20 12:19:26 +00:00
$user = DBA :: selectFirst ( 'user' , [ 'uid' ], [ 'nickname' => $nick ]);
Cleanups: isResult() more used, readability improved (#5608)
* [diaspora]: Maybe SimpleXMLElement is the right type-hint?
* Changes proposed + pre-renaming:
- pre-renamed $db -> $connection
- added TODOs for not allowing bad method invocations (there is a
BadMethodCallException in SPL)
* If no record is found, below $r[0] will fail with a E_NOTICE and the code
doesn't behave as expected.
* Ops, one more left ...
* Continued:
- added documentation for Contact::updateSslPolicy() method
- added type-hint for $contact of same method
- empty lines added + TODO where the bug origins that $item has no element 'body'
* Added empty lines for better readability
* Cleaned up:
- no more x() (deprecated) usage but empty() instead
- fixed mixing of space/tab indending
- merged else/if block goether in elseif() (lesser nested code blocks)
* Re-fixed DBM -> DBA switch
* Fixes/rewrites:
- use empty()/isset() instead of deprecated x()
- merged 2 nested if() blocks into one
- avoided nested if() block inside else block by rewriting it to elseif()
- $contact_id is an integer, let's test on > 0 here
- added a lot spaces and some empty lines for better readability
* Rewrite:
- moved all CONTACT_* constants from boot.php to Contact class
* CR request:
- renamed Contact::CONTACT_IS_* -> Contact::* ;-)
* Rewrites:
- moved PAGE_* to Friendica\Model\Profile class
- fixed mixure with "Contact::* rewrite"
* Ops, one still there (return is no function)
* Rewrite to Proxy class:
- introduced new Friendica\Network\Proxy class for in exchange of proxy_*()
functions
- moved also all PROXY_* constants there as Proxy::*
- removed now no longer needed mod/proxy.php loading as composer's auto-load
will do this for us
- renamed those proxy_*() functions to better names:
+ proxy_init() -> Proxy::init() (public)
+ proxy_url() -> Proxy::proxifyUrl() (public)
+ proxy_parse_html() -> Proxy::proxifyHtml() (public)
+ proxy_is_local_image() -> Proxy::isLocalImage() (private)
+ proxy_parse_query() -> Proxy::parseQuery() (private)
+ proxy_img_cb() -> Proxy::replaceUrl() (private)
* CR request:
- moved all PAGE_* constants to Friendica\Model\Contact class
- fixed all references of both classes
* Ops, need to set $a here ...
* CR request:
- moved Proxy class to Friendica\Module
- extended BaseModule
* Ops, no need for own instance of $a when self::getApp() is around.
* Proxy-rewrite:
- proxy_url() and proxy_parse_html() are both non-module functions (now
methods)
- so they must be splitted into a seperate class
- also the SIZE_* and DEFAULT_TIME constants are both not relevant to module
* No instances from utility classes
* Fixed error:
- proxify*() is now located in `Friendica\Util\ProxyUtils`
* Moved back to original place, ops? How did they move here? Well, it was not
intended by me.
* Removed duplicate (left-over from split) constants and static array. Thank to
MrPetovan finding it.
* Renamed ProxyUtils -> Proxy and aliased it back to ProxyUtils.
* Rewrite:
- stopped using deprecated NETWORK_* constants, now Protocol::* should be used
- still left them intact for slow/lazy developers ...
* Ops, was added accidentally ...
* Ops, why these wrong moves?
* Ops, one to much (thanks to MrPetovan)
* Ops, wrong moving ...
* moved back to original place ...
* spaces added
* empty lines add for better readability.
* convertered spaces -> tab for code indenting.
* CR request: Add space between if and brace.
* CR requests fixed + move reverted
- ops, src/Module/*.php has been moved to src/Network/ accidentally
- reverted some parts in src/Database/DBA.php as pointed out by Annando
- removed internal TODO items
- added some spaces for better readability
2018-08-24 05:05:49 +00:00
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $user )) {
2018-06-18 20:36:34 +00:00
return false ;
}
Cleanups: isResult() more used, readability improved (#5608)
* [diaspora]: Maybe SimpleXMLElement is the right type-hint?
* Changes proposed + pre-renaming:
- pre-renamed $db -> $connection
- added TODOs for not allowing bad method invocations (there is a
BadMethodCallException in SPL)
* If no record is found, below $r[0] will fail with a E_NOTICE and the code
doesn't behave as expected.
* Ops, one more left ...
* Continued:
- added documentation for Contact::updateSslPolicy() method
- added type-hint for $contact of same method
- empty lines added + TODO where the bug origins that $item has no element 'body'
* Added empty lines for better readability
* Cleaned up:
- no more x() (deprecated) usage but empty() instead
- fixed mixing of space/tab indending
- merged else/if block goether in elseif() (lesser nested code blocks)
* Re-fixed DBM -> DBA switch
* Fixes/rewrites:
- use empty()/isset() instead of deprecated x()
- merged 2 nested if() blocks into one
- avoided nested if() block inside else block by rewriting it to elseif()
- $contact_id is an integer, let's test on > 0 here
- added a lot spaces and some empty lines for better readability
* Rewrite:
- moved all CONTACT_* constants from boot.php to Contact class
* CR request:
- renamed Contact::CONTACT_IS_* -> Contact::* ;-)
* Rewrites:
- moved PAGE_* to Friendica\Model\Profile class
- fixed mixure with "Contact::* rewrite"
* Ops, one still there (return is no function)
* Rewrite to Proxy class:
- introduced new Friendica\Network\Proxy class for in exchange of proxy_*()
functions
- moved also all PROXY_* constants there as Proxy::*
- removed now no longer needed mod/proxy.php loading as composer's auto-load
will do this for us
- renamed those proxy_*() functions to better names:
+ proxy_init() -> Proxy::init() (public)
+ proxy_url() -> Proxy::proxifyUrl() (public)
+ proxy_parse_html() -> Proxy::proxifyHtml() (public)
+ proxy_is_local_image() -> Proxy::isLocalImage() (private)
+ proxy_parse_query() -> Proxy::parseQuery() (private)
+ proxy_img_cb() -> Proxy::replaceUrl() (private)
* CR request:
- moved all PAGE_* constants to Friendica\Model\Contact class
- fixed all references of both classes
* Ops, need to set $a here ...
* CR request:
- moved Proxy class to Friendica\Module
- extended BaseModule
* Ops, no need for own instance of $a when self::getApp() is around.
* Proxy-rewrite:
- proxy_url() and proxy_parse_html() are both non-module functions (now
methods)
- so they must be splitted into a seperate class
- also the SIZE_* and DEFAULT_TIME constants are both not relevant to module
* No instances from utility classes
* Fixed error:
- proxify*() is now located in `Friendica\Util\ProxyUtils`
* Moved back to original place, ops? How did they move here? Well, it was not
intended by me.
* Removed duplicate (left-over from split) constants and static array. Thank to
MrPetovan finding it.
* Renamed ProxyUtils -> Proxy and aliased it back to ProxyUtils.
* Rewrite:
- stopped using deprecated NETWORK_* constants, now Protocol::* should be used
- still left them intact for slow/lazy developers ...
* Ops, was added accidentally ...
* Ops, why these wrong moves?
* Ops, one to much (thanks to MrPetovan)
* Ops, wrong moving ...
* moved back to original place ...
* spaces added
* empty lines add for better readability.
* convertered spaces -> tab for code indenting.
* CR request: Add space between if and brace.
* CR requests fixed + move reverted
- ops, src/Module/*.php has been moved to src/Network/ accidentally
- reverted some parts in src/Database/DBA.php as pointed out by Annando
- removed internal TODO items
- added some spaces for better readability
2018-08-24 05:05:49 +00:00
2018-06-18 20:36:34 +00:00
return self :: getOwnerDataById ( $user [ 'uid' ]);
}
2017-12-04 03:15:31 +00:00
/**
2020-01-19 06:05:23 +00:00
* Returns the default group for a given user and network
2017-12-09 18:31:00 +00:00
*
2020-07-21 06:27:05 +00:00
* @ param int $uid User id
2017-12-09 18:31:00 +00:00
* @ param string $network network name
*
* @ return int group id
2020-07-21 06:27:05 +00:00
* @ throws Exception
2017-12-09 18:31:00 +00:00
*/
public static function getDefaultGroup ( $uid , $network = '' )
{
$default_group = 0 ;
2018-08-11 20:40:44 +00:00
if ( $network == Protocol :: OSTATUS ) {
2020-01-18 15:50:57 +00:00
$default_group = DI :: pConfig () -> get ( $uid , " ostatus " , " default_group " );
2017-12-09 18:31:00 +00:00
}
if ( $default_group != 0 ) {
return $default_group ;
}
2018-07-20 12:19:26 +00:00
$user = DBA :: selectFirst ( 'user' , [ 'def_gid' ], [ 'uid' => $uid ]);
2017-12-09 18:31:00 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $user )) {
2017-12-09 18:31:00 +00:00
$default_group = $user [ " def_gid " ];
}
return $default_group ;
}
/**
2018-02-09 05:08:01 +00:00
* Authenticate a user with a clear text password
2017-12-04 03:15:31 +00:00
*
2019-07-22 11:56:36 +00:00
* @ param mixed $user_info
2017-12-04 03:15:31 +00:00
* @ param string $password
2019-07-22 11:56:36 +00:00
* @ param bool $third_party
2018-02-09 05:08:01 +00:00
* @ return int | boolean
* @ deprecated since version 3.6
2019-07-22 11:56:36 +00:00
* @ see User :: getIdFromPasswordAuthentication ()
2017-12-04 03:15:31 +00:00
*/
2019-07-22 11:56:36 +00:00
public static function authenticate ( $user_info , $password , $third_party = false )
2017-11-26 19:25:25 +00:00
{
2018-02-09 05:08:01 +00:00
try {
2019-07-22 11:56:36 +00:00
return self :: getIdFromPasswordAuthentication ( $user_info , $password , $third_party );
2018-02-09 05:08:01 +00:00
} catch ( Exception $ex ) {
return false ;
2017-11-26 19:25:25 +00:00
}
2018-02-09 05:08:01 +00:00
}
2017-11-26 19:25:25 +00:00
2018-02-09 05:08:01 +00:00
/**
2020-01-19 06:05:23 +00:00
* Authenticate a user with a clear text password
*
2018-02-09 05:08:01 +00:00
* Returns the user id associated with a successful password authentication
*
2019-07-22 11:56:36 +00:00
* @ param mixed $user_info
2018-02-09 05:08:01 +00:00
* @ param string $password
2019-07-22 11:56:36 +00:00
* @ param bool $third_party
2018-02-09 05:08:01 +00:00
* @ return int User Id if authentication is successful
2019-07-27 22:14:39 +00:00
* @ throws HTTPException\ForbiddenException
* @ throws HTTPException\NotFoundException
2018-02-09 05:08:01 +00:00
*/
2019-07-22 11:56:36 +00:00
public static function getIdFromPasswordAuthentication ( $user_info , $password , $third_party = false )
2018-02-09 05:08:01 +00:00
{
$user = self :: getAuthenticationInfo ( $user_info );
2017-11-26 19:25:25 +00:00
2020-01-18 15:50:57 +00:00
if ( $third_party && DI :: pConfig () -> get ( $user [ 'uid' ], '2fa' , 'verified' )) {
2019-07-22 11:56:36 +00:00
// Third-party apps can't verify two-factor authentication, we use app-specific passwords instead
if ( AppSpecificPassword :: authenticateUser ( $user [ 'uid' ], $password )) {
return $user [ 'uid' ];
}
} elseif ( strpos ( $user [ 'password' ], '$' ) === false ) {
2018-04-15 09:12:32 +00:00
//Legacy hash that has not been replaced by a new hash yet
2018-04-08 10:28:04 +00:00
if ( self :: hashPasswordLegacy ( $password ) === $user [ 'password' ]) {
2019-01-01 06:06:28 +00:00
self :: updatePasswordHashed ( $user [ 'uid' ], self :: hashPassword ( $password ));
2018-04-08 10:28:04 +00:00
2018-04-15 09:12:32 +00:00
return $user [ 'uid' ];
}
} elseif ( ! empty ( $user [ 'legacy_password' ])) {
//Legacy hash that has been double-hashed and not replaced by a new hash yet
//Warning: `legacy_password` is not necessary in sync with the content of `password`
if ( password_verify ( self :: hashPasswordLegacy ( $password ), $user [ 'password' ])) {
2019-01-01 06:06:28 +00:00
self :: updatePasswordHashed ( $user [ 'uid' ], self :: hashPassword ( $password ));
2018-04-15 09:12:32 +00:00
2018-04-08 10:28:04 +00:00
return $user [ 'uid' ];
}
2018-04-08 14:02:25 +00:00
} elseif ( password_verify ( $password , $user [ 'password' ])) {
2018-04-15 09:12:32 +00:00
//New password hash
2018-04-08 14:02:25 +00:00
if ( password_needs_rehash ( $user [ 'password' ], PASSWORD_DEFAULT )) {
2019-01-01 06:06:28 +00:00
self :: updatePasswordHashed ( $user [ 'uid' ], self :: hashPassword ( $password ));
2018-04-08 14:02:25 +00:00
}
return $user [ 'uid' ];
2017-11-26 19:25:25 +00:00
}
2019-07-27 22:14:39 +00:00
throw new HTTPException\ForbiddenException ( DI :: l10n () -> t ( 'Login failed' ));
2018-02-09 05:08:01 +00:00
}
/**
* Returns authentication info from various parameters types
*
* User info can be any of the following :
* - User DB object
* - User Id
* - User email or username or nickname
* - User array with at least the uid and the hashed password
*
* @ param mixed $user_info
* @ return array
2019-07-27 22:14:39 +00:00
* @ throws HTTPException\NotFoundException
2018-02-09 05:08:01 +00:00
*/
private static function getAuthenticationInfo ( $user_info )
{
2018-02-14 05:05:00 +00:00
$user = null ;
2018-02-09 05:08:01 +00:00
if ( is_object ( $user_info ) || is_array ( $user_info )) {
if ( is_object ( $user_info )) {
$user = ( array ) $user_info ;
} else {
$user = $user_info ;
}
2019-10-10 23:21:41 +00:00
if (
! isset ( $user [ 'uid' ])
2018-02-09 05:08:01 +00:00
|| ! isset ( $user [ 'password' ])
2018-04-15 08:51:22 +00:00
|| ! isset ( $user [ 'legacy_password' ])
2018-02-09 05:08:01 +00:00
) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Not enough information to authenticate' ));
2018-02-09 05:08:01 +00:00
}
} elseif ( is_int ( $user_info ) || is_string ( $user_info )) {
if ( is_int ( $user_info )) {
2019-10-10 23:21:41 +00:00
$user = DBA :: selectFirst (
'user' ,
[ 'uid' , 'password' , 'legacy_password' ],
2018-02-09 05:08:01 +00:00
[
'uid' => $user_info ,
'blocked' => 0 ,
'account_expired' => 0 ,
'account_removed' => 0 ,
'verified' => 1
]
);
} else {
2018-06-19 21:33:07 +00:00
$fields = [ 'uid' , 'password' , 'legacy_password' ];
2019-10-10 23:21:41 +00:00
$condition = [
" (`email` = ? OR `username` = ? OR `nickname` = ?)
2018-06-19 21:33:07 +00:00
AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified` " ,
2019-10-10 23:21:41 +00:00
$user_info , $user_info , $user_info
];
2018-07-20 12:19:26 +00:00
$user = DBA :: selectFirst ( 'user' , $fields , $condition );
2018-02-09 05:08:01 +00:00
}
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $user )) {
2019-07-27 22:14:39 +00:00
throw new HTTPException\NotFoundException ( DI :: l10n () -> t ( 'User not found' ));
2018-02-09 05:08:01 +00:00
}
}
return $user ;
2017-11-26 19:25:25 +00:00
}
2018-01-20 03:49:06 +00:00
/**
* Generates a human - readable random password
*
* @ return string
2020-07-21 06:27:05 +00:00
* @ throws Exception
2018-01-20 03:49:06 +00:00
*/
public static function generateNewPassword ()
{
2019-10-10 23:21:41 +00:00
return ucfirst ( Strings :: getRandomName ( 8 )) . random_int ( 1000 , 9999 );
2018-01-20 03:49:06 +00:00
}
2018-03-21 05:33:35 +00:00
/**
* Checks if the provided plaintext password has been exposed or not
*
* @ param string $password
* @ return bool
2019-10-13 01:06:47 +00:00
* @ throws Exception
2018-03-21 05:33:35 +00:00
*/
2018-03-21 06:14:43 +00:00
public static function isPasswordExposed ( $password )
2018-03-21 05:33:35 +00:00
{
2020-07-21 06:27:05 +00:00
$cache = new CacheItemPool ();
2018-10-19 15:26:48 +00:00
$cache -> changeConfig ([
'cacheDirectory' => get_temppath () . '/password-exposed-cache/' ,
]);
2019-10-13 01:06:47 +00:00
try {
$passwordExposedChecker = new PasswordExposed\PasswordExposedChecker ( null , $cache );
return $passwordExposedChecker -> passwordExposed ( $password ) === PasswordExposed\PasswordStatus :: EXPOSED ;
2020-07-21 06:27:05 +00:00
} catch ( Exception $e ) {
2019-10-13 01:06:47 +00:00
Logger :: error ( 'Password Exposed Exception: ' . $e -> getMessage (), [
'code' => $e -> getCode (),
'file' => $e -> getFile (),
'line' => $e -> getLine (),
'trace' => $e -> getTraceAsString ()
]);
2018-10-19 15:26:48 +00:00
2019-10-13 01:06:47 +00:00
return false ;
}
2018-03-21 05:33:35 +00:00
}
2018-01-20 03:49:06 +00:00
/**
2018-01-21 03:29:03 +00:00
* Legacy hashing function , kept for password migration purposes
2018-01-20 03:49:06 +00:00
*
* @ param string $password
* @ return string
*/
2018-01-21 03:29:03 +00:00
private static function hashPasswordLegacy ( $password )
2018-01-20 03:49:06 +00:00
{
return hash ( 'whirlpool' , $password );
}
2018-01-21 03:29:03 +00:00
/**
* Global user password hashing function
*
* @ param string $password
* @ return string
2019-01-01 06:08:55 +00:00
* @ throws Exception
2018-01-21 03:29:03 +00:00
*/
public static function hashPassword ( $password )
{
2018-04-19 02:49:14 +00:00
if ( ! trim ( $password )) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Password can\'t be empty' ));
2018-04-19 02:49:14 +00:00
}
2018-01-21 03:29:03 +00:00
return password_hash ( $password , PASSWORD_DEFAULT );
}
2018-01-20 03:49:06 +00:00
/**
* Updates a user row with a new plaintext password
*
* @ param int $uid
* @ param string $password
* @ return bool
2019-01-01 06:08:55 +00:00
* @ throws Exception
2018-01-20 03:49:06 +00:00
*/
public static function updatePassword ( $uid , $password )
{
2019-01-01 06:08:55 +00:00
$password = trim ( $password );
if ( empty ( $password )) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Empty passwords are not allowed.' ));
2019-01-01 06:08:55 +00:00
}
2020-01-19 20:21:13 +00:00
if ( ! DI :: config () -> get ( 'system' , 'disable_password_exposed' , false ) && self :: isPasswordExposed ( $password )) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'The new password has been exposed in a public data dump, please choose another.' ));
2019-01-01 06:08:55 +00:00
}
$allowed_characters = '!"#$%&\'()*+,-./;<=>?@[\]^_`{|}~' ;
if ( ! preg_match ( '/^[a-z0-9' . preg_quote ( $allowed_characters , '/' ) . ']+$/i' , $password )) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'The password can\'t contain accentuated letters, white spaces or colons (:)' ));
2019-01-01 06:08:55 +00:00
}
2018-01-20 03:49:06 +00:00
return self :: updatePasswordHashed ( $uid , self :: hashPassword ( $password ));
}
/**
* Updates a user row with a new hashed password .
* Empties the password reset token field just in case .
*
* @ param int $uid
* @ param string $pasword_hashed
* @ return bool
2019-01-06 21:06:53 +00:00
* @ throws Exception
2018-01-20 03:49:06 +00:00
*/
private static function updatePasswordHashed ( $uid , $pasword_hashed )
{
2018-01-20 23:15:55 +00:00
$fields = [
'password' => $pasword_hashed ,
'pwdreset' => null ,
2018-01-21 03:29:03 +00:00
'pwdreset_time' => null ,
2018-04-15 08:51:22 +00:00
'legacy_password' => false
2018-01-20 23:15:55 +00:00
];
2018-07-20 12:19:26 +00:00
return DBA :: update ( 'user' , $fields , [ 'uid' => $uid ]);
2018-01-20 03:49:06 +00:00
}
2018-07-06 13:32:56 +00:00
/**
2020-01-19 06:05:23 +00:00
* Checks if a nickname is in the list of the forbidden nicknames
2018-07-06 13:32:56 +00:00
*
* Check if a nickname is forbidden from registration on the node by the
* admin . Forbidden nicknames ( e . g . role namess ) can be configured in the
* admin panel .
*
* @ param string $nickname The nickname that should be checked
* @ return boolean True is the nickname is blocked on the node
*/
public static function isNicknameBlocked ( $nickname )
{
2020-01-19 20:21:13 +00:00
$forbidden_nicknames = DI :: config () -> get ( 'system' , 'forbidden_nicknames' , '' );
2020-08-22 14:48:09 +00:00
if ( ! empty ( $forbidden_nicknames )) {
$forbidden = explode ( ',' , $forbidden_nicknames );
$forbidden = array_map ( 'trim' , $forbidden );
} else {
$forbidden = [];
}
// Add the name of the internal actor to the "forbidden" list
2020-08-22 18:52:37 +00:00
$actor_name = self :: getActorName ();
2020-08-22 14:48:09 +00:00
if ( ! empty ( $actor_name )) {
$forbidden [] = $actor_name ;
}
2018-07-23 01:18:21 +00:00
2020-08-22 14:48:09 +00:00
if ( empty ( $forbidden )) {
2018-07-06 13:32:56 +00:00
return false ;
}
2018-07-23 01:18:21 +00:00
2020-08-22 19:40:31 +00:00
// check if the nickname is in the list of blocked nicknames
2018-07-06 13:32:56 +00:00
if ( in_array ( strtolower ( $nickname ), $forbidden )) {
return true ;
}
2018-07-23 01:18:21 +00:00
2018-07-06 13:32:56 +00:00
// else return false
return false ;
}
2017-12-04 03:27:49 +00:00
/**
2020-01-19 06:05:23 +00:00
* Catch - all user creation function
2017-12-04 03:27:49 +00:00
*
* Creates a user from the provided data array , either form fields or OpenID .
* Required : { username , nickname , email } or { openid_url }
*
* Performs the following :
* - Sends to the OpenId auth URL ( if relevant )
* - Creates new key pairs for crypto
* - Create self - contact
* - Create profile image
*
2019-01-01 06:08:55 +00:00
* @ param array $data
* @ return array
2020-07-21 06:27:05 +00:00
* @ throws ErrorException
* @ throws HTTPException\InternalServerErrorException
* @ throws ImagickException
2019-01-01 06:08:55 +00:00
* @ throws Exception
2017-12-04 03:27:49 +00:00
*/
public static function create ( array $data )
{
2017-12-13 01:43:21 +00:00
$return = [ 'user' => null , 'password' => '' ];
2017-12-04 03:27:49 +00:00
2020-01-19 20:21:13 +00:00
$using_invites = DI :: config () -> get ( 'system' , 'invitation_only' );
2017-12-04 03:27:49 +00:00
2018-11-09 18:29:42 +00:00
$invite_id = ! empty ( $data [ 'invite_id' ]) ? Strings :: escapeTags ( trim ( $data [ 'invite_id' ])) : '' ;
$username = ! empty ( $data [ 'username' ]) ? Strings :: escapeTags ( trim ( $data [ 'username' ])) : '' ;
$nickname = ! empty ( $data [ 'nickname' ]) ? Strings :: escapeTags ( trim ( $data [ 'nickname' ])) : '' ;
$email = ! empty ( $data [ 'email' ]) ? Strings :: escapeTags ( trim ( $data [ 'email' ])) : '' ;
$openid_url = ! empty ( $data [ 'openid_url' ]) ? Strings :: escapeTags ( trim ( $data [ 'openid_url' ])) : '' ;
$photo = ! empty ( $data [ 'photo' ]) ? Strings :: escapeTags ( trim ( $data [ 'photo' ])) : '' ;
2018-07-23 01:18:21 +00:00
$password = ! empty ( $data [ 'password' ]) ? trim ( $data [ 'password' ]) : '' ;
$password1 = ! empty ( $data [ 'password1' ]) ? trim ( $data [ 'password1' ]) : '' ;
$confirm = ! empty ( $data [ 'confirm' ]) ? trim ( $data [ 'confirm' ]) : '' ;
2018-11-30 14:06:22 +00:00
$blocked = ! empty ( $data [ 'blocked' ]);
$verified = ! empty ( $data [ 'verified' ]);
2018-11-09 18:29:42 +00:00
$language = ! empty ( $data [ 'language' ]) ? Strings :: escapeTags ( trim ( $data [ 'language' ])) : 'en' ;
2018-07-23 01:18:21 +00:00
2020-02-16 15:39:44 +00:00
$netpublish = $publish = ! empty ( $data [ 'profile_publish_reg' ]);
2017-12-04 03:27:49 +00:00
if ( $password1 != $confirm ) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Passwords do not match. Password unchanged.' ));
2017-12-13 02:07:03 +00:00
} elseif ( $password1 != '' ) {
2017-12-04 03:27:49 +00:00
$password = $password1 ;
}
if ( $using_invites ) {
if ( ! $invite_id ) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'An invitation is required.' ));
2017-12-04 03:27:49 +00:00
}
2017-12-13 02:07:03 +00:00
2018-10-14 15:57:28 +00:00
if ( ! Register :: existsByHash ( $invite_id )) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Invitation could not be verified.' ));
2017-12-04 03:27:49 +00:00
}
}
2019-10-24 20:23:26 +00:00
/// @todo Check if this part is really needed. We should have fetched all this data in advance
2018-07-23 01:18:21 +00:00
if ( empty ( $username ) || empty ( $email ) || empty ( $nickname )) {
2017-12-04 03:27:49 +00:00
if ( $openid_url ) {
2018-01-27 16:13:41 +00:00
if ( ! Network :: isUrlValid ( $openid_url )) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Invalid OpenID url' ));
2017-12-04 03:27:49 +00:00
}
$_SESSION [ 'register' ] = 1 ;
$_SESSION [ 'openid' ] = $openid_url ;
2019-12-15 23:47:24 +00:00
$openid = new LightOpenID ( DI :: baseUrl () -> getHostname ());
2017-12-04 03:27:49 +00:00
$openid -> identity = $openid_url ;
2019-12-30 22:00:08 +00:00
$openid -> returnUrl = DI :: baseUrl () . '/openid' ;
2018-01-15 13:05:12 +00:00
$openid -> required = [ 'namePerson/friendly' , 'contact/email' , 'namePerson' ];
$openid -> optional = [ 'namePerson/first' , 'media/image/aspect11' , 'media/image/default' ];
2017-12-04 03:27:49 +00:00
try {
$authurl = $openid -> authUrl ();
} catch ( Exception $e ) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.' ) . EOL . EOL . DI :: l10n () -> t ( 'The error message was:' ) . $e -> getMessage (), 0 , $e );
2017-12-04 03:27:49 +00:00
}
2018-10-19 18:11:27 +00:00
System :: externalRedirect ( $authurl );
2017-12-04 03:27:49 +00:00
// NOTREACHED
}
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Please enter the required information.' ));
2017-12-04 03:27:49 +00:00
}
2018-01-27 16:13:41 +00:00
if ( ! Network :: isUrlValid ( $openid_url )) {
2017-12-04 03:27:49 +00:00
$openid_url = '' ;
}
// collapse multiple spaces in name
$username = preg_replace ( '/ +/' , ' ' , $username );
2020-01-19 20:21:13 +00:00
$username_min_length = max ( 1 , min ( 64 , intval ( DI :: config () -> get ( 'system' , 'username_min_length' , 3 ))));
$username_max_length = max ( 1 , min ( 64 , intval ( DI :: config () -> get ( 'system' , 'username_max_length' , 48 ))));
2018-10-20 20:33:54 +00:00
2018-10-21 12:28:24 +00:00
if ( $username_min_length > $username_max_length ) {
2020-01-18 19:52:34 +00:00
Logger :: log ( DI :: l10n () -> t ( 'system.username_min_length (%s) and system.username_max_length (%s) are excluding each other, swapping values.' , $username_min_length , $username_max_length ), Logger :: WARNING );
2018-10-21 12:28:24 +00:00
$tmp = $username_min_length ;
$username_min_length = $username_max_length ;
$username_max_length = $tmp ;
}
2018-10-20 20:33:54 +00:00
if ( mb_strlen ( $username ) < $username_min_length ) {
2020-01-18 19:53:01 +00:00
throw new Exception ( DI :: l10n () -> tt ( 'Username should be at least %s character.' , 'Username should be at least %s characters.' , $username_min_length ));
2017-12-04 03:27:49 +00:00
}
2018-10-20 20:33:54 +00:00
if ( mb_strlen ( $username ) > $username_max_length ) {
2020-01-18 19:53:01 +00:00
throw new Exception ( DI :: l10n () -> tt ( 'Username should be at most %s character.' , 'Username should be at most %s characters.' , $username_max_length ));
2017-12-04 03:27:49 +00:00
}
// So now we are just looking for a space in the full name.
2020-01-19 20:21:13 +00:00
$loose_reg = DI :: config () -> get ( 'system' , 'no_regfullname' );
2017-12-04 03:27:49 +00:00
if ( ! $loose_reg ) {
$username = mb_convert_case ( $username , MB_CASE_TITLE , 'UTF-8' );
2018-10-20 20:33:54 +00:00
if ( strpos ( $username , ' ' ) === false ) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( " That doesn't appear to be your full (First Last) name. " ));
2017-12-04 03:27:49 +00:00
}
}
2018-01-27 16:13:41 +00:00
if ( ! Network :: isEmailDomainAllowed ( $email )) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Your email domain is not among those allowed on this site.' ));
2017-12-04 03:27:49 +00:00
}
2018-11-09 18:18:42 +00:00
if ( ! filter_var ( $email , FILTER_VALIDATE_EMAIL ) || ! Network :: isEmailDomainValid ( $email )) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Not a valid email address.' ));
2017-12-04 03:27:49 +00:00
}
2018-07-06 13:32:56 +00:00
if ( self :: isNicknameBlocked ( $nickname )) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'The nickname was blocked from registration by the nodes admin.' ));
2018-07-06 13:32:56 +00:00
}
2017-12-04 03:27:49 +00:00
2020-01-19 20:21:13 +00:00
if ( DI :: config () -> get ( 'system' , 'block_extended_register' , false ) && DBA :: exists ( 'user' , [ 'email' => $email ])) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Cannot use that email.' ));
2017-12-13 02:07:03 +00:00
}
2017-12-04 03:27:49 +00:00
// Disallow somebody creating an account using openid that uses the admin email address,
// since openid bypasses email verification. We'll allow it if there is not yet an admin account.
2020-01-19 20:21:13 +00:00
if ( DI :: config () -> get ( 'config' , 'admin_email' ) && strlen ( $openid_url )) {
$adminlist = explode ( ',' , str_replace ( ' ' , '' , strtolower ( DI :: config () -> get ( 'config' , 'admin_email' ))));
2017-12-13 02:07:03 +00:00
if ( in_array ( strtolower ( $email ), $adminlist )) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Cannot use that email.' ));
2017-12-04 03:27:49 +00:00
}
}
$nickname = $data [ 'nickname' ] = strtolower ( $nickname );
2020-07-21 06:27:05 +00:00
if ( ! preg_match ( '/^[a-z0-9][a-z0-9_]*$/' , $nickname )) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Your nickname can only contain a-z, 0-9 and _.' ));
2017-12-04 03:27:49 +00:00
}
2017-12-13 02:07:03 +00:00
// Check existing and deleted accounts for this nickname.
2019-10-10 23:21:41 +00:00
if (
DBA :: exists ( 'user' , [ 'nickname' => $nickname ])
2018-07-20 12:19:26 +00:00
|| DBA :: exists ( 'userd' , [ 'username' => $nickname ])
2017-12-13 02:07:03 +00:00
) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Nickname is already registered. Please choose another.' ));
2017-12-04 03:27:49 +00:00
}
2018-01-20 03:49:06 +00:00
$new_password = strlen ( $password ) ? $password : User :: generateNewPassword ();
$new_password_encoded = self :: hashPassword ( $new_password );
2017-12-04 03:27:49 +00:00
2017-12-13 01:43:21 +00:00
$return [ 'password' ] = $new_password ;
2017-12-04 03:27:49 +00:00
2017-12-30 16:51:49 +00:00
$keys = Crypto :: newKeypair ( 4096 );
2017-12-04 03:27:49 +00:00
if ( $keys === false ) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'SERIOUS ERROR: Generation of security keys failed.' ));
2017-12-04 03:27:49 +00:00
}
$prvkey = $keys [ 'prvkey' ];
$pubkey = $keys [ 'pubkey' ];
// Create another keypair for signing/verifying salmon protocol messages.
2017-12-30 16:51:49 +00:00
$sres = Crypto :: newKeypair ( 512 );
2017-12-04 03:27:49 +00:00
$sprvkey = $sres [ 'prvkey' ];
$spubkey = $sres [ 'pubkey' ];
2018-07-20 12:19:26 +00:00
$insert_result = DBA :: insert ( 'user' , [
2018-09-27 11:52:15 +00:00
'guid' => System :: createUUID (),
2017-12-13 02:07:03 +00:00
'username' => $username ,
'password' => $new_password_encoded ,
'email' => $email ,
'openid' => $openid_url ,
'nickname' => $nickname ,
'pubkey' => $pubkey ,
'prvkey' => $prvkey ,
'spubkey' => $spubkey ,
'sprvkey' => $sprvkey ,
'verified' => $verified ,
'blocked' => $blocked ,
2018-05-31 06:27:27 +00:00
'language' => $language ,
2017-12-13 02:07:03 +00:00
'timezone' => 'UTC' ,
2018-01-27 02:38:34 +00:00
'register_date' => DateTimeFormat :: utcNow (),
2017-12-13 02:07:03 +00:00
'default-location' => ''
]);
if ( $insert_result ) {
2018-07-20 12:19:26 +00:00
$uid = DBA :: lastInsertId ();
$user = DBA :: selectFirst ( 'user' , [], [ 'uid' => $uid ]);
2017-12-04 03:27:49 +00:00
} else {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'An error occurred during registration. Please try again.' ));
2017-12-04 03:27:49 +00:00
}
2017-12-13 02:07:03 +00:00
if ( ! $uid ) {
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'An error occurred during registration. Please try again.' ));
2017-12-04 03:27:49 +00:00
}
2017-12-13 02:07:03 +00:00
// if somebody clicked submit twice very quickly, they could end up with two accounts
// due to race condition. Remove this one.
2018-07-20 12:19:26 +00:00
$user_count = DBA :: count ( 'user' , [ 'nickname' => $nickname ]);
2017-12-13 02:07:03 +00:00
if ( $user_count > 1 ) {
2018-07-20 12:19:26 +00:00
DBA :: delete ( 'user' , [ 'uid' => $uid ]);
2017-12-04 03:27:49 +00:00
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'Nickname is already registered. Please choose another.' ));
2017-12-13 02:07:03 +00:00
}
2017-12-13 01:43:21 +00:00
2018-07-20 12:19:26 +00:00
$insert_result = DBA :: insert ( 'profile' , [
2017-12-13 02:07:03 +00:00
'uid' => $uid ,
'name' => $username ,
2019-12-30 22:00:08 +00:00
'photo' => DI :: baseUrl () . " /photo/profile/ { $uid } .jpg " ,
'thumb' => DI :: baseUrl () . " /photo/avatar/ { $uid } .jpg " ,
2017-12-13 02:07:03 +00:00
'publish' => $publish ,
'net-publish' => $netpublish ,
]);
if ( ! $insert_result ) {
2018-07-20 12:19:26 +00:00
DBA :: delete ( 'user' , [ 'uid' => $uid ]);
2017-12-13 02:07:03 +00:00
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'An error occurred creating your default profile. Please try again.' ));
2017-12-13 02:07:03 +00:00
}
2017-12-04 03:27:49 +00:00
2017-12-13 02:07:03 +00:00
// Create the self contact
if ( ! Contact :: createSelfFromUserId ( $uid )) {
2018-07-20 12:19:26 +00:00
DBA :: delete ( 'user' , [ 'uid' => $uid ]);
2017-12-13 01:43:21 +00:00
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'An error occurred creating your self contact. Please try again.' ));
2017-12-13 02:07:03 +00:00
}
2017-12-04 03:27:49 +00:00
2017-12-13 02:07:03 +00:00
// Create a group with no members. This allows somebody to use it
// right away as a default group for new contacts.
2020-01-18 19:52:34 +00:00
$def_gid = Group :: create ( $uid , DI :: l10n () -> t ( 'Friends' ));
2017-12-13 02:07:03 +00:00
if ( ! $def_gid ) {
2018-07-20 12:19:26 +00:00
DBA :: delete ( 'user' , [ 'uid' => $uid ]);
2017-12-04 03:27:49 +00:00
2020-01-18 19:52:34 +00:00
throw new Exception ( DI :: l10n () -> t ( 'An error occurred creating your default contact group. Please try again.' ));
2017-12-13 02:07:03 +00:00
}
2017-12-04 03:27:49 +00:00
2017-12-13 02:07:03 +00:00
$fields = [ 'def_gid' => $def_gid ];
2020-01-19 20:21:13 +00:00
if ( DI :: config () -> get ( 'system' , 'newuser_private' ) && $def_gid ) {
2017-12-13 02:07:03 +00:00
$fields [ 'allow_gid' ] = '<' . $def_gid . '>' ;
2017-12-04 03:27:49 +00:00
}
2018-07-20 12:19:26 +00:00
DBA :: update ( 'user' , $fields , [ 'uid' => $uid ]);
2017-12-13 02:07:03 +00:00
2017-12-04 03:27:49 +00:00
// if we have no OpenID photo try to look up an avatar
if ( ! strlen ( $photo )) {
2018-01-27 16:13:41 +00:00
$photo = Network :: lookupAvatarByEmail ( $email );
2017-12-04 03:27:49 +00:00
}
2018-01-17 19:22:38 +00:00
// unless there is no avatar-addon loaded
2017-12-04 03:27:49 +00:00
if ( strlen ( $photo )) {
$photo_failure = false ;
$filename = basename ( $photo );
2020-03-04 21:35:09 +00:00
$curlResult = DI :: httpRequest () -> get ( $photo , true );
2020-04-01 05:42:44 +00:00
if ( $curlResult -> isSuccess ()) {
$img_str = $curlResult -> getBody ();
$type = $curlResult -> getContentType ();
} else {
$img_str = '' ;
$type = '' ;
}
$type = Images :: getMimeTypeByData ( $img_str , $photo , $type );
2017-12-04 03:27:49 +00:00
2017-12-07 13:56:11 +00:00
$Image = new Image ( $img_str , $type );
if ( $Image -> isValid ()) {
2018-10-23 14:36:57 +00:00
$Image -> scaleToSquare ( 300 );
2017-12-04 03:27:49 +00:00
2019-10-26 13:05:35 +00:00
$resource_id = Photo :: newResource ();
2017-12-04 03:27:49 +00:00
2020-01-18 19:52:34 +00:00
$r = Photo :: store ( $Image , $uid , 0 , $resource_id , $filename , DI :: l10n () -> t ( 'Profile Photos' ), 4 );
2017-12-04 03:27:49 +00:00
if ( $r === false ) {
$photo_failure = true ;
}
2017-12-07 13:56:11 +00:00
$Image -> scaleDown ( 80 );
2017-12-04 03:27:49 +00:00
2020-01-18 19:52:34 +00:00
$r = Photo :: store ( $Image , $uid , 0 , $resource_id , $filename , DI :: l10n () -> t ( 'Profile Photos' ), 5 );
2017-12-04 03:27:49 +00:00
if ( $r === false ) {
$photo_failure = true ;
}
2017-12-07 13:56:11 +00:00
$Image -> scaleDown ( 48 );
2017-12-04 03:27:49 +00:00
2020-01-18 19:52:34 +00:00
$r = Photo :: store ( $Image , $uid , 0 , $resource_id , $filename , DI :: l10n () -> t ( 'Profile Photos' ), 6 );
2017-12-04 03:27:49 +00:00
if ( $r === false ) {
$photo_failure = true ;
}
if ( ! $photo_failure ) {
2019-10-26 13:05:35 +00:00
Photo :: update ([ 'profile' => 1 ], [ 'resource-id' => $resource_id ]);
2017-12-04 03:27:49 +00:00
}
}
}
2018-12-26 06:06:24 +00:00
Hook :: callAll ( 'register_account' , $uid );
2017-12-04 03:27:49 +00:00
2017-12-16 01:47:10 +00:00
$return [ 'user' ] = $user ;
return $return ;
2017-12-04 03:27:49 +00:00
}
2020-02-20 22:43:52 +00:00
/**
* Sets block state for a given user
*
* @ param int $uid The user id
* @ param bool $block Block state ( default is true )
*
* @ return bool True , if successfully blocked
* @ throws Exception
*/
public static function block ( int $uid , bool $block = true )
{
2020-02-21 22:12:07 +00:00
return DBA :: update ( 'user' , [ 'blocked' => $block ], [ 'uid' => $uid ]);
2020-02-20 22:43:52 +00:00
}
2020-02-21 21:57:17 +00:00
/**
* Allows a registration based on a hash
*
* @ param string $hash
*
* @ return bool True , if the allow was successful
*
2020-07-21 06:27:05 +00:00
* @ throws HTTPException\InternalServerErrorException
2020-02-21 21:57:17 +00:00
* @ throws Exception
*/
public static function allow ( string $hash )
{
$register = Register :: getByHash ( $hash );
if ( ! DBA :: isResult ( $register )) {
return false ;
}
$user = User :: getById ( $register [ 'uid' ]);
if ( ! DBA :: isResult ( $user )) {
return false ;
}
Register :: deleteByHash ( $hash );
DBA :: update ( 'user' , [ 'blocked' => false , 'verified' => true ], [ 'uid' => $register [ 'uid' ]]);
$profile = DBA :: selectFirst ( 'profile' , [ 'net-publish' ], [ 'uid' => $register [ 'uid' ]]);
if ( DBA :: isResult ( $profile ) && $profile [ 'net-publish' ] && DI :: config () -> get ( 'system' , 'directory' )) {
$url = DI :: baseUrl () . '/profile/' . $user [ 'nickname' ];
Worker :: add ( PRIORITY_LOW , " Directory " , $url );
}
$l10n = DI :: l10n () -> withLang ( $register [ 'language' ]);
return User :: sendRegisterOpenEmail (
$l10n ,
$user ,
DI :: config () -> get ( 'config' , 'sitename' ),
DI :: baseUrl () -> get (),
( $register [ 'password' ] ? ? '' ) ? : 'Sent in a previous email'
);
}
2020-02-21 22:03:33 +00:00
/**
* Denys a pending registration
*
* @ param string $hash The hash of the pending user
*
* This does not have to go through user_remove () and save the nickname
* permanently against re - registration , as the person was not yet
* allowed to have friends on this system
*
* @ return bool True , if the deny was successfull
* @ throws Exception
*/
public static function deny ( string $hash )
{
$register = Register :: getByHash ( $hash );
if ( ! DBA :: isResult ( $register )) {
return false ;
}
$user = User :: getById ( $register [ 'uid' ]);
if ( ! DBA :: isResult ( $user )) {
return false ;
}
return DBA :: delete ( 'user' , [ 'uid' => $register [ 'uid' ]]) &&
Register :: deleteByHash ( $register [ 'hash' ]);
}
2020-02-21 21:57:17 +00:00
/**
* Creates a new user based on a minimal set and sends an email to this user
*
* @ param string $name The user ' s name
* @ param string $email The user ' s email address
* @ param string $nick The user ' s nick name
* @ param string $lang The user ' s language ( default is english )
*
* @ return bool True , if the user was created successfully
2020-07-21 06:27:05 +00:00
* @ throws HTTPException\InternalServerErrorException
* @ throws ErrorException
* @ throws ImagickException
2020-02-21 21:57:17 +00:00
*/
public static function createMinimal ( string $name , string $email , string $nick , string $lang = L10n :: DEFAULT )
{
if ( empty ( $name ) ||
empty ( $email ) ||
empty ( $nick )) {
2020-07-21 06:27:05 +00:00
throw new HTTPException\InternalServerErrorException ( 'Invalid arguments.' );
2020-02-21 21:57:17 +00:00
}
$result = self :: create ([
'username' => $name ,
'email' => $email ,
'nickname' => $nick ,
'verified' => 1 ,
'language' => $lang
]);
$user = $result [ 'user' ];
$preamble = Strings :: deindent ( DI :: l10n () -> t ( '
Dear % 1 $s ,
the administrator of % 2 $s has set up an account for you . ' ));
$body = Strings :: deindent ( DI :: l10n () -> t ( '
The login details are as follows :
Site Location : % 1 $s
Login Name : % 2 $s
Password : % 3 $s
You may change your password from your account " Settings " page after logging
in .
Please take a few moments to review the other account settings on that page .
You may also wish to add some basic information to your default profile
( on the " Profiles " page ) so that other people can easily find you .
We recommend setting your full name , adding a profile photo ,
adding some profile " keywords " ( very useful in making new friends ) - and
perhaps what country you live in ; if you do not wish to be more specific
than that .
We fully respect your right to privacy , and none of these items are necessary .
If you are new and do not know anybody here , they may help
you to make some new and interesting friends .
If you ever want to delete your account , you can do so at % 1 $s / removeme
Thank you and welcome to % 4 $s . ' ));
$preamble = sprintf ( $preamble , $user [ 'username' ], DI :: config () -> get ( 'config' , 'sitename' ));
$body = sprintf ( $body , DI :: baseUrl () -> get (), $user [ 'nickname' ], $result [ 'password' ], DI :: config () -> get ( 'config' , 'sitename' ));
$email = DI :: emailer ()
-> newSystemMail ()
-> withMessage ( DI :: l10n () -> t ( 'Registration details for %s' , DI :: config () -> get ( 'config' , 'sitename' )), $preamble , $body )
-> forUser ( $user )
-> withRecipient ( $user [ 'email' ])
-> build ();
return DI :: emailer () -> send ( $email );
}
2017-12-04 03:27:49 +00:00
/**
2020-01-19 06:05:23 +00:00
* Sends pending registration confirmation email
2017-12-04 03:27:49 +00:00
*
2018-10-15 15:58:52 +00:00
* @ param array $user User record array
2017-12-04 03:27:49 +00:00
* @ param string $sitename
2018-10-15 15:58:52 +00:00
* @ param string $siteurl
2018-10-14 15:57:28 +00:00
* @ param string $password Plaintext password
2017-12-04 03:27:49 +00:00
* @ return NULL | boolean from notification () and email () inherited
2020-07-21 06:27:05 +00:00
* @ throws HTTPException\InternalServerErrorException
2017-12-04 03:27:49 +00:00
*/
2018-10-15 15:58:52 +00:00
public static function sendRegisterPendingEmail ( $user , $sitename , $siteurl , $password )
2017-12-04 03:27:49 +00:00
{
2020-01-18 19:52:34 +00:00
$body = Strings :: deindent ( DI :: l10n () -> t (
2019-10-10 23:21:41 +00:00
'
2017-12-04 03:27:49 +00:00
Dear % 1 $s ,
Thank you for registering at % 2 $s . Your account is pending for approval by the administrator .
2018-10-14 15:57:28 +00:00
Your login details are as follows :
Site Location : % 3 $s
Login Name : % 4 $s
Password : % 5 $s
' ,
2019-10-10 23:21:41 +00:00
$user [ 'username' ],
$sitename ,
$siteurl ,
$user [ 'nickname' ],
$password
2018-10-14 15:57:28 +00:00
));
2017-12-04 03:27:49 +00:00
2020-02-01 19:08:54 +00:00
$email = DI :: emailer ()
2020-02-04 20:04:08 +00:00
-> newSystemMail ()
2020-02-02 08:22:30 +00:00
-> withMessage ( DI :: l10n () -> t ( 'Registration at %s' , $sitename ), $body )
2020-02-04 20:04:08 +00:00
-> forUser ( $user )
2020-02-02 08:22:30 +00:00
-> withRecipient ( $user [ 'email' ])
-> build ();
2020-02-01 19:08:54 +00:00
return DI :: emailer () -> send ( $email );
2017-12-04 03:27:49 +00:00
}
/**
2020-01-19 06:05:23 +00:00
* Sends registration confirmation
2017-12-04 03:27:49 +00:00
*
* It ' s here as a function because the mail is sent from different parts
*
2020-07-21 06:27:05 +00:00
* @ param L10n $l10n The used language
* @ param array $user User record array
* @ param string $sitename
* @ param string $siteurl
* @ param string $password Plaintext password
2020-01-18 19:59:39 +00:00
*
2017-12-04 03:27:49 +00:00
* @ return NULL | boolean from notification () and email () inherited
2020-07-21 06:27:05 +00:00
* @ throws HTTPException\InternalServerErrorException
2017-12-04 03:27:49 +00:00
*/
2020-07-21 06:27:05 +00:00
public static function sendRegisterOpenEmail ( L10n $l10n , $user , $sitename , $siteurl , $password )
2017-12-04 03:27:49 +00:00
{
2019-12-28 22:12:01 +00:00
$preamble = Strings :: deindent ( $l10n -> t (
2019-10-10 23:21:41 +00:00
'
Dear % 1 $s ,
2017-12-04 03:27:49 +00:00
Thank you for registering at % 2 $s . Your account has been created .
2019-10-10 23:21:41 +00:00
' ,
$user [ 'username' ],
$sitename
2018-10-14 15:57:28 +00:00
));
2019-12-28 22:12:01 +00:00
$body = Strings :: deindent ( $l10n -> t (
2019-10-10 23:21:41 +00:00
'
2017-12-04 03:27:49 +00:00
The login details are as follows :
2018-04-04 19:56:34 +00:00
Site Location : % 3 $s
Login Name : % 1 $s
Password : % 5 $s
2018-04-02 16:40:52 +00:00
You may change your password from your account " Settings " page after logging
2017-12-04 03:27:49 +00:00
in .
Please take a few moments to review the other account settings on that page .
You may also wish to add some basic information to your default profile
2018-01-24 21:51:32 +00:00
' . "\x28" . ' on the " Profiles " page ' . "\x29" . ' so that other people can easily find you .
2017-12-04 03:27:49 +00:00
We recommend setting your full name , adding a profile photo ,
2018-04-02 16:40:52 +00:00
adding some profile " keywords " ' . "\x28" . ' very useful in making new friends ' . "\x29" . ' - and
2017-12-04 03:27:49 +00:00
perhaps what country you live in ; if you do not wish to be more specific
than that .
We fully respect your right to privacy , and none of these items are necessary .
If you are new and do not know anybody here , they may help
you to make some new and interesting friends .
2018-04-04 19:56:34 +00:00
If you ever want to delete your account , you can do so at % 3 $s / removeme
2017-12-04 03:27:49 +00:00
2018-10-14 15:57:28 +00:00
Thank you and welcome to % 2 $s . ' ,
2019-10-10 23:21:41 +00:00
$user [ 'nickname' ],
$sitename ,
$siteurl ,
$user [ 'username' ],
$password
2018-10-14 15:57:28 +00:00
));
2017-12-04 03:27:49 +00:00
2020-02-01 19:08:54 +00:00
$email = DI :: emailer ()
2020-02-04 20:04:08 +00:00
-> newSystemMail ()
2020-02-02 08:22:30 +00:00
-> withMessage ( DI :: l10n () -> t ( 'Registration details for %s' , $sitename ), $preamble , $body )
2020-02-04 20:04:08 +00:00
-> forUser ( $user )
2020-02-02 08:22:30 +00:00
-> withRecipient ( $user [ 'email' ])
-> build ();
2020-02-01 19:08:54 +00:00
return DI :: emailer () -> send ( $email );
2017-12-04 03:27:49 +00:00
}
2017-11-20 16:14:35 +00:00
/**
2020-02-21 22:12:07 +00:00
* @ param int $uid user to remove
2019-01-06 21:06:53 +00:00
* @ return bool
2020-07-21 06:27:05 +00:00
* @ throws HTTPException\InternalServerErrorException
2017-11-20 16:14:35 +00:00
*/
2020-02-21 22:12:07 +00:00
public static function remove ( int $uid )
2017-11-19 21:55:28 +00:00
{
if ( ! $uid ) {
2018-11-25 01:58:41 +00:00
return false ;
2017-11-19 21:55:28 +00:00
}
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Removing user: ' . $uid );
2017-11-19 21:55:28 +00:00
2018-07-20 12:19:26 +00:00
$user = DBA :: selectFirst ( 'user' , [], [ 'uid' => $uid ]);
2017-11-19 21:55:28 +00:00
2018-11-25 01:56:38 +00:00
Hook :: callAll ( 'remove_user' , $user );
2017-11-19 21:55:28 +00:00
// save username (actually the nickname as it is guaranteed
// unique), so it cannot be re-registered in the future.
2018-07-20 12:19:26 +00:00
DBA :: insert ( 'userd' , [ 'username' => $user [ 'nickname' ]]);
2017-11-19 21:55:28 +00:00
2020-08-16 08:39:04 +00:00
// The user and related data will be deleted in Friendica\Worker\ExpireAndRemoveUsers
2018-11-25 01:59:18 +00:00
DBA :: update ( 'user' , [ 'account_removed' => true , 'account_expires_on' => DateTimeFormat :: utc ( 'now + 7 day' )], [ 'uid' => $uid ]);
2019-06-10 14:19:24 +00:00
Worker :: add ( PRIORITY_HIGH , 'Notifier' , Delivery :: REMOVAL , $uid );
2017-11-19 21:55:28 +00:00
// Send an update to the directory
2018-08-02 05:21:01 +00:00
$self = DBA :: selectFirst ( 'contact' , [ 'url' ], [ 'uid' => $uid , 'self' => true ]);
2018-11-25 01:58:11 +00:00
Worker :: add ( PRIORITY_LOW , 'Directory' , $self [ 'url' ]);
2017-11-19 21:55:28 +00:00
2018-08-25 21:48:50 +00:00
// Remove the user relevant data
2019-02-03 23:55:46 +00:00
Worker :: add ( PRIORITY_NEGLIGIBLE , 'RemoveUser' , $uid );
2018-08-25 21:48:50 +00:00
2018-11-25 01:58:41 +00:00
return true ;
2017-11-19 21:55:28 +00:00
}
2018-11-07 23:22:15 +00:00
/**
* Return all identities to a user
*
* @ param int $uid The user id
* @ return array All identities for this user
*
* Example for a return :
2019-01-06 21:06:53 +00:00
* [
* [
* 'uid' => 1 ,
* 'username' => 'maxmuster' ,
* 'nickname' => 'Max Mustermann'
* ],
* [
* 'uid' => 2 ,
* 'username' => 'johndoe' ,
* 'nickname' => 'John Doe'
* ]
* ]
* @ throws Exception
2018-11-07 23:22:15 +00:00
*/
public static function identities ( $uid )
{
$identities = [];
$user = DBA :: selectFirst ( 'user' , [ 'uid' , 'nickname' , 'username' , 'parent-uid' ], [ 'uid' => $uid ]);
if ( ! DBA :: isResult ( $user )) {
return $identities ;
}
if ( $user [ 'parent-uid' ] == 0 ) {
// First add our own entry
2019-10-10 23:21:41 +00:00
$identities = [[
'uid' => $user [ 'uid' ],
2018-11-07 23:22:15 +00:00
'username' => $user [ 'username' ],
2019-10-10 23:21:41 +00:00
'nickname' => $user [ 'nickname' ]
]];
2018-11-07 23:22:15 +00:00
// Then add all the children
2019-10-10 23:21:41 +00:00
$r = DBA :: select (
'user' ,
[ 'uid' , 'username' , 'nickname' ],
[ 'parent-uid' => $user [ 'uid' ], 'account_removed' => false ]
);
2018-11-07 23:22:15 +00:00
if ( DBA :: isResult ( $r )) {
$identities = array_merge ( $identities , DBA :: toArray ( $r ));
}
} else {
// First entry is our parent
2019-10-10 23:21:41 +00:00
$r = DBA :: select (
'user' ,
[ 'uid' , 'username' , 'nickname' ],
[ 'uid' => $user [ 'parent-uid' ], 'account_removed' => false ]
);
2018-11-07 23:22:15 +00:00
if ( DBA :: isResult ( $r )) {
$identities = DBA :: toArray ( $r );
}
// Then add all siblings
2019-10-10 23:21:41 +00:00
$r = DBA :: select (
'user' ,
[ 'uid' , 'username' , 'nickname' ],
[ 'parent-uid' => $user [ 'parent-uid' ], 'account_removed' => false ]
);
2018-11-07 23:22:15 +00:00
if ( DBA :: isResult ( $r )) {
$identities = array_merge ( $identities , DBA :: toArray ( $r ));
}
}
2019-10-10 23:21:41 +00:00
$r = DBA :: p (
" SELECT `user`.`uid`, `user`.`username`, `user`.`nickname`
2018-11-07 23:22:15 +00:00
FROM `manage`
INNER JOIN `user` ON `manage` . `mid` = `user` . `uid`
WHERE `user` . `account_removed` = 0 AND `manage` . `uid` = ? " ,
$user [ 'uid' ]
);
if ( DBA :: isResult ( $r )) {
$identities = array_merge ( $identities , DBA :: toArray ( $r ));
}
return $identities ;
}
2019-04-22 12:00:17 +00:00
/**
* Returns statistical information about the current users of this node
*
* @ return array
*
* @ throws Exception
*/
public static function getStatistics ()
{
$statistics = [
'total_users' => 0 ,
'active_users_halfyear' => 0 ,
'active_users_monthly' => 0 ,
2020-07-12 21:53:17 +00:00
'active_users_weekly' => 0 ,
2019-04-22 12:00:17 +00:00
];
2020-04-24 11:04:50 +00:00
$userStmt = DBA :: select ( 'owner-view' , [ 'uid' , 'login_date' , 'last-item' ],
[ " `verified` AND `login_date` > ? AND NOT `blocked`
AND NOT `account_removed` AND NOT `account_expired` " ,
DBA :: NULL_DATETIME ]);
2019-04-22 12:00:17 +00:00
if ( ! DBA :: isResult ( $userStmt )) {
return $statistics ;
}
$halfyear = time () - ( 180 * 24 * 60 * 60 );
$month = time () - ( 30 * 24 * 60 * 60 );
2020-07-12 21:53:17 +00:00
$week = time () - ( 7 * 24 * 60 * 60 );
2019-04-22 12:00:17 +00:00
while ( $user = DBA :: fetch ( $userStmt )) {
$statistics [ 'total_users' ] ++ ;
2019-10-10 23:21:41 +00:00
if (( strtotime ( $user [ 'login_date' ]) > $halfyear ) || ( strtotime ( $user [ 'last-item' ]) > $halfyear )
) {
2019-04-22 12:00:17 +00:00
$statistics [ 'active_users_halfyear' ] ++ ;
}
2019-10-10 23:21:41 +00:00
if (( strtotime ( $user [ 'login_date' ]) > $month ) || ( strtotime ( $user [ 'last-item' ]) > $month )
) {
2019-04-22 12:00:17 +00:00
$statistics [ 'active_users_monthly' ] ++ ;
}
2020-07-12 21:53:17 +00:00
if (( strtotime ( $user [ 'login_date' ]) > $week ) || ( strtotime ( $user [ 'last-item' ]) > $week )
) {
$statistics [ 'active_users_weekly' ] ++ ;
}
2019-04-22 12:00:17 +00:00
}
2020-04-28 05:55:17 +00:00
DBA :: close ( $userStmt );
2019-04-22 12:00:17 +00:00
return $statistics ;
}
2020-02-25 21:16:27 +00:00
/**
* Get all users of the current node
*
* @ param int $start Start count ( Default is 0 )
* @ param int $count Count of the items per page ( Default is @ see Pager :: ITEMS_PER_PAGE )
2020-02-25 22:22:47 +00:00
* @ param string $type The type of users , which should get ( all , bocked , removed )
2020-02-25 21:16:27 +00:00
* @ param string $order Order of the user list ( Default is 'contact.name' )
2020-04-24 11:04:50 +00:00
* @ param bool $descending Order direction ( Default is ascending )
2020-02-25 21:16:27 +00:00
*
* @ return array The list of the users
* @ throws Exception
*/
2020-04-24 11:04:50 +00:00
public static function getList ( $start = 0 , $count = Pager :: ITEMS_PER_PAGE , $type = 'all' , $order = 'name' , bool $descending = false )
2020-02-25 21:16:27 +00:00
{
2020-04-24 11:04:50 +00:00
$param = [ 'limit' => [ $start , $count ], 'order' => [ $order => $descending ]];
$condition = [];
2020-02-25 22:22:47 +00:00
switch ( $type ) {
case 'active' :
2020-07-06 19:26:39 +00:00
$condition [ 'account_removed' ] = false ;
2020-04-24 11:04:50 +00:00
$condition [ 'blocked' ] = false ;
2020-02-25 22:22:47 +00:00
break ;
case 'blocked' :
2020-04-24 11:04:50 +00:00
$condition [ 'blocked' ] = true ;
2020-02-25 22:22:47 +00:00
break ;
case 'removed' :
2020-04-24 11:04:50 +00:00
$condition [ 'account_removed' ] = true ;
2020-02-25 22:22:47 +00:00
break ;
}
2020-04-24 11:04:50 +00:00
return DBA :: selectToArray ( 'owner-view' , [], $condition , $param );
2020-02-25 21:16:27 +00:00
}
2017-11-19 21:55:28 +00:00
}