<?php
namespace XF\Import\Data;
use function is_null, is_string;
class User extends AbstractData
{
/**
* @var EntityEmulator[]
*/
protected $tables = [];
protected $regIp;
protected $avatarPath;
/**
* @var Admin|null
*/
protected $admin;
/**
* @var UserBan|null
*/
protected $ban;
protected $permissions = [];
protected $rejection = [
'date' => null,
'user_id' => 0,
'reason' => ''
];
public function getImportType()
{
return 'user';
}
protected function init()
{
$em = $this->em();
$vf = $em->getValueFormatter();
$this->tables = [
'user' => new EntityEmulator($this, $em->getEntityStructure('XF:User'), $vf),
'option' => new EntityEmulator($this, $em->getEntityStructure('XF:UserOption'), $vf),
'privacy' => new EntityEmulator($this, $em->getEntityStructure('XF:UserPrivacy'), $vf),
'profile' => new EntityEmulator($this, $em->getEntityStructure('XF:UserProfile'), $vf),
'auth' => new EntityEmulator($this, $em->getEntityStructure('XF:UserAuth'), $vf),
];
}
public function setPasswordData($scheme, array $data)
{
$auth = $this->tables['auth'];
$auth->scheme_class = $scheme;
$auth->data = $data;
}
public function setRegistrationIp($ip)
{
$this->regIp = $ip;
}
public function setRejectionDetails(array $details)
{
foreach ($details AS $k => $v)
{
if (substr($k, 0, 7) == 'reject_')
{
$k = substr($k, 7);
}
if (isset($this->rejection[$k]))
{
$this->rejection[$k] = $v;
}
}
}
public function setAdmin(array $data)
{
if (!$this->admin)
{
$this->admin = $this->dataManager->newHandler('XF:Admin', false);
}
$this->admin->bulkSet($data);
}
public function setBan(array $data)
{
if (!$this->ban)
{
$this->ban = $this->dataManager->newHandler('XF:UserBan', false);
}
$this->ban->bulkSet($data);
}
public function setPermissions(array $permissions)
{
$this->permissions = $permissions;
}
public function setCustomFields(array $customFields)
{
foreach ($customFields AS $k => &$fieldValue)
{
if (is_string($fieldValue))
{
$fieldValue = $this->convertToUtf8($fieldValue);
}
else if (is_null($fieldValue))
{
unset($customFields[$k]);
}
}
$this->tables['profile']->custom_fields = $customFields;
}
public function setAvatarPath($path)
{
$this->avatarPath = $path;
}
public function set($field, $value, array $options = [])
{
$set = false;
foreach ($this->tables AS $table)
{
if ($table->exists($field))
{
$table->set($field, $value, $options);
$set = true;
}
}
if (!$set)
{
throw new \InvalidArgumentException("Unknown column '$field'");
}
}
public function bulkSetDirect($part, array $fields, array $options = [])
{
foreach ($fields AS $field => $value)
{
$this->setDirect($part, $field, $value, $options);
}
}
public function setDirect($part, $field, $value, array $options = [])
{
if (!isset($this->tables[$part]))
{
throw new \InvalidArgumentException("Invalid table '$part'");
}
$this->tables[$part]->set($field, $value, $options);
}
public function get($field)
{
foreach ($this->tables AS $table)
{
if ($table->exists($field))
{
return $table->get($field);
}
}
throw new \InvalidArgumentException("Unknown column '$field'");
}
protected function write($oldId)
{
$db = $this->db();
$user = $this->tables['user'];
if ($oldId == 1 && $this->retainIds())
{
// We need to keep admin access for user 1 if retaining IDs as otherwise, we might not be able to continue.
// For safety, keep the email and password that was registered with the install to prevent someone from
// being able to gain access to this.
$user->remove(['last_activity', 'is_admin', 'email']);
$this->admin = null;
$this->tables['profile']->remove('password_date');
$this->tables['auth']->remove(['scheme_class', 'data']);
foreach ($this->tables AS $table)
{
$table->update($oldId, $db);
}
$newId = $oldId;
}
else
{
$newId = $user->insert($oldId, $db);
foreach ($this->tables AS $type => $table)
{
if ($type == 'user')
{
continue;
}
$table->set('user_id', $newId);
$table->insert(false, $db);
}
}
return $newId;
}
protected function importedIdFound($oldId, $newId)
{
foreach ($this->tables AS $table)
{
$table->set('user_id', $newId);
}
}
protected function preSave($oldId)
{
$user = $this->tables['user'];
if (strpos($user->username, ',') !== false)
{
throw new \LogicException("Username cannot contain a comma");
}
if ($oldId == 1 && $this->retainIds())
{
// we're just doing an update so don't do any other work we'd be doing for an insert
return;
}
if (!$user->secret_key)
{
$user->secret_key = \XF::generateRandomString(32);
}
if (!$user->language_id)
{
// unless a language ID has been set, set the default (as XF doesn't use 0).
$user->language_id = \XF::options()->defaultLanguageId;
}
// if primary user_group_id is also in secondary_group_ids then exclude it.
$secondaryGroupIds = $user->secondary_group_ids;
$user->secondary_group_ids = array_values(array_diff($secondaryGroupIds, [$user->user_group_id]));
$privacy = $this->tables['privacy'];
// we commonly have imported this as 'everyone' but we don't expose that as an option
// in the UI so it can cause the value to be overwritten to 'none' just set that here
if ($privacy->allow_send_personal_conversation == 'everyone')
{
$privacy->allow_send_personal_conversation = 'members';
}
if ($privacy->allow_post_profile == 'everyone')
{
$privacy->allow_post_profile = 'members';
}
// TODO: check DoB
}
protected function postSave($oldId, $newId)
{
$isMainUser = ($oldId == 1 && $this->retainIds());
$user = $this->tables['user'];
$groupInserts = [
[
'user_id' => $newId,
'user_group_id' => $user->user_group_id,
'is_primary' => 1
]
];
foreach ($user->secondary_group_ids AS $groupId)
{
$groupInserts[] = [
'user_id' => $newId,
'user_group_id' => $groupId,
'is_primary' => 0
];
}
$this->db()->insertBulk('xf_user_group_relation', $groupInserts, false, 'is_primary = VALUES(is_primary)');
$profile = $this->tables['profile'];
if ($profile->custom_fields)
{
$this->insertCustomFieldValues('xf_user_field_value', 'user_id', $newId, $profile->custom_fields);
}
$option = $this->tables['option'];
if ($option->alert_optout)
{
$optOutInserts = [];
foreach ($option->alert_optout AS $optOut)
{
$optOutInserts[] = [
'user_id' => $newId,
'alert' => $optOut
];
}
$this->db()->insertBulk('xf_user_alert_optout', $optOutInserts, false, false, 'IGNORE');
}
if ($this->user_state == 'moderated')
{
$this->db()->insert('xf_approval_queue', [
'content_type' => 'user',
'content_id' => $newId,
'content_date' => $this->register_date
], true);
}
else if ($this->user_state == 'rejected')
{
$this->db()->insert('xf_user_reject', [
'user_id' => $newId,
'reject_date' => $this->rejection['date'] ?: $this->register_date,
'reject_user_id' => $this->rejection['user_id'] ?: 0,
'reject_reason' => $this->convertToUtf8($this->rejection['reason']) ?: ''
], true);
}
if ($this->regIp)
{
$this->importRawIp($newId, 'user', $newId, 'register', $this->regIp, $this->register_date);
}
if ($this->permissions)
{
/** @var \XF\Import\DataHelper\Permission $permissionHelper */
$permissionHelper = $this->dataManager->helper('XF:Permission');
$permissionHelper->insertUserPermissions($newId, $this->permissions);
}
if ($this->admin && !$isMainUser)
{
$this->admin->user_id = $newId;
$this->admin->save($oldId);
}
if ($this->ban)
{
$this->ban->user_id = $newId;
$this->ban->save($oldId);
}
/** @var \XF\Entity\User $user */
$user = $this->em()->find('XF:User', $newId);
$user->rebuildPermissionCombination();
if ($this->avatarPath)
{
/** @var \XF\Import\DataHelper\Avatar $avatarHelper */
$avatarHelper = $this->dataManager->helper('XF:Avatar');
$avatarHelper->setAvatarFromFile($this->avatarPath, $user);
}
$this->em()->detachEntity($user);
}
public function mergeFromInto($oldUserId, $targetUserId)
{
$mappedId = $this->dataManager->lookup($this->getImportType(), $oldUserId);
if ($mappedId !== false)
{
return $mappedId;
}
$db = $this->db();
$db->beginTransaction();
foreach ($this->getMergeUpdateSql() AS $table => $updates)
{
$db->query("
UPDATE `{$table}` SET
" . implode(",\n", $updates) . "
WHERE user_id = ?
", $targetUserId);
}
$this->dataManager->log($this->getImportType(), $oldUserId, $targetUserId);
$db->commit();
return $targetUserId;
}
protected function getMergeUpdateSql()
{
$updates = [];
$db = $this->db();
if ($this->message_count)
{
$updates['xf_user']['message_count'] = 'message_count = message_count + ' . $db->quote($this->message_count);
}
if ($this->register_date)
{
$updates['xf_user']['register_date'] = 'register_date = LEAST(register_date, ' . $db->quote($this->register_date) . ')';
}
return $updates;
}
}