Browse Source

Merge pull request #7726 from tobiasd/20191010-uexport

move uexport module to src
pull/7801/head
Hypolite Petovan 2 years ago
committed by GitHub
parent
commit
b543ee8ac7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      mod/settings.php
  2. 197
      mod/uexport.php
  3. 4
      src/Module/BaseSettingsModule.php
  4. 212
      src/Module/Settings/UserExport.php
  5. 4
      src/Module/Tos.php
  6. 1
      static/routes.config.php

4
mod/settings.php

@ -131,8 +131,8 @@ function settings_init(App $a)
$tabs[] = [
'label' => L10n::t('Export personal data'),
'url' => 'uexport',
'selected' => (($a->argc == 1) && ($a->argv[0] === 'uexport')?'active':''),
'url' => 'settings/userexport',
'selected' => (($a->argc > 1) && ($a->argv[1] === 'userexport')?'active':''),
'accesskey' => 'e',
];

197
mod/uexport.php

@ -1,197 +0,0 @@
<?php
/**
* @file mod/uexport.php
*/
use Friendica\App;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
function uexport_init(App $a) {
/// @todo Don't forget to move this global field as static field in src/Modules
global $dbStructure;
if (!local_user()) {
exit();
}
require_once("mod/settings.php");
settings_init($a);
$dbStructure = DBStructure::definition($a->getBasePath());
}
function uexport_content(App $a) {
if ($a->argc > 1) {
header("Content-type: application/json");
header('Content-Disposition: attachment; filename="' . $a->user['nickname'] . '.' . $a->argv[1] . '"');
switch ($a->argv[1]) {
case "backup":
uexport_all($a);
exit();
break;
case "account":
uexport_account($a);
exit();
break;
default:
exit();
}
}
/**
* options shown on "Export personal data" page
* list of array( 'link url', 'link text', 'help text' )
*/
$options = [
['uexport/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.')],
['uexport/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")],
];
Hook::callAll('uexport_options', $options);
$tpl = Renderer::getMarkupTemplate("uexport.tpl");
return Renderer::replaceMacros($tpl, [
'$title' => L10n::t('Export personal data'),
'$options' => $options
]);
}
function _uexport_multirow($query) {
global $dbStructure;
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) {
$p = [];
foreach ($rr as $k => $v) {
switch ($dbStructure[$table]['fields'][$k]['type']) {
case 'datetime':
$p[$k] = $v ?? DBA::NULL_DATETIME;
break;
default:
$p[$k] = $v;
break;
}
}
$result[] = $p;
}
}
return $result;
}
function _uexport_row($query) {
global $dbStructure;
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;
}
function uexport_account($a) {
$user = _uexport_row(
sprintf("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval(local_user()))
);
$contact = _uexport_multirow(
sprintf("SELECT * FROM `contact` WHERE `uid` = %d ", intval(local_user()))
);
$profile = _uexport_multirow(
sprintf("SELECT * FROM `profile` WHERE `uid` = %d ", intval(local_user()))
);
$photo = _uexport_multirow(
sprintf("SELECT * FROM `photo` WHERE uid = %d AND profile = 1", intval(local_user()))
);
foreach ($photo as &$p) {
$p['data'] = bin2hex($p['data']);
}
$pconfig = _uexport_multirow(
sprintf("SELECT * FROM `pconfig` WHERE uid = %d", intval(local_user()))
);
$group = _uexport_multirow(
sprintf("SELECT * FROM `group` WHERE uid = %d", intval(local_user()))
);
$group_member = _uexport_multirow(
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,
'baseurl' => System::baseUrl(),
'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
*/
function uexport_all(App $a) {
uexport_account($a);
echo "\n";
$total = 0;
$r = q("SELECT count(*) as `total` FROM `item` WHERE `uid` = %d ",
intval(local_user())
);
if (DBA::isResult($r)) {
$total = $r[0]['total'];
}
// 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";
}
}

4
src/Module/BaseSettingsModule.php

@ -87,8 +87,8 @@ class BaseSettingsModule extends BaseModule
$tabs[] = [
'label' => L10n::t('Export personal data'),
'url' => 'uexport',
'selected' => (($a->argc == 1) && ($a->argv[0] === 'uexport') ? 'active' : ''),
'url' => 'settings/userexport',
'selected' => (($a->argc > 1) && ($a->argv[1] === 'userexport') ? 'active' : ''),
'accesskey' => 'e',
];

212
src/Module/Settings/UserExport.php

@ -0,0 +1,212 @@
<?php
/**
* @file src/Modules/Settings/UserExport.php
*/
namespace Friendica\Module\Settings;
use Friendica\App;
use Friendica\App\Arguments;
use Friendica\BaseModule;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\Module\BaseSettingsModule;
/**
* Module to export user data
**/
class UserExport extends BaseSettingsModule
{
/**
* Handle the request to export data.
* At the moment one can export two different data set
* 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
*
* If there is an action required through the URL / path, react
* accordingly and export the requested data.
**/
public static function content()
{
parent::content();
/**
* options shown on "Export personal data" page
* list of array( 'link url', 'link text', 'help text' )
*/
$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/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")],
];
Hook::callAll('uexport_options', $options);
$tpl = Renderer::getMarkupTemplate("settings/userexport.tpl");
return Renderer::replaceMacros($tpl, [
'$title' => L10n::t('Export personal data'),
'$options' => $options
]);
}
/**
* 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.
**/
public static function rawContent()
{
$args = self::getClass(Arguments::class);
if ($args->getArgc() == 3) {
// @TODO Replace with router-provided arguments
$action = $args->get(2);
$user = self::getApp()->user;
header("Content-type: application/json");
header('Content-Disposition: attachment; filename="' . $user['nickname'] . '.' . $action . '"');
switch ($action) {
case "backup":
self::exportAll(self::getApp());
exit();
break;
case "account":
self::exportAccount(self::getApp());
exit();
break;
default:
exit();
}
}
}
private static function exportMultiRow(string $query)
{
$dbStructure = DBStructure::definition(self::getApp()->getBasePath(), false);
preg_match("/\s+from\s+`?([a-z\d_]+)`?/i", $query, $match);
$table = $match[1];
$result = [];
$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;
}
}
$result[] = $p;
}
DBA::close($rows);
return $result;
}
private static function exportRow(string $query)
{
$dbStructure = DBStructure::definition(self::getApp()->getBasePath(), false);
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;
}
private static function exportAccount(App $a)
{
$user = self::exportRow(
sprintf("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval(local_user()))
);
$contact = self::exportMultiRow(
sprintf("SELECT * FROM `contact` WHERE `uid` = %d ", intval(local_user()))
);
$profile = self::exportMultiRow(
sprintf("SELECT * FROM `profile` WHERE `uid` = %d ", intval(local_user()))
);
$photo = self::exportMultiRow(
sprintf("SELECT * FROM `photo` WHERE uid = %d AND profile = 1", intval(local_user()))
);
foreach ($photo as &$p) {
$p['data'] = bin2hex($p['data']);
}
$pconfig = self::exportMultiRow(
sprintf("SELECT * FROM `pconfig` WHERE uid = %d", intval(local_user()))
);
$group = self::exportMultiRow(
sprintf("SELECT * FROM `group` WHERE uid = %d", intval(local_user()))
);
$group_member = self::exportMultiRow(
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,
'baseurl' => System::baseUrl(),
'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
*/
private static function exportAll(App $a)
{
self::exportAccount($a);
echo "\n";
$total = DBA::count('item', ['uid' => local_user()]);
// 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";
}
}
}

4
src/Module/Tos.php

@ -34,7 +34,7 @@ class Tos extends BaseModule
{
$this->privacy_operate = L10n::t('At the time of registration, and for providing communications between the user account and their contacts, the user has to provide a display name (pen name), an username (nickname) and a working email address. The names will be accessible on the profile page of the account by any visitor of the page, even if other profile details are not displayed. The email address will only be used to send the user notifications about interactions, but wont be visibly displayed. The listing of an account in the node\'s user directory or the global user directory is optional and can be controlled in the user settings, it is not necessary for communication.');
$this->privacy_distribute = L10n::t('This data is required for communication and is passed on to the nodes of the communication partners and is stored there. Users can enter additional private data that may be transmitted to the communication partners accounts.');
$this->privacy_delete = L10n::t('At any point in time a logged in user can export their account data from the <a href="%1$s/settings/uexport">account settings</a>. If the user wants to delete their account they can do so at <a href="%1$s/removeme">%1$s/removeme</a>. The deletion of the account will be permanent. Deletion of the data will also be requested from the nodes of the communication partners.', System::baseurl());
$this->privacy_delete = L10n::t('At any point in time a logged in user can export their account data from the <a href="%1$s/settings/userexport">account settings</a>. If the user wants to delete their account they can do so at <a href="%1$s/removeme">%1$s/removeme</a>. The deletion of the account will be permanent. Deletion of the data will also be requested from the nodes of the communication partners.', System::baseurl());
// In some cases we don't need every single one of the above separate, but all in one block.
// So here is an array to look over
$this->privacy_complete = [L10n::t('Privacy Statement'), $this->privacy_operate, $this->privacy_distribute, $this->privacy_delete];
@ -76,7 +76,7 @@ class Tos extends BaseModule
'$privstatementtitle' => L10n::t('Privacy Statement'),
'$privacy_operate' => L10n::t('At the time of registration, and for providing communications between the user account and their contacts, the user has to provide a display name (pen name), an username (nickname) and a working email address. The names will be accessible on the profile page of the account by any visitor of the page, even if other profile details are not displayed. The email address will only be used to send the user notifications about interactions, but wont be visibly displayed. The listing of an account in the node\'s user directory or the global user directory is optional and can be controlled in the user settings, it is not necessary for communication.'),
'$privacy_distribute' => L10n::t('This data is required for communication and is passed on to the nodes of the communication partners and is stored there. Users can enter additional private data that may be transmitted to the communication partners accounts.'),
'$privacy_delete' => L10n::t('At any point in time a logged in user can export their account data from the <a href="%1$s/settings/uexport">account settings</a>. If the user wants to delete their account they can do so at <a href="%1$s/removeme">%1$s/removeme</a>. The deletion of the account will be permanent. Deletion of the data will also be requested from the nodes of the communication partners.', System::baseurl())
'$privacy_delete' => L10n::t('At any point in time a logged in user can export their account data from the <a href="%1$s/settings/userexport">account settings</a>. If the user wants to delete their account they can do so at <a href="%1$s/removeme">%1$s/removeme</a>. The deletion of the account will be permanent. Deletion of the data will also be requested from the nodes of the communication partners.', System::baseurl())
]);
} else {
return;

1
static/routes.config.php

@ -214,6 +214,7 @@ return [
'/verify' => [Module\Settings\TwoFactor\Verify::class, [R::GET, R::POST]],
],
'/delegation[/{action}/{user_id}]' => [Module\Settings\Delegation::class, [R::GET, R::POST]],
'/userexport[/{action}]' => [Module\Settings\UserExport::class, [R::GET, R::POST]],
],
'/randprof' => [Module\RandomProfile::class, [R::GET]],

Loading…
Cancel
Save