<?php
/**
* @module Users
*/
/**
* Class representing 'User' rows in the 'Users' database
* You can create an object of this class either to
* access its non-static methods, or to actually
* represent a user row in the Users database.
*
* @class Users_User
* @extends Base_Users_User
*/
class Users_User extends Base_Users_User
{
/**
* The setUp() method is called the first time
* an object of this class is constructed.
* @method setUp
*/
function setUp()
{
parent::setUp();
}
/**
* Intelligently retrieves user by id
* @method fetch
* @static
* @param {string} $userId
* @param {boolean} [$throwIfMissing=false] If true, throws an exception if the user can't be fetched
* @return {Users_User|null|array} If $userId is an array, returns an array of ($userId => $user) pairs.
* Otherwise returns a Users_User object, or null.
* @throws {Users_Exception_NoSuchUser} if the user wasn't found in the database
*/
static function fetch ($userId, $throwIfMissing = false)
{
if (is_array($userId)) {
$users = Users_User::select()
->where(array('id' => $userId))
->fetchDbRows('id');
if ($throwIfMissing) {
foreach ($userId as $uid) {
if (!isset($users[$uid])) {
$missing[] = $uid;
}
}
if ($missing) {
throw new Q_Exception("Missing users with ids " . implode(', ', $missing));
}
}
return $users;
}
if (empty($userId)) {
$result = null;
} else if (!empty(self::$cache['getUser'][$userId])) {
$result = self::$cache['getUser'][$userId];
} else {
$user = new Users_User();
$user->id = $userId;
if (!$user->retrieve()) {
$user = null;
}
self::$cache['getUser'][$userId] = $user;
$result = $user;
}
if (!$result and $throwIfMissing) {
throw new Users_Exception_NoSuchUser();
}
return $result;
}
/**
* Get the url of the user's icon
* @param {string} [$basename=""] The last part after the slash, such as "50.png"
* @return {string} The stream's icon url
*/
function iconUrl($basename = null)
{
$replacements = array(
'userId' => Q_Utils::splitId($this->id)
);
return Users::iconUrl(
isset($this->icon) ? Q::interpolate($this->icon, $replacements) : 'default',
$basename
);
}
/**
* Returns the fields and values we can export to clients.
* Can also contain "messageTotals", "relatedToTotals" and "relatedFromTotals".
* @method exportArray
* @param {$array} [$options=null] can include the following:
* @param {string} [$options.asAvatar] set to true if only the avatar fields should be exported
* @return {array}
*/
function exportArray($options = null)
{
$fields = empty($options['asAvatar'])
? Q_Config::expect('Users', 'exportFields')
: Q_Config::expect('Users', 'avatarFields');
$u = array();
foreach ($fields as $field) {
if (isset($this->$field)) {
$u[$field] = $this->$field;
}
}
if (isset($u['uids'])) {
$u['uids'] = $this->getAllUids();
}
return $u;
}
/**
* Add this user to the list of user objects to be preloaded onto the client with the rest of the page
* @method addPreloaded
*/
function addPreloaded()
{
self::$preloaded[$this->id] = $this;
}
/**
* Remove this user from the list of user objects to be preloaded onto the client with the rest of the page
* @method addPreloaded
*/
function removePreloaded()
{
unset(self::$preloaded[$this->id]);
}
/**
* Use this function to display the name of a user
* @param {array} $options to pass to any hooks
*/
function displayName($options = array())
{
$user = $this;
$name = Q::event('Users/User/displayName', compact('user', 'options'), 'before');
return isset($name) ? $name : $this->username;
}
/**
* Call this to prepare the passphrase before passing it to
* Users::hashPassphrase() and Users::verifyPassphrase()
* @param {array} $passphrase The value to be checked depends on value of isHashed.
* @param {integer} $isHashed You can pass 0 if this is actually the passphrase,
* 1 if it has been hashed using sha1("$realPassphrase\t$userId")
*/
function preparePassphrase($passphrase, $isHashed)
{
if ($result = Q::event("Users/preparePassphraseHash", compact(
'passphrase', 'isHashed', 'user'), 'before'
)) {
return $result;
}
if (!$isHashed and $passphrase) {
$passphrase = sha1($passphrase . "\t" . $this->id);
}
return $passphrase;
}
/**
* @method beforeSet_username
* @param {string} $username
* @return {array}
*/
function beforeSet_username($username)
{
parent::beforeSet_username($username);
if (!isset($username)) {
return array('username', $username);
}
/**
* @event Users/validate/username
* @param {&string} username
*/
Q::event(
'Users/validate/username',
array('username' => & $username, 'user' => $this)
);
return array('username', $username);
}
/**
* @method beforeSet_emailAddress
* @param {string} $emailAddress
* @return {array}
*/
function beforeSet_emailAddress($emailAddress)
{
parent::beforeSet_emailAddress($emailAddress);
/**
* @event Users/validate/emailAddress
* @param {&string} emailAddress
*/
Q::event(
'Users/validate/emailAddress',
array('emailAddress' => & $emailAddress)
);
if (!isset($emailAddress)) {
return array('emailAddress', $emailAddress);
}
return array('emailAddress', $emailAddress);
}
/**
* @method idFilter
* @param {string} $params
* @return {boolean}
*/
static function idFilter($params)
{
/**
* @event Users/filter/id
* @param {string} id
* @return {boolean}
*/
return Q::event('Users/filter/id', $params);
}
/**
* Assigns 'id' and verifies 'username' fields
* @method beforeSave
* @param {array} $modifiedFields
* @return {array}
* @throws {Users_Exception_UsernameExists}
* If username already exists
*/
function beforeSave($updatedFields)
{
if (!$this->retrieved) {
if (!isset($updatedFields['id'])) {
$this->id = $updatedFields['id'] =
self::db()->uniqueId(self::table(), 'id', null, array(
'filter' => array('Users_User', 'idFilter')
));
}
if (!isset($updatedFields['username'])) {
// put an empty username for now
$this->username = $updatedFields['username'] = '';
}
if (empty($updatedFields['icon'])) {
$this->icon = $updatedFields['icon'] = '{{Users}}/img/icons/default';
}
} else if (isset($updatedFields['icon']) and !$updatedFields['icon']) {
$this->icon = $updatedFields['icon'] = '{{Users}}/img/icons/default';
}
if (!empty($updatedFields['username'])) {
$app = Q::app();
$unique = Q_Config::get('Users', 'model', $app, 'uniqueUsername', false);
if ($unique) {
$username = $updatedFields['username'];
$criteria = compact('username');
if (isset($this->id)) {
$criteria['id != '] = $this->id;
}
$rows = Users_User::select('COUNT(1)')
->where($criteria)
->fetchAll(PDO::FETCH_NUM);
if ($rows[0][0] > 0) {
throw new Users_Exception_UsernameExists($criteria, 'username');
}
}
}
$user = $this;
Q::event(
'Users/User/save',
compact('user', 'updatedFields'),
'before'
);
return parent::beforeSave($updatedFields);
}
function afterSaveExecute($result, $query, $modifiedFields, $where, $inserted)
{
$user = $this;
if ($inserted) {
if (Users::isCommunityid($user->id)) {
$roles = Q_Config::get('Users', 'onInsert', 'roles', array());
Users_Label::addLabel($labels, $this->id, null, null, $this->id, true);
} else {
$labels = Q_Config::get('Users', 'onInsert', 'labels', array());
Users_Label::addLabel($labels, $this->id, null, null, $this->id, true);
}
}
Q::event(
'Users/User/save',
compact('user', 'result', 'query', 'modifiedFields', 'where'),
'after'
);
return $result;
}
/**
* Add a contact label
* @method {boolean} addLabel
* @param {string|array} $label the label or array of ($label => array($title, $icon))
* @param {string} [$title=''] specify the title, otherwise a default one is generated
* @param {string} [$icon='default']
* @param {string} [$asUserId=null] The user to do this operation as.
* Defaults to the logged-in user. Pass false to skip access checks.
* @return {Users_Label}
*/
function addLabel($label, $title='', $icon='default', $asUserId = null)
{
Users_Label::addLabel($label, $this->id, $title, $icon, $asUserId);
}
/**
* Update labels
* @method updateLabel
* @param {string} $label
* @param {array} $updates Can contain one or more of "title", "icon"
* @param {string} [$asUserId=null] The user to do this operation as.
* Defaults to the logged-in user. Pass false to skip access checks.
* @throws {Users_Exception_NotAuthorized}
* @return {Db_Query_Mysql}
*/
function updateLabel($label, $updates, $asUserId = null)
{
Users_Label::updateLabel($label, $updates, $this->id, $asUserId);
}
/**
* Remove label
* @method removeLabel
* @param {string} $label
* @param {string|null} [$userId=null]
* The user whose label is to be removed
* @param {string} [$asUserId=null] The user to do this operation as.
* Defaults to the logged-in user. Pass false to skip access checks.
* @return {Db_Query_MySql}
*/
static function removeLabel($label, $userId = null, $asUserId = null)
{
Users_Label::removeLabel($label, $userId, $asUserId);
}
/**
* @method addContact
* @param {string} $contactUserId
* The id of the user who is the contact
* @param {string|array} $label
* The label of the contact. This can be a string or an array of strings, in which case
* multiple contact rows are saved.
* @param {string} [$nickname='']
* Optional nickname to assign to the contact
* @optional
* @param {string} [$asUserId=null] The user to do this operation as.
* Defaults to the logged-in user. Pass false to skip access checks.
* @throws {Q_Exception_RequiredField}
* if $label is missing
* @return {array} Array of contacts that are saved
*/
function addContact($label, $contactUserId, $nickname = '', $asUserId = null)
{
Users_Contact::addContact($this->id, $label, $contactUserId, $nickname, $asUserId);
}
/**
* Update a particular contact with a given userId, label, contactId.
* @method updateContact
* @static
* @param {string} $label
* @param {string} $contactUserId
* @param {array} $updates should be an array with only one key: "nickname"
* @param {string} [$asUserId=null] The user to do this operation as.
* Defaults to the logged-in user. Pass false to skip access checks.
* @throws {Users_Exception_NotAuthorized}
* @return {Db_Query_Mysql}
*/
function updateContact($label, $contactUserId, $updates, $asUserId = null)
{
Users_Contact::updateContact($this->id, $label, $contactUserId, $updates, $asUserId);
}
/**
* @method removeContact
* @param {string|array} $label
* The label of the contact.
* @param {string} $contactUserId
* The id of the user who is the contact
* @param {string} [$asUserId=null]
* The id of the user who is the contact
* @return {Db_Mysql}
*/
function removeContact($label, $contactUserId, $asUserId = null)
{
Users_Contact::removeContact($this->id, $label, $contactUserId, $asUserId);
}
/**
* Starts the process of adding an email to a saved user object.
* Also modifies and saves this user object back to the database.
* @method addEmail
* @param {string} $emailAddress
* The email address to add.
* @param {string|array} [$activationEmailSubject=null]
* The subject of the activation email to send.
* You can also pass an array($source, array($key1, $key2)) to use Q_Text.
* @param {string} [$activationEmailView=null]
* The view to use for the body of the activation email to send.
* @param {boolean} [$html=true]
* Defaults to true. Whether to send as HTML email.
* @param {array} [$fields=array()]
* An array of additional fields to pass to the email view.
* @param {array} [$options=array()] Array of options. Can include:
* @param {string} [$options.html=false] Whether to send as HTML email.<br/>
* @param {string} [$options.name] A human-readable name in addition to the address.
* @param {string} [$options.from] An array of (emailAddress, human_readable_name)
* @param {string} [$options.delay] A delay, in milliseconds, to wait until sending email. Only works if Node server is listening.
* @param {string} [$options.activation] The key under "Users"/"transactional" config to use for getting activation messages by default. Set to false to skip sending the activation message for some reason.
* @param {Users_Email} [&$email] Optional reference to be filled
* @return {boolean}
* Returns true on success.
* Returns false if this email address is already verified for this user.
* @throws {Q_Exception_WrongValue}
* If the email address is in an invalid format, this is thrown.
* @throws {Users_Exception_AlreadyVerified}
* If the email address already exists and has been verified for
* another user, then this exception is thrown.
*/
function addEmail(
$emailAddress,
$activationEmailSubject = null,
$activationEmailView = null,
array $fields = array(),
array $options = array(),
&$email = null)
{
if (!isset($options['html'])) {
$options['html'] = true;
}
if (!Q_Valid::email($emailAddress, $normalized)) {
throw new Q_Exception_WrongValue(array(
'field' => 'Email',
'range' => 'a valid address'
), 'emailAddress');
}
$email = new Users_Email();
$email->address = $normalized;
if ($email->retrieve(null, array('ignoreCache' => true))
and $email->state !== 'unverified') {
if ($email->userId === $this->id) {
$email->set('user', $this);
return $email;
}
// Otherwise, say it's verified for another user,
// even if it unsubscribed or was suspended.
throw new Users_Exception_AlreadyVerified(array(
'key' => 'email address',
'userId' => $email->userId
), 'emailAddress');
}
$user = $this;
// If we are here, then the email record either
// doesn't exist, or hasn't been verified yet.
// In either event, update the record in the database,
// and re-send the email.
$minutes = Q_Config::get('Users', 'activation', 'expires', 60*24*7);
$email->state = 'unverified';
$email->userId = $this->id;
$email->activationCode = strtolower(Q_Utils::unique(7));
$email->activationCodeExpires = new Db_Expression(
"CURRENT_TIMESTAMP + INTERVAL $minutes MINUTE"
);
$email->authCode = sha1(microtime() . mt_rand());
$link = Q_Uri::url(
'Users/activate?code='.urlencode($email->activationCode)
. ' emailAddress='.urlencode($email->address)
);
$unsubscribe = Q_Uri::url('Users/unsubscribe?' . http_build_query(array(
'authCode' => $email->authCode,
'emailAddress' => $email->address
)));
$communityName = Users::communityName();
$communitySuffix = Users::communitySuffix();
/**
* @event Users/addIdentifier {before}
* @param {string} user
* @param {string} email
*/
Q::event('Users/addIdentifier', compact('user', 'email', 'link', 'unsubscribe'), 'before');
$email->save();
$this->emailAddressPending = $normalized;
$this->save();
if ($activation = Q::ifset($options, 'activation', 'activation')) {
if (!isset($activationEmailView)) {
$activationEmailView = Q_Config::get(
'Users', 'transactional', $activation, 'body', 'Users/email/activation.php'
);
}
if (!isset($activationEmailSubject)) {
$activationEmailSubject = Q_Config::get(
'Users', 'transactional', $activation, 'subject',
"Welcome! Please confirm your email address."
);
}
$fields2 = array_merge($fields, array(
'user' => $this,
'email' => $email,
'app' => Q::app(),
'communityName' => $communityName,
'communitySuffix' => $communitySuffix,
'baseUrl' => Q_Request::baseUrl(),
'link' => $link,
'unsubscribe' => $unsubscribe
));
$email->sendMessage(
$activationEmailSubject,
$activationEmailView,
$fields2,
$options
); // may throw exception if badly configured
}
/**
* @event Users/addIdentifier {after}
* @param {string} user
* @param {string} email
*/
Q::event('Users/addIdentifier', compact('user', 'email', 'link'), 'after');
}
/**
* @method setEmailAddress
* @param {string} $emailAddress
* @param {boolean} [$verified=false]
* Whether to force the email address to be marked verified for this user
* @param {Users_Email} [&$email] Optional reference to be filled
* @throws {Q_Exception_MissingRow}
* If e-mail address is missing
* @throws {Users_Exception_AlreadyVerified}
* If user is already verified
* @throws {Users_Exception_WrongState}
* If verification state is wrong
*/
function setEmailAddress($emailAddress, $verified = false, &$email = null)
{
$email = new Users_Email();
Q_Valid::email($emailAddress, $normalized);
$email->address = $normalized;
$retrieved = $email->retrieve(null, array('ignoreCache' => true));
if (empty($email->activationCode)) {
$email->activationCode = '';
$email->activationCodeExpires = null;
}
$email->authCode = sha1(microtime() . mt_rand());
if ($verified) {
$email->userId = $this->id;
} else {
if (!$retrieved) {
throw new Q_Exception_MissingRow(array(
'table' => "an email",
'criteria' => "address $emailAddress"
), 'emailAddress');
}
if ($email->userId != $this->id) {
// We're going to tell them it's verified for someone else,
// even though it may not have been verified yet.
// In the future, might throw a more accurate exception.
throw new Users_Exception_AlreadyVerified(array(
'key' => 'email address',
'userId' => $email->userId
));
}
if (!in_array($email->state, array('unverified', 'active'))) {
throw new Users_Exception_WrongState(array(
'key' => $email->address,
'state' => $email->state
), 'emailAddress');
}
}
// Everything is okay. Assign it!
$email->state = 'active';
$email->save();
$ui = new Users_Identify();
$ui->identifier = "email_hashed:".Q_Utils::hash($normalized);
$ui->state = 'verified';
$ui->userId = $this->id;
$ui->save(true);
$this->emailAddressPending = '';
$this->emailAddress = $emailAddress;
$this->save();
$user = $this;
Q_Response::removeNotice('Users/email');
/**
* @event Users/setEmailAddress {after}
* @param {string} user
* @param {string} email
*/
Q::event('Users/setEmailAddress', compact('user', 'email'), 'after');
return true;
}
/**
* Starts the process of adding a mobile to a saved user object.
* Also modifies and saves this user object back to the database.
* @method addMobile
* @param {string} $mobileNumber
* The mobile number to add.
* @param {string} [$activationMessageView=null]
* The view to use for the body of the activation message to send.
* @param {array} [$fields=array()]
* An array of additional fields to pass to the mobile view.
* @param {array} [$options=array()] Array of options. Can include:
* @param {string} [$options.delay] A delay, in milliseconds, to wait until sending email. Only works if Node server is listening.
* @param {string} [$options.activation] The key under "Users"/"transactional" config to use for getting activation messages by default. Set to false to skip sending the activation message for some reason.
* @param {Users_Mobile} [&$mobile] Optional reference to be filled
* @return {boolean}
* Returns true on success.
* Returns false if this mobile number is already verified for this user.
* @throws {Q_Exception_WrongValue}
* If the mobile number is in an invalid format, this is thrown.
* @throws {Users_Exception_AlreadyVerified}
* If the mobile number already exists and has been verified for
* another user, then this exception is thrown.
*/
function addMobile(
$mobileNumber,
$activationMessageView = null,
array $fields = array(),
array $options = array(),
&$mobile = null)
{
if (!Q_Valid::phone($mobileNumber, $normalized)) {
throw new Q_Exception_WrongValue(array(
'field' => 'Mobile phone',
'range' => 'a valid number'
), 'mobileNumber');
}
$mobile = new Users_Mobile();
$mobile->number = $normalized;
if ($mobile->retrieve(null, array('ignoreCache' => true))
and $mobile->state !== 'unverified') {
if ($mobile->userId === $this->id) {
$mobile->set('user', $this);
return $mobile;
}
// Otherwise, say it's verified for another user,
// even if it unsubscribed or was suspended.
throw new Users_Exception_AlreadyVerified(array(
'key' => 'mobile number',
'userId' => $mobile->userId
), 'mobileNumber');
}
$user = $this;
// If we are here, then the mobile record either
// doesn't exist, or hasn't been verified yet.
// In either event, update the record in the database,
// and re-send the mobile.
$minutes = Q_Config::get('Users', 'activation', 'expires', 60*24*7);
$mobile->state = 'unverified';
$mobile->userId = $this->id;
$mobile->activationCode = strtolower(Q_Utils::unique(7));
$mobile->activationCodeExpires = new Db_Expression(
"CURRENT_TIMESTAMP + INTERVAL $minutes MINUTE"
);
$number = $mobile->number;
if (substr($number, 0, 2) == '+1') {
$number = substr($number, 2);
}
$mobile->authCode = sha1(microtime() . mt_rand());
$link = Q_Uri::url(
'Users/activate?code='.urlencode($mobile->activationCode)
. ' mobileNumber='.urlencode($number)
);
$communityName = Users::communityName();
$communitySuffix = Users::communitySuffix();
/**
* @event Users/addIdentifier {before}
* @param {string} user
* @param {string} mobile
*/
Q::event('Users/addIdentifier', compact('user', 'mobile', 'link'), 'before');
$mobile->save();
$this->mobileNumberPending = $normalized;
$this->save();
if ($activation = Q::ifset($options, 'activation', 'activation')) {
if (!isset($activationMessageView)) {
$activationMessageView = Q_Config::get(
'Users', 'transactional', $activation, 'sms', 'Users/sms/activation.php'
);
}
$fields2 = array_merge($fields, array(
'user' => $this,
'mobile' => $mobile,
'app' => Q::app(),
'communityName' => $communityName,
'communitySuffix' => $communitySuffix,
'baseUrl' => Q_Request::baseUrl(),
'link' => $link
));
$mobile->sendMessage(
$activationMessageView,
$fields2,
$options
);
}
Q_Response::removeNotice('Users/mobile');
/**
* @event Users/addIdentifier {after}
* @param {string} user
* @param {string} mobile
*/
Q::event('Users/addIdentifier', compact('user', 'mobile', 'link'), 'after');
}
/**
* @method setMobileNumber
* @param {string} $mobileNumber
* @param {boolean} [$verified=false]
* Whether to force the mobile number to be marked verified for this user
* @param {Users_Mobile} [&$mobile] Optional reference to be filled
* @throws {Q_Exception_MissingRow}
* If mobile number is missing
* @throws {Users_Exception_AlreadyVerified}
* If user was already verified
* @throws {Users_Exception_WrongState}
* If verification state is wrong
*/
function setMobileNumber($mobileNumber, $verified = false, &$mobile = null)
{
Q_Valid::phone($mobileNumber, $normalized);
$mobile = new Users_Mobile();
$mobile->number = $normalized;
$retrieved = $mobile->retrieve(null, array('ignoreCache' => true));
if (empty($mobile->activationCode)) {
$mobile->activationCode = '';
$mobile->activationCodeExpires = null;
}
$mobile->authCode = sha1(microtime() . mt_rand());
if ($verified) {
$mobile->userId = $this->id;
} else if ($retrieved) {
if (!$retrieved) {
throw new Q_Exception_MissingRow(array(
'table' => "a mobile phone",
'criteria' => "number $normalized"
), 'mobileNumber');
}
if ($retrieved and $mobile->userId != $this->id) {
// We're going to tell them it's verified for someone else,
// even though it may not have been verified yet.
// In the future, might throw a more accurate exception.
throw new Users_Exception_AlreadyVerified(array(
'key' => 'mobile number',
'userId' => $mobile->userId
));
}
if (!in_array($mobile->state, array('unverified', 'active'))) {
throw new Users_Exception_WrongState(array(
'key' => $mobile->number,
'state' => $mobile->state
), 'mobileNumber');
}
}
// Everything is okay. Assign it!
$mobile->state = 'active';
$mobile->save();
$ui = new Users_Identify();
$ui->identifier = "mobile_hashed:".Q_Utils::hash($normalized);
$ui->state = 'verified';
$ui->userId = $this->id;
$ui->save(true);
$this->mobileNumberPending = '';
$this->mobileNumber = $normalized;
$this->save();
$user = $this;
/**
* @event Users/setMobileNumber {after}
* @param {string} user
* @param {string} mobile
*/
Q::event('Users/setMobileNumber', compact('user', 'mobile'), 'after');
return true;
}
/**
* Sets the user as verified without any further ado.
* Can be used e.g. after following an invitation link
* May download the gravatar icon for the user.
* To log in using another session, the user would be asked to set up
* a passphrase.
* @return {boolean} whether the user was successfully set as verified
*/
function setVerified()
{
$identifier = null;
if ($this->signedUpWith === 'none') {
if (!empty($this->emailAddressPending)) {
// invite must have been sent to email address
$identifier = $this->emailAddressPending;
$this->emailAddressPending = '';
$this->signedUpWith = 'email';
} else if (!empty($this->mobileNumberPending)) {
// invite must have been sent to mobile number
$identifier = $this->mobileNumberPending;
$this->mobileNumberPending = '';
$this->signedUpWith = 'mobile';
}
}
if (empty($identifier)) return false;
if (Q_Valid::email($identifier, $emailAddress)) {
$this->setEmailAddress($emailAddress, true);
} else if (Q_Valid::phone($identifier, $mobileNumber)) {
$this->setMobileNumber($mobileNumber, true);
} else {
throw new Q_Exception_WrongType(array(
'field' => 'identifier',
'type' => 'email address or mobile number'
), array('emailAddress', 'mobileNumber'));
}
// Import the user's icon and save it
if (empty($this->icon)
|| substr($this->icon, 0, 7) === 'default'
|| substr($this->icon, 0, 6) === 'future') {
$hash = md5(strtolower(trim($identifier)));
$icon = array(
'40.png' => array('hash' => $hash, 'size' => 40),
'50.png' => array('hash' => $hash, 'size' => 50),
'80.png' => array('hash' => $hash, 'size' => 80)
);
Users::importIcon($this, $icon);
}
return true;
}
/**
* Get the Users_Email object corresponding to this user's email address, if any
* @method email
* @return Users_Email
*/
function email()
{
if ($this->emailAddress) {
return null;
}
$email = new Users_Email();
$email->address = $this->emailAddress;
return $email->retrieve();
}
/**
* Get the Users_Mobile object corresponding to this user's email address, if any
* @method mobile
* @return Users_Mobile
*/
function mobile()
{
if ($this->mobileNumber) {
return null;
}
$mobile = new Users_Mobile();
$mobile->number = $this->mobileNumber;
return $mobile->retrieve();
}
/**
* @method getAllUids
* @return {array} An array of ($platform => $uid) pairs
*/
function getAllUids()
{
return empty($this->uids)
? array()
: json_decode($this->uids, true);
}
/**
* @method getUid
* @param {string} $platform The name of the platform
* @param {string|null} $default The value to return if the uid is missing
* @return {string|null} The value of the uid, or the default value, or null
*/
function getUid($platform, $default = null)
{
$uids = $this->getAllUids();
return isset($uids[$platform]) ? $uids[$platform] : $default;
}
/**
* @method setUid
* @param {string|array} $platform The name of the platform,
* or an array of $platform => $uid pairs
* @param {string} $uid The value to set the uid to
*/
function setUid($platform, $uid = null)
{
$uids = $this->getAllUids();
if (is_array($platform)) {
foreach ($platform as $k => $v) {
$uids[$k] = $v;
}
} else {
$uids[$platform] = $uid;
}
$this->uids = Q::json_encode($uids);
}
/**
* @method clearUid
* @param {string} $platform The name of the platform
*/
function clearUid($platform)
{
$uids = $this->getAllUids();
unset($uids[$platform]);
$this->uids = Q::json_encode($uids);
}
/**
* @method clearAllUids
*/
function clearAllUids()
{
$this->uids = '{}';
}
/**
* get user id
* @method _getId
* @static
* @private
* @param {Users_User} $u
* @return {string}
*/
private static function _getId ($u) {
return $u->id;
}
/**
* Check label or array of labels and return existing users
* @method labelsToIds
* @static
* @param $asUserId {string} The user id of inviting user
* @param $labels {string|array}
* @return {array} The array of user ids
*/
static function labelsToIds ($asUserId, $labels)
{
if (empty($labels)) {
return array();
}
if (!is_array($labels)) {
$labels = array_map('trim', explode(',', $labels));
}
$userIds = array();
foreach ($labels as $label) {
$userIds = array_merge($userIds, Users_Contact::select('contactUserId')
->where(array(
'userId' => $asUserId,
'label' => $label
))->fetchAll(PDO::FETCH_COLUMN));
}
return $userIds;
}
/**
* Check identifier or array of identifiers and return users - existing or future
* @method idsFromIdentifiers
* @static
* @param $asUserId {string} The user id of inviting user
* @param {string|array} $identifiers Can be email addresses or mobile numbers,
* passed either as an array or separated by "\t"
* @param {array} $statuses Optional reference to an array to populate with $status values ('verified' or 'future') in the same order as the $identifiers.
* @param {array} $identifierTypes Optional reference to an array to populate with $identifierTypes values in the same order as $identifiers
* @return {array} The array of user ids, in the same order as the $identifiers.
*/
static function idsFromIdentifiers (
$identifiers,
&$statuses = array(),
&$identifierTypes = array())
{
if (empty($identifiers)) {
return array();
}
if (!is_array($identifiers)) {
$identifiers = array_map('trim', explode("\t", $identifiers));
}
$users = array();
foreach ($identifiers as $identifier) {
if (Q_Valid::email($identifier, $emailAddress)) {
$ui_identifier = $emailAddress;
$identifierType = 'email';
} else if (Q_Valid::phone($identifier, $mobileNumber)) {
$ui_identifier = $mobileNumber;
$identifierType = 'mobile';
} else {
throw new Q_Exception_WrongType(array(
'field' => "identifier '$identifier",
'type' => 'email address or mobile number'
), array('identifier', 'emailAddress', 'mobileNumber'));
}
$status = null;
$users[] = $user = Users::futureUser($identifierType, $ui_identifier, $status);
$statuses[] = $status;
$identifierTypes[] = $identifierType;
}
return array_map(array('Users_User', '_getId'), $users);
}
/**
* Check platform uids or array of uids and return users - existing or future
* @method idsFromPlatformUids
* @static
* @param {string} $platform The name of the platform
* @param {array|string} $uids An array of facebook user ids, or a comma-delimited string
* @param {array} $statuses Optional reference to an array to populate with $status values ('verified' or 'future') in the same order as the $identifiers.
* @return {array} The array of user ids
*/
static function idsFromPlatformUids (
$platform,
$uids,
&$statuses = array()
) {
if (empty($uids)) {
return array();
}
if (!is_array($uids)) {
$uids = array_map('trim', explode(',', $uids));
}
$users = array();
foreach ($uids as $uid) {
$users[] = Users::futureUser($platform, $uid, $status);
$statuses[] = $status;
}
return array_map(array('Users_User', '_getId'), $users);
}
/**
* Verifies that users exist for these ids
* @method verifyUserIds
* @static
* @param $userIds {string|array}
* @param $throw=false {boolean}
* @return {array} The array of found user ids
*/
static function verifyUserIds($userIds, $throw = false)
{
if (empty($userIds)) {
return array();
}
if (is_string($userIds)) {
$userIds = array_map('trim', explode(',', $userIds));
}
$users = Users_User::select('id')
->where(array('id' => $userIds))
->fetchAll(PDO::FETCH_COLUMN);
if ($throw && count($users) < count($userIds)) {
$diff = array_diff($userIds, $users);
if (count($diff)) {
$ids = join(', ', $diff);
throw new Q_Exception_MissingRow(array(
'table' => 'user',
'criteria' => "ids ($ids)"
), 'id');
}
}
return $users;
}
/**
* Remove users from system
* @method removeUser
* @static
* @param {string|array} $userId Array of id's or single id
*/
static function removeUser($userIds) {
if (is_array($userIds)) {
foreach ($userIds as $userId) {
self::removeUser($userId);
}
return;
}
Streams_RelatedTo::delete()
->where(array('toPublisherId' => $userIds))
->orWhere(array('fromPublisherId' => $userIds))
->execute();
Streams_RelatedFrom::delete()
->where(array('toPublisherId' => $userIds))
->orWhere(array('fromPublisherId' => $userIds))
->execute();
Streams_Message::delete()
->where(array('publisherId' => $userIds))
->orWhere(array('byUserId' => $userIds))
->execute();
Streams_Avatar::delete()
->where(array('publisherId' => $userIds))
->orWhere(array('toUserId' => $userIds))
->execute();
Streams_Participant::delete()
->where(array('publisherId' => $userIds))
->orWhere(array('userId' => $userIds))
->execute();
Streams_Notification::delete()
->where(array('publisherId' => $userIds))
->orWhere(array('userId' => $userIds))
->execute();
Streams_Subscription::delete()
->where(array('publisherId' => $userIds))
->orWhere(array('ofUserId' => $userIds))
->execute();
Streams_SubscriptionRule::delete()
->where(array('ofUserId' => $userIds))
->orWhere(array('publisherId' => $userIds))
->execute();
Streams_MessageTotal::delete()
->where(array('publisherId' => $userIds))
->execute();
Streams_RelatedFromTotal::delete()
->where(array('fromPublisherId' => $userIds))
->execute();
Streams_RelatedToTotal::delete()
->where(array('toPublisherId' => $userIds))
->execute();
Streams_Access::delete()
->where(array('publisherId' => $userIds))
->orWhere(array('grantedByUserId' => $userIds))
->execute();
Streams_Stream::delete()
->where(array('publisherId' => $userIds))
->execute();
Users_Vote::delete()
->where(array('userId' => $userIds))
->execute();
Users_Session::delete()
->where(array('userId' => $userIds))
->execute();
Users_Mobile::delete()
->where(array('userId' => $userIds))
->execute();
Users_Email::delete()
->where(array('userId' => $userIds))
->execute();
Users_Identify::delete()
->where(array('userId' => $userIds))
->execute();
Users_Device::delete()
->where(array('userId' => $userIds))
->execute();
Users_Contact::delete()
->where(array('userId' => $userIds))
->orWhere(array('contactUserId' => $userIds))
->execute();
Users_Label::delete()
->where(array('userId' => $userIds))
->execute();
Users_User::delete()
->where(array('id' => $userIds))
->execute();
}
/* * * */
/**
* Implements the __set_state method, so it can work with
* with var_export and be re-imported successfully.
* @method __set_state
* @param {array} $array
* @return {Users_User} Class instance
*/
static function __set_state(array $array) {
$result = new Users_User();
foreach($array as $k => $v)
$result->$k = $v;
return $result;
}
/**
* @property $fetch_cache
* @type array
* @protected
*/
protected $fetch_cache = array();
/**
* @property $cache
* @type array
* @protected
* @static
*/
protected static $cache = array();
/**
* @property $preloaded
* @static
* @type array
*/
static $preloaded = array();
};