Skip to content

Commit

Permalink
Merge pull request #77 from humhub/enh/167-user-data-export-rest
Browse files Browse the repository at this point in the history
User data export
  • Loading branch information
luke- authored Sep 13, 2024
2 parents c5d1119 + cb79a69 commit 806e173
Show file tree
Hide file tree
Showing 13 changed files with 604 additions and 20 deletions.
36 changes: 31 additions & 5 deletions Events.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@
use humhub\modules\legal\widgets\CookieNote;
use humhub\modules\post\models\Post;
use humhub\modules\rest\components\BaseController;
use humhub\modules\ui\menu\MenuLink;
use humhub\modules\user\models\forms\Registration;
use humhub\modules\user\models\User;
use humhub\modules\user\widgets\AccountSettingsMenu;
use humhub\widgets\LayoutAddons;
use Yii;
use yii\base\ActionEvent;
use yii\helpers\Url;
use yii\web\UserEvent;


/**
* @author luke
*/
class Events
{

const SESSION_KEY_LEGAL_CHECK = 'legalModuleChecked';
const SESSION_KEY_LEGAL_AFTER_REGISTRATION = 'legalModuleAfterRegistration';
public const SESSION_KEY_LEGAL_CHECK = 'legalModuleChecked';
public const SESSION_KEY_LEGAL_AFTER_REGISTRATION = 'legalModuleAfterRegistration';

public static function onFooterMenuInit($event)
{
Expand Down Expand Up @@ -226,7 +226,7 @@ public static function onRegistrationFormRender($event)

$hForm->definition['elements']['RegistrationChecks'] = [
'type' => 'form',
'elements' => $elements
'elements' => $elements,
];
}

Expand Down Expand Up @@ -264,4 +264,30 @@ public static function onAfterRunRichText($event)
$event->result = Content::widget(['content' => $event->result, 'richtext' => false]);
}
}

public static function onAccountSettingsMenuInit($event)
{
/* @var AccountSettingsMenu $menu */
$menu = $event->sender;

/* @var Module $module */
$module = Yii::$app->getModule('legal');

if ($module->isEnabledExportUserData()) {
$menu->addEntry(new MenuLink([
'label' => Yii::t('LegalModule.base', 'Export personal data'),
'url' => ['/legal/export'],
'sortOrder' => 1000,
'isActive' => MenuLink::isActiveState('legal', 'export'),
]));
}
}

/**
* Callback on daily cron job run
*/
public static function onCronDailyRun()
{
Yii::$app->queue->push(new jobs\DeletePackages());
}
}
22 changes: 17 additions & 5 deletions Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,24 @@ public function getDefaultLanguage()
return $this->settings->get('defaultLanguage');
}

/**
* @return bool
*/
public function showAgeCheck()
public function isAllowedExportUserData(): bool
{
return Yii::$app->hasModule('rest') && Yii::$app->getModule('rest')->isActivated;
}

public function isEnabledExportUserData(): bool
{
return $this->isAllowedExportUserData() && $this->settings->get('exportUserData', false);
}

public function getExportUserDays(): int
{
return (int) $this->settings->get('exportUserDays', 1);
}

public function showAgeCheck(): bool
{
return (bool)$this->settings->get('showAgeCheck', false);
return (bool) $this->settings->get('showAgeCheck', false);
}

/**
Expand Down
10 changes: 7 additions & 3 deletions config.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<?php /** @noinspection MissedFieldInspection */

use humhub\commands\CronController;
use humhub\components\Controller;
use humhub\modules\content\widgets\richtext\ProsemirrorRichText;
use humhub\modules\user\models\forms\Registration;
use humhub\modules\user\widgets\AccountSettingsMenu;
use humhub\widgets\FooterMenu;
use humhub\widgets\LayoutAddons;

Expand All @@ -23,6 +25,8 @@
['class' => Registration::class, 'event' => Registration::EVENT_AFTER_INIT, 'callback' => ['humhub\modules\legal\Events', 'onRegistrationFormInit']],
['class' => Registration::class, 'event' => Registration::EVENT_AFTER_REGISTRATION, 'callback' => ['humhub\modules\legal\Events', 'onRegistrationAfterRegistration']],
['class' => Controller::class, 'event' => Controller::EVENT_BEFORE_ACTION, 'callback' => ['humhub\modules\legal\Events', 'onBeforeControllerAction']],
['class' => ProsemirrorRichText::class, 'event' => ProsemirrorRichText::EVENT_AFTER_RUN, 'callback' => ['humhub\modules\legal\Events', 'onAfterRunRichText']]
]
];
['class' => ProsemirrorRichText::class, 'event' => ProsemirrorRichText::EVENT_AFTER_RUN, 'callback' => ['humhub\modules\legal\Events', 'onAfterRunRichText']],
['class' => AccountSettingsMenu::class, 'event' => AccountSettingsMenu::EVENT_INIT, 'callback' => ['humhub\modules\legal\Events', 'onAccountSettingsMenuInit']],
['class' => CronController::class, 'event' => CronController::EVENT_ON_DAILY_RUN, 'callback' => ['humhub\modules\legal\Events', 'onCronDailyRun']],
],
];
74 changes: 74 additions & 0 deletions controllers/ExportController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/

namespace humhub\modules\legal\controllers;

use humhub\modules\legal\Module;
use humhub\modules\legal\services\ExportService;
use humhub\modules\user\components\BaseAccountController;
use Yii;
use yii\web\BadRequestHttpException;
use yii\web\NotFoundHttpException;

/* @property Module $module */
class ExportController extends BaseAccountController
{
/**
* @inheritdoc
*/
protected function getAccessRules()
{
return array_merge(parent::getAccessRules(), [
['checkEnabledExportUserData'],
]);
}

public function checkEnabledExportUserData($rule, $access)
{
return $this->module->isEnabledExportUserData();
}

public function actionIndex()
{
return $this->render('index', [
'service' => ExportService::instance(),
]);
}

public function actionRequest()
{
if (ExportService::instance()->requestPackage()) {
$this->view->success(Yii::t('LegalModule.base', 'The exporting of your data has been started, please wait some time.'));
} else {
$this->view->error('Cannot start the exporting of your data, please try again.');
}

return $this->redirect(['index']);
}

public function actionDownload()
{
$package = ExportService::instance()->downloadPackage();

if ($package === null) {
throw new NotFoundHttpException();
}

return $package;
}

public function actionDelete()
{
if (ExportService::instance()->deletePackage()) {
$this->view->success(Yii::t('LegalModule.base', 'The package has been deleted.'));
} else {
$this->view->error('Cannot delete the package, please try again.');
}

return $this->redirect(['index']);
}
}
34 changes: 34 additions & 0 deletions docs/DEVELOPER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Development вocumentation

## Export user data

Please note that this feature will only work if the [RESTful API](https://github.com/humhub/rest) module is enabled.

To append user data from another module:

1) Add the following line to the `events` section of your module's `config.php` file:

```php
['class' => 'humhub\modules\legal\services\ExportService', 'event' => 'collectUserData', 'callback' => ['humhub\modules\your_module\Events', 'onLegalModuleUserDataExport']],
```

2) Implement the `humhub\modules\your_module\Events::onLegalModuleUserDataExport` method in your `Events` class like this:

```php
public static function onLegalModuleUserDataExport(\humhub\modules\legal\events\UserDataCollectionEvent $event)
{
$event->addExportData('wiki', array_map(function ($page) {
return \humhub\modules\wiki\helpers\RestDefinitions::getWikiPage($page);
}, \humhub\modules\wiki\models\WikiPage::find()
->joinWith('content')
->andWhere(['content.created_by' => $event->user->id])
->all()));

$files = File::findAll(['created_by' => $event->user->id]);
foreach ($files as $file) {
$event->addExportFile($file->file_name, $file->store->get());
}
}
```

To test it, go to edit your profile, and run "Export your data". You should find the file `/files/wiki.json` in the ZIP archive, along with all the user's uploaded files in the `/uploads/` folder.
17 changes: 17 additions & 0 deletions docs/MANUAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Manual

## Modules that support user data export

- [Calendar](https://github.com/humhub/calendar)
- [Files](https://github.com/humhub/cfiles)
- [Messenger](https://github.com/humhub/mail)
- [Polls](https://github.com/humhub/polls)
- [Tasks](https://github.com/humhub/tasks)
- [Wiki](https://github.com/humhub/wiki)

Please note that the "Export your data" feature only works if the [RESTful API](https://github.com/humhub/rest) module is enabled. By default, it exports the following data from the database:
- User data
- Posts
- Comments
- Likes
- Files (physical files are also attached to the ZIP archive in the `/uploads/` folder)
130 changes: 130 additions & 0 deletions events/UserDataCollectionEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/

namespace humhub\modules\legal\events;

use humhub\modules\file\libs\FileHelper;
use humhub\modules\legal\services\ExportService;
use humhub\modules\user\events\UserEvent;
use Yii;
use yii\base\Exception;
use ZipArchive;

class UserDataCollectionEvent extends UserEvent
{
/**
* Array with export data:
* - Key is a category name which will be used to name JSON file in ZIP archive
* - Value is an array with any data
* @var array $exportData
*/
public array $exportData = [];

/**
* Array with export files:
* - Key is a file name which will be used in ZIP archive
* - Value is a file path where it is really located on the server disk
* @var array $exportFiles
*/
public array $exportFiles = [];

/**
* Add data to export
*
* @param string $category Category name which will be used to name JSON file in ZIP archive
* @param array $data Array with any data
*/
public function addExportData(string $category, array $data)
{
$index = $this->getUniqueArrayIndex($this->exportData, $category);
$this->exportData[$index] = $data;
}

/**
* Add a file to export
*
* @param string $fileName File name which will be used in ZIP archive
* @param string $sourceFilePath File path where it is really located on the server disk
*/
public function addExportFile(string $fileName, string $sourceFilePath)
{
$index = $this->getUniqueArrayIndex($this->exportFiles, $fileName);
$this->exportFiles[$index] = $sourceFilePath;
}

private function getUniqueArrayIndex(array $array, string $index): string
{
$origIndex = $index;
$i = 1;
while (isset($array[$index])) {
$index = preg_replace('/(^.+?)(\.[a-z0-9]+)?$/i', '$1-' . (++$i) . '$2', $origIndex);
}

return $index;
}

/**
* @inheritdoc
* @throws Exception
* @throws \Throwable
*/
public static function trigger($class, $name, $event = null)
{
parent::trigger($class, $name, $event);

if ($event instanceof self) {
$event->createPackage();
}
}

/**
* Create a package with data json files and with uploaded files
*
* @throws Exception
* @throws \Throwable
* @throws \yii\web\ForbiddenHttpException
*/
private function createPackage()
{
if (!Yii::$app->getModule('legal')->isEnabledExportUserData()) {
return;
}

$packagePath = ExportService::instance($this->user)->getPackagePath();

$exportDirPath = dirname($packagePath);
if (!is_dir($exportDirPath)) {
try {
if (!FileHelper::createDirectory($exportDirPath)) {
return;
}
} catch (Exception $e) {
Yii::error('Cannot create a folder for legal module user export data! ' . $e->getMessage(), 'legal');
return;
}
}

if (file_exists($packagePath)) {
unlink($packagePath);
}

$archive = new ZipArchive();
if (!$archive->open($packagePath, ZipArchive::CREATE)) {
throw new Exception('Error on creating of ZIP archive!');
}

foreach ($this->exportData as $category => $data) {
$archive->addFromString('files/' . $category . '.json', json_encode($data));
}

foreach ($this->exportFiles as $fileName => $sourceFilePath) {
$archive->addFile($sourceFilePath, 'uploads/' . $fileName);
}

$archive->close();
}
}
Loading

0 comments on commit 806e173

Please sign in to comment.