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.

282 lines
8.2KB

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