]> git.mxchange.org Git - friendica.git/blob - src/Module/Settings/UserExport.php
Changed function name
[friendica.git] / src / Module / Settings / UserExport.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2021, the Friendica project
4  *
5  * @license   GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Module\Settings;
23
24 use Friendica\Core\Hook;
25 use Friendica\Core\Renderer;
26 use Friendica\Database\DBA;
27 use Friendica\Database\DBStructure;
28 use Friendica\DI;
29 use Friendica\Model\Item;
30 use Friendica\Model\Post;
31 use Friendica\Module\BaseSettings;
32 use Friendica\Network\HTTPException;
33
34 /**
35  * Module to export user data
36  **/
37 class UserExport extends BaseSettings
38 {
39         /**
40          * Handle the request to export data.
41          * At the moment one can export three different data set
42          * 1. The profile data that can be used by uimport to resettle
43          *    to a different Friendica instance
44          * 2. The entire data-set, profile plus postings
45          * 3. A list of contacts as CSV file similar to the export of Mastodon
46          *
47          * If there is an action required through the URL / path, react
48          * accordingly and export the requested data.
49          *
50          * @param array $parameters Router-supplied parameters
51          * @return string
52          * @throws HTTPException\ForbiddenException
53          * @throws HTTPException\InternalServerErrorException
54          */
55         public static function content(array $parameters = [])
56         {
57                 if (!local_user()) {
58                         throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.'));
59                 }
60
61                 parent::content($parameters);
62
63                 /**
64                  * options shown on "Export personal data" page
65                  * list of array( 'link url', 'link text', 'help text' )
66                  */
67                 $options = [
68                         ['settings/userexport/account', DI::l10n()->t('Export account'), DI::l10n()->t('Export your account info and contacts. Use this to make a backup of your account and/or to move it to another server.')],
69                         ['settings/userexport/backup', DI::l10n()->t('Export all'), DI::l10n()->t("Export your account info, contacts and all your items as json. Could be a very big file, and could take a lot of time. Use this to make a full backup of your account \x28photos are not exported\x29")],
70                         ['settings/userexport/contact', DI::l10n()->t('Export Contacts to CSV'), DI::l10n()->t("Export the list of the accounts you are following as CSV file. Compatible to e.g. Mastodon.")],
71                 ];
72                 Hook::callAll('uexport_options', $options);
73
74                 $tpl = Renderer::getMarkupTemplate("settings/userexport.tpl");
75                 return Renderer::replaceMacros($tpl, [
76                         '$title' => DI::l10n()->t('Export personal data'),
77                         '$options' => $options
78                 ]);
79         }
80
81         /**
82          * raw content generated for the different choices made
83          * by the user. At the moment this returns a JSON file
84          * to the browser which then offers a save / open dialog
85          * to the user.
86          *
87          * @param array $parameters Router-supplied parameters
88          * @throws HTTPException\ForbiddenException
89          */
90         public static function rawContent(array $parameters = [])
91         {
92                 if (!DI::app()->isLoggedIn()) {
93                         throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.'));
94                 }
95
96                 $args = DI::args();
97                 if ($args->getArgc() == 3) {
98                         // @TODO Replace with router-provided arguments
99                         $action = $args->get(2);
100                         switch ($action) {
101                                 case "backup":
102                                         header("Content-type: application/json");
103                                         header('Content-Disposition: attachment; filename="' . DI::app()->getUserNickname() . '.' . $action . '"');
104                                         self::exportAll(local_user());
105                                         break;
106                                 case "account":
107                                         header("Content-type: application/json");
108                                         header('Content-Disposition: attachment; filename="' . DI::app()->getUserNickname() . '.' . $action . '"');
109                                         self::exportAccount(local_user());
110                                         break;
111                                 case "contact":
112                                         header("Content-type: application/csv");
113                                         header('Content-Disposition: attachment; filename="' . DI::app()->getUserNickname() . '-contacts.csv' . '"');
114                                         self::exportContactsAsCSV(local_user());
115                                         break;
116                         }
117
118                         exit();
119                 }
120         }
121
122         /**
123          * @param string $query
124          * @return array
125          * @throws \Exception
126          */
127         private static function exportMultiRow(string $query)
128         {
129                 $dbStructure = DBStructure::definition(DI::app()->getBasePath(), false);
130
131                 preg_match("/\s+from\s+`?([a-z\d_]+)`?/i", $query, $match);
132                 $table = $match[1];
133
134                 $result = [];
135                 $rows = DBA::p($query);
136                 while ($row = DBA::fetch($rows)) {
137                         $p = [];
138                         foreach ($dbStructure[$table]['fields'] as $column => $field) {
139                                 if (!isset($row[$column])) {
140                                         continue;
141                                 }
142                                 if ($field['type'] == 'datetime') {
143                                         $p[$column] = $row[$column] ?? DBA::NULL_DATETIME;
144                                 } else {
145                                         $p[$column] = $row[$column];
146                                 }
147                         }
148                         $result[] = $p;
149                 }
150                 DBA::close($rows);
151                 return $result;
152         }
153
154         /**
155          * @param string $query
156          * @return array
157          * @throws \Exception
158          */
159         private static function exportRow(string $query)
160         {
161                 $dbStructure = DBStructure::definition(DI::app()->getBasePath(), false);
162
163                 preg_match("/\s+from\s+`?([a-z\d_]+)`?/i", $query, $match);
164                 $table = $match[1];
165
166                 $result = [];
167                 $r = q($query);
168                 if (DBA::isResult($r)) {
169                         foreach ($r as $rr) {
170                                 foreach ($rr as $k => $v) {
171                                         if (empty($dbStructure[$table]['fields'][$k])) {
172                                                 continue;
173                                         }
174
175                                         switch ($dbStructure[$table]['fields'][$k]['type']) {
176                                                 case 'datetime':
177                                                         $result[$k] = $v ?? DBA::NULL_DATETIME;
178                                                         break;
179                                                 default:
180                                                         $result[$k] = $v;
181                                                         break;
182                                         }
183                                 }
184                         }
185                 }
186
187                 return $result;
188         }
189
190         /**
191          * Export a list of the contacts as CSV file as e.g. Mastodon and Pleroma are doing.
192          *
193          * @param int $user_id
194          * @throws \Exception
195          */
196         private static function exportContactsAsCSV(int $user_id)
197         {
198                 if (!$user_id) {
199                         throw new \RuntimeException(DI::l10n()->t('Permission denied.'));
200                 }
201
202                 // write the table header (like Mastodon)
203                 echo "Account address, Show boosts\n";
204                 // get all the contacts
205                 $contacts = DBA::select('contact', ['addr', 'url'], ['uid' => $user_id, 'self' => false, 'rel' => [1, 3], 'deleted' => false]);
206                 while ($contact = DBA::fetch($contacts)) {
207                         echo ($contact['addr'] ?: $contact['url']) . ", true\n";
208                 }
209                 DBA::close($contacts);
210         }
211
212         /**
213          * @param int $user_id
214          * @throws \Exception
215          */
216         private static function exportAccount(int $user_id)
217         {
218                 if (!$user_id) {
219                         throw new \RuntimeException(DI::l10n()->t('Permission denied.'));
220                 }
221
222                 $user = self::exportRow(
223                         sprintf("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", $user_id)
224                 );
225
226                 $contact = self::exportMultiRow(
227                         sprintf("SELECT * FROM `contact` WHERE `uid` = %d ", $user_id)
228                 );
229
230
231                 $profile = self::exportMultiRow(
232                         sprintf("SELECT *, 'default' AS `profile_name`, 1 AS `is-default` FROM `profile` WHERE `uid` = %d ", $user_id)
233                 );
234
235                 $profile_fields = self::exportMultiRow(
236                         sprintf("SELECT * FROM `profile_field` WHERE `uid` = %d ", $user_id)
237                 );
238
239                 $photo = self::exportMultiRow(
240                         sprintf("SELECT * FROM `photo` WHERE uid = %d AND profile = 1", $user_id)
241                 );
242                 foreach ($photo as &$p) {
243                         $p['data'] = bin2hex($p['data']);
244                 }
245
246                 $pconfig = self::exportMultiRow(
247                         sprintf("SELECT * FROM `pconfig` WHERE uid = %d", $user_id)
248                 );
249
250                 $group = self::exportMultiRow(
251                         sprintf("SELECT * FROM `group` WHERE uid = %d", $user_id)
252                 );
253
254                 $group_member = self::exportMultiRow(
255                         sprintf("SELECT `group_member`.`gid`, `group_member`.`contact-id` FROM `group_member` INNER JOIN `group` ON `group`.`id` = `group_member`.`gid` WHERE `group`.`uid` = %d", $user_id)
256                 );
257
258                 $output = [
259                         'version' => FRIENDICA_VERSION,
260                         'schema' => DB_UPDATE_VERSION,
261                         'baseurl' => DI::baseUrl(),
262                         'user' => $user,
263                         'contact' => $contact,
264                         'profile' => $profile,
265                         'profile_fields' => $profile_fields,
266                         'photo' => $photo,
267                         'pconfig' => $pconfig,
268                         'group' => $group,
269                         'group_member' => $group_member,
270                 ];
271
272                 echo json_encode($output, JSON_PARTIAL_OUTPUT_ON_ERROR);
273         }
274
275         /**
276          * echoes account data and items as separated json, one per line
277          *
278          * @param int $user_id
279          * @throws \Exception
280          */
281         private static function exportAll(int $user_id)
282         {
283                 if (!$user_id) {
284                         throw new \RuntimeException(DI::l10n()->t('Permission denied.'));
285                 }
286
287                 self::exportAccount($user_id);
288                 echo "\n";
289
290                 $total = Post::count(['uid' => $user_id]);
291                 // chunk the output to avoid exhausting memory
292
293                 for ($x = 0; $x < $total; $x += 500) {
294                         $items = Post::selectToArray(Item::ITEM_FIELDLIST, ['uid' => $user_id], ['limit' => [$x, 500]]);
295                         $output = ['item' => $items];
296                         echo json_encode($output, JSON_PARTIAL_OUTPUT_ON_ERROR) . "\n";
297                 }
298         }
299 }