2019-10-11 07:38:42 +02:00
< ? php
/**
2019-11-02 11:36:31 +01:00
* @ file src / Modules / Settings / UserExport . php
2019-10-11 07:38:42 +02:00
*/
namespace Friendica\Module\Settings ;
use Friendica\App ;
use Friendica\Core\Hook ;
use Friendica\Core\Renderer ;
use Friendica\Database\DBA ;
use Friendica\Database\DBStructure ;
2019-12-15 22:34:11 +01:00
use Friendica\DI ;
2019-10-11 07:38:42 +02:00
use Friendica\Module\BaseSettingsModule ;
/**
* Module to export user data
**/
2019-11-02 11:36:31 +01:00
class UserExport extends BaseSettingsModule
2019-10-11 07:38:42 +02:00
{
/**
* Handle the request to export data .
2019-11-03 00:12:16 +01:00
* At the moment one can export three different data set
2019-10-11 07:38:42 +02:00
* 1. The profile data that can be used by uimport to resettle
* to a different Friendica instance
* 2. The entire data - set , profile plus postings
2019-11-03 00:12:16 +01:00
* 3. A list of contacts as CSV file similar to the export of Mastodon
2019-10-11 11:49:28 +02:00
*
* If there is an action required through the URL / path , react
* accordingly and export the requested data .
2019-10-11 07:38:42 +02:00
**/
2019-11-05 22:48:54 +01:00
public static function content ( array $parameters = [])
2019-10-11 07:38:42 +02:00
{
2019-11-05 21:22:54 +01:00
parent :: content ( $parameters );
2019-10-17 07:45:48 +02:00
2019-10-11 07:38:42 +02:00
/**
* options shown on " Export personal data " page
* list of array ( 'link url' , 'link text' , 'help text' )
*/
$options = [
2020-01-18 20:52:34 +01:00
[ '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.' )],
[ 'settings/userexport/backup' , DI :: l10n () -> t ( 'Export all' ), DI :: 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 \x28 photos are not exported \x29 " )],
[ '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. " )],
2019-10-11 07:38:42 +02:00
];
Hook :: callAll ( 'uexport_options' , $options );
2019-11-02 11:24:46 +01:00
$tpl = Renderer :: getMarkupTemplate ( " settings/userexport.tpl " );
2019-10-11 07:38:42 +02:00
return Renderer :: replaceMacros ( $tpl , [
2020-01-18 20:52:34 +01:00
'$title' => DI :: l10n () -> t ( 'Export personal data' ),
2019-10-11 07:38:42 +02:00
'$options' => $options
]);
}
2019-10-17 07:45:48 +02:00
/**
* raw content generated for the different choices made
* by the user . At the moment this returns a JSON file
* to the browser which then offers a save / open dialog
* to the user .
**/
2019-11-05 22:48:54 +01:00
public static function rawContent ( array $parameters = [])
2019-11-02 11:24:46 +01:00
{
2019-12-15 23:28:01 +01:00
$args = DI :: args ();
2019-10-17 07:45:48 +02:00
if ( $args -> getArgc () == 3 ) {
// @TODO Replace with router-provided arguments
$action = $args -> get ( 2 );
2019-12-15 22:34:11 +01:00
$user = DI :: app () -> user ;
2019-10-17 07:45:48 +02:00
switch ( $action ) {
case " backup " :
2019-11-03 00:12:16 +01:00
header ( " Content-type: application/json " );
header ( 'Content-Disposition: attachment; filename="' . $user [ 'nickname' ] . '.' . $action . '"' );
2019-12-15 22:34:11 +01:00
self :: exportAll ( DI :: app ());
2019-10-17 07:45:48 +02:00
exit ();
break ;
case " account " :
2019-11-03 00:12:16 +01:00
header ( " Content-type: application/json " );
header ( 'Content-Disposition: attachment; filename="' . $user [ 'nickname' ] . '.' . $action . '"' );
2019-12-15 22:34:11 +01:00
self :: exportAccount ( DI :: app ());
2019-10-17 07:45:48 +02:00
exit ();
break ;
2019-11-03 12:59:14 +01:00
case " contact " :
2019-11-03 00:12:16 +01:00
header ( " Content-type: application/csv " );
header ( 'Content-Disposition: attachment; filename="' . $user [ 'nickname' ] . '-contacts.csv' . '"' );
2019-11-03 15:53:32 +01:00
self :: exportContactsAsCSV ();
2019-11-03 00:12:16 +01:00
exit ();
break ;
2019-10-17 07:45:48 +02:00
default :
exit ();
}
}
}
2019-11-02 11:24:46 +01:00
private static function exportMultiRow ( string $query )
{
2019-12-15 22:34:11 +01:00
$dbStructure = DBStructure :: definition ( DI :: app () -> getBasePath (), false );
2019-10-11 07:38:42 +02:00
preg_match ( " / \ s+from \ s+`?([a-z \ d_]+)`?/i " , $query , $match );
$table = $match [ 1 ];
$result = [];
2019-11-02 12:12:29 +01:00
$rows = DBA :: p ( $query );
while ( $row = DBA :: fetch ( $rows )) {
$p = [];
foreach ( $row as $k => $v ) {
switch ( $dbStructure [ $table ][ 'fields' ][ $k ][ 'type' ]) {
case 'datetime' :
$p [ $k ] = $v ? ? DBA :: NULL_DATETIME ;
break ;
default :
$p [ $k ] = $v ;
break ;
2019-10-11 07:38:42 +02:00
}
}
2019-11-02 12:12:29 +01:00
$result [] = $p ;
2019-10-11 07:38:42 +02:00
}
2019-11-02 12:12:29 +01:00
DBA :: close ( $rows );
2019-10-11 07:38:42 +02:00
return $result ;
}
2019-11-02 11:24:46 +01:00
private static function exportRow ( string $query )
{
2019-12-15 22:34:11 +01:00
$dbStructure = DBStructure :: definition ( DI :: app () -> getBasePath (), false );
2019-10-11 07:38:42 +02:00
preg_match ( " / \ s+from \ s+`?([a-z \ d_]+)`?/i " , $query , $match );
$table = $match [ 1 ];
$result = [];
$r = q ( $query );
if ( DBA :: isResult ( $r )) {
foreach ( $r as $rr ) {
foreach ( $rr as $k => $v ) {
switch ( $dbStructure [ $table ][ 'fields' ][ $k ][ 'type' ]) {
case 'datetime' :
$result [ $k ] = $v ? ? DBA :: NULL_DATETIME ;
break ;
default :
$result [ $k ] = $v ;
break ;
}
}
}
}
return $result ;
}
2019-11-03 00:12:16 +01:00
/**
* Export a list of the contacts as CSV file as e . g . Mastodon and Pleroma are doing .
**/
2019-11-03 15:07:29 +01:00
private static function exportContactsAsCSV ()
2019-11-03 00:12:16 +01:00
{
// write the table header (like Mastodon)
echo " Account address, Show boosts \n " ;
// get all the contacts
2019-11-03 14:03:11 +01:00
$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 " ;
2019-11-03 00:12:16 +01:00
}
2019-11-03 14:03:11 +01:00
DBA :: close ( $contacts );
2019-11-03 00:12:16 +01:00
}
2019-11-02 11:24:46 +01:00
private static function exportAccount ( App $a )
{
2019-10-17 07:45:48 +02:00
$user = self :: exportRow (
2019-10-11 07:38:42 +02:00
sprintf ( " SELECT * FROM `user` WHERE `uid` = %d LIMIT 1 " , intval ( local_user ()))
);
2019-10-17 07:45:48 +02:00
$contact = self :: exportMultiRow (
2019-10-11 07:38:42 +02:00
sprintf ( " SELECT * FROM `contact` WHERE `uid` = %d " , intval ( local_user ()))
);
2019-10-17 07:45:48 +02:00
$profile = self :: exportMultiRow (
2019-10-11 07:38:42 +02:00
sprintf ( " SELECT * FROM `profile` WHERE `uid` = %d " , intval ( local_user ()))
);
2019-10-17 07:45:48 +02:00
$photo = self :: exportMultiRow (
2019-10-11 07:38:42 +02:00
sprintf ( " SELECT * FROM `photo` WHERE uid = %d AND profile = 1 " , intval ( local_user ()))
);
foreach ( $photo as & $p ) {
$p [ 'data' ] = bin2hex ( $p [ 'data' ]);
}
2019-10-17 07:45:48 +02:00
$pconfig = self :: exportMultiRow (
2019-10-11 07:38:42 +02:00
sprintf ( " SELECT * FROM `pconfig` WHERE uid = %d " , intval ( local_user ()))
);
2019-10-17 07:45:48 +02:00
$group = self :: exportMultiRow (
2019-10-11 07:38:42 +02:00
sprintf ( " SELECT * FROM `group` WHERE uid = %d " , intval ( local_user ()))
);
2019-10-17 07:45:48 +02:00
$group_member = self :: exportMultiRow (
2019-10-11 07:38:42 +02:00
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 " , intval ( local_user ()))
);
$output = [
'version' => FRIENDICA_VERSION ,
'schema' => DB_UPDATE_VERSION ,
2019-12-30 23:00:08 +01:00
'baseurl' => DI :: baseUrl (),
2019-10-11 07:38:42 +02:00
'user' => $user ,
'contact' => $contact ,
'profile' => $profile ,
'photo' => $photo ,
'pconfig' => $pconfig ,
'group' => $group ,
'group_member' => $group_member ,
];
echo json_encode ( $output , JSON_PARTIAL_OUTPUT_ON_ERROR );
}
/**
* echoes account data and items as separated json , one per line
*
* @ param App $a
* @ throws Exception
*/
2019-11-02 11:24:46 +01:00
private static function exportAll ( App $a )
{
2019-10-17 07:45:48 +02:00
self :: exportAccount ( $a );
2019-10-11 07:38:42 +02:00
echo " \n " ;
2019-10-17 07:45:48 +02:00
$total = DBA :: count ( 'item' , [ 'uid' => local_user ()]);
2019-10-11 07:38:42 +02:00
// chunk the output to avoid exhausting memory
for ( $x = 0 ; $x < $total ; $x += 500 ) {
$r = q ( " SELECT * FROM `item` WHERE `uid` = %d LIMIT %d, %d " ,
intval ( local_user ()),
intval ( $x ),
intval ( 500 )
);
$output = [ 'item' => $r ];
echo json_encode ( $output , JSON_PARTIAL_OUTPUT_ON_ERROR ) . " \n " ;
}
}
}