Create new User\Import module class
- Merge Core\UserImport class in Module class
This commit is contained in:
parent
ffa31d6641
commit
4ab260042c
6 changed files with 434 additions and 334 deletions
|
@ -9,8 +9,8 @@ How to move your account between servers
|
||||||
* Go to "Settings" -> "[Export personal data](uexport)"
|
* Go to "Settings" -> "[Export personal data](uexport)"
|
||||||
* Click on "Export account" to save your account data.
|
* Click on "Export account" to save your account data.
|
||||||
* **Save the file in a secure place!** It contains your details, your contacts, groups, and personal settings. It also contains your secret keys to authenticate yourself to your contacts.
|
* **Save the file in a secure place!** It contains your details, your contacts, groups, and personal settings. It also contains your secret keys to authenticate yourself to your contacts.
|
||||||
* Go to your new server, and open *http://newserver.com/uimport* (there is not a direct link to this page at the moment). Please consider that this is only possible on servers with open registration. On other systems only the administrator can add accounts with an uploaded file.
|
* Go to your new server, and open *http://newserver.com/user/import* (there is not a direct link to this page at the moment). Please consider that this is only possible on servers with open registration. On other systems only the administrator can add accounts with an uploaded file.
|
||||||
* Do NOT create a new account prior to importing your old settings - uimport should be used *instead* of register.
|
* Do NOT create a new account prior to importing your old settings - user import should be used *instead* of register.
|
||||||
* Load your saved account file and click "Import".
|
* Load your saved account file and click "Import".
|
||||||
* After the move, the account on the old server will not work reliably anymore, and should be not used.
|
* After the move, the account on the old server will not work reliably anymore, and should be not used.
|
||||||
|
|
||||||
|
|
|
@ -15,14 +15,14 @@ Außerdem enthält sie deinen geheimen Schlüssel mit dem du dich deinen Kontakt
|
||||||
|
|
||||||
**Speichere diese Datei an einem sicheren Ort**!
|
**Speichere diese Datei an einem sicheren Ort**!
|
||||||
|
|
||||||
Rufe nun dem neuen Server die Seite *http://newserver.com/uimport* auf (es gibt derzeit keinen direkten Link auf diese Seite).
|
Rufe nun dem neuen Server die Seite *http://newserver.com/user/import* auf (es gibt derzeit keinen direkten Link auf diese Seite).
|
||||||
|
|
||||||
Bitte beachte, dass dies nur auf Servern möglich ist, an denen man sich offen anmelden kann.
|
Bitte beachte, dass dies nur auf Servern möglich ist, an denen man sich offen anmelden kann.
|
||||||
Bei Servern, bei denen der Administrator Accounts freigeben muss, ist das Hochladen nicht möglich.
|
Bei Servern, bei denen der Administrator Accounts freigeben muss, ist das Hochladen nicht möglich.
|
||||||
Hier kann dies nur der Administrator selber durchführen.
|
Hier kann dies nur der Administrator selber durchführen.
|
||||||
|
|
||||||
Lege auf dem neuen Server auf keinen Fall einen gleichnamigen Account an!
|
Lege auf dem neuen Server auf keinen Fall einen gleichnamigen Account an!
|
||||||
uimport muss anstelle des Registrierens verwendet werden.
|
user import muss anstelle des Registrierens verwendet werden.
|
||||||
|
|
||||||
Wähle die gesicherte Account Datei aus und klicke "Importieren".
|
Wähle die gesicherte Account Datei aus und klicke "Importieren".
|
||||||
|
|
||||||
|
|
|
@ -1,330 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (C) 2010-2022, the Friendica project
|
|
||||||
*
|
|
||||||
* @license GNU AGPL version 3 or any later version
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Friendica\Core;
|
|
||||||
|
|
||||||
use Friendica\Database\DBA;
|
|
||||||
use Friendica\Database\DBStructure;
|
|
||||||
use Friendica\DI;
|
|
||||||
use Friendica\Model\Photo;
|
|
||||||
use Friendica\Model\Profile;
|
|
||||||
use Friendica\Object\Image;
|
|
||||||
use Friendica\Security\PermissionSet\Repository\PermissionSet;
|
|
||||||
use Friendica\Util\Strings;
|
|
||||||
use Friendica\Worker\Delivery;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UserImport class
|
|
||||||
*/
|
|
||||||
class UserImport
|
|
||||||
{
|
|
||||||
const IMPORT_DEBUG = false;
|
|
||||||
|
|
||||||
private static function lastInsertId()
|
|
||||||
{
|
|
||||||
if (self::IMPORT_DEBUG) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DBA::lastInsertId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove columns from array $arr that aren't in table $table
|
|
||||||
*
|
|
||||||
* @param string $table Table name
|
|
||||||
* @param array &$arr Column=>Value array from json (by ref)
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
private static function checkCols($table, &$arr)
|
|
||||||
{
|
|
||||||
$tableColumns = DBStructure::getColumns($table);
|
|
||||||
|
|
||||||
$tcols = [];
|
|
||||||
$ttype = [];
|
|
||||||
// get a plain array of column names
|
|
||||||
foreach ($tableColumns as $tcol) {
|
|
||||||
$tcols[] = $tcol['Field'];
|
|
||||||
$ttype[$tcol['Field']] = $tcol['Type'];
|
|
||||||
}
|
|
||||||
// remove inexistent columns
|
|
||||||
foreach ($arr as $icol => $ival) {
|
|
||||||
if (!in_array($icol, $tcols)) {
|
|
||||||
unset($arr[$icol]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($ttype[$icol] === 'datetime') {
|
|
||||||
$arr[$icol] = $ival ?? DBA::NULL_DATETIME;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import data into table $table
|
|
||||||
*
|
|
||||||
* @param string $table Table name
|
|
||||||
* @param array $arr Column=>Value array from json
|
|
||||||
* @return array|bool
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
private static function dbImportAssoc(string $table, array $arr)
|
|
||||||
{
|
|
||||||
if (isset($arr['id'])) {
|
|
||||||
unset($arr['id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
self::checkCols($table, $arr);
|
|
||||||
|
|
||||||
if (self::IMPORT_DEBUG) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DBA::insert($table, $arr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import account file exported from mod/uexport
|
|
||||||
*
|
|
||||||
* @param array $file array from $_FILES
|
|
||||||
* @return void
|
|
||||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
|
||||||
* @throws \ImagickException
|
|
||||||
*/
|
|
||||||
public static function importAccount(array $file)
|
|
||||||
{
|
|
||||||
Logger::notice("Start user import from " . $file['tmp_name']);
|
|
||||||
/*
|
|
||||||
STEPS
|
|
||||||
1. checks
|
|
||||||
2. replace old baseurl with new baseurl
|
|
||||||
3. import data (look at user id and contacts id)
|
|
||||||
4. archive non-dfrn contacts
|
|
||||||
5. send message to dfrn contacts
|
|
||||||
*/
|
|
||||||
|
|
||||||
$account = json_decode(file_get_contents($file['tmp_name']), true);
|
|
||||||
if ($account === null) {
|
|
||||||
DI::sysmsg()->addNotice(DI::l10n()->t("Error decoding account file"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (empty($account['version'])) {
|
|
||||||
DI::sysmsg()->addNotice(DI::l10n()->t("Error! No version data in file! This is not a Friendica account file?"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for username
|
|
||||||
// check if username matches deleted account
|
|
||||||
if (DBA::exists('user', ['nickname' => $account['user']['nickname']])
|
|
||||||
|| DBA::exists('userd', ['username' => $account['user']['nickname']])) {
|
|
||||||
DI::sysmsg()->addNotice(DI::l10n()->t("User '%s' already exists on this server!", $account['user']['nickname']));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oldbaseurl = $account['baseurl'];
|
|
||||||
$newbaseurl = DI::baseUrl();
|
|
||||||
|
|
||||||
$oldaddr = str_replace('http://', '@', Strings::normaliseLink($oldbaseurl));
|
|
||||||
$newaddr = str_replace('http://', '@', Strings::normaliseLink($newbaseurl));
|
|
||||||
|
|
||||||
if (!empty($account['profile']['addr'])) {
|
|
||||||
$old_handle = $account['profile']['addr'];
|
|
||||||
} else {
|
|
||||||
$old_handle = $account['user']['nickname'].$oldaddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creating a new guid to avoid problems with Diaspora
|
|
||||||
$account['user']['guid'] = System::createUUID();
|
|
||||||
|
|
||||||
$olduid = $account['user']['uid'];
|
|
||||||
|
|
||||||
unset($account['user']['uid']);
|
|
||||||
unset($account['user']['account_expired']);
|
|
||||||
unset($account['user']['account_expires_on']);
|
|
||||||
unset($account['user']['expire_notification_sent']);
|
|
||||||
|
|
||||||
$callback = function (&$value) use ($oldbaseurl, $oldaddr, $newbaseurl, $newaddr) {
|
|
||||||
$value = str_replace([$oldbaseurl, $oldaddr], [$newbaseurl, $newaddr], $value);
|
|
||||||
};
|
|
||||||
|
|
||||||
array_walk($account['user'], $callback);
|
|
||||||
|
|
||||||
// import user
|
|
||||||
$r = self::dbImportAssoc('user', $account['user']);
|
|
||||||
if ($r === false) {
|
|
||||||
Logger::warning("uimport:insert user : ERROR : " . DBA::errorMessage());
|
|
||||||
DI::sysmsg()->addNotice(DI::l10n()->t("User creation error"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$newuid = self::lastInsertId();
|
|
||||||
|
|
||||||
DI::pConfig()->set($newuid, 'system', 'previous_addr', $old_handle);
|
|
||||||
|
|
||||||
$errorcount = 0;
|
|
||||||
foreach ($account['contact'] as &$contact) {
|
|
||||||
if ($contact['uid'] == $olduid && $contact['self'] == '1') {
|
|
||||||
foreach ($contact as $k => &$v) {
|
|
||||||
$v = str_replace([$oldbaseurl, $oldaddr], [$newbaseurl, $newaddr], $v);
|
|
||||||
foreach (["profile", "avatar", "micro"] as $k) {
|
|
||||||
$v = str_replace($oldbaseurl . "/photo/" . $k . "/" . $olduid . ".jpg", $newbaseurl . "/photo/" . $k . "/" . $newuid . ".jpg", $v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($contact['uid'] == $olduid && $contact['self'] == '0') {
|
|
||||||
// set contacts 'avatar-date' to NULL_DATE to let worker to update urls
|
|
||||||
$contact["avatar-date"] = DBA::NULL_DATETIME;
|
|
||||||
|
|
||||||
switch ($contact['network']) {
|
|
||||||
case Protocol::DFRN:
|
|
||||||
case Protocol::DIASPORA:
|
|
||||||
// send relocate message (below)
|
|
||||||
break;
|
|
||||||
case Protocol::FEED:
|
|
||||||
case Protocol::MAIL:
|
|
||||||
// Nothing to do
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// archive other contacts
|
|
||||||
$contact['archive'] = "1";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$contact['uid'] = $newuid;
|
|
||||||
$r = self::dbImportAssoc('contact', $contact);
|
|
||||||
if ($r === false) {
|
|
||||||
Logger::warning("uimport:insert contact " . $contact['nick'] . "," . $contact['network'] . " : ERROR : " . DBA::errorMessage());
|
|
||||||
$errorcount++;
|
|
||||||
} else {
|
|
||||||
$contact['newid'] = self::lastInsertId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($errorcount > 0) {
|
|
||||||
DI::sysmsg()->addNotice(DI::l10n()->tt("%d contact not imported", "%d contacts not imported", $errorcount));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($account['group'] as &$group) {
|
|
||||||
$group['uid'] = $newuid;
|
|
||||||
$r = self::dbImportAssoc('group', $group);
|
|
||||||
if ($r === false) {
|
|
||||||
Logger::warning("uimport:insert group " . $group['name'] . " : ERROR : " . DBA::errorMessage());
|
|
||||||
} else {
|
|
||||||
$group['newid'] = self::lastInsertId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($account['group_member'] as &$group_member) {
|
|
||||||
$import = 0;
|
|
||||||
foreach ($account['group'] as $group) {
|
|
||||||
if ($group['id'] == $group_member['gid'] && isset($group['newid'])) {
|
|
||||||
$group_member['gid'] = $group['newid'];
|
|
||||||
$import++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($account['contact'] as $contact) {
|
|
||||||
if ($contact['id'] == $group_member['contact-id'] && isset($contact['newid'])) {
|
|
||||||
$group_member['contact-id'] = $contact['newid'];
|
|
||||||
$import++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($import == 2) {
|
|
||||||
$r = self::dbImportAssoc('group_member', $group_member);
|
|
||||||
if ($r === false) {
|
|
||||||
Logger::warning("uimport:insert group member " . $group_member['id'] . " : ERROR : " . DBA::errorMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($account['profile'] as &$profile) {
|
|
||||||
unset($profile['id']);
|
|
||||||
$profile['uid'] = $newuid;
|
|
||||||
|
|
||||||
foreach ($profile as $k => &$v) {
|
|
||||||
$v = str_replace([$oldbaseurl, $oldaddr], [$newbaseurl, $newaddr], $v);
|
|
||||||
foreach (["profile", "avatar"] as $k) {
|
|
||||||
$v = str_replace($oldbaseurl . "/photo/" . $k . "/" . $olduid . ".jpg", $newbaseurl . "/photo/" . $k . "/" . $newuid . ".jpg", $v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($account['profile']) === 1 || $profile['is-default']) {
|
|
||||||
$r = self::dbImportAssoc('profile', $profile);
|
|
||||||
|
|
||||||
if ($r === false) {
|
|
||||||
Logger::warning("uimport:insert profile: ERROR : " . DBA::errorMessage());
|
|
||||||
DI::sysmsg()->addNotice(DI::l10n()->t("User profile creation error"));
|
|
||||||
DBA::delete('user', ['uid' => $newuid]);
|
|
||||||
DBA::delete('profile_field', ['uid' => $newuid]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$profile['id'] = DBA::lastInsertId();
|
|
||||||
}
|
|
||||||
|
|
||||||
Profile::migrate($profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
$permissionSet = DI::permissionSet()->selectDefaultForUser($newuid);
|
|
||||||
|
|
||||||
foreach ($account['profile_fields'] ?? [] as $profile_field) {
|
|
||||||
$profile_field['uid'] = $newuid;
|
|
||||||
|
|
||||||
///@TODO Replace with permissionset import
|
|
||||||
$profile_field['psid'] = $profile_field['psid'] ? $permissionSet->uid : PermissionSet::PUBLIC;
|
|
||||||
|
|
||||||
if (self::dbImportAssoc('profile_field', $profile_field) === false) {
|
|
||||||
Logger::info("uimport:insert profile field " . $profile_field['id'] . " : ERROR : " . DBA::errorMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($account['photo'] as &$photo) {
|
|
||||||
$photo['uid'] = $newuid;
|
|
||||||
$photo['data'] = hex2bin($photo['data']);
|
|
||||||
|
|
||||||
$Image = new Image($photo['data'], $photo['type']);
|
|
||||||
$r = Photo::store(
|
|
||||||
$Image,
|
|
||||||
$photo['uid'], $photo['contact-id'], //0
|
|
||||||
$photo['resource-id'], $photo['filename'], $photo['album'], $photo['scale'], $photo['profile'], //1
|
|
||||||
$photo['allow_cid'], $photo['allow_gid'], $photo['deny_cid'], $photo['deny_gid']
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($r === false) {
|
|
||||||
Logger::warning("uimport:insert photo " . $photo['resource-id'] . "," . $photo['scale'] . " : ERROR : " . DBA::errorMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($account['pconfig'] as &$pconfig) {
|
|
||||||
$pconfig['uid'] = $newuid;
|
|
||||||
$r = self::dbImportAssoc('pconfig', $pconfig);
|
|
||||||
if ($r === false) {
|
|
||||||
Logger::warning("uimport:insert pconfig " . $pconfig['id'] . " : ERROR : " . DBA::errorMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// send relocate messages
|
|
||||||
Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $newuid);
|
|
||||||
|
|
||||||
DI::sysmsg()->addInfo(DI::l10n()->t("Done. You can now login with your username and password"));
|
|
||||||
DI::baseUrl()->redirect('login');
|
|
||||||
}
|
|
||||||
}
|
|
415
src/Module/User/Import.php
Normal file
415
src/Module/User/Import.php
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2022, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Friendica\Module\User;
|
||||||
|
|
||||||
|
use Friendica\App;
|
||||||
|
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||||
|
use Friendica\Core\L10n;
|
||||||
|
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
|
||||||
|
use Friendica\Core\Protocol;
|
||||||
|
use Friendica\Core\Renderer;
|
||||||
|
use Friendica\Core\System;
|
||||||
|
use Friendica\Core\Worker;
|
||||||
|
use Friendica\Database\Database;
|
||||||
|
use Friendica\Database\DBA;
|
||||||
|
use Friendica\Database\DBStructure;
|
||||||
|
use Friendica\Model\Photo;
|
||||||
|
use Friendica\Model\Profile;
|
||||||
|
use Friendica\Module\Response;
|
||||||
|
use Friendica\Navigation\SystemMessages;
|
||||||
|
use Friendica\Network\HTTPException;
|
||||||
|
use Friendica\Object\Image;
|
||||||
|
use Friendica\Security\PermissionSet\Repository\PermissionSet;
|
||||||
|
use Friendica\Util\Profiler;
|
||||||
|
use Friendica\Util\Strings;
|
||||||
|
use Friendica\Worker\Delivery;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class Import extends \Friendica\BaseModule
|
||||||
|
{
|
||||||
|
const IMPORT_DEBUG = false;
|
||||||
|
|
||||||
|
/** @var App */
|
||||||
|
private $app;
|
||||||
|
|
||||||
|
/** @var IManageConfigValues */
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
/** @var IManagePersonalConfigValues */
|
||||||
|
private $pconfig;
|
||||||
|
|
||||||
|
/** @var SystemMessages */
|
||||||
|
private $systemMessages;
|
||||||
|
|
||||||
|
/** @var Database */
|
||||||
|
private $database;
|
||||||
|
|
||||||
|
/** @var PermissionSet */
|
||||||
|
private $permissionSet;
|
||||||
|
|
||||||
|
public function __construct(PermissionSet $permissionSet, IManagePersonalConfigValues $pconfig, Database $database, SystemMessages $systemMessages, IManageConfigValues $config, App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
|
||||||
|
{
|
||||||
|
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
|
||||||
|
|
||||||
|
$this->app = $app;
|
||||||
|
$this->config = $config;
|
||||||
|
$this->pconfig = $pconfig;
|
||||||
|
$this->systemMessages = $systemMessages;
|
||||||
|
$this->database = $database;
|
||||||
|
$this->permissionSet = $permissionSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function post(array $request = [])
|
||||||
|
{
|
||||||
|
if ($this->config->get('config', 'register_policy') != \Friendica\Module\Register::OPEN && !$this->app->isSiteAdmin()) {
|
||||||
|
throw new HttpException\ForbiddenException($this->t('Permission denied.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$max_dailies = intval($this->config->get('system', 'max_daily_registrations'));
|
||||||
|
if ($max_dailies) {
|
||||||
|
$total = $this->database->count('user', ["`register_date` > UTC_TIMESTAMP - INTERVAL 1 DAY"]);
|
||||||
|
if ($total >= $max_dailies) {
|
||||||
|
throw new HttpException\ForbiddenException($this->t('Permission denied.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_FILES['accountfile'])) {
|
||||||
|
$this->importAccount($_FILES['accountfile']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function content(array $request = []): string
|
||||||
|
{
|
||||||
|
if (($this->config->get('config', 'register_policy') != \Friendica\Module\Register::OPEN) && !$this->app->isSiteAdmin()) {
|
||||||
|
$this->systemMessages->addNotice($this->t('User imports on closed servers can only be done by an administrator.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$max_dailies = intval($this->config->get('system', 'max_daily_registrations'));
|
||||||
|
if ($max_dailies) {
|
||||||
|
$total = $this->database->count('user', ["`register_date` > UTC_TIMESTAMP - INTERVAL 1 DAY"]);
|
||||||
|
if ($total >= $max_dailies) {
|
||||||
|
$this->logger->notice('max daily registrations exceeded.');
|
||||||
|
$this->systemMessages->addNotice($this->t('This site has exceeded the number of allowed daily account registrations. Please try again tomorrow.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$tpl = Renderer::getMarkupTemplate('user/import.tpl');
|
||||||
|
return Renderer::replaceMacros($tpl, [
|
||||||
|
'$regbutt' => $this->t('Import'),
|
||||||
|
'$import' => [
|
||||||
|
'title' => $this->t('Move account'),
|
||||||
|
'intro' => $this->t('You can import an account from another Friendica server.'),
|
||||||
|
'instruct' => $this->t('You need to export your account from the old server and upload it here. We will recreate your old account here with all your contacts. We will try also to inform your friends that you moved here.'),
|
||||||
|
'warn' => $this->t("This feature is experimental. We can't import contacts from the OStatus network (GNU Social/Statusnet) or from Diaspora"),
|
||||||
|
'field' => ['accountfile', $this->t('Account file'), '<input id="id_accountfile" name="accountfile" type="file">', $this->t('To export your account, go to "Settings->Export your personal data" and select "Export account"')],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lastInsertId(): int
|
||||||
|
{
|
||||||
|
if (self::IMPORT_DEBUG) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->database->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove columns from array $arr that aren't in table $table
|
||||||
|
*
|
||||||
|
* @param string $table Table name
|
||||||
|
* @param array &$arr Column=>Value array from json (by ref)
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
private function checkCols(string $table, array &$arr)
|
||||||
|
{
|
||||||
|
$tableColumns = DBStructure::getColumns($table);
|
||||||
|
|
||||||
|
$tcols = [];
|
||||||
|
$ttype = [];
|
||||||
|
// get a plain array of column names
|
||||||
|
foreach ($tableColumns as $tcol) {
|
||||||
|
$tcols[] = $tcol['Field'];
|
||||||
|
$ttype[$tcol['Field']] = $tcol['Type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove inexistent columns
|
||||||
|
foreach ($arr as $icol => $ival) {
|
||||||
|
if (!in_array($icol, $tcols)) {
|
||||||
|
unset($arr[$icol]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ttype[$icol] === 'datetime') {
|
||||||
|
$arr[$icol] = $ival ?? DBA::NULL_DATETIME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import data into table $table
|
||||||
|
*
|
||||||
|
* @param string $table Table name
|
||||||
|
* @param array $arr Column=>Value array from json
|
||||||
|
* @return bool
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
private function dbImportAssoc(string $table, array $arr): bool
|
||||||
|
{
|
||||||
|
if (isset($arr['id'])) {
|
||||||
|
unset($arr['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->checkCols($table, $arr);
|
||||||
|
|
||||||
|
if (self::IMPORT_DEBUG) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->database->insert($table, $arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import account file exported from mod/uexport
|
||||||
|
*
|
||||||
|
* @param array $file array from $_FILES
|
||||||
|
* @return void
|
||||||
|
* @throws HTTPException\FoundException
|
||||||
|
* @throws HTTPException\InternalServerErrorException
|
||||||
|
* @throws HTTPException\MovedPermanentlyException
|
||||||
|
* @throws HTTPException\TemporaryRedirectException
|
||||||
|
* @throws \ImagickException
|
||||||
|
*/
|
||||||
|
private function importAccount(array $file)
|
||||||
|
{
|
||||||
|
$this->logger->notice('Start user import from ' . $file['tmp_name']);
|
||||||
|
/*
|
||||||
|
STEPS
|
||||||
|
1. checks
|
||||||
|
2. replace old baseurl with new baseurl
|
||||||
|
3. import data (look at user id and contacts id)
|
||||||
|
4. archive non-dfrn contacts
|
||||||
|
5. send message to dfrn contacts
|
||||||
|
*/
|
||||||
|
|
||||||
|
$account = json_decode(file_get_contents($file['tmp_name']), true);
|
||||||
|
if ($account === null) {
|
||||||
|
$this->systemMessages->addNotice($this->t('Error decoding account file'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($account['version'])) {
|
||||||
|
$this->systemMessages->addNotice($this->t('Error! No version data in file! This is not a Friendica account file?'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for username
|
||||||
|
// check if username matches deleted account
|
||||||
|
if ($this->database->exists('user', ['nickname' => $account['user']['nickname']])
|
||||||
|
|| $this->database->exists('userd', ['username' => $account['user']['nickname']])) {
|
||||||
|
$this->systemMessages->addNotice($this->t("User '%s' already exists on this server!", $account['user']['nickname']));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldBaseUrl = $account['baseurl'];
|
||||||
|
$newBaseUrl = $this->baseUrl;
|
||||||
|
|
||||||
|
$oldAddr = str_replace('http://', '@', Strings::normaliseLink($oldBaseUrl));
|
||||||
|
$newAddr = str_replace('http://', '@', Strings::normaliseLink($newBaseUrl));
|
||||||
|
|
||||||
|
if (!empty($account['profile']['addr'])) {
|
||||||
|
$oldHandle = $account['profile']['addr'];
|
||||||
|
} else {
|
||||||
|
$oldHandle = $account['user']['nickname'] . $oldAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating a new guid to avoid problems with Diaspora
|
||||||
|
$account['user']['guid'] = System::createUUID();
|
||||||
|
|
||||||
|
$oldUid = $account['user']['uid'];
|
||||||
|
|
||||||
|
unset($account['user']['uid']);
|
||||||
|
unset($account['user']['account_expired']);
|
||||||
|
unset($account['user']['account_expires_on']);
|
||||||
|
unset($account['user']['expire_notification_sent']);
|
||||||
|
|
||||||
|
array_walk($account['user'], function (&$user) use ($oldBaseUrl, $oldAddr, $newBaseUrl, $newAddr) {
|
||||||
|
$user = str_replace([$oldBaseUrl, $oldAddr], [$newBaseUrl, $newAddr], $user);
|
||||||
|
});
|
||||||
|
|
||||||
|
// import user
|
||||||
|
if ($this->dbImportAssoc('user', $account['user']) === false) {
|
||||||
|
$this->logger->warning('Error inserting user', ['user' => $account['user'], 'error' => $this->database->errorMessage()]);
|
||||||
|
$this->systemMessages->addNotice($this->t('User creation error'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newUid = $this->lastInsertId();
|
||||||
|
|
||||||
|
$this->pconfig->set($newUid, 'system', 'previous_addr', $oldHandle);
|
||||||
|
|
||||||
|
$errorCount = 0;
|
||||||
|
|
||||||
|
array_walk($account['contact'], function (&$contact) use (&$errorCount, $oldUid, $oldBaseUrl, $oldAddr, $newUid, $newBaseUrl, $newAddr) {
|
||||||
|
if ($contact['uid'] == $oldUid && $contact['self'] == '1') {
|
||||||
|
array_walk($contact, function (&$field) use ($oldUid, $oldBaseUrl, $oldAddr, $newUid, $newBaseUrl, $newAddr) {
|
||||||
|
$field = str_replace([$oldBaseUrl, $oldAddr], [$newBaseUrl, $newAddr], $field);
|
||||||
|
foreach (['profile', 'avatar', 'micro'] as $key) {
|
||||||
|
$field = str_replace($oldBaseUrl . '/photo/' . $key . '/' . $oldUid . '.jpg', $newBaseUrl . '/photo/' . $key . '/' . $newUid . '.jpg', $field);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($contact['uid'] == $oldUid && $contact['self'] == '0') {
|
||||||
|
// set contacts 'avatar-date' to NULL_DATE to let worker update the URLs
|
||||||
|
$contact['avatar-date'] = DBA::NULL_DATETIME;
|
||||||
|
|
||||||
|
switch ($contact['network']) {
|
||||||
|
case Protocol::DFRN:
|
||||||
|
case Protocol::DIASPORA:
|
||||||
|
// send relocate message (below)
|
||||||
|
break;
|
||||||
|
case Protocol::FEED:
|
||||||
|
case Protocol::MAIL:
|
||||||
|
// Nothing to do
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// archive other contacts
|
||||||
|
$contact['archive'] = '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$contact['uid'] = $newUid;
|
||||||
|
if ($this->dbImportAssoc('contact', $contact) === false) {
|
||||||
|
$this->logger->warning('Error inserting contact', ['nick' => $contact['nick'], 'network' => $contact['network'], 'error' => $this->database->errorMessage()]);
|
||||||
|
$errorCount++;
|
||||||
|
} else {
|
||||||
|
$contact['newid'] = $this->lastInsertId();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($errorCount > 0) {
|
||||||
|
$this->systemMessages->addNotice($this->tt('%d contact not imported', '%d contacts not imported', $errorCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
array_walk($account['group'], function (&$group) use ($newUid) {
|
||||||
|
$group['uid'] = $newUid;
|
||||||
|
if ($this->dbImportAssoc('group', $group) === false) {
|
||||||
|
$this->logger->warning('Error inserting group', ['name' => $group['name'], 'error' => $this->database->errorMessage()]);
|
||||||
|
} else {
|
||||||
|
$group['newid'] = $this->lastInsertId();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach ($account['group_member'] as $group_member) {
|
||||||
|
$import = 0;
|
||||||
|
foreach ($account['group'] as $group) {
|
||||||
|
if ($group['id'] == $group_member['gid'] && isset($group['newid'])) {
|
||||||
|
$group_member['gid'] = $group['newid'];
|
||||||
|
$import++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($account['contact'] as $contact) {
|
||||||
|
if ($contact['id'] == $group_member['contact-id'] && isset($contact['newid'])) {
|
||||||
|
$group_member['contact-id'] = $contact['newid'];
|
||||||
|
$import++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($import == 2 && $this->dbImportAssoc('group_member', $group_member) === false) {
|
||||||
|
$this->logger->warning('Error inserting group member', ['gid' => $group_member['id'], 'error' => $this->database->errorMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($account['profile'] as $profile) {
|
||||||
|
unset($profile['id']);
|
||||||
|
$profile['uid'] = $newUid;
|
||||||
|
|
||||||
|
array_walk($profile, function (&$field) use ($oldUid, $oldBaseUrl, $oldAddr, $newUid, $newBaseUrl, $newAddr) {
|
||||||
|
$field = str_replace([$oldBaseUrl, $oldAddr], [$newBaseUrl, $newAddr], $field);
|
||||||
|
foreach (['profile', 'avatar'] as $key) {
|
||||||
|
$field = str_replace($oldBaseUrl . '/photo/' . $key . '/' . $oldUid . '.jpg', $newBaseUrl . '/photo/' . $key . '/' . $newUid . '.jpg', $field);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (count($account['profile']) === 1 || $profile['is-default']) {
|
||||||
|
if ($this->dbImportAssoc('profile', $profile) === false) {
|
||||||
|
$this->logger->warning('Error inserting profile', ['error' => $this->database->errorMessage()]);
|
||||||
|
$this->systemMessages->addNotice($this->t('User profile creation error'));
|
||||||
|
$this->database->delete('user', ['uid' => $newUid]);
|
||||||
|
$this->database->delete('profile_field', ['uid' => $newUid]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile['id'] = $this->database->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile::migrate($profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$permissionSet = $this->permissionSet->selectDefaultForUser($newUid);
|
||||||
|
|
||||||
|
foreach ($account['profile_fields'] ?? [] as $profile_field) {
|
||||||
|
$profile_field['uid'] = $newUid;
|
||||||
|
|
||||||
|
///@TODO Replace with permissionset import
|
||||||
|
$profile_field['psid'] = $profile_field['psid'] ? $permissionSet->id : PermissionSet::PUBLIC;
|
||||||
|
|
||||||
|
if ($this->dbImportAssoc('profile_field', $profile_field) === false) {
|
||||||
|
$this->logger->info('Error inserting profile field', ['profile_id' => $profile_field['id'], 'error' => $this->database->errorMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($account['photo'] as $photo) {
|
||||||
|
$photo['uid'] = $newUid;
|
||||||
|
$photo['data'] = hex2bin($photo['data']);
|
||||||
|
|
||||||
|
$r = Photo::store(
|
||||||
|
new Image($photo['data'], $photo['type']),
|
||||||
|
$photo['uid'], $photo['contact-id'], //0
|
||||||
|
$photo['resource-id'], $photo['filename'], $photo['album'], $photo['scale'], $photo['profile'], //1
|
||||||
|
$photo['allow_cid'], $photo['allow_gid'], $photo['deny_cid'], $photo['deny_gid']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($r === false) {
|
||||||
|
$this->logger->warning('Error inserting photo', ['resource-id' => $photo['resource-id'], 'scale' => $photo['scale'], 'error' => $this->database->errorMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($account['pconfig'] as $pconfig) {
|
||||||
|
$pconfig['uid'] = $newUid;
|
||||||
|
if ($this->dbImportAssoc('pconfig', $pconfig) === false) {
|
||||||
|
$this->logger->warning('Error inserting pconfig', ['pconfig_id' => $pconfig['id'], 'error' => $this->database->errorMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send relocate messages
|
||||||
|
Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $newUid);
|
||||||
|
|
||||||
|
$this->systemMessages->addInfo($this->t('Done. You can now login with your username and password'));
|
||||||
|
$this->baseUrl->redirect('login');
|
||||||
|
}
|
||||||
|
}
|
|
@ -611,6 +611,8 @@ return [
|
||||||
|
|
||||||
'/update_profile' => [Module\Update\Profile::class, [R::GET]],
|
'/update_profile' => [Module\Update\Profile::class, [R::GET]],
|
||||||
|
|
||||||
|
'/user/import' => [Module\User\Import::class, [R::GET, R::POST]],
|
||||||
|
|
||||||
'/view/theme/{theme}/style.pcss' => [Module\Theme::class, [R::GET]],
|
'/view/theme/{theme}/style.pcss' => [Module\Theme::class, [R::GET]],
|
||||||
'/viewsrc/{item:\d+}' => [Module\Debug\ItemBody::class, [R::GET]],
|
'/viewsrc/{item:\d+}' => [Module\Debug\ItemBody::class, [R::GET]],
|
||||||
'/webfinger' => [Module\Debug\WebFinger::class, [R::GET]],
|
'/webfinger' => [Module\Debug\WebFinger::class, [R::GET]],
|
||||||
|
|
13
view/templates/user/import.tpl
Normal file
13
view/templates/user/import.tpl
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<form action="uimport" method="post" id="uimport-form" enctype="multipart/form-data">
|
||||||
|
<h2>{{$import.title}}</h2>
|
||||||
|
<p>{{$import.intro}}</p>
|
||||||
|
<p>{{$import.instruct}}</p>
|
||||||
|
<p><b>{{$import.warn}}</b></p>
|
||||||
|
|
||||||
|
{{include file="field_custom.tpl" field=$import.field}}
|
||||||
|
|
||||||
|
<div id="register-submit-wrapper">
|
||||||
|
<button type="submit" name="submit" id="register-submit-button" class="btn btn-primary">{{$regbutt}}</button>
|
||||||
|
</div>
|
||||||
|
<div id="register-submit-end"></div>
|
||||||
|
</form>
|
Loading…
Reference in a new issue