Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

287 lines
8.2 KiB

  1. <?php
  2. /**
  3. * @file src/Core/UserImport.php
  4. */
  5. namespace Friendica\Core;
  6. use Friendica\App;
  7. use Friendica\Database\DBA;
  8. use Friendica\Database\DBStructure;
  9. use Friendica\DI;
  10. use Friendica\Model\Photo;
  11. use Friendica\Object\Image;
  12. use Friendica\Util\Strings;
  13. use Friendica\Worker\Delivery;
  14. /**
  15. * @brief UserImport class
  16. */
  17. class UserImport
  18. {
  19. const IMPORT_DEBUG = false;
  20. private static function lastInsertId()
  21. {
  22. if (self::IMPORT_DEBUG) {
  23. return 1;
  24. }
  25. return DBA::lastInsertId();
  26. }
  27. /**
  28. * Remove columns from array $arr that aren't in table $table
  29. *
  30. * @param string $table Table name
  31. * @param array &$arr Column=>Value array from json (by ref)
  32. * @throws \Exception
  33. */
  34. private static function checkCols($table, &$arr)
  35. {
  36. $tableColumns = DBStructure::getColumns($table);
  37. $tcols = [];
  38. $ttype = [];
  39. // get a plain array of column names
  40. foreach ($tableColumns as $tcol) {
  41. $tcols[] = $tcol['Field'];
  42. $ttype[$tcol['Field']] = $tcol['Type'];
  43. }
  44. // remove inexistent columns
  45. foreach ($arr as $icol => $ival) {
  46. if (!in_array($icol, $tcols)) {
  47. unset($arr[$icol]);
  48. continue;
  49. }
  50. if ($ttype[$icol] === 'datetime') {
  51. $arr[$icol] = $ival ?? DBA::NULL_DATETIME;
  52. }
  53. }
  54. }
  55. /**
  56. * Import data into table $table
  57. *
  58. * @param string $table Table name
  59. * @param array $arr Column=>Value array from json
  60. * @return array|bool
  61. * @throws \Exception
  62. */
  63. private static function dbImportAssoc($table, $arr)
  64. {
  65. if (isset($arr['id'])) {
  66. unset($arr['id']);
  67. }
  68. self::checkCols($table, $arr);
  69. if (self::IMPORT_DEBUG) {
  70. return true;
  71. }
  72. return DBA::insert($table, $arr);
  73. }
  74. /**
  75. * @brief Import account file exported from mod/uexport
  76. *
  77. * @param array $file array from $_FILES
  78. * @throws \Friendica\Network\HTTPException\InternalServerErrorException
  79. * @throws \ImagickException
  80. */
  81. public static function importAccount($file)
  82. {
  83. Logger::log("Start user import from " . $file['tmp_name']);
  84. /*
  85. STEPS
  86. 1. checks
  87. 2. replace old baseurl with new baseurl
  88. 3. import data (look at user id and contacts id)
  89. 4. archive non-dfrn contacts
  90. 5. send message to dfrn contacts
  91. */
  92. $account = json_decode(file_get_contents($file['tmp_name']), true);
  93. if ($account === null) {
  94. notice(L10n::t("Error decoding account file"));
  95. return;
  96. }
  97. if (empty($account['version'])) {
  98. notice(L10n::t("Error! No version data in file! This is not a Friendica account file?"));
  99. return;
  100. }
  101. // check for username
  102. // check if username matches deleted account
  103. if (DBA::exists('user', ['nickname' => $account['user']['nickname']])
  104. || DBA::exists('userd', ['username' => $account['user']['nickname']])) {
  105. notice(L10n::t("User '%s' already exists on this server!", $account['user']['nickname']));
  106. return;
  107. }
  108. $oldbaseurl = $account['baseurl'];
  109. $newbaseurl = DI::baseUrl();
  110. $oldaddr = str_replace('http://', '@', Strings::normaliseLink($oldbaseurl));
  111. $newaddr = str_replace('http://', '@', Strings::normaliseLink($newbaseurl));
  112. if (!empty($account['profile']['addr'])) {
  113. $old_handle = $account['profile']['addr'];
  114. } else {
  115. $old_handle = $account['user']['nickname'].$oldaddr;
  116. }
  117. // Creating a new guid to avoid problems with Diaspora
  118. $account['user']['guid'] = System::createUUID();
  119. $olduid = $account['user']['uid'];
  120. unset($account['user']['uid']);
  121. unset($account['user']['account_expired']);
  122. unset($account['user']['account_expires_on']);
  123. unset($account['user']['expire_notification_sent']);
  124. $callback = function (&$value) use ($oldbaseurl, $oldaddr, $newbaseurl, $newaddr) {
  125. $value = str_replace([$oldbaseurl, $oldaddr], [$newbaseurl, $newaddr], $value);
  126. };
  127. array_walk($account['user'], $callback);
  128. // import user
  129. $r = self::dbImportAssoc('user', $account['user']);
  130. if ($r === false) {
  131. Logger::log("uimport:insert user : ERROR : " . DBA::errorMessage(), Logger::INFO);
  132. notice(L10n::t("User creation error"));
  133. return;
  134. }
  135. $newuid = self::lastInsertId();
  136. DI::pConfig()->set($newuid, 'system', 'previous_addr', $old_handle);
  137. foreach ($account['profile'] as &$profile) {
  138. foreach ($profile as $k => &$v) {
  139. $v = str_replace([$oldbaseurl, $oldaddr], [$newbaseurl, $newaddr], $v);
  140. foreach (["profile", "avatar"] as $k) {
  141. $v = str_replace($oldbaseurl . "/photo/" . $k . "/" . $olduid . ".jpg", $newbaseurl . "/photo/" . $k . "/" . $newuid . ".jpg", $v);
  142. }
  143. }
  144. $profile['uid'] = $newuid;
  145. $r = self::dbImportAssoc('profile', $profile);
  146. if ($r === false) {
  147. Logger::log("uimport:insert profile " . $profile['profile-name'] . " : ERROR : " . DBA::errorMessage(), Logger::INFO);
  148. info(L10n::t("User profile creation error"));
  149. DBA::delete('user', ['uid' => $newuid]);
  150. return;
  151. }
  152. }
  153. $errorcount = 0;
  154. foreach ($account['contact'] as &$contact) {
  155. if ($contact['uid'] == $olduid && $contact['self'] == '1') {
  156. foreach ($contact as $k => &$v) {
  157. $v = str_replace([$oldbaseurl, $oldaddr], [$newbaseurl, $newaddr], $v);
  158. foreach (["profile", "avatar", "micro"] as $k) {
  159. $v = str_replace($oldbaseurl . "/photo/" . $k . "/" . $olduid . ".jpg", $newbaseurl . "/photo/" . $k . "/" . $newuid . ".jpg", $v);
  160. }
  161. }
  162. }
  163. if ($contact['uid'] == $olduid && $contact['self'] == '0') {
  164. // set contacts 'avatar-date' to NULL_DATE to let worker to update urls
  165. $contact["avatar-date"] = DBA::NULL_DATETIME;
  166. switch ($contact['network']) {
  167. case Protocol::DFRN:
  168. case Protocol::DIASPORA:
  169. // send relocate message (below)
  170. break;
  171. case Protocol::FEED:
  172. case Protocol::MAIL:
  173. // Nothing to do
  174. break;
  175. default:
  176. // archive other contacts
  177. $contact['archive'] = "1";
  178. }
  179. }
  180. $contact['uid'] = $newuid;
  181. $r = self::dbImportAssoc('contact', $contact);
  182. if ($r === false) {
  183. Logger::log("uimport:insert contact " . $contact['nick'] . "," . $contact['network'] . " : ERROR : " . DBA::errorMessage(), Logger::INFO);
  184. $errorcount++;
  185. } else {
  186. $contact['newid'] = self::lastInsertId();
  187. }
  188. }
  189. if ($errorcount > 0) {
  190. notice(L10n::tt("%d contact not imported", "%d contacts not imported", $errorcount));
  191. }
  192. foreach ($account['group'] as &$group) {
  193. $group['uid'] = $newuid;
  194. $r = self::dbImportAssoc('group', $group);
  195. if ($r === false) {
  196. Logger::log("uimport:insert group " . $group['name'] . " : ERROR : " . DBA::errorMessage(), Logger::INFO);
  197. } else {
  198. $group['newid'] = self::lastInsertId();
  199. }
  200. }
  201. foreach ($account['group_member'] as &$group_member) {
  202. $import = 0;
  203. foreach ($account['group'] as $group) {
  204. if ($group['id'] == $group_member['gid'] && isset($group['newid'])) {
  205. $group_member['gid'] = $group['newid'];
  206. $import++;
  207. break;
  208. }
  209. }
  210. foreach ($account['contact'] as $contact) {
  211. if ($contact['id'] == $group_member['contact-id'] && isset($contact['newid'])) {
  212. $group_member['contact-id'] = $contact['newid'];
  213. $import++;
  214. break;
  215. }
  216. }
  217. if ($import == 2) {
  218. $r = self::dbImportAssoc('group_member', $group_member);
  219. if ($r === false) {
  220. Logger::log("uimport:insert group member " . $group_member['id'] . " : ERROR : " . DBA::errorMessage(), Logger::INFO);
  221. }
  222. }
  223. }
  224. foreach ($account['photo'] as &$photo) {
  225. $photo['uid'] = $newuid;
  226. $photo['data'] = hex2bin($photo['data']);
  227. $Image = new Image($photo['data'], $photo['type']);
  228. $r = Photo::store(
  229. $Image,
  230. $photo['uid'], $photo['contact-id'], //0
  231. $photo['resource-id'], $photo['filename'], $photo['album'], $photo['scale'], $photo['profile'], //1
  232. $photo['allow_cid'], $photo['allow_gid'], $photo['deny_cid'], $photo['deny_gid']
  233. );
  234. if ($r === false) {
  235. Logger::log("uimport:insert photo " . $photo['resource-id'] . "," . $photo['scale'] . " : ERROR : " . DBA::errorMessage(), Logger::INFO);
  236. }
  237. }
  238. foreach ($account['pconfig'] as &$pconfig) {
  239. $pconfig['uid'] = $newuid;
  240. $r = self::dbImportAssoc('pconfig', $pconfig);
  241. if ($r === false) {
  242. Logger::log("uimport:insert pconfig " . $pconfig['id'] . " : ERROR : " . DBA::errorMessage(), Logger::INFO);
  243. }
  244. }
  245. // send relocate messages
  246. Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $newuid);
  247. info(L10n::t("Done. You can now login with your username and password"));
  248. DI::baseUrl()->redirect('login');
  249. }
  250. }