mirror of
https://github.com/e107inc/e107.git
synced 2025-01-16 20:28:28 +01:00
dd36fbd51d
Along with extensive documentation, `getperms()` is now deprecated and its replacements now have first-class support: * `e_user_model::checkAdminPerms()` and `getperms()` both use `e_userperms::simulateHasAdminPerms()`. * `e_user_model::checkPluginAdminPerms()` and `getperms('P', …, …)` both use `e_userperms::simulateHasPluginAdminPerms()`. ---- Partially reverts: https://github.com/e107inc/e107/commit/44526b43 Reverts: https://github.com/e107inc/e107/commit/001799cb Fixes: https://github.com/e107inc/e107/issues/5064
2540 lines
72 KiB
PHP
2540 lines
72 KiB
PHP
<?php
|
|
/*
|
|
* e107 website system
|
|
*
|
|
* Copyright (C) 2008-2016 e107 Inc (e107.org)
|
|
* Released under the terms and conditions of the
|
|
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
|
|
*
|
|
* Handler - user-related functions
|
|
*
|
|
*/
|
|
|
|
|
|
/**
|
|
*
|
|
* @package e107
|
|
* @subpackage e107_handlers
|
|
*
|
|
* USER HANDLER CLASS - manages login and various user functions
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
if (!defined('e107_INIT')) { exit; }
|
|
|
|
// Codes for `user_ban` field (not all used ATM)
|
|
define('USER_VALIDATED',0);
|
|
define('USER_BANNED',1);
|
|
define('USER_REGISTERED_NOT_VALIDATED',2);
|
|
define('USER_EMAIL_BOUNCED', 3);
|
|
define('USER_BOUNCED_RESET', 4);
|
|
define('USER_TEMPORARY_ACCOUNT', 5);
|
|
|
|
|
|
define('PASSWORD_E107_MD5',0);
|
|
define('PASSWORD_E107_SALT',1);
|
|
define('PASSWORD_E107_PHP', 3); // PHP Default - Using the bcrypt algorithm (default as of PHP 5.5.0).
|
|
|
|
define('PASSWORD_E107_ID','$E$'); // E107 salted
|
|
|
|
|
|
define('PASSWORD_INVALID', false);
|
|
define('PASSWORD_VALID',TRUE);
|
|
define ('PASSWORD_DEFAULT_TYPE',PASSWORD_E107_MD5);
|
|
//define ('PASSWORD_DEFAULT_TYPE',PASSWORD_E107_SALT);
|
|
|
|
// Required language file - if not loaded elsewhere, uncomment next line
|
|
e107::includeLan(e_LANGUAGEDIR.e_LANGUAGE.'/lan_user.php');
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class UserHandler
|
|
{
|
|
var $userVettingInfo = array();
|
|
var $preferred = PASSWORD_DEFAULT_TYPE; // Preferred password format
|
|
var $passwordOpts = 0; // Copy of pref
|
|
var $passwordEmail = false; // True if can use email address to log in
|
|
private $otherFields = array();
|
|
private $passwordAPI = false;
|
|
|
|
// Constructor
|
|
public function __construct()
|
|
{
|
|
$pref = e107::getPref();
|
|
e107::lan('core','user');
|
|
|
|
if(function_exists('password_verify'))
|
|
{
|
|
$this->passwordAPI = true;
|
|
}
|
|
|
|
/**
|
|
Table of vetting methods for user data - lists every field whose value could be set manually.
|
|
Valid 'vetMethod' values (use comma separated list for multiple vetting):
|
|
0 - Null method
|
|
1 - Check for duplicates
|
|
2 - Check against $pref['signup_disallow_text']
|
|
3 - Check email address against remote server, only if option enabled
|
|
|
|
Index is the destination field name. If the source index name is different, specify 'srcName' in the array.
|
|
|
|
Possible processing options:
|
|
'dbClean' - 'sanitising' method for final value:
|
|
- 'toDB' - passes final value through $tp->toDB()
|
|
- 'intval' - converts to an integer
|
|
- 'image' - checks image for size
|
|
- 'avatar' - checks an image in the avatars directory
|
|
'stripTags' - strips HTML tags from the value (not an error if there are some)
|
|
'minLength' - minimum length (in utf-8 characters) for the string
|
|
'maxLength' - minimum length (in utf-8 characters) for the string
|
|
'longTrim' - if set, and the string exceeds maxLength, its trimmed
|
|
'enablePref' - value is processed only if the named $pref evaluates to true; otherwise any input is discarded without error
|
|
*/
|
|
$this->userVettingInfo = array(
|
|
'user_name' => array('niceName'=> LAN_USER_01, 'fieldType' => 'string', 'vetMethod' => '1,2', 'vetParam' => 'signup_disallow_text', 'srcName' => 'username', 'stripTags' => TRUE, 'stripChars' => '/ |\#|\=|\$/', 'fixedBlock' => 'anonymous', 'minLength' => 2, 'maxLength' => varset($pref['displayname_maxlength'],15)), // Display name
|
|
//'user_loginname' => array('niceName'=> LAN_USER_02, 'fieldType' => 'string', 'vetMethod' => '1', 'vetParam' => '', 'srcName' => 'loginname', 'stripTags' => TRUE, 'stripChars' => '#[^a-z0-9_\.]#i', 'minLength' => 2, 'maxLength' => varset($pref['loginname_maxlength'],30)), // User name
|
|
'user_loginname' => array('niceName'=> LAN_USER_02, 'fieldType' => 'string', 'vetMethod' => '1', 'vetParam' => '', 'srcName' => 'loginname', 'stripTags' => TRUE, 'stripChars' => '#[^\p{L}\p{M}a-z0-9_\.]#ui', 'minLength' => 2, 'maxLength' => varset($pref['loginname_maxlength'],30)), // User name
|
|
'user_login' => array('niceName'=> LAN_USER_03, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'realname', 'dbClean' => 'toDB', 'stripTags' => TRUE, 'stripChars' => '#<|>#i'), // Real name (no real vetting)
|
|
'user_customtitle' => array('niceName'=> LAN_USER_04, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'customtitle', 'dbClean' => 'toDB', 'enablePref' => 'signup_option_customtitle', 'stripTags' => TRUE, 'stripChars' => '#<|>#i'), // No real vetting
|
|
'user_password' => array('niceName'=> LAN_PASSWORD, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'password1', 'dataType' => 2, 'minLength' => varset($pref['signup_pass_len'],1)),
|
|
'user_sess' => array('niceName'=> LAN_USER_06, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'stripChars' => "#\"|'|(|)#", 'dbClean' => 'image', 'imagePath' => e_AVATAR_UPLOAD, 'maxHeight' => varset($pref['im_height'], 100), 'maxWidth' => varset($pref['im_width'], 120)), // Photo
|
|
'user_image' => array('niceName'=> LAN_USER_07, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'image', 'stripChars' => "#\"|'|(|)#", 'dbClean' => 'avatar'), //, 'maxHeight' => varset($pref['im_height'], 100), 'maxWidth' => varset($pref['im_width'], 120) resized on-the-fly // Avatar
|
|
'user_email' => array('niceName'=> LAN_EMAIL, 'fieldType' => 'string', 'vetMethod' => '1,3', 'vetParam' => '', 'fieldOptional' => varset($pref['disable_emailcheck'],0), 'srcName' => 'email', 'dbClean' => 'toDB'),
|
|
'user_signature' => array('niceName'=> LAN_USER_09, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'signature', 'dbClean' => 'toDB'),
|
|
'user_hideemail' => array('niceName'=> LAN_USER_10, 'fieldType' => 'int', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'hideemail', 'dbClean' => 'intval'),
|
|
'user_xup' => array('niceName'=> "XUP File", 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'user_xup', 'dbClean' => 'toDB'),
|
|
'user_class' => array('niceName'=> LAN_USER_12, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'class', 'dataType' => '1')
|
|
);
|
|
|
|
$this->otherFields = array(
|
|
'user_join' => LAN_USER_14,
|
|
'user_lastvisit' => LAN_USER_15,
|
|
'user_currentvisit' => LAN_USER_16,
|
|
'user_comments' => LAN_COMMENTS,
|
|
'user_ip' => LAN_USER_18,
|
|
'user_ban' => LAN_USER_19,
|
|
'user_prefs' => LAN_USER_20,
|
|
'user_visits' => LAN_USER_21,
|
|
'user_admin' => LAN_USER_22,
|
|
'user_perms' => LAN_USER_23,
|
|
'user_pwchange' => LAN_USER_24
|
|
// user_chats int(10) unsigned NOT NULL default '0',
|
|
);
|
|
|
|
$this->otherFieldTypes = array(
|
|
'user_join' => 'int',
|
|
'user_lastvisit' => 'int',
|
|
'user_currentvisit' => 'int',
|
|
'user_comments' => 'int',
|
|
'user_ip' => 'string',
|
|
'user_ban' => 'int',
|
|
'user_prefs' => 'string',
|
|
'user_visits' => 'int',
|
|
'user_admin' => 'int',
|
|
'user_perms' => 'string',
|
|
'user_pwchange' => 'int'
|
|
);
|
|
|
|
$this->passwordOpts = varset($pref['passwordEncoding'], 0);
|
|
$this->passwordEmail = varset($pref['allowEmailLogin'], false);
|
|
|
|
switch ($this->passwordOpts)
|
|
{
|
|
case 3 :
|
|
$this->preferred = PASSWORD_E107_PHP;
|
|
break;
|
|
|
|
case 1 :
|
|
case 2 :
|
|
$this->preferred = PASSWORD_E107_SALT;
|
|
break;
|
|
|
|
case 0 :
|
|
default :
|
|
$this->preferred = PASSWORD_E107_MD5;
|
|
$this->passwordOpts = 0; // In case it got set to some stupid value
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the code for the current default password hash-type
|
|
* @return int
|
|
*/
|
|
public function getDefaultHashType()
|
|
{
|
|
return $this->preferred;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns true if PHP5.5+ password API is found, otherwise return false.
|
|
* @return bool
|
|
*/
|
|
public function passwordAPIExists()
|
|
{
|
|
return $this->passwordAPI;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if a user posted field is readonly (should not be user-editable) - used in usersettings.php
|
|
* @param array $posted
|
|
* @return bool
|
|
*/
|
|
public function hasReadonlyField($posted)
|
|
{
|
|
$restricted = array_keys($this->otherFields);
|
|
|
|
$pref = e107::getPref();
|
|
|
|
if(empty($pref['signup_option_class']))
|
|
{
|
|
$restricted[] = 'user_class';
|
|
}
|
|
|
|
foreach($posted as $k=>$v)
|
|
{
|
|
if(in_array($k,$restricted))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Given plaintext password and login name, generate password string to store in DB
|
|
*
|
|
* @param string $password - plaintext password as entered by user
|
|
* @param string $login_name - string used to log in (could actually be email address)
|
|
* @param string $force empty| PASSWORD_E107_MD 5| PASSWORD_E107_SALT | PASSWORD_E107_PHP $force - if non-empty, forces a particular type of password
|
|
*
|
|
* @return string|boolean - false if invalid emcoding method, else encoded password to store in DB
|
|
*/
|
|
public function HashPassword($password, $login_name='', $force=false)
|
|
{
|
|
if($force === false)
|
|
{
|
|
$force = $this->preferred;
|
|
}
|
|
|
|
if(($force == PASSWORD_E107_PHP) && $this->passwordAPI === false)
|
|
{
|
|
$force = PASSWORD_E107_SALT; // fallback.
|
|
}
|
|
|
|
switch ($force)
|
|
{
|
|
case PASSWORD_E107_MD5 :
|
|
return md5($password);
|
|
|
|
case PASSWORD_E107_SALT :
|
|
return PASSWORD_E107_ID.md5(md5($password).$login_name);
|
|
break;
|
|
|
|
case PASSWORD_E107_PHP :
|
|
return password_hash($password, PASSWORD_DEFAULT);
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Verify existing plaintext password against a stored hash value (which defines the encoding format and any 'salt')
|
|
*
|
|
* @param string $password - plaintext password as entered by user
|
|
* @param string $login_name - string used to log in (could actually be email address)
|
|
* @param string $stored_hash - required value for password to match
|
|
*
|
|
* @return string PASSWORD_INVALID|PASSWORD_VALID|string
|
|
* PASSWORD_INVALID if no match
|
|
* PASSWORD_VALID if valid password
|
|
* Return a new hash to store if valid password but non-preferred encoding
|
|
*/
|
|
public function CheckPassword($password, $login_name, $stored_hash)
|
|
{
|
|
$password = trim($password);
|
|
|
|
if(empty($password))
|
|
{
|
|
return PASSWORD_INVALID;
|
|
}
|
|
|
|
$type = $this->getHashType($stored_hash);
|
|
|
|
switch($type)
|
|
{
|
|
case PASSWORD_E107_MD5:// &&
|
|
if (md5($password) !== $stored_hash) return PASSWORD_INVALID;
|
|
if ($this->preferred == PASSWORD_E107_MD5 && ($this->passwordOpts <= 1)) return PASSWORD_VALID;
|
|
return $this->HashPassword($password); // Valid password, but non-preferred encoding; return the new hash
|
|
break;
|
|
|
|
case PASSWORD_E107_SALT:
|
|
$hash = $this->HashPassword($password, $login_name, PASSWORD_E107_SALT);
|
|
if ($hash === false) return PASSWORD_INVALID;
|
|
return ($hash == $stored_hash) ? PASSWORD_VALID : PASSWORD_INVALID;
|
|
break;
|
|
|
|
case PASSWORD_E107_PHP: // PHP 5.5+ Blowfish+
|
|
if($this->passwordAPI === true && password_verify($password,$stored_hash))
|
|
{
|
|
return PASSWORD_VALID;
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
return PASSWORD_INVALID;
|
|
}
|
|
|
|
|
|
/**
|
|
* If necessary, rehash the user password to the currently set algorythm and updated database. .
|
|
* @param array $user - user fields. required: user_id, user_loginname, user_password
|
|
* @param string $password - plain text password.
|
|
* @return bool|string returns new password hash on success or false.
|
|
*/
|
|
public function rehashPassword($user, $password)
|
|
{
|
|
$type = $this->getHashType($user['user_password']);
|
|
|
|
if($type == $this->preferred || empty($user['user_id']) || empty($user['user_password']) || empty($user['user_loginname']))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$sql = e107::getDb();
|
|
|
|
$newPasswordHash = $this->HashPassword($password, $user['user_loginname']);
|
|
|
|
$update = array(
|
|
|
|
'data' => array(
|
|
'user_password' => $newPasswordHash,
|
|
|
|
),
|
|
'WHERE' => "user_id = ".intval($user['user_id'])." LIMIT 1",
|
|
'_FIELD_TYPES' => array('user_password' => 'safestr'),
|
|
|
|
);
|
|
|
|
if($sql->update('user', $update)!==false)
|
|
{
|
|
return $newPasswordHash;
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Detect Password Hash Algorythm type
|
|
* @param string $hash - Password hash to analyse
|
|
* @param string $mode - (optional) set to 'text' for a plain-text description.
|
|
* @return bool|int|array
|
|
*/
|
|
public function getHashType($hash, $mode='constant')
|
|
{
|
|
if(empty($hash))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$num = false;
|
|
$name = '';
|
|
|
|
if((strlen($hash) === 32))
|
|
{
|
|
$num = PASSWORD_E107_MD5;
|
|
$name = 'md5';
|
|
}
|
|
elseif ((strlen($hash) === 35) && (strpos($hash, PASSWORD_E107_ID) === 0))
|
|
{
|
|
$num = PASSWORD_E107_SALT;
|
|
$name = 'md5-salt';
|
|
}
|
|
elseif($this->passwordAPI)
|
|
{
|
|
$info = password_get_info($hash);
|
|
if(!empty($info['algo']))
|
|
{
|
|
$num = PASSWORD_E107_PHP;
|
|
$name = $info['algoName'];
|
|
}
|
|
}
|
|
|
|
if($mode == 'array' && !empty($name))
|
|
{
|
|
return array($num,$name);
|
|
}
|
|
|
|
return $num;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Reset the user's password with an auto-generated string.
|
|
* @param $uid
|
|
* @param string $loginName (optional)
|
|
* @param array $options
|
|
* @return bool|string rawPassword
|
|
*/
|
|
public function resetPassword($uid, $loginName='', $options=array())
|
|
{
|
|
if(empty($uid))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$rawPassword = $this->generateRandomString(str_repeat('*', rand(8, 12)));
|
|
$hash = $this->HashPassword($rawPassword, $loginName);
|
|
|
|
$updateQry = array(
|
|
'data' => array( 'user_password' => $hash ),
|
|
'WHERE' => 'user_id = '.intval($uid)." LIMIT 1",
|
|
'_FIELD_TYPES' => array( 'user_password' => 'safestr' )
|
|
);
|
|
|
|
if(e107::getDb()->update('user', $updateQry))
|
|
{
|
|
if(!empty($options['return']) && $options['return'] == 'array')
|
|
{
|
|
return array('password'=>$rawPassword, 'hash'=>$hash);
|
|
}
|
|
|
|
|
|
return $rawPassword;
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Verifies a standard response to a CHAP challenge
|
|
*
|
|
* @param string $challenge - the string sent to the user
|
|
* @param string $response - the response returned by the user
|
|
* @param string $login_name - user's login name
|
|
* @param string $stored_hash - password hash as stored in DB
|
|
*
|
|
* @return bool|string
|
|
*/
|
|
public function CheckCHAP($challenge, $response, $login_name, $stored_hash )
|
|
{
|
|
if (strlen($challenge) != 40) return PASSWORD_INVALID;
|
|
if (strlen($response) != 32) return PASSWORD_INVALID;
|
|
$valid_ret = PASSWORD_VALID;
|
|
if (strlen($stored_hash) == 32)
|
|
{ // Its simple md5 password storage
|
|
$stored_hash = PASSWORD_E107_ID.md5($stored_hash.$login_name); // Convert to the salted format always used by CHAP
|
|
if ($this->passwordOpts != PASSWORD_E107_MD5) $valid_ret = $stored_hash;
|
|
}
|
|
$testval = md5(substr($stored_hash,strlen(PASSWORD_E107_ID)).$challenge);
|
|
if ($testval == $response) return $valid_ret;
|
|
return PASSWORD_INVALID;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Checks whether the user has to validate a change of user settings by entering password (basically, if that field affects the
|
|
* stored password value)
|
|
*
|
|
* @param string $fieldName - name of field being changed
|
|
*
|
|
* @return bool TRUE if change required, false otherwise
|
|
*/
|
|
public function isPasswordRequired($fieldName)
|
|
{
|
|
if ($this->preferred == PASSWORD_E107_MD5) return false;
|
|
switch ($fieldName)
|
|
{
|
|
case 'user_email' :
|
|
return $this->passwordEmail;
|
|
case 'user_loginname' :
|
|
return TRUE;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Determines whether its necessary to store a separate password for email address validation
|
|
*
|
|
* @return bool TRUE if separate password
|
|
*/
|
|
public function needEmailPassword()
|
|
{
|
|
if ($this->preferred == PASSWORD_E107_MD5) return false;
|
|
if ($this->passwordEmail) return TRUE;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param $userjoined
|
|
* @return bool
|
|
*/
|
|
public function newUserExpired($userjoined)
|
|
{
|
|
$new_user_period = (int) e107::getPref('user_new_period', 0);
|
|
|
|
if(empty($new_user_period))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
$userjoined = (int) $userjoined;
|
|
|
|
return (time() > ($userjoined + ( $new_user_period)*86400));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Checks whether the password value can be converted to the current default
|
|
*
|
|
* @param string $password - hashed password
|
|
* @return bool TRUE if conversion possible, false if not possible, or not needed.
|
|
*/
|
|
public function canConvert($password)
|
|
{
|
|
if ($this->preferred == PASSWORD_E107_MD5) return false;
|
|
if (strlen($password) == 32) return TRUE; // Can convert from md5 to salted
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Given md5-encoded password and login name, generate password string to store in DB
|
|
*
|
|
* @param string $password - MD5-hashed password
|
|
* @param string $login_name - user's login name
|
|
*
|
|
* @return string hashed password to store in DB, converted as necessary
|
|
*/
|
|
public function ConvertPassword($password, $login_name)
|
|
{
|
|
if ($this->canConvert($password) === false) return $password;
|
|
return PASSWORD_E107_ID.md5($password.$login_name);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Generates a random user login name according to some pattern.
|
|
* Checked for uniqueness.
|
|
*
|
|
* @param string $pattern - defines the format of the username
|
|
* @param int $seed - may be used with the random pattern generator
|
|
*
|
|
* @return string a user login name, guaranteed unique in the database.
|
|
*/
|
|
public function generateUserLogin($pattern, $seed='')
|
|
{
|
|
$ul_sql = new db;
|
|
if (strlen($pattern) < 6) $pattern = '##....';
|
|
do
|
|
{
|
|
$newname = $this->generateRandomString($pattern, $seed);
|
|
} while ($ul_sql->select('user','user_id',"`user_loginname`='{$newname}'"));
|
|
return $newname;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Generates a random string - for user login name, password etc, according to some pattern.
|
|
* @param string $pattern - defines the output format:
|
|
* # - an alpha character
|
|
* . - a numeric character
|
|
* * - an alphanumeric character
|
|
* ! - symbol character
|
|
* ? - alpha, numeric or symbol character.
|
|
* ^ - next character from seed
|
|
* alphanumerics are included 'as is'
|
|
* @param int $seed - may be used with the random pattern generator
|
|
*
|
|
* @return string - the required random string
|
|
*/
|
|
public function generateRandomString($pattern='', $seed = '')
|
|
{
|
|
if (empty($pattern))
|
|
{
|
|
$pattern = '##....';
|
|
}
|
|
|
|
$newname = '';
|
|
|
|
// Create alpha [A-Z][a-z]
|
|
$alpha = 'AaBbCcDdEeFfGgHhIiJjKkLMmNnPpQqRrSsTtUuVvWwXxYyZz'; // O, o and l removed to avoid possible confusion with numbers.
|
|
$alphaLength = strlen($alpha) - 1;
|
|
|
|
// Create digit [0-9]
|
|
$digit = '0123456789';
|
|
$digitLength = strlen($digit) - 1;
|
|
|
|
// Create alpha numeric [A-Z][a-z]
|
|
$alphaNum = $alpha.$digit.chr(45).chr(95); // add support for - and _
|
|
$alphaNumLength = strlen($alphaNum) - 1;
|
|
|
|
$symbols = "~!@#$%^*-+?;:"; // avoid < > and quotes.
|
|
$symbolsLength = strlen($symbols) - 1;
|
|
|
|
$alphaNumSymbol = $alphaNum.$symbols;
|
|
$alphaNumSymbolLength = strlen($alphaNumSymbol) - 1;
|
|
|
|
// Next character of seed (if used)
|
|
$seed_ptr = 0;
|
|
for ($i = 0, $patternLength = strlen($pattern); $i < $patternLength; $i++)
|
|
{
|
|
$c = $pattern[$i];
|
|
switch ($c)
|
|
{
|
|
// Symbols only.
|
|
case '!':
|
|
$t = rand(0, $symbolsLength);
|
|
$newname .= $symbols[$t];
|
|
break;
|
|
|
|
// Alphanumeric + Symbols (most secure)
|
|
case '?':
|
|
$t = rand(0, $alphaNumSymbolLength);
|
|
$newname .= $alphaNumSymbol[$t];
|
|
break;
|
|
|
|
case '#' :
|
|
$t = rand(0, $alphaLength);
|
|
$newname .= $alpha[$t];
|
|
break;
|
|
|
|
// Numeric only - [0-9]
|
|
case '.' :
|
|
$t = rand(0, $digitLength);
|
|
$newname .= $digit[$t];
|
|
break;
|
|
|
|
// Alphanumeric
|
|
case '*' :
|
|
$t = rand(0, $alphaNumLength);
|
|
$newname .= $alphaNum[$t];
|
|
break;
|
|
|
|
// Next character from seed
|
|
case '^' :
|
|
if ($seed_ptr < strlen($seed))
|
|
{
|
|
$newname .= $seed[$seed_ptr];
|
|
$seed_ptr++;
|
|
}
|
|
break;
|
|
|
|
// (else just ignore other characters in pattern)
|
|
default :
|
|
if (strrpos($alphaNum, $c) !== false)
|
|
{
|
|
$newname .= $c;
|
|
}
|
|
}
|
|
}
|
|
return $newname;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Split up an email address to check for banned domains.
|
|
* @param string $email - email address to process
|
|
* @param string $fieldname - name of field being searched in DB
|
|
*
|
|
* @return bool|string false if invalid address. Otherwise returns a set of values to check
|
|
* Moved to IPHandler
|
|
*/
|
|
/*
|
|
public function make_email_query($email, $fieldname = 'banlist_ip')
|
|
{
|
|
return e107::getIPHandler()->makeEmailQuery($v, $fieldname); // Valid 'stub' if required
|
|
|
|
$tp = e107::getParser();
|
|
$tmp = strtolower($tp -> toDB(trim(substr($email, strrpos($email, "@")+1)))); // Pull out the domain name
|
|
if ($tmp == '') return false;
|
|
if (strpos($tmp,'.') === false) return false;
|
|
$em = array_reverse(explode('.',$tmp));
|
|
$line = '';
|
|
$out = array($fieldname."='*@{$tmp}'"); // First element looks for domain as email address
|
|
foreach ($em as $e)
|
|
{
|
|
$line = '.'.$e.$line;
|
|
$out[] = '`'.$fieldname."`='*{$line}'";
|
|
}
|
|
return implode(' OR ',$out);
|
|
}
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
* Create user cookie
|
|
*
|
|
* @param array $lode - user information from DB - 'user_id' and 'user_password' required
|
|
* @param bool $autologin - TRUE if the 'Remember Me' box ticked
|
|
*
|
|
* @return void
|
|
*/
|
|
public function makeUserCookie($lode,$autologin = false)
|
|
{
|
|
if(e107::isCli())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
$cookieval = $lode['user_id'].'.'.md5($lode['user_password']); // (Use extra md5 on cookie value to obscure hashed value for password)
|
|
if (e107::getPref('user_tracking','session') == 'session')
|
|
{
|
|
$_SESSION[e107::getPref('cookie_name')] = $cookieval;
|
|
}
|
|
else
|
|
{
|
|
if ($autologin == 1)
|
|
{ // Cookie valid for up to 30 days
|
|
cookie(e107::getPref('cookie_name'), $cookieval, (time() + 3600 * 24 * 30));
|
|
$_COOKIE[e107::getPref('cookie_name')] = $cookieval; // make it available to the global scope before the page is reloaded
|
|
}
|
|
else
|
|
{
|
|
cookie(e107::getPref('cookie_name'), $cookieval);
|
|
$_COOKIE[e107::getPref('cookie_name')] = $cookieval; // make it available to the global scope before the page is reloaded
|
|
}
|
|
}
|
|
|
|
|
|
// echo "Debug: making cookie: ".$cookieval ." from ".print_a($lode,true);
|
|
// exit;
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate an array of all the basic classes a user belongs to
|
|
*
|
|
* Note that the passed data may relate to the currently logged in user, or if an admin is logged in, to a different user
|
|
*
|
|
* @param array $userData - user's data record - must include the 'user_class' element
|
|
* @param boolean $asArray if TRUE, returns results in an array; else as a comma-separated string
|
|
* @param boolean $incInherited if TRUE, includes inherited classes
|
|
* @param boolean $fromAdmin - if TRUE, adds e_UC_ADMIN and e_UC_MAINADMIN in if current user's entitlement permits
|
|
*
|
|
* @return array|string of userclass information according to $asArray
|
|
*/
|
|
public function addCommonClasses($userData, $asArray = false, $incInherited = false, $fromAdmin = false)
|
|
{
|
|
if ($incInherited)
|
|
{
|
|
$classList = e107::getUserClass()->get_all_user_classes($userData['user_class']);
|
|
}
|
|
else
|
|
{
|
|
if (!empty($userData['user_class'])) $classList = explode(',',$userData['user_class']);
|
|
}
|
|
foreach (array(e_UC_MEMBER, e_UC_READONLY, e_UC_PUBLIC) as $c)
|
|
{
|
|
if (!in_array($c, vartrue($classList, array())))
|
|
{
|
|
$classList[] = $c;
|
|
}
|
|
}
|
|
if (((varset($userData['user_admin'],0) == 1) && strlen($userData['user_perms'])) || ($fromAdmin && ADMIN))
|
|
{
|
|
$classList[] = e_UC_ADMIN;
|
|
if ((strpos($userData['user_perms'],'0') === 0) || getperms('0'))
|
|
{
|
|
$classList[] = e_UC_MAINADMIN;
|
|
}
|
|
}
|
|
if ($asArray) return $classList;
|
|
return implode(',',$classList);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return an array of descriptive names for each field in the user DB.
|
|
* @param bool $all if false, just returns modifiable fields. Else returns all
|
|
* @return array - key is field name, value is 'nice name' (descriptive name)
|
|
*/
|
|
public function getNiceNames($all = false)
|
|
{
|
|
// $ret = array('user_id' => LAN_USER_13);
|
|
foreach ($this->userVettingInfo as $k => $v)
|
|
{
|
|
$ret[$k] = $v['niceName'];
|
|
}
|
|
if ($all)
|
|
{
|
|
$ret = array_merge($ret, $this->otherFields);
|
|
}
|
|
return $ret;
|
|
}
|
|
//===================================================
|
|
// User Field validation
|
|
//===================================================
|
|
|
|
/* $_POST field names:
|
|
|
|
DB signup usersettings quick add function
|
|
------------------------------------------------------------------------------
|
|
user_id - user_id - Unique user ID
|
|
user_name name$ username username Display name
|
|
user_loginname loginname loginname loginname User name (login name)
|
|
user_customtitle - customtitle - Custom title
|
|
user_password password1 password1 password1 Password (prior to encoding)
|
|
password2 password2 password1 (Check password field)
|
|
user_sess * - Photo (file on server)
|
|
user_email email email email Email address
|
|
email_confirm
|
|
user_signature signature signature - User signature
|
|
user_image image image* - Avatar (may be external URL or file on server)
|
|
user_hideemail hideemail hideemail - Flag to hide user's email address
|
|
user_login realname realname realname User Real name
|
|
user_xup xupexist$ user_xup - XUP file link
|
|
user_class class class userclass User class (array on form)
|
|
|
|
user_loginname may be auto-generated
|
|
* avatar (user_image) and photo (user_sess) may be uploaded files
|
|
$changed to match the majority vote
|
|
|
|
Following fields auto-filled in code as required:
|
|
user_join
|
|
user_lastvisit
|
|
user_currentvisit
|
|
user_chats
|
|
user_comments
|
|
user_forums
|
|
user_ip
|
|
user_ban
|
|
user_prefs
|
|
user_viewed
|
|
user_visits
|
|
user_admin
|
|
user_perms
|
|
user_pwchange
|
|
|
|
*/
|
|
|
|
|
|
/**
|
|
* Function does validation specific to user data. Updates the $targetData array as appropriate.
|
|
*
|
|
* @param array $targetData - user data generated from earlier vetting stages - only the data in $targetData['data'] is checked
|
|
*
|
|
* @return bool TRUE if nothing updated; false if errors found
|
|
*/
|
|
public function userValidation(&$targetData)
|
|
{
|
|
$u_sql = e107::getDb('u');
|
|
$ret = TRUE;
|
|
$errMsg = '';
|
|
if (isset($targetData['data']['user_email']))
|
|
{
|
|
$v = trim($targetData['data']['user_email']); // Always check email address if its entered
|
|
if ($v == '')
|
|
{
|
|
if (!e107::getPref('disable_emailcheck'))
|
|
{
|
|
$errMsg = ERR_MISSING_VALUE;
|
|
}
|
|
}
|
|
elseif (!check_email($v))
|
|
{
|
|
$errMsg = ERR_INVALID_EMAIL;
|
|
}
|
|
elseif ($u_sql->count('user', '(*)', "WHERE `user_email`='".filter_var($v,FILTER_SANITIZE_EMAIL)."' AND `user_ban`=1 "))
|
|
{
|
|
$errMsg = ERR_BANNED_USER;
|
|
}
|
|
else
|
|
{ // See if email address banned
|
|
$wc = e107::getIPHandler()->makeEmailQuery($v); // Generate the query for the ban list
|
|
if ($wc) { $wc = "`banlist_ip`='{$v}' OR ".$wc; }
|
|
if (($wc === false) || !e107::getIPHandler()->checkBan($wc, false, TRUE))
|
|
{
|
|
// echo "Email banned<br />";
|
|
$errMsg = ERR_BANNED_EMAIL;
|
|
}
|
|
}
|
|
if ($errMsg)
|
|
{
|
|
unset($targetData['data']['user_email']); // Remove the valid entry
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!isset($targetData['errors']['user_email']) && !e107::getPref('disable_emailcheck'))
|
|
{ // We may have already picked up an error on the email address - or it may be allowed to be empty
|
|
$errMsg = ERR_MISSING_VALUE;
|
|
}
|
|
}
|
|
if ($errMsg)
|
|
{ // Update the error
|
|
$targetData['errors']['user_email'] = $errMsg;
|
|
$targetData['failed']['user_email'] = $v;
|
|
$ret = false;
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Given an array of user data intended to be written to the DB, adds empty strings (or other default value) for any field which doesn't have a default in the SQL definition.
|
|
* (Avoids problems with MySQL in STRICT mode.).
|
|
*
|
|
* @param array $userInfo - user data destined for the database
|
|
*
|
|
* @return bool TRUE if additions made, false if no change.
|
|
*
|
|
* @todo - may be unnecessary with auto-generation of _NOTNULL array in db handler
|
|
*/
|
|
public function addNonDefaulted(&$userInfo)
|
|
{
|
|
// $nonDefaulted = array('user_signature' => '', 'user_prefs' => '', 'user_class' => '', 'user_perms' => '');
|
|
$nonDefaulted = array('user_signature' => '', 'user_prefs' => '', 'user_class' => '', 'user_perms' => '', 'user_realm' => ''); // Delete when McFly finished
|
|
$ret = false;
|
|
foreach ($nonDefaulted as $k => $v)
|
|
{
|
|
if (!isset($userInfo[$k]))
|
|
{
|
|
$userInfo[$k] = $v;
|
|
$ret = TRUE;
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Delete time-expired partial registrations from the user DB, clean up user_extended table
|
|
*
|
|
* @param bool $force - set TRUE to force check of user_extended table
|
|
*
|
|
* @return int number of user records deleted
|
|
*/
|
|
public function deleteExpired($force = false)
|
|
{
|
|
$pref = e107::getPref();
|
|
$sql = e107::getDb();
|
|
|
|
$temp1 = 0;
|
|
if (isset($pref['del_unv']) && $pref['del_unv'] && intval($pref['user_reg_veri']) != 2)
|
|
{
|
|
$threshold= intval(time() - ($pref['del_unv'] * 60));
|
|
if(($temp1 = $sql->delete('user', 'user_ban = 2 AND user_join < '.$threshold)) > 0)
|
|
{
|
|
$force = true;
|
|
}
|
|
}
|
|
|
|
if ($force) // Remove 'orphaned' extended user field records
|
|
{
|
|
$sql->gen("DELETE `#user_extended` FROM `#user_extended` LEFT JOIN `#user` ON `#user_extended`.`user_extended_id` = `#user`.`user_id`
|
|
WHERE `#user`.`user_id` IS NULL");
|
|
}
|
|
|
|
return $temp1;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Called to update initial user classes, probationary user class etc after various user events
|
|
*
|
|
* @param array $user - user data. 'user_class' must be present
|
|
* @param string $event = userveri|userall|userfull|userpartial - defines event
|
|
*
|
|
* @return boolean - true if $user['user_class'] updated, false otherwise
|
|
*/
|
|
public function userClassUpdate($user, $event='userfull')
|
|
{
|
|
$pref = e107::getPref();
|
|
$tp = e107::getParser();
|
|
|
|
$initClassStage = isset($pref['init_class_stage']) ? intval($pref['init_class_stage']) : 0;
|
|
$initClasses = array();
|
|
$doClasses = false;
|
|
$doProbation = false;
|
|
$ret = false;
|
|
|
|
switch($event)
|
|
{
|
|
case 'userall':
|
|
$doClasses = true;
|
|
$doProbation = true;
|
|
break;
|
|
case 'userfull':
|
|
if(!$pref['user_reg_veri'] || ($initClassStage == 2))
|
|
{
|
|
$doClasses = true;
|
|
}
|
|
$doProbation = true;
|
|
break;
|
|
case 'userpartial' :
|
|
if($initClassStage === 1)
|
|
{
|
|
// Set initial classes if to be done on partial signup, or if selected to add them now
|
|
$doClasses = true;
|
|
}
|
|
$doProbation = true;
|
|
break;
|
|
}
|
|
|
|
if($doClasses)
|
|
{
|
|
if(isset($pref['initial_user_classes'])) // Any initial user classes to be set at some stage
|
|
{
|
|
$initClasses = explode(',', $pref['initial_user_classes']);
|
|
}
|
|
|
|
if($doProbation && (varset($pref['user_new_period'], 0) > 0))
|
|
{
|
|
$initClasses[] = e_UC_NEWUSER; // Probationary user class
|
|
}
|
|
|
|
if(count($initClasses))
|
|
{ // Update the user classes
|
|
if($user['user_class'])
|
|
{
|
|
$initClasses = array_unique(array_merge($initClasses, explode(',', $user['user_class'])));
|
|
}
|
|
$user['user_class'] = $tp->toDB(implode(',', $initClasses));
|
|
//$ret = TRUE;
|
|
$ret = $user['user_class'];
|
|
}
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Updates user status, primarily the user_ban field, to reflect outside events
|
|
*
|
|
* @param string $action - 'ban', 'bounce'
|
|
* @param integer $uid - internal user ID, zero if not known
|
|
* @param string $emailAddress - email address (optional)
|
|
*
|
|
* @return boolean | string - false if user found, error message if not
|
|
*/
|
|
public function userStatusUpdate($action, $uid, $emailAddress = '')
|
|
{
|
|
$db = e107::getDb('user');
|
|
$qry = '';
|
|
$error = false; // Assume no error to start with
|
|
$uid = intval($uid); // Precautionary - should have already been done
|
|
switch ($action)
|
|
{
|
|
case 'ban' :
|
|
$newVal = USER_BANNED;
|
|
$logEvent = USER_AUDIT_BANNED;
|
|
break;
|
|
case 'bounce' :
|
|
$newVal = USER_EMAIL_BOUNCED;
|
|
$logEvent = USER_AUDIT_MAIL_BOUNCE;
|
|
break;
|
|
case 'reset' :
|
|
$newVal = USER_BOUNCED_RESET;
|
|
$logEvent = USER_AUDIT_BOUNCE_RESET;
|
|
break;
|
|
case 'temp' :
|
|
$newVal = USER_TEMPORARY_ACCOUNT;
|
|
$logEvent = USER_AUDIT_TEMP_ACCOUNT;
|
|
break;
|
|
default :
|
|
return 'Invalid action: '.$action;
|
|
}
|
|
if ($uid) { $qry = '`user_id`='.$uid; }
|
|
if ($emailAddress) { if ($qry) $qry .= ' OR '; $qry .= "`user_email` = '{$emailAddress}'"; }
|
|
if (false === $db->select('user', 'user_id, user_email, user_ban, user_loginname', $qry.' LIMIT 1'))
|
|
{
|
|
$error = 'User not found: '.$uid.'/'.$emailAddress;
|
|
}
|
|
else
|
|
{
|
|
$row = $db->fetch();
|
|
if ($uid && ($uid != $row['user_id']))
|
|
{
|
|
$error = 'UID mismatch: '.$uid.'/'.$row['user_id'];
|
|
}
|
|
elseif ($emailAddress && ($emailAddress != $row['user_email']))
|
|
{
|
|
$error = 'User email mismatch: '.$emailAddress.'/'.$row['user_email'];
|
|
}
|
|
else
|
|
{ // Valid user!
|
|
if ($row['user_ban'] != $newVal) // We could implement a hierarchy here, so that an important status isn't overridden by a lesser one
|
|
{ // Only update if needed
|
|
$db->update('user', '`user_ban` = '.$newVal.', `user_email` = \'\' WHERE `user_id` = '.$row['user_id'].' LIMIT 1');
|
|
// Add to user audit log TODO: Should we log to admin log as well?
|
|
$adminLog = e107::getLog();
|
|
$adminLog->user_audit($logEvent, array('user_ban' => $newVal, 'user_email' => $row['user_email']), $row['user_id'], $row['user_loginname']);
|
|
}
|
|
}
|
|
}
|
|
return $error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Social login provider
|
|
*/
|
|
class e_user_provider
|
|
{
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $_provider;
|
|
|
|
/**
|
|
* Hybridauth adapter
|
|
*
|
|
* @var \Hybridauth\Adapter\AdapterInterface|null
|
|
*/
|
|
public $adapter;
|
|
|
|
/**
|
|
* Hybridauth object
|
|
*
|
|
* @var Hybridauth\Hybridauth
|
|
*/
|
|
protected $hybridauth;
|
|
protected $_config = array();
|
|
/**
|
|
* @var social_login_config|null
|
|
*/
|
|
protected $social_login_config_manager = null;
|
|
|
|
/**
|
|
* Create a new Hybridauth-backed social login provider
|
|
*
|
|
* This constructor suppresses exceptions due to client usages not handling exceptions and instead sends error
|
|
* messages to logged in admins. To check if a Hybridauth configuration is valid, use
|
|
* {@link e107::getUserProvider()} with the provider name while logged in as an admin.
|
|
*
|
|
* @param string|null $provider The name of the provider to use
|
|
* @param array $config An override Hybridauth configuration that takes precedence over the
|
|
* database Hybridauth configuration for this provider. Leave blank to use
|
|
* the database configuration.
|
|
* @param bool $suppress_exceptions Set to false to propagate Hybridauth exceptions
|
|
* @throws \Hybridauth\Exception\UnexpectedValueException if the provider is disabled
|
|
* @throws \Hybridauth\Exception\InvalidArgumentException if the provider configuration validation failed
|
|
*/
|
|
public function __construct($provider = null, $config = array(), $suppress_exceptions = true)
|
|
{
|
|
@include_once(e_PLUGIN . "social/includes/social_login_config.php");
|
|
if (!class_exists('social_login_config')) return;
|
|
|
|
$this->social_login_config_manager = new social_login_config(e107::getConfig());
|
|
|
|
if (!empty($config))
|
|
{
|
|
$this->_config = $config;
|
|
}
|
|
else
|
|
{
|
|
$this->_config = array(
|
|
"callback" => $this->generateCallbackUrl($provider),
|
|
"providers" => $this->social_login_config_manager->getSupportedConfiguredProviderConfigs(),
|
|
"debug_mode" => 'error',
|
|
"debug_file" => e_LOG . "hybridAuth.log"
|
|
);
|
|
|
|
}
|
|
|
|
try
|
|
{
|
|
$this->respawnHybridauth();
|
|
$this->setProvider($provider);
|
|
|
|
$providerId = $this->getProvider();
|
|
if ($providerId && $this->hybridauth->isConnectedWith($providerId))
|
|
{
|
|
$this->adapter = $this->hybridauth->getAdapter($providerId);
|
|
}
|
|
}
|
|
catch (\Hybridauth\Exception\InvalidArgumentException $e)
|
|
{
|
|
if (!$suppress_exceptions) throw $e;
|
|
}
|
|
catch (\Hybridauth\Exception\UnexpectedValueException $e)
|
|
{
|
|
if (!$suppress_exceptions) throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws \Hybridauth\Exception\InvalidArgumentException
|
|
*/
|
|
private function respawnHybridauth()
|
|
{
|
|
$this->hybridauth = new Hybridauth\Hybridauth($this->_config);
|
|
}
|
|
|
|
/**
|
|
* @param $provider
|
|
* @return void
|
|
*/
|
|
public function setProvider($provider)
|
|
{
|
|
$this->_provider = $provider;
|
|
}
|
|
|
|
/**
|
|
* @param $url
|
|
* @return void
|
|
* @throws \Hybridauth\Exception\InvalidArgumentException
|
|
*/
|
|
public function setBackUrl($url)
|
|
{
|
|
# system/xup/login by default
|
|
$this->_config['callback'] = $this->generateCallbackUrl($url);
|
|
$this->respawnHybridauth();
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getProvider()
|
|
{
|
|
// $this->log(__CLASS__, __METHOD__, __LINE__);
|
|
return $this->_provider;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getConfig()
|
|
{
|
|
return $this->_config;
|
|
}
|
|
|
|
/**
|
|
* @return \Hybridauth\User\Profile|null
|
|
*/
|
|
public function getUserProfile()
|
|
{
|
|
if ($this->adapter)
|
|
{
|
|
try
|
|
{
|
|
return $this->adapter->getUserProfile();
|
|
}
|
|
catch (\Hybridauth\Exception\Exception $e)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @return string|null
|
|
*/
|
|
public function userId()
|
|
{
|
|
if ($profile = $this->getUserProfile())
|
|
{
|
|
return $this->getProvider() . '_' . $profile->identifier;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the social login providers for which we have adapters
|
|
*
|
|
* Despite this being a static method, it memoizes (caches) the slow reflection code in the {@link e107} registry
|
|
* after the first run, so subsequent calls to this method are fast.
|
|
*
|
|
* @return string[] String list of supported providers. Empty if Hybridauth is broken.
|
|
*/
|
|
public static function getSupportedProviders()
|
|
{
|
|
$regId = 'core/e107/user/provider';
|
|
|
|
if($cached = e107::getRegistry($regId))
|
|
{
|
|
return $cached;
|
|
}
|
|
|
|
$providers = [];
|
|
|
|
try
|
|
{
|
|
$reflector = new ReflectionClass('Hybridauth\Hybridauth');
|
|
}
|
|
catch (ReflectionException $e)
|
|
{
|
|
return $providers;
|
|
}
|
|
$hybridauth_path = $reflector->getFileName();
|
|
$hybridauth_providers_path = dirname($hybridauth_path) . "/Provider/";
|
|
$fs_iterator = new FilesystemIterator($hybridauth_providers_path);
|
|
foreach ($fs_iterator as $file)
|
|
{
|
|
if (!$file->isFile()) continue;
|
|
|
|
$provider_source_code = file_get_contents($file);
|
|
$provider_source_tokens = token_get_all($provider_source_code);
|
|
for ($token_index = 0; isset($provider_source_tokens[$token_index]); $token_index++)
|
|
{
|
|
if (!isset($provider_source_tokens[$token_index][0])) continue;
|
|
|
|
if (T_CLASS === $provider_source_tokens[$token_index][0])
|
|
{
|
|
$token_index += 2;
|
|
$providers[] = $provider_source_tokens[$token_index][1];
|
|
}
|
|
}
|
|
}
|
|
|
|
sort($providers);
|
|
|
|
e107::setRegistry($regId, $providers);
|
|
|
|
return $providers;
|
|
}
|
|
|
|
/**
|
|
* Get the type of provider from a provider name
|
|
* @param $providerName string Name of the supported social login provider
|
|
* @return string|bool "OAuth1", "OAuth2", or "OpenID". If false, the provider name is invalid.
|
|
* Other values are technically possible but not supported.
|
|
*/
|
|
public static function getTypeOf($providerName)
|
|
{
|
|
$class_name = "Hybridauth\Provider\\{$providerName}";
|
|
$parent_class = eShims::get_parent_class($class_name);
|
|
if (!$parent_class) return false;
|
|
|
|
$parent_class_split = explode("\\", eShims::get_parent_class($class_name));
|
|
$type = end($parent_class_split);
|
|
if ($type == "AbstractAdapter") return $providerName;
|
|
if (!in_array($type, ['OAuth1', 'OAuth2', 'OpenID'])) return self::getTypeOf($type);
|
|
return $type;
|
|
}
|
|
|
|
/**
|
|
* Get standard and supplementary fields of the specified provider
|
|
* @param $providerName string Name of the supported social login provider
|
|
* @return array Multidimensional associative array where the keys are the known field names and the values are a
|
|
* description of what their key is for. Keys can be nested in parent keys. Parent keys will not
|
|
* have a description of the key. All fields take a string value. Return will be empty if the
|
|
* specified provider does not have any known fields.
|
|
*/
|
|
public static function getFieldsOf($providerName)
|
|
{
|
|
$standardFields = self::getStandardFieldsOf($providerName);
|
|
$supplementaryFields = self::getSupplementalFieldsOf($providerName);
|
|
return self::array_merge_recursive_distinct($standardFields, $supplementaryFields);
|
|
}
|
|
|
|
/**
|
|
* Get the standard/common/parent fields of the specified provider
|
|
* @param $providerName string Name of the supported social login provider
|
|
* @return array Multidimensional associative array where the keys are the standard field names and the values are
|
|
* a description of what each key is for. Keys can be nested in parent keys. Parent keys will not
|
|
* have a description of the key. All fields take a string value. Return will be empty if the
|
|
* specified provider does not have any known standard fields.
|
|
*/
|
|
public static function getStandardFieldsOf($providerName)
|
|
{
|
|
$providerType = self::getTypeOf($providerName);
|
|
switch ($providerType)
|
|
{
|
|
case 'OAuth2':
|
|
$fieldPart = [
|
|
'keys' => [
|
|
'id' => "Client ID given to you by $providerName",
|
|
'secret' => "Client secret given to you by $providerName",
|
|
],
|
|
'scope' => "Permissions to request from $providerName. See the $providerName OAuth2 documentation for details.",
|
|
];
|
|
break;
|
|
case 'OAuth1':
|
|
$fieldPart = [
|
|
'keys' => [
|
|
'key' => "Consumer key given to you by $providerName",
|
|
'secret' => "Consumer secret given to you by $providerName",
|
|
]
|
|
];
|
|
break;
|
|
case 'OpenID':
|
|
$fieldPart = [
|
|
'openid_identifier' => "OpenID endpoint URL"
|
|
];
|
|
break;
|
|
default:
|
|
$fieldPart = [];
|
|
}
|
|
|
|
return $fieldPart;
|
|
}
|
|
|
|
/**
|
|
* Get the supplemental fields specific to the specified provider
|
|
* @param $providerName string Name of the supported social login provider
|
|
* @return array Multidimensional associative array where the keys are the supplemental field names and the values
|
|
* are a description of what each key is for. Keys can be nested in parent keys. Parent keys will
|
|
* not have a description of the key. All fields take a string value. Return will be empty if the
|
|
* specified provider does not have any known supplemental fields.
|
|
*/
|
|
public static function getSupplementalFieldsOf($providerName)
|
|
{
|
|
$supplementalFields = [];
|
|
$className = "Hybridauth\Provider\\$providerName";
|
|
try
|
|
{
|
|
$reflector = new ReflectionClass($className);
|
|
}
|
|
catch (ReflectionException $e)
|
|
{
|
|
return $supplementalFields;
|
|
}
|
|
|
|
$adapterPath = $reflector->getFileName();
|
|
$adapterSourceCode = file_get_contents($adapterPath);
|
|
$adapterTokens = token_get_all($adapterSourceCode);
|
|
$rawDocumentation = null;
|
|
for ($index = 0; isset($adapterTokens[$index]); $index ++)
|
|
{
|
|
if (!isset($adapterTokens[$index][1])) continue;
|
|
|
|
if (T_DOC_COMMENT === $adapterTokens[$index][0] &&
|
|
FALSE !== strpos($adapterTokens[$index][1], '$config'))
|
|
{
|
|
$rawDocumentation = $adapterTokens[$index][1];
|
|
}
|
|
|
|
if (T_VARIABLE == $adapterTokens[$index][0])
|
|
{
|
|
$supplementalFieldPathSplit = self::adapterTokenParseConfig($adapterTokens, $index, null);
|
|
if (!is_null($supplementalFieldPathSplit))
|
|
{
|
|
$value = $rawDocumentation;
|
|
$level = [];
|
|
foreach (array_reverse($supplementalFieldPathSplit) as $supplementalFieldPathItem)
|
|
{
|
|
$level[$supplementalFieldPathItem] = $value;
|
|
$value = $level;
|
|
$level = [];
|
|
}
|
|
$supplementalFields = self::array_merge_recursive_distinct($supplementalFields, $value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $supplementalFields;
|
|
}
|
|
|
|
/**
|
|
* @param $adapterTokens
|
|
* @param $index
|
|
* @param $carry
|
|
* @return mixed
|
|
*/
|
|
private static function adapterTokenParseConfig($adapterTokens, &$index, $carry)
|
|
{
|
|
if (!isset($adapterTokens[$index][1]))
|
|
{
|
|
if (in_array($adapterTokens[$index], [';', '.', ',', '?'])) return $carry;
|
|
++$index;
|
|
return self::adapterTokenParseConfig($adapterTokens, $index, $carry);
|
|
}
|
|
$token = $adapterTokens[$index];
|
|
$tokenType = $token[0];
|
|
$tokenValue = $token[1];
|
|
|
|
switch ($tokenType)
|
|
{
|
|
case T_VARIABLE:
|
|
if ($tokenValue == '$this') break;
|
|
return $carry;
|
|
case T_OBJECT_OPERATOR:
|
|
break;
|
|
case T_STRING:
|
|
switch ($tokenValue)
|
|
{
|
|
case 'config':
|
|
$carry = [];
|
|
break;
|
|
case 'filter':
|
|
case 'get':
|
|
break;
|
|
default:
|
|
return $carry;
|
|
}
|
|
break;
|
|
case T_CONSTANT_ENCAPSED_STRING:
|
|
$carry[] = trim($tokenValue, '\'"');
|
|
break;
|
|
}
|
|
++$index;
|
|
return self::adapterTokenParseConfig($adapterTokens, $index, $carry);
|
|
}
|
|
|
|
/**
|
|
* @param $array1
|
|
* @param $array2
|
|
* @return mixed
|
|
*/
|
|
private static function array_merge_recursive_distinct(&$array1, &$array2)
|
|
{
|
|
$merged = $array1;
|
|
|
|
foreach ($array2 as $key => &$value)
|
|
{
|
|
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key]))
|
|
{
|
|
$merged[$key] = self::array_merge_recursive_distinct($merged[$key], $value);
|
|
}
|
|
else
|
|
{
|
|
$merged[$key] = $value;
|
|
}
|
|
}
|
|
|
|
return $merged;
|
|
}
|
|
|
|
/**
|
|
* Check if social logins are enabled site-wide
|
|
* @return bool TRUE if the site has social logins enabled; FALSE otherwise
|
|
*/
|
|
public function isSocialLoginEnabled()
|
|
{
|
|
if ($this->social_login_config_manager === null) return false;
|
|
|
|
return $this->social_login_config_manager->isFlagActive(social_login_config::ENABLE_BIT_GLOBAL);
|
|
}
|
|
|
|
/**
|
|
* XUP Signup Method (falls-back to XUP login when existing user is detected).
|
|
* May be used as a simple XUP login link for existing and non-existing users.
|
|
*/
|
|
public function login($redirectUrl = true, $loginAfterSuccess = true, $emailAfterSuccess = true)
|
|
{
|
|
if (!$this->isSocialLoginEnabled())
|
|
{
|
|
throw new Exception("Login failed! This feature is disabled.", 100); // TODO lan
|
|
}
|
|
|
|
if (!$this->getProvider())
|
|
{
|
|
throw new Exception("Login failed! Wrong provider.", 2); // TODO lan
|
|
}
|
|
|
|
if ($redirectUrl)
|
|
{
|
|
if (true === $redirectUrl)
|
|
{
|
|
$redirectUrl = SITEURL;
|
|
}
|
|
elseif (strpos($redirectUrl, 'http://') !== 0 && strpos($redirectUrl, 'https://') !== 0)
|
|
{
|
|
$redirectUrl = e107::getUrl()->create($redirectUrl);
|
|
}
|
|
}
|
|
|
|
|
|
if (e107::getUser()->isUser())
|
|
{
|
|
if ($redirectUrl)
|
|
{
|
|
$this->redirectAndForwardMessages($redirectUrl);
|
|
}
|
|
return false;
|
|
// throw new Exception( "Signup failed! User already signed in. ", 1); // TODO lan
|
|
}
|
|
|
|
$this->setBackUrl($redirectUrl);
|
|
|
|
$this->adapter = $this->hybridauth->authenticate($this->getProvider());
|
|
$profile = $this->adapter->getUserProfile();
|
|
|
|
// returned back, if success...
|
|
if ($profile->identifier)
|
|
{
|
|
|
|
$sql = e107::getDb();
|
|
$userMethods = e107::getUserSession();
|
|
|
|
$plainPwd = $userMethods->generateRandomString('************'); // auto plain passwords
|
|
|
|
|
|
// TODO - auto login name, shouldn't be used if system set to user_email login...
|
|
$userdata['user_loginname'] = $this->getProvider() . $userMethods->generateUserLogin(e107::getPref('predefinedLoginName', '_..#..#..#'));
|
|
$userdata['user_email'] = $sql->escape($profile->emailVerified ? $profile->emailVerified : $profile->email) ?: '';
|
|
$userdata['user_name'] = $sql->escape($profile->displayName);
|
|
$userdata['user_login'] = $userdata['user_name'];
|
|
$userdata['user_customtitle'] = ''; // not used
|
|
$userdata['user_password'] = $userMethods->HashPassword($plainPwd, $userdata['user_loginname']); // pwd
|
|
$userdata['user_sess'] = ''; //
|
|
$userdata['user_image'] = $profile->photoURL; // avatar
|
|
$userdata['user_signature'] = ''; // not used
|
|
$userdata['user_hideemail'] = 1; // hide it by default
|
|
$userdata['user_xup'] = $sql->escape($this->userId());
|
|
|
|
$pref = e107::pref('core');
|
|
|
|
if (!empty($pref['initial_user_classes']))
|
|
{
|
|
$userdata['user_class'] = $pref['initial_user_classes'];
|
|
}
|
|
elseif (!empty($pref['user_new_period']))
|
|
{
|
|
$userdata['user_class'] = e_UC_NEWUSER;
|
|
}
|
|
else
|
|
{
|
|
$userdata['user_class'] = '';
|
|
}
|
|
|
|
// print_a($userdata);
|
|
|
|
|
|
// user_name, user_xup, user_email and user_loginname shouldn't match
|
|
$insert = (!empty($userdata['user_email'])) ? "OR user_email='" . $userdata['user_email'] . "' " : "";
|
|
|
|
if ($uid = $sql->retrieve("user", "user_id", "user_xup='" . $sql->escape($this->userId()) . "' " . $insert . " OR user_loginname='{$userdata['user_loginname']}' OR user_name='{$userdata['user_name']}'"))
|
|
{
|
|
// $this->login($redirectUrl); // auto-login
|
|
$result = e107::getUser()->loginProvider($this->userId());
|
|
$this->updateXupProfile();
|
|
|
|
if (!$result)
|
|
{
|
|
e107::getMessage()->addError("User already exists but is not connected through this social login provider");
|
|
}
|
|
|
|
if ($redirectUrl)
|
|
{
|
|
$this->redirectAndForwardMessages($redirectUrl);
|
|
}
|
|
|
|
return false;
|
|
// throw new Exception( "Signup failed! User already exists. Please use 'login' instead.", 3);
|
|
}
|
|
|
|
if (empty($userdata['user_email']) && e107::getPref('disable_emailcheck', 0) == 0) // Allow it if set-up that way.
|
|
{
|
|
// Twitter will not provide email addresses.
|
|
// throw new Exception( "Signup failed! Can't access user email - registration without an email is impossible.".print_a($userdata,true), 4); // TODO lan
|
|
}
|
|
|
|
// other fields
|
|
$now = time();
|
|
$userdata['user_id'] = null;
|
|
$userdata['user_image'] = '';
|
|
$userdata['user_join'] = $now;
|
|
$userdata['user_lastvisit'] = 0;
|
|
$userdata['user_currentvisit'] = 0;
|
|
$userdata['user_comments'] = 0;
|
|
$userdata['user_ip'] = e107::getIPHandler()->getIP(false);
|
|
$userdata['user_ban'] = USER_VALIDATED;
|
|
$userdata['user_prefs'] = '';
|
|
$userdata['user_visits'] = 0;
|
|
$userdata['user_admin'] = 0;
|
|
$userdata['user_perms'] = '';
|
|
$userdata['user_realm'] = '';
|
|
$userdata['user_pwchange'] = $now;
|
|
|
|
/** @var e_system_user' $user */
|
|
$user = e107::getSystemUser(0, false);
|
|
$user->setData($userdata);
|
|
$user->getExtendedModel(); // init
|
|
//$user->setEditor(e107::getSystemUser(1, false));
|
|
$user->save(true);
|
|
|
|
// user model error
|
|
if ($user->hasError())
|
|
{
|
|
e107::getLog()->add('XUP Signup Failure', $userdata, E_LOG_WARNING, "XUP_SIGNUP");
|
|
throw new Exception($user->renderMessages(), 5);
|
|
}
|
|
|
|
|
|
### Successful signup!
|
|
//$user->set('provider', $this->getProvider());
|
|
$userdata = $user->getData();
|
|
$userdata['provider'] = $this->getProvider();
|
|
|
|
$userdata['callback_data'] = $profile;
|
|
|
|
// e107::getEvent()->trigger('userveri', $userdata); // Trigger New verified user.
|
|
|
|
e107::getEvent()->trigger('user_xup_signup', $userdata);
|
|
|
|
$ret = e107::getEvent()->trigger('usersupprov', $userdata); // XXX - it's time to pass objects instead of array?
|
|
|
|
if (true === $ret) return $this;
|
|
|
|
// send email
|
|
if ($emailAfterSuccess && !empty($userdata['user_email']))
|
|
{
|
|
$user->set('user_password', $plainPwd)->email('signup');
|
|
}
|
|
|
|
e107::getUser()->setProvider($this);
|
|
|
|
// auto login
|
|
if ($loginAfterSuccess)
|
|
{
|
|
e107::getUser()->loginProvider($this->userId()); // if not proper after-login, return true so user can see login screen
|
|
}
|
|
|
|
if ($redirectUrl)
|
|
{
|
|
$this->redirectAndForwardMessages($redirectUrl);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return bool|string
|
|
* @throws \Hybridauth\Exception\InvalidArgumentException
|
|
* @throws \Hybridauth\Exception\UnexpectedValueException
|
|
*/
|
|
public function logout()
|
|
{
|
|
if (
|
|
!$this->adapter ||
|
|
!$this->hybridauth->isConnectedWith($this->getProvider())
|
|
) return true;
|
|
try
|
|
{
|
|
$this->adapter->disconnect();
|
|
$this->adapter = null;
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
return $e->getMessage();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param string $backUrl
|
|
* @return string
|
|
*/
|
|
public function generateCallbackUrl($backUrl = null)
|
|
{
|
|
return e107::getUrl()->create(
|
|
"system/xup/login",
|
|
array(
|
|
'provider' => $this->getProvider(),
|
|
'back' => $backUrl,
|
|
),
|
|
array('full' => true, 'encode' => false)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param $redirectUrl
|
|
*/
|
|
private function redirectAndForwardMessages($redirectUrl)
|
|
{
|
|
$messages = e107::getMessage()->getAll('default', true, false);
|
|
foreach ($messages as $type => $message_stack)
|
|
{
|
|
e107::getMessage()->addSessionStack($message_stack, 'default', $type);
|
|
}
|
|
e107::getRedirect()->redirect($redirectUrl);
|
|
}
|
|
|
|
/**
|
|
* Synchronize user profile fields from social login provider
|
|
*/
|
|
private function updateXupProfile()
|
|
{
|
|
try
|
|
{
|
|
// detect all currently connected providers
|
|
$connected = $this->hybridauth->getConnectedProviders();
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
e107::getMessage()->addError('[' . $e->getCode() . ']' . $e->getMessage(), 'default', true);
|
|
$session = e107::getSession();
|
|
$session->set('HAuthError', true);
|
|
$connected = false;
|
|
}
|
|
// no active session found
|
|
if (!$connected) return;
|
|
|
|
// query DB
|
|
$sql = e107::getDb();
|
|
$where = array();
|
|
$userdata = array();
|
|
|
|
foreach ($connected as $providerId)
|
|
{
|
|
try
|
|
{
|
|
$adapter = $this->hybridauth->getAdapter($providerId);
|
|
$profile = $adapter->getUserProfile();
|
|
}
|
|
catch (\Hybridauth\Exception\Exception $e)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!$profile->identifier) continue;
|
|
|
|
$userdata['user_name'] = $sql->escape($profile->displayName);
|
|
$userdata['user_image'] = $profile->photoURL; // avatar
|
|
$userdata['user_email'] = $profile->email;
|
|
|
|
$id = $providerId . '_' . $profile->identifier;
|
|
$where[] = "user_xup='" . $sql->escape($id) . "'";
|
|
}
|
|
// no active session found
|
|
if (empty($where)) return;
|
|
|
|
$where = implode(' OR ', $where);
|
|
if ($sql->select('user', 'user_id, user_name, user_email, user_image, user_password, user_xup', $where))
|
|
{
|
|
|
|
$user = $sql->fetch();
|
|
e107::getUserSession()->makeUserCookie($user);
|
|
|
|
$spref = e107::pref('social');
|
|
|
|
// Update display name or avatar image if they have changed.
|
|
if (
|
|
(empty($user['user_email']) && !empty($userdata['user_email'])) ||
|
|
($userdata['user_name'] != $user['user_name']) ||
|
|
($userdata['user_image'] != $user['user_image'])
|
|
)
|
|
{
|
|
$updateQry = array();
|
|
|
|
if (!empty($spref['xup_login_update_username']))
|
|
{
|
|
$updateQry['user_name'] = $userdata['user_name'];
|
|
}
|
|
|
|
if (!empty($spref['xup_login_update_avatar']))
|
|
{
|
|
$updateQry['user_image'] = $userdata['user_image'];
|
|
}
|
|
|
|
if (empty($user['user_email']))
|
|
{
|
|
$updateQry['user_email'] = $userdata['user_email'];
|
|
}
|
|
|
|
$updateQry['WHERE'] = "user_id=" . $user['user_id'] . " LIMIT 1";
|
|
|
|
if ($sql->update('user', $updateQry) !== false)
|
|
{
|
|
$updatedProfile = array_replace($user, $userdata);
|
|
e107::getEvent()->trigger('user_xup_updated', $updatedProfile);
|
|
e107::getLog()->add('User Profile Updated', $userdata, E_LOG_INFORMATIVE, "XUP_LOGIN", LOG_TO_ADMIN, array('user_id' => $user['user_id'], 'user_name' => $user['user_name'], 'user_email' => $userdata['user_email']));
|
|
}
|
|
else
|
|
{
|
|
e107::getLog()->add('User Profile Update Failed', $userdata, E_LOG_WARNING, "XUP_LOGIN", LOG_TO_ADMIN, $updateQry);
|
|
}
|
|
}
|
|
|
|
unset($user['user_password']);
|
|
e107::getLog()->user_audit(USER_AUDIT_LOGIN, null, $user['user_id'], $user['user_name']);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
e107::coreLan('administrator', true);
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class e_userperms
|
|
{
|
|
|
|
protected $core_perms = array();
|
|
|
|
protected $plugin_perms = array();
|
|
|
|
protected $language_perms = array();
|
|
|
|
protected $main_perms = array();
|
|
|
|
protected $full_perms = array();
|
|
|
|
protected $permSectionDiz = array(
|
|
'core' => LAN_GENERAL,
|
|
'plugin' => ADLAN_CL_7,
|
|
'language' => ADLAN_132,
|
|
'main' => ADMSLAN_58
|
|
);
|
|
|
|
|
|
|
|
|
|
function __construct()
|
|
{
|
|
|
|
$this->core_perms = array(
|
|
|
|
// In the same order as admin navigation! Plus same labels.
|
|
|
|
// Settings
|
|
"C" => array(ADLAN_74,E_16_CACHE, E_32_CACHE), // Clear the system cache
|
|
"F" => array(ADLAN_58,E_16_EMOTE, E_32_EMOTE), // Emoticons
|
|
"G" => array(ADLAN_60,E_16_FRONT, E_32_FRONT), // Front-Page Configuration
|
|
"L" => array(ADLAN_132,E_16_LANGUAGE, E_32_LANGUAGE), // Language Packs
|
|
"T" => array(ADLAN_66,E_16_META, E_32_META), // Meta tags
|
|
|
|
"1" => array(LAN_PREFS,E_16_PREFS, E_32_PREFS), // Alter Site Preferences
|
|
"X" => array(LAN_SEARCH,E_16_SEARCH, E_32_SEARCH), // Search
|
|
"I" => array(LAN_NAVIGATION,E_16_LINKS, E_32_LINKS), // Post SiteLinks
|
|
"8" => array(ADMSLAN_27,E_16_LINKS, E_32_LINKS), // Oversee SiteLink Categories
|
|
"K" => array(ADLAN_159,E_16_EURL, E_32_EURL), // Configure URLs
|
|
|
|
// Users
|
|
"3" => array(ADLAN_8,E_16_ADMIN, E_32_ADMIN), // Modify Admin perms
|
|
"4" => array(LAN_USER_MANAGEALL,E_16_USER, E_32_USER), // Manage all user access and settings etc
|
|
"U0" => array(ADLAN_34,E_16_USER, E_32_USER), // moderate users/bans but not userclasses or extended fields,
|
|
"U1" => array(LAN_USER_QUICKADD,E_16_USER, E_32_USER), // "User: Quick Add User",
|
|
"U2" => array(LAN_USER_OPTIONS,E_16_USER, E_32_USER), // Manage only user-options
|
|
"U3" => array(LAN_USER_RANKS,E_16_USER, E_32_USER), // Manage only user-ranks
|
|
"W" => array(ADLAN_136,E_16_MAIL, E_32_MAIL), // Configure mail settings and mailout
|
|
|
|
|
|
// Content
|
|
"5" => array(ADLAN_42,E_16_CUST, E_32_CUST), // create/edit custom PAGES
|
|
"J" => array(ADLAN_42,E_16_CUST, E_32_CUST), // create/edit custom MENUS
|
|
"J1" => array(ADLAN_42." (".LAN_DELETE.")",E_16_CUST, E_32_CUST), // Delete Pages/Menus.
|
|
|
|
"H" => array(ADLAN_0,E_16_NEWS, E_32_NEWS), // Post News - All Areas except settings.
|
|
"H0" => array(ADLAN_0." (".LAN_CREATE.")",E_16_NEWS, E_32_NEWS), // Create News Items
|
|
"H1" => array(ADLAN_0." (".LAN_EDIT.")",E_16_NEWS, E_32_NEWS), // Edit News Items
|
|
"H2" => array(ADLAN_0." (".LAN_DELETE.")",E_16_NEWS, E_32_NEWS), // Delete News Items
|
|
"H3" => array(ADLAN_0." (".LAN_CATEGORY." - ".LAN_CREATE.")",E_16_NEWS, E_32_NEWS), // Create News Category
|
|
"H4" => array(ADLAN_0." (".LAN_CATEGORY." - ".LAN_EDIT.")",E_16_NEWS, E_32_NEWS), // Edit News Category
|
|
"H5" => array(ADLAN_0." (".LAN_CATEGORY." - ".LAN_DELETE.")",E_16_NEWS, E_32_NEWS), // Delete News Category
|
|
|
|
"N" => array(ADLAN_0." (".LAN_SUBMITTED.")",E_16_NEWS, E_32_NEWS), // Moderate submitted news
|
|
"V" => array(ADLAN_31,E_16_UPLOADS, E_32_UPLOADS), // Configure public file uploads
|
|
"M" => array(ADLAN_28,E_16_WELCOME, E_32_WELCOME), // Welcome Messages
|
|
|
|
// Tools
|
|
"Y" => array(ADLAN_147,E_16_INSPECT, E_32_INSPECT), // File inspector
|
|
"9" => array(ADLAN_40, E_16_MAINTAIN, E_32_MAINTAIN), // Take Down site for Maintenance
|
|
"O" => array(ADLAN_149,E_16_NOTIFY, E_32_NOTIFY), // Notify
|
|
"U" => array(ADLAN_157,E_16_CRON, E_32_CRON), // Schedule Tasks
|
|
"S" => array(ADLAN_155,E_16_ADMINLOG, E_32_ADMINLOG), // System Logging
|
|
|
|
// Manage
|
|
"B" => array(LAN_COMMENTMAN,E_16_COMMENT, E_32_COMMENT), // Moderate Comments
|
|
"6" => array(LAN_MEDIAMANAGER,E_16_FILE, E_32_FILE), // File-Manager - Upload /manage files -
|
|
"A" => array(LAN_MEDIAMANAGER." (".LAN_ALL.")",E_16_IMAGES, E_32_IMAGES), // Media-Manager All Areas.
|
|
"A1"=> array(LAN_MEDIAMANAGER." (".LAN_UPLOAD."/".LAN_IMPORT.")",E_16_IMAGES, E_32_IMAGES), // Media-Manager (Media Upload/Add/Import)
|
|
"A2"=> array(LAN_MEDIAMANAGER." (".LAN_CATEGORIES.")",E_16_IMAGES, E_32_IMAGES), // Media-Manager (Media-Categories)
|
|
|
|
"TMP"=> array(ADLAN_140." (".LAN_PREFS.")",E_16_THEMEMANAGER, E_32_THEMEMANAGER),
|
|
|
|
"2" => array(ADLAN_6,E_16_MENUS, E_32_MENUS), // Alter Menus
|
|
|
|
|
|
// "D"=> ADMSLAN_29, // Manage Banners (deprecated - now a plugin)
|
|
// "E"=> ADMSLAN_30, // News feed headlines (deprecated - now a plugin)
|
|
// "K"=>
|
|
|
|
// "P" // Reserved for Plugins
|
|
|
|
// "Q"=> array(ADMSLAN_24), // Manage download categories (deprecated - now a plugin)
|
|
// "R"=> ADMSLAN_44, // Post Downloads (deprecated)
|
|
|
|
|
|
// "Z"=> ADMSLAN_62, // Plugin Manager.. included under Plugins category.
|
|
);
|
|
|
|
|
|
// $sql = e107::getDb('sql2');
|
|
// $tp = e107::getParser();
|
|
|
|
$pg = e107::getPlug();
|
|
$installed = $pg->getInstalled();
|
|
foreach($installed as $plug=>$version)
|
|
{
|
|
$pg->load($plug);
|
|
|
|
$arr = array(
|
|
0 => $pg->getName(),
|
|
1 => $pg->getIcon(16),
|
|
2 => $pg->getIcon(32)
|
|
);
|
|
|
|
$key = "P".$pg->getId();
|
|
$this->plugin_perms[$key] = $arr;
|
|
}
|
|
|
|
/*
|
|
$plg = e107::getPlugin();
|
|
$allPlugins = $plg->getall(1); // Needs all for 'reading' and 'installed' for writing.
|
|
|
|
foreach($allPlugins as $k=>$row2)
|
|
{
|
|
if($plg->parse_plugin($row2['plugin_path']))
|
|
{
|
|
$plug_vars = $plg->plug_vars;
|
|
$this->plugin_perms[("P".$row2['plugin_id'])] = array($tp->toHTML($row2['plugin_name'], false, 'RAWTEXT,defs'));
|
|
$this->plugin_perms[("P".$row2['plugin_id'])][1] = $plg->getIcon($row2['plugin_path'],16);
|
|
$this->plugin_perms[("P".$row2['plugin_id'])][2] = $plg->getIcon($row2['plugin_path'],32);
|
|
}
|
|
}
|
|
*/
|
|
|
|
|
|
asort($this->plugin_perms);
|
|
|
|
$this->plugin_perms = array("Z"=> array('0'=>ADLAN_98)) + $this->plugin_perms;
|
|
|
|
if(e107::getConfig()->getPref('multilanguage'))
|
|
{
|
|
$lanlist = explode(",",e_LANLIST);
|
|
sort($lanlist);
|
|
foreach($lanlist as $langs)
|
|
{
|
|
$this->language_perms[$langs] = array("0"=>$langs);
|
|
}
|
|
}
|
|
|
|
if(getperms('0'))
|
|
{
|
|
$this->main_perms = array('0' => array('0'=>ADMSLAN_58));
|
|
}
|
|
|
|
// Note: Using array_merge or array_merge_recursive will corrupt the array.
|
|
$this->full_perms = $this->core_perms + $this->plugin_perms + $this->language_perms + $this->main_perms;
|
|
|
|
}
|
|
|
|
/**
|
|
* @param $key
|
|
* @return mixed
|
|
*/
|
|
function renderSectionDiz($key)
|
|
{
|
|
return $this->permSectionDiz[$key];
|
|
}
|
|
|
|
|
|
/**
|
|
* @param $type
|
|
* @return array|array[]
|
|
*/
|
|
function getPermList($type='all')
|
|
{
|
|
if($type == 'core')
|
|
{
|
|
return $this->core_perms;
|
|
}
|
|
if($type == 'plugin')
|
|
{
|
|
return $this->plugin_perms;
|
|
}
|
|
if($type == 'language')
|
|
{
|
|
return $this->language_perms;
|
|
}
|
|
if($type == 'main')
|
|
{
|
|
return $this->main_perms;
|
|
}
|
|
|
|
if($type == 'grouped')
|
|
{
|
|
$ret = array();
|
|
$ret['core'] = $this->core_perms;
|
|
$ret['plugin'] = $this->plugin_perms;
|
|
|
|
if(!empty($this->language_perms))
|
|
{
|
|
$ret['language'] = $this->language_perms;
|
|
}
|
|
|
|
if(!empty($this->main_perms))
|
|
{
|
|
$ret['main'] = $this->main_perms;
|
|
}
|
|
|
|
return $ret;
|
|
|
|
}
|
|
|
|
return $this->full_perms;
|
|
}
|
|
|
|
/**
|
|
* @param $arg
|
|
* @param $perms
|
|
* @param $info
|
|
* @return string
|
|
*/
|
|
function checkb($arg, $perms, $info='')
|
|
{
|
|
$frm = e107::getForm();
|
|
|
|
if(is_array($info))
|
|
{
|
|
$label = $info[0];
|
|
$icon_16 = varset($info[1]);
|
|
$icon_32 = varset($info[2]);
|
|
}
|
|
elseif($info)
|
|
{
|
|
$label = $info;
|
|
$icon_16 = "";
|
|
$icon_32 = "";
|
|
}
|
|
|
|
$class = getperms($arg, $perms) ? 'active' : '';
|
|
|
|
$par = "<tr class='{$class}'>
|
|
<td style='text-align:center'>".$icon_16."</td>
|
|
<td style='text-align:center'>".$frm->checkbox('perms[]', $arg, getperms($arg, $perms))."</td>
|
|
<td>".$frm->label($label,'perms[]', $arg)."</td>
|
|
</tr>";
|
|
|
|
return $par;
|
|
}
|
|
|
|
/**
|
|
* @param $perms
|
|
* @param $uniqueID
|
|
* @return string
|
|
*/
|
|
function renderPerms($perms, $uniqueID='')
|
|
{
|
|
$tmp = explode(".",$perms);
|
|
$tmp = array_filter($tmp);
|
|
|
|
$permdiz = $this->getPermList();
|
|
|
|
$ptext = array();
|
|
|
|
foreach($tmp as $p)
|
|
{
|
|
// if(trim($p) == ""){ continue; }
|
|
$val = vartrue($permdiz[$p],'missing '.$p);
|
|
$ptext[] = is_array($permdiz[$p]) ? $permdiz[$p][0] : $val;
|
|
}
|
|
|
|
$id = "id_".$uniqueID;
|
|
|
|
|
|
$text = "<div href='#id_{$id}' class='e-pointer e-expandit' title='".ADMSLAN_71."'>{$perms}</div>\n";
|
|
|
|
if(varset($ptext))
|
|
{
|
|
$text .= "<div id='id_{$id}' class='e-hideme'><ul><li>".implode("</li>\n<li>",$ptext)."</li></ul></div>\n";
|
|
}
|
|
|
|
/*
|
|
$text = "<a href='#".$id."' class='e-expandit' title='".ADMSLAN_71."'>{$perms}</a>";
|
|
|
|
if(varset($ptext))
|
|
{
|
|
$text .= "<div class='e-hideme' id='".$id."' ><ul><li>".implode("</li><li>",$ptext)."</li></ul></div>";
|
|
}
|
|
*/
|
|
return $text;
|
|
}
|
|
|
|
/**
|
|
* Render edit admin perms form.
|
|
*
|
|
* @param array $row [optional] containing $row['user_id'], $row['user_name'], $row['user_perms'];
|
|
* @return void
|
|
*/
|
|
function edit_administrator($row = '')
|
|
{
|
|
$pref = e107::getPref();
|
|
$lanlist = explode(",", e_LANLIST);
|
|
require_once(e_HANDLER."user_handler.php");
|
|
$prm = $this;
|
|
$ns = e107::getRender();
|
|
$sql = e107::getDb();
|
|
$frm = e107::getForm();
|
|
|
|
|
|
$a_id = $row['user_id'];
|
|
$ad_name = $row['user_name'];
|
|
$a_perms = $row['user_perms'];
|
|
/*
|
|
$text = "
|
|
<form method='post' action='".e_SELF."' id='myform'>
|
|
<fieldset id='core-administrator-edit'>
|
|
<legend class='e-hideme'>".ADMSLAN_52."</legend>
|
|
<table class='adminform'>
|
|
<colgroup>
|
|
<col class='col-label' />
|
|
<col class='col-control' />
|
|
</colgroup>
|
|
<tbody>
|
|
<tr>
|
|
<td class='control'>
|
|
|
|
|
|
";
|
|
*/
|
|
|
|
$text = "<form method='post' action='".e_SELF."' id='myform'>
|
|
<fieldset id='core-administrator-edit'>
|
|
<legend class='e-hideme'>".ADMSLAN_52."</legend>";
|
|
|
|
//XXX Bootstrap Tabs (as used below) should eventually be the default for all of the admin area.
|
|
$text .= '
|
|
<ul class="nav nav-tabs">
|
|
<li class="active"><a href="#tab1" data-toggle="tab" data-bs-toggle="tab">'.$this->renderSectionDiz('core').'</a></li>
|
|
<li><a href="#tab2" data-toggle="tab" data-bs-toggle="tab">'.$this->renderSectionDiz('plugin').'</a></li>
|
|
<li><a href="#tab3" data-toggle="tab" data-bs-toggle="tab">'.$this->renderSectionDiz('language').'</a></li>
|
|
<li><a href="#tab4" data-toggle="tab" data-bs-toggle="tab">'.$this->renderSectionDiz('main').'</a></li>
|
|
</ul>
|
|
|
|
<div class="tab-content">
|
|
|
|
<div class="tab-pane active " id="tab1">
|
|
<div class="separator">
|
|
'.$this->renderPermTable('core',$a_perms).'
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane" id="tab2">
|
|
<div class="separator">
|
|
'.$this->renderPermTable('plugin',$a_perms).'
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane" id="tab3">
|
|
<div class="separator">
|
|
'.$this->renderPermTable('language',$a_perms).'
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane" id="tab4">
|
|
<div class="separator">
|
|
'.$this->renderPermTable('main',$a_perms).'
|
|
</div>
|
|
</div>
|
|
|
|
</div>';
|
|
|
|
|
|
// $text .= $this->renderPermTable('grouped',$a_perms);
|
|
|
|
|
|
|
|
$text .= $this->renderCheckAllButtons();
|
|
|
|
// $text .= "</td></tr></tbody></table>";
|
|
|
|
$text .= "
|
|
".$this->renderSubmitButtons()."
|
|
<input type='hidden' name='ad_name' value='{$ad_name}' />
|
|
<input type='hidden' name='a_id' value='{$a_id}' />
|
|
</fieldset>
|
|
</form>
|
|
";
|
|
|
|
// $text .= $this->renderPermTable('core',$a_perms);
|
|
|
|
$ns->tablerender(ADMSLAN_52.SEP.$ad_name, $text);
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
function renderCheckAllButtons()
|
|
{
|
|
$frm = e107::getForm();
|
|
return "
|
|
<div class='field-section'>
|
|
".$frm->admin_button('check_all', 'jstarget:perms', 'action', LAN_CHECKALL)."
|
|
".$frm->admin_button('uncheck_all', 'jstarget:perms', 'action', LAN_UNCHECKALL)."
|
|
</div>
|
|
";
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
function renderSubmitButtons()
|
|
{
|
|
$frm = e107::getForm();
|
|
return "
|
|
<div class='buttons-bar center'>
|
|
".$frm->admin_button('update_admin', LAN_UPDATE, 'update')."
|
|
".$frm->admin_button('go_back', LAN_BACK, 'cancel')."
|
|
</div>
|
|
";
|
|
}
|
|
|
|
/**
|
|
* @param $type
|
|
* @param $a_perms
|
|
* @return string
|
|
*/
|
|
function renderPermTable($type, $a_perms='')
|
|
{
|
|
|
|
|
|
|
|
if($type == 'tabs')
|
|
{
|
|
$groupedList = $this->getPermList('grouped');
|
|
$tab = array();
|
|
foreach($groupedList as $section=>$list)
|
|
{
|
|
$text = '';
|
|
// $text .= "\t\t<div class='field-section'><h4>".$prm->renderSectionDiz($section)."</h4>"; //XXX Lan - General
|
|
$text .= "\t\t<table class='table adminlist'>
|
|
<colgroup>
|
|
<col class='center' style='width:50px' />
|
|
<col style='width:50px' />
|
|
<col />
|
|
</colgroup>
|
|
<tbody>";
|
|
|
|
foreach($list as $key=>$diz)
|
|
{
|
|
$text .= $this->checkb($key, $a_perms, $diz);
|
|
}
|
|
|
|
$text .= "</tbody></table>";
|
|
|
|
$tab[] = array('caption'=>$this->renderSectionDiz($section), 'text'=>$text);
|
|
|
|
}
|
|
|
|
// return print_a($groupedList);
|
|
return e107::getForm()->tabs($tab);
|
|
}
|
|
|
|
$groupedList = $this->getPermList($type);
|
|
|
|
if($type != 'grouped')
|
|
{
|
|
$text = "\t\t<table class='table adminform'>
|
|
<colgroup>
|
|
<col class='center' style='width:50px' />
|
|
<col style='width:50px' />
|
|
<col />
|
|
</colgroup>
|
|
<tbody>";
|
|
// $text .= "<tr><td class='field-section' colspan='3'><h4>".$this->renderSectionDiz($type)."</h4></td></tr>"; //XXX Lan - General
|
|
// $text .= "\t\t<div class='field-section'><h4>".$prm->renderSectionDiz($section)."</h4>"; //XXX Lan - General
|
|
foreach($groupedList as $key=>$diz)
|
|
{
|
|
$text .= $this->checkb($key, $a_perms, $diz);
|
|
}
|
|
$text .= "</tbody>
|
|
</table>";
|
|
|
|
return $text;
|
|
}
|
|
|
|
$text = "";
|
|
foreach($groupedList as $section=>$list)
|
|
{
|
|
$text .= "\t\t<table class='table adminlist'>
|
|
<colgroup>
|
|
<col class='center' style='width:50px' />
|
|
<col style='width:50px' />
|
|
<col />
|
|
</colgroup>
|
|
<tbody><tr><td class='field-section' colspan='3'><h4>".$this->renderSectionDiz($section)."</h4></td></tr>"; //XXX Lan - General
|
|
// $text .= "\t\t<div class='field-section'><h4>".$prm->renderSectionDiz($section)."</h4>"; //XXX Lan - General
|
|
foreach($list as $key=>$diz)
|
|
{
|
|
$text .= $this->checkb($key, $a_perms, $diz);
|
|
}
|
|
$text .= "</tbody>
|
|
</table>";
|
|
}
|
|
|
|
return $text;
|
|
}
|
|
|
|
/**
|
|
* Update user (admin) permissions.
|
|
* NOTE: exit if $uid is not an integer or is 0.
|
|
*
|
|
* @param integer $uid
|
|
* @param array $permArray eg. array('A', 'K', '1');
|
|
* @return void
|
|
*/
|
|
function updatePerms($uid, $permArray)
|
|
{
|
|
global $admin_log;
|
|
|
|
$sql = e107::getDb();
|
|
$tp = e107::getParser();
|
|
|
|
$modID = intval($uid);
|
|
$mes = e107::getMessage();
|
|
if ($modID == 0)
|
|
{
|
|
$mes->addError("Malfunction at line ".__LINE__ ." of user_handler.php");
|
|
return;
|
|
}
|
|
|
|
$sysuser = e107::getSystemUser($modID, false);
|
|
$row = $sysuser->getData();
|
|
$a_name = $row['user_name'];
|
|
|
|
$perm = "";
|
|
|
|
foreach($permArray as $value)
|
|
{
|
|
$value = $tp->toDB($value);
|
|
if ($value == "0")
|
|
{
|
|
if (!getperms('0')) { $value = ""; break; }
|
|
$perm = "0"; break;
|
|
}
|
|
|
|
if ($value)
|
|
{
|
|
$perm .= $value.".";
|
|
}
|
|
}
|
|
|
|
//$sql->db_Update("user", "user_perms='{$perm}' WHERE user_id='{$modID}' ")
|
|
if(!$sysuser->isAdmin())
|
|
{
|
|
$sysuser->set('user_admin', 1)->save();
|
|
$vars = array('x'=>$sysuser->getId(), 'y'=> $sysuser->getName(), 'z'=>$sysuser->getValue('email'));
|
|
$lan = e107::getParser()->lanVars( USRLAN_164, $vars);
|
|
e107::getLog()->add('USET_08', $lan, E_LOG_INFORMATIVE);
|
|
}
|
|
|
|
e107::getMessage()->addAuto($sysuser->set('user_perms', $perm)->save(), 'update', sprintf(LAN_UPDATED, $tp->toDB($_POST['ad_name'])), false, false);
|
|
$logMsg = str_replace(array('[x]', '[y]'),array($modID, $a_name),ADMSLAN_72).$perm;
|
|
e107::getLog()->add('ADMIN_01',$logMsg,E_LOG_INFORMATIVE,'');
|
|
}
|
|
|
|
/**
|
|
* Simulate whether a user has admin permissions based on the requested access code(s) and admin's permissions.
|
|
*
|
|
* @param string $requestedAccess The serialized requested access code or codes which will match if any of the
|
|
* codes are in the admin user's admin permissions.
|
|
* This is a pipe-delimited (`|`) list of access codes.
|
|
* Example: `C|4`
|
|
* @param string $adminPermissions The serialized admin user's admin permissions.
|
|
* This is a dot-delimited (`.`) list of access codes.
|
|
* Example: `C.F.G.L.T.1.X.I.8.K.3.4.U0.U1.U2.U3.6.A.A1.A2.TMP.2.Z.P3.P4.English`
|
|
* @return bool true if the user has matching permissions, false otherwise.
|
|
*/
|
|
public static function simulateHasAdminPerms($requestedAccess, $adminPermissions)
|
|
{
|
|
if(trim($adminPermissions) === '')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if($requestedAccess === 0)
|
|
{
|
|
$requestedAccess = '0';
|
|
}
|
|
|
|
if($adminPermissions === '0' || $adminPermissions === '0.')
|
|
{
|
|
return true;
|
|
}
|
|
|
|
$adminPermissionsArray = explode('.', $adminPermissions);
|
|
|
|
if(in_array($requestedAccess, $adminPermissionsArray, false))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if(strpos($requestedAccess, '|'))
|
|
{
|
|
$requestedAccessCodes = explode('|', $requestedAccess);
|
|
foreach($requestedAccessCodes as $requestedAccessCode)
|
|
{
|
|
if(in_array($requestedAccessCode, $adminPermissionsArray))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Simulate whether a user has admin permissions to a plugin.
|
|
*
|
|
* @param e_db $db The database handle to query installed plugins.
|
|
* @param string $pluginName The plugin name, not the plugin path like in {@link getperms()}.
|
|
* @param string $adminPermissions The serialized admin user's admin permissions.
|
|
* This is a dot-delimited (`.`) list of access codes.
|
|
* Example: `C.F.G.L.T.1.X.I.8.K.3.4.U0.U1.U2.U3.6.A.A1.A2.TMP.2.Z.P3.P4.English`
|
|
* @return bool true if the user has matching permissions, false otherwise.
|
|
*/
|
|
public static function simulateHasPluginAdminPerms($db, $pluginName, $adminPermissions)
|
|
{
|
|
$arg = "0";
|
|
if($db->select(
|
|
'plugin',
|
|
'plugin_id',
|
|
"plugin_path = :plugin_path LIMIT 1",
|
|
["plugin_path" => $pluginName]
|
|
))
|
|
{
|
|
$row = $db->fetch();
|
|
$arg = 'P' . $row['plugin_id'];
|
|
}
|
|
return self::simulateHasAdminPerms($arg, $adminPermissions);
|
|
}
|
|
}
|