Merge pull request #7806 from tobiasd/20191102-csvexport

added export and import of followed contacts to and from  CSV files
This commit is contained in:
Philipp 2019-11-03 20:28:08 +01:00 committed by GitHub
commit d0068170db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 149 additions and 4 deletions

View File

@ -0,0 +1,23 @@
# Export / Import of followed Contacts
* [Home](help)
In addition to [move your account](help/Move-Account) you can export and import the list of accounts you follow.
The exported list is stored as CSV file that is compatible to the format used by other platforms as e.g. Mastodon or Pleroma.
## Export of followed Contacts
To export the list of accounts that you follow, go to the [Settings Export personal date](settings/userexport) and click the [Export Contacts to CSV](settings/userexport/contact).
## Import of followed Contacts
To import contacts from a CSV file, go to the [Settings page](settings).
At the bottom of the *account settings* page you'll find the *import contacts* section.
Upload the CSV file there.
### Supported File Format
The CSV file *must* contain at least one column.
In the first column the table should contain either the handle or URL of an followed account.
(one account per row.)
Other columns in the CSV file will be ignored.

View File

@ -21,6 +21,7 @@ Friendica Documentation and Resources
* [Chats](help/Chats) * [Chats](help/Chats)
* Further information * Further information
* [Move your account](help/Move-Account) * [Move your account](help/Move-Account)
* [Export / Import of followed Contacts](help/Export-Import-Contacts)
* [Delete your account](help/Remove-Account) * [Delete your account](help/Remove-Account)
* [Frequently asked questions (FAQ)](help/FAQ) * [Frequently asked questions (FAQ)](help/FAQ)

View File

@ -0,0 +1,23 @@
# Export / Import von gefolgten Kontakte
* [Home](help)
Zusätzlich zum [Umziehen des Accounts](help/Move-Account) kannst du die Liste der von dir gefolgten Kontakte exportieren und importieren.
Die exportierte Liste wird als CSV Datei in einem zu anderen Plattformen, z.B. Mastodon oder Pleroma, kompatiblen Format gespeichert.
## Export der gefolgten Kontakte
Um die Liste der Kontakte *denen du folgst* zu exportieren, geht die [Einstellungen Persönliche Daten exportieren](settings/userexport) und klicke den [Exportiere Kontakte als CSV](settings/userexport/contact) an.
## Import der gefolgten Kontakte
Um die Kontakt CSV Datei zu importieren, gehe in die [Einstellungen](settings).
Am Ende der Einstellungen zum Nutzerkonto findest du den Abschnitt "Kontakte Importieren".
Hier kannst du die CSV Datei auswählen und hoch laden.
### Unterstütztes Datei Format
Die CSV Datei *muss* mindestens eine Spalte beinhalten.
In der ersten Spalte der Tabelle sollte *sollte* entweder das Handle oder die URL des gefolgten Kontakts.
(Ein Kontakt pro Zeile.)
Alle anderen Spalten der CSV Datei werden beim Importieren ignoriert.

View File

@ -21,6 +21,7 @@ Friendica - Dokumentation und Ressourcen
* [Chats](help/Chats) * [Chats](help/Chats)
* Weiterführende Informationen * Weiterführende Informationen
* [Account umziehen](help/Move-Account) * [Account umziehen](help/Move-Account)
* [Export / Import gefolgter Kontakte](help/Export-Import-Contacts)
* [Account löschen](help/Remove-Account) * [Account löschen](help/Remove-Account)
* [Bugs und Probleme](help/Bugs-and-Issues) * [Bugs und Probleme](help/Bugs-and-Issues)
* [Häufig gestellte Fragen (FAQ)](help/FAQ) * [Häufig gestellte Fragen (FAQ)](help/FAQ)

View File

@ -390,6 +390,33 @@ function settings_post(App $a)
BaseModule::checkFormSecurityTokenRedirectOnError('/settings', 'settings'); BaseModule::checkFormSecurityTokenRedirectOnError('/settings', 'settings');
// Import Contacts from CSV file
if (!empty($_POST['importcontact-submit'])) {
if (isset($_FILES['importcontact-filename'])) {
// was there an error
if ($_FILES['importcontact-filename']['error'] > 0) {
Logger::notice('Contact CSV file upload error');
info(L10n::t('Contact CSV file upload error'));
} else {
$csvArray = array_map('str_getcsv', file($_FILES['importcontact-filename']['tmp_name']));
// import contacts
foreach ($csvArray as $csvRow) {
// The 1st row may, or may not contain the headers of the table
// We expect the 1st field of the row to contain either the URL
// or the handle of the account, therefore we check for either
// "http" or "@" to be present in the string.
// All other fields from the row will be ignored
if ((strpos($csvRow[0],'@') !== false) || (strpos($csvRow[0],'http') !== false)) {
$arr = Contact::createFromProbe($_SESSION['uid'], $csvRow[0], '', false);
}
}
info(L10n::t('Importing Contacts done'));
// delete temp file
unlink($filename);
}
}
}
if (!empty($_POST['resend_relocate'])) { if (!empty($_POST['resend_relocate'])) {
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, local_user()); Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, local_user());
info(L10n::t("Relocate message has been send to your contacts")); info(L10n::t("Relocate message has been send to your contacts"));
@ -1223,6 +1250,10 @@ function settings_content(App $a)
'$h_descadvn' => L10n::t('Change the behaviour of this account for special situations'), '$h_descadvn' => L10n::t('Change the behaviour of this account for special situations'),
'$pagetype' => $pagetype, '$pagetype' => $pagetype,
'$importcontact' => L10n::t('Import Contacts'),
'$importcontact_text' => L10n::t('Upload a CSV file that contains the handle of your followed accounts in the first column you exported from the old account.'),
'$importcontact_button' => L10n::t('Upload File'),
'$importcontact_maxsize' => Config::get('system', max_csv_file_size, 30720),
'$relocate' => L10n::t('Relocate'), '$relocate' => L10n::t('Relocate'),
'$relocate_text' => L10n::t("If you have moved this profile from another server, and some of your contacts don't receive your updates, try pushing this button."), '$relocate_text' => L10n::t("If you have moved this profile from another server, and some of your contacts don't receive your updates, try pushing this button."),
'$relocate_button' => L10n::t("Resend relocate message to contacts"), '$relocate_button' => L10n::t("Resend relocate message to contacts"),

View File

@ -23,10 +23,11 @@ class UserExport extends BaseSettingsModule
{ {
/** /**
* Handle the request to export data. * Handle the request to export data.
* At the moment one can export two different data set * At the moment one can export three different data set
* 1. The profile data that can be used by uimport to resettle * 1. The profile data that can be used by uimport to resettle
* to a different Friendica instance * to a different Friendica instance
* 2. The entire data-set, profile plus postings * 2. The entire data-set, profile plus postings
* 3. A list of contacts as CSV file similar to the export of Mastodon
* *
* If there is an action required through the URL / path, react * If there is an action required through the URL / path, react
* accordingly and export the requested data. * accordingly and export the requested data.
@ -42,6 +43,7 @@ class UserExport extends BaseSettingsModule
$options = [ $options = [
['settings/userexport/account', L10n::t('Export account'), 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.')], ['settings/userexport/account', L10n::t('Export account'), 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.')],
['settings/userexport/backup', L10n::t('Export all'), L10n::t("Export your accout 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")], ['settings/userexport/backup', L10n::t('Export all'), L10n::t("Export your accout 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")],
['settings/userexport/contact', L10n::t('Export Contacts to CSV'), L10n::t("Export the list of the accounts you are following as CSV file. Compatible to e.g. Mastodon.")],
]; ];
Hook::callAll('uexport_options', $options); Hook::callAll('uexport_options', $options);
@ -64,17 +66,25 @@ class UserExport extends BaseSettingsModule
// @TODO Replace with router-provided arguments // @TODO Replace with router-provided arguments
$action = $args->get(2); $action = $args->get(2);
$user = self::getApp()->user; $user = self::getApp()->user;
header("Content-type: application/json");
header('Content-Disposition: attachment; filename="' . $user['nickname'] . '.' . $action . '"');
switch ($action) { switch ($action) {
case "backup": case "backup":
header("Content-type: application/json");
header('Content-Disposition: attachment; filename="' . $user['nickname'] . '.' . $action . '"');
self::exportAll(self::getApp()); self::exportAll(self::getApp());
exit(); exit();
break; break;
case "account": case "account":
header("Content-type: application/json");
header('Content-Disposition: attachment; filename="' . $user['nickname'] . '.' . $action . '"');
self::exportAccount(self::getApp()); self::exportAccount(self::getApp());
exit(); exit();
break; break;
case "contact":
header("Content-type: application/csv");
header('Content-Disposition: attachment; filename="' . $user['nickname'] . '-contacts.csv'. '"');
self::exportContactsAsCSV();
exit();
break;
default: default:
exit(); exit();
} }
@ -134,6 +144,20 @@ class UserExport extends BaseSettingsModule
return $result; return $result;
} }
/**
* Export a list of the contacts as CSV file as e.g. Mastodon and Pleroma are doing.
**/
private static function exportContactsAsCSV()
{
// write the table header (like Mastodon)
echo "Account address, Show boosts\n";
// get all the contacts
$contacts = DBA::select('contact', ['addr'], ['uid' => $_SESSION['uid'], 'self' => false, 'rel' => [1,3], 'deleted' => false]);
while ($contact = DBA::fetch($contacts)) {
echo $contact['addr'] . ", true\n";
}
DBA::close($contacts);
}
private static function exportAccount(App $a) private static function exportAccount(App $a)
{ {
$user = self::exportRow( $user = self::exportRow(

View File

@ -124,6 +124,12 @@ return [
// The fully-qualified URL of this Friendica node. // The fully-qualified URL of this Friendica node.
// Used by the worker in a non-HTTP execution environment. // Used by the worker in a non-HTTP execution environment.
'url' => '', 'url' => '',
// max_csv_file_size (Integer)
// When uploading a CSV with account addresses to follow
// in the user settings, this controls the maximum file
// size of the upload file.
'max_csv_file_size' => 30720,
], ],
// Used in the admin settings to lock certain features // Used in the admin settings to lock certain features

View File

@ -2,7 +2,7 @@
{{$nickname_block nofilter}} {{$nickname_block nofilter}}
<form action="settings" id="settings-form" method="post" autocomplete="off" > <form action="settings" id="settings-form" method="post" autocomplete="off" enctype="multipart/form-data" >
<input type='hidden' name='form_security_token' value='{{$form_security_token}}'> <input type='hidden' name='form_security_token' value='{{$form_security_token}}'>
<h3 class="settings-heading"><a href="javascript:;">{{$h_pass}}</a></h3> <h3 class="settings-heading"><a href="javascript:;">{{$h_pass}}</a></h3>
@ -196,6 +196,17 @@
</div> </div>
</div> </div>
<h3 class="settings-heading"><a href="javascript:;">{{$importcontact}}</a></h3>
<div class="settings-content-block">
<input type="hidden" name="MAX_FILE_SIZE" value="{{$importcontact_maxsize}}" />
<div id="settings-pagetype-desc">{{$importcontact_text}}</div>
<input type="file" name="importcontact-filename" />
<div class="settings-submit-wrapper" >
<input type="submit" name="importcontact-submit" class="importcontact-submit" value="{{$importcontact_button}}" />
</div>
</div>
<h3 class="settings-heading"><a href="javascript:;">{{$relocate}}</a></h3> <h3 class="settings-heading"><a href="javascript:;">{{$relocate}}</a></h3>
<div class="settings-content-block"> <div class="settings-content-block">
<div id="settings-pagetype-desc">{{$relocate_text}}</div> <div id="settings-pagetype-desc">{{$relocate_text}}</div>

View File

@ -270,6 +270,31 @@
</div> </div>
</div> </div>
{{* Import contacts CSV *}}
<div class="panel">
<div class="section-subtitle-wrapper" role="tab" id="importcontact-settings">
<h4>
<a class="accordion-toggle collapsed" data-toggle="collapse" data-parent="#settings" href="#importcontact-settings-collapse" aria-expanded="false" aria-controls="importcontact-settings-collapse">
{{$importcontact}}
</a>
</h4>
</div>
<div id="importcontact-settings-collapse" class="panel-collapse collapse" role="tabpanel" aria-labelledby="importcontact-settings">
<div class="section-content-tools-wrapper">
<div id="importcontact-relocate-desc">{{$importcontact_text}}</div>
<input type="hidden" name="MAX_FILE_SIZE" value="{{$importcontact_maxsize}}" />
<input type="file" name="importcontact-filename" />
<br/>
<div class="form-group pull-right settings-submit-wrapper" >
<button type="submit" name="importcontact-submit" class="btn btn-primary" value="{{$importcontact_button}}">{{$importcontact_button}}</button>
</div>
<div class="clear"></div>
</div>
</div>
</div>
{{* The relocate setting section *}} {{* The relocate setting section *}}
<div class="panel"> <div class="panel">
<div class="section-subtitle-wrapper" role="tab" id="relocate-settings"> <div class="section-subtitle-wrapper" role="tab" id="relocate-settings">