Code Monkey home page Code Monkey logo

laravel-personal-data-export's Introduction

Create zip files containing personal data

Latest Version on Packagist run-tests Check & fix styling Total Downloads

This package makes it easy to let a user download an export containing all the personal data. Such an export consists of a zip file containing all the user properties and related info.

You can create and mail such a zip by dispatching the CreatePersonalDataExportJob job:

// somewhere in your app

use Spatie\PersonalDataExport\Jobs\CreatePersonalDataExportJob;

// ...

dispatch(new CreatePersonalDataExportJob(auth()->user()));

The package will create a zip containing all the personal data. When the zip has been created, a link to it will be mailed to the user. By default, the zips are saved in a non-public location, and the user should be logged in to be able to download the zip.

You can configure which data will be exported in the selectPersonalData method on the user.

// in your User model

public function selectPersonalData(PersonalDataSelection $personalDataSelection): void {
    $personalDataSelection
        ->add('user.json', ['name' => $this->name, 'email' => $this->email])
        ->addFile(storage_path("avatars/{$this->id}.jpg"))
        ->addFile('other-user-data.xml', 's3');
}

You can store files in a directory of the archive. For this, add the directory path as the third parameter.

public function selectPersonalData(PersonalDataSelection $personalDataSelection): void {
    $personalDataSelection
        ->addFile(storage_path("avatars/{$this->id}.jpg"), directory: 'avatars');
}

This package also offers an artisan command to remove old zip files.

Support us

We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.

Installation

You can install the package via composer:

composer require spatie/laravel-personal-data-export

You need to use this macro in your routes file. It'll register a route where users can download their personal data exports.

// in your routes file

Route::personalDataExports('personal-data-exports');

You must add a disk named personal-data-exports to config/filesystems (the name of the disk can be configured in config/personal-data-export). You can use any driver that you want. We recommend that your disk is not publicly accessible. If you're using the local driver, make sure you use a path that is not inside the public path of your app.

// in config/filesystems.php

// ...

'disks' => [

    'personal-data-exports' => [
        'driver' => 'local',
        'root' => storage_path('app/personal-data-exports'),
    ],

// ...

To automatically clean up older personal data exports, you can schedule this command in your console kernel:

// app/Console/Kernel.php

protected function schedule(Schedule $schedule)
{
   $schedule->command('personal-data-export:clean')->daily();
}

Optionally, you can publish the config file with:

php artisan vendor:publish --provider="Spatie\PersonalDataExport\PersonalDataExportServiceProvider" --tag="personal-data-export-config"

This is the content of the config file, which will be published at config/personal-data-export.php:

return [
    /*
     * The disk where the exports will be stored by default.
     */
    'disk' => 'personal-data-exports',

    /*
     * The amount of days the exports will be available.
     */
    'delete_after_days' => 5,

    /*
     * Determines whether the user should be logged in to be able
     * to access the export.
     */
    'authentication_required' => true,

    /*
     * The notification which will be sent to the user when the export
     * has been created.
     */
    'notification' => \Spatie\PersonalDataExport\Notifications\PersonalDataExportedNotification::class,

    /*
     * Configure the queue and connection used by `CreatePersonalDataExportJob`
     * which will create the export.
     */
    'job' => [
        'queue' => null,
        'connection' => null,
    ],
];

Usage

Selecting personal data

First, you'll have to prepare your user model. You should let your model implement the Spatie\PersonalDataExport\ExportsPersonalData interface. This is what that interface looks like:

namespace Spatie\PersonalDataExport;

interface ExportsPersonalData
{
    public function selectPersonalData(PersonalDataSelection $personalDataSelection): void;

    public function personalDataExportName(): string;
}

The selectPersonalData is used to determine the content of the personal download. Here's an example implementation:

// in your user model

public function selectPersonalData(PersonalDataSelection $personalDataSelection): void {
    $personalDataSelection
        ->add('user.json', ['name' => $this->name, 'email' => $this->email])
        ->addFile(storage_path("avatars/{$this->id}.jpg"))
        ->addFile('other-user-data.xml', 's3');
}

$personalData is used to determine the content of the zip file that the user will be able to download. You can call these methods on it:

  • add: the first parameter is the name of the file in the inside the zip file. The second parameter is the content that should go in that file. If you pass an array here, we will encode it to JSON.
  • addFile: the first parameter is a path to a file which will be copied to the zip. You can also add a disk name as the second parameter.

The name of the export itself can be set using the personalDataExportName on the user. This will only affect the name of the download that will be sent as a response to the user, not the name of the zip stored on disk.

// on your user

public function personalDataExportName(): string {
    $userName = Str::slug($this->name);

    return "personal-data-{$userName}.zip";
}

Creating an export

You can create a personal data export by executing this job somewhere in your application:

// somewhere in your app

use Spatie\PersonalDataExport\Jobs\CreatePersonalDataExportJob;

// ...

dispatch(new CreatePersonalDataExportJob(auth()->user()));

By default, this job is queued. It will copy all files and content you selected in the selectPersonalData on your user to a temporary directory. Next, that temporary directory will be zipped and copied over to the personal-data-exports disk. A link to this zip will be mailed to the user.

Securing the export

We recommend that the personal-data-exports disk is not publicly accessible. If you're using the local driver for this disk, make sure you use a path that is not inside the public path of your app.

When the user clicks the download link in the mail that gets sent after creating the export, a request will be sent to underlying PersonalDataExportController. This controller will check if there is a user logged in and if the request personal data zip belongs to the user. If this is the case, that controller will stream the zip to the user.

If you don't want to enforce that a user should be logged in to able to download a personal data export, you can set the authentication_required config value to false. Setting the value to false is less secure because anybody with a link to a zip file will be able to download it, but because the name of the zip file contains many random characters, it will be hard to guess it.

Translating the notification

You need to publish the translations:

php artisan vendor:publish --provider="Spatie\PersonalDataExport\PersonalDataExportServiceProvider" --tag="personal-data-export-translations"

Customizing the mail

You can customize the mailable itself by creating your own mailable that extends Spatie\PersonalDataExport\Notifications\PersonalDataExportedNotification and register the class name of your mailable in the mailable config key of config/personal-data-export.php.

Here is an example:

use Spatie\PersonalDataExport\Notifications\PersonalDataExportedNotification as SpatiePersonalDataExportedNotification;
class PersonalDataExportedNotification extends SpatiePersonalDataExportedNotification
{
    public function via($notifiable)
    {
        return ['mail'];
    }
    public function toMail($notifiable)
    {
        $downloadUrl = route('personal-data-exports', $this->zipFilename);
        if (static::$toMailCallback) {
            return call_user_func(static::$toMailCallback, $notifiable, $downloadUrl);
        }
        return (new MailMessage)
            ->subject(trans('personal-data-exports.notifications.subject'))
            ->line(trans('personal-data-exports.notifications.instructions'))
            ->action(trans('personal-data-exports.notifications.action'), $downloadUrl)
            ->line(trans('personal-data-exports.notifications.deletion_message', ['date' => $this->deletionDatetime->format('Y-m-d H:i:s')]));
    }
}

Then register the class name of your mailable in the mailable config key of config/personal-data-export.php

    /*
     * Something like that
     */
    'notification' => \App\Notifications\PersonalDataExportedNotification::class,

Customizing the queue

You can customize the job that creates the zip file and mails it by extending the Spatie\PersonalDataExport\Jobs\CreatePersonalDataExportJob and dispatching your own custom job class.

use Spatie\PersonalDataExport\Jobs\CreatePersonalDataExportJob;

class MyCustomJobClass extends CreatePersonalDataExportJob
{
    public $queue = 'my-custom-queue';
}
dispatch(new MyCustomJobClass(auth()->user()));

Events

PersonalDataSelected

This event will be fired after the personal data has been selected. It has two public properties:

  • $personalData: an instance of PersonalData. In your listeners you can call the add, addFile methods on this object to add extra content to the zip.
  • $user: the user for which this personal data has been selected.

PersonalDataExportCreated

This event will be fired after the personal data zip has been created. It has two public properties:

  • $zipFilename: the name of the zip filename.
  • $user: the user for which this zip has been created.

PersonalDataExportDownloaded

This event will be fired after the export has been download. It has two public properties:

  • $zipFilename: the name of the zip filename.
  • $user: the user for which this zip has been created.

You could use this event to immediately clean up the downloaded zip.

Testing

You can run all tests by issuing this command:

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you've found a bug regarding security please mail [email protected] instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

laravel-personal-data-export's People

Contributors

adrianmrn avatar alexisserneels avatar arnaudlier avatar bostjanob avatar brendt avatar buismaarten avatar crnkovic avatar ericlun21 avatar freekmurze avatar gummibeer avatar jelleschneiders avatar koenhoeijmakers avatar lambdadigamma avatar laravel-shift avatar lemaur avatar lorenzosapora avatar lucasdcrk avatar mrkriskrisu avatar patinthehat avatar pelmered avatar quentingab avatar richardkeep avatar rubenvanassche avatar sebastiaanluca avatar vishalinfyom avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-personal-data-export's Issues

Files added using addFile() with disk parameter are not included in final zip

This issue is debugged and fixed in my PR #19

The tests were green because selectPersonalData in the User test Model was not using the optional disk parameter and then not testing against copyFileFromDisk method.
I've improved the test to cover this case scenario.

You can recreate the issue by removing $this->files[] = $pathInTemporaryDirectory; (line 88) from PersonalDataSelection.php from my PR and the test will fail.

Not working when "authentication_required" is true

When is set authentication_required to true on the config, it always returns forbidden. Even when the user is logged in. I've dump EnsureAuthorizedToDownload Middleware, and it can't access current user instance.

I've tried so many ways and can't get it working with authorization, only works when disabled.

I'm using Laravel 8.

Thanks,
Miguel

Queue job should use default Job traits

By default a Laravel Job uses Dispatchable, InteractsWithQueue, Queueable, SerializesModels.
The queue job provided by the package should use all of them.

  • Dispatchable: will allow to let the job dispatch itself
  • InteractsWithQueue: is optional sugar on the cake
  • Queueable: will allow to define the queue and connection of the job
  • SerializesModels: should be required because the job accepts and handles an eloquent model

Error when creating new export

Hello,

I'm getting the following error if i try to run
dispatch(new CreatePersonalDataExportJob(auth()->user()));

[2020-09-04 15:25:12] local.ERROR: method_exists(): The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "Spatie\PersonalDataExport\Jobs\CreatePersonalDataExportJob" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide an autoloader to load the class definition {"exception":"[object] (Symfony\\Component\\ErrorHandler\\Error\\FatalError(code: 0): method_exists(): The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition \"Spatie\\PersonalDataExport\\Jobs\\CreatePersonalDataExportJob\" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide an autoloader to load the class definition at /Users/felixschmid/sites/freelancer-crm/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php:173) [stacktrace] #0 {main} "}

My user class implements ExportsPersonalData.

Best regards

Usage instructions not working

Laravel: 5.8~
PHP: 7.3~
Nova: 2.6.1

I'm stuck on this section.

  • Added the routes to my routes/web.php
<?php
// Authentication Routes
Auth::routes(['verify' => true]);
Route::personalDataExports('personal-data-exports');
[...]
  • Added filesystem to config/filesystems.php
    'disks' => [
[...]
        'personal-data-exports' => [
            'driver' => 'local',
            'root' => storage_path('app/personal-data-exports'),
        ],
    ],
  • Added scheduler

  • Added to App\Models\User.php

<?php
namespace App\Models;
[...]
use Spatie\PersonalDataExport\ExportsPersonalData;

class User extends Authenticatable implements MustVerifyEmail, ExportsPersonalData
{
[...]
public function selectPersonalData(PersonalDataSelection $personalData): void {
    $personalData
        ->add('user.json', ['name' => $this->name, 'email' => $this->email])
        ->addFile(storage_path("avatars/{$this->id}.jpg"))
        ->addFile('other-user-data.xml', 's3');
}
public function personalDataExportName(string $realFilename): string {
    $userName = Str::slug($this->name);

    return "personal-data-{$userName}.zip";
}
}

At this point, it errors

Declaration of App\Models\User::selectPersonalData(App\Models\PersonalDataSelection $personalData): void must be compatible with Spatie\PersonalDataExport\Expor` 

And I'm not sure how to progress.

However, I do continue the usage instructions, just in case it requires something in a later step.

In a controller App\Http\Controllers\UserController.php

    public function requestData()
    {
        dispatch(new CreatePersonalDataExportJob(auth()->user());
        return view('user.yourdata');
    }

But I can't seem to shake that void error above.

Views don't exist

Hello,

I get many errors with this package that say views could not be found.

Is it possible that this is because you switched to notifications but the views still referenced in the service provider?

Errors is:
local.ERROR: Uncaught Symfony\Component\Finder\Exception\DirectoryNotFoundException: The "/Users/user/Code/app/vendor/spatie/laravel-personal-data-export/src/../resources/views" directory does not exist.

It spams the log file with many entries per second. Can you also please see your log file because the application does not crash - it is a silent error

Not uuid compatible

The package is not uuid compatible.

Because you’re exploding the zip file name on the - character to get the user id from the url to check if the user is authorized.

So if you’re using uuid, it will always return a authorized false to the user.

Dispatch won't work

Argument 1 passed to Spatie\PersonalDataExport\Jobs\CreatePersonalDataExportJob::__construct() must be an instance of Spatie\PersonalDataExport\ExportsPersonalData, instance of App\User given, called in /app/Http/Controllers/ExportController.php on line 17

I'm dispatching it as follow:
dispatch(new CreatePersonalDataExportJob(auth()->user()));

PHP Version issue

Hi! I'd love to use this package. However, when installing, I get the following error from composer:

Could not find package spatie/laravel-personal-data-export at any version matching your PHP version 7.1.8.0

Should my php version be even higher than PHP 7.1?

Empty zips on my windows dev box

Hi,

First of all thanks for this awesome package.
I have an issue though I have no clue where to look into further.

I'm developing with phpstorm on my windows 11 machine. For some reason the zip files end up empty.
Eventually I've noticed it is creating a folder in C:\Users******\AppData\Local\Temp\ with the files in it.

So I've pushed a test to my test installation of my project on my linux webserver. And everything ended up working as expected.

Do you have any idea why this isn't properly functioning on Windows?

Laravel 10.7.1
Spatie/laravel-personal-data-export 4.2.1

filesystems.php

        'personal-data-exports' => [
            'driver' => 'local',
            'root' => storage_path('app/personal-data-exports'),
        ],

Many thanks

Notification translation : Consider date parameter in transalation string

Version : 2.0.2

The last line of the notification can't be translated because it is concatained with the deletion date :

return (new MailMessage)
            ->subject(Lang::get('Personal Data Download'))
            ->line(Lang::get('Please click the button below to download a zip file containg all data we got for your account.'))
            ->action(Lang::get('Download Zip File'), $downloadUrl)
            ->line(Lang::get('This file will be deleted at ' . $this->deletionDatetime->format('Y-m-d H:i:s') . '.'));

You should use parameter for the date so we can really translate the mail.

Laravel 5.7 Support

Hi,
This package doesn't support Laravel 5.7.
Is there any way I can use this in my 5.7 setup as my production environment uses 5.7.

Invokable controller not working as intended.

After defining the required route macro, you get the following error.

Invalid route action: [App\Http\Controllers\Spatie\PersonalDataExport\Http\Controllers\PersonalDataExportController].

I'm not sure if I'm missing anything, but it seems like invokable controllers just do not work.

If I modify the macro directly to use a defined method, everything works as intended and I no longer get the error.

        Route::macro('personalDataExports', function (string $url) {
            Route::get("$url/{zipFilename}", 'Spatie\PersonalDataExport\Http\Controllers\PersonalDataExportController@download')
                ->name('personal-data-exports');
        });

Obsolete Mail documentation

Version : 2.0.2

The documentation suggest to customize Mail notification by extending \Spatie\PersonalDataExport\Mail\PersonalDataExportCreatedMail but actually it is Spatie\PersonalDataExport\Notifications\PersonalDataExportedNotification that has to be extended.

use signed URL by default

For API driven Apps the current authentication/authorization middleware doesn't work because the link in email can't provide a Bearer token. But the links still shouldn't be fully public.
A nice and easy solution would be a temporary signed URL.

// mail.blade.php
\Illuminate\Support\Facades\URL::temporarySignedRoute('personal-data-exports', $deletionDatetime, ['zipFilename' => $zipFilename]);

// PersonalDataExportServiceProvider
Route::get("$url/{zipFilename}", [PersonalDataExportController::class, 'export'])
    ->middleware('signed')
    ->name('personal-data-exports');

This way there isn't any need for a session/cookie anymore and the export is still protected.

I can't create download in a specific instance

Hi, I get this error: 'Spatie\PersonalDataExport\Exceptions\InvalidUser
Could not create a personal data download for App\Models\User because it does have an email property.' when I call the create personal data export job. My user model already implements the ExportsPersonalData trait and I called the dispatch job like this: return dispatch(new CreatePersonalDataExportJob($user)); I get the same error with a controller or a closure.

This is my closure: Route::middleware(['auth:sanctum', 'verified'])->post('/user/export-personal-data', function (User $user) {
return dispatch(new CreatePersonalDataExportJob($user));
})->name('user.export-personal-data');

When I use: return dispatch(new CreatePersonalDataExportJob(auth()->user())); I get 'Expected type 'Spatie\PersonalDataExport\ExportsPersonalData'. Found 'Illuminate\Contracts\Auth\Authenticable|null', and a different error message.

Interestingly, when I call: return dispatch(new CreatePersonalDataExportJob($user)); in an existing Jetstream DeleteUser class, I get the email with the zip file and in storage.

Thanks.

Laravel ^6.0 support

Wouldn't it have to be written?
Laravel 6.0.3 is now live.

        "illuminate/filesystem": "^5.8.3|^6.0",
        "illuminate/queue": "^5.8.3|^6.0",
        "illuminate/support": "^5.8.3|^6.0",

Corrupted (.pcgz) file is generated when opening the downloaded .zip file

Discussed in #61

Originally posted by hazem-taha June 24, 2021
Hello,

After clicking the downloaded .zip file, that file is generated as per the below screenshot..
image

this is the implemented methods in my model class..

    public function selectPersonalData(PersonalDataSelection $personalData): void
    {
        $personalData
            ->add('user.json', [
             'name' => $this->name,
            'email' => $this->email,
            ]);
    }

    public function personalDataExportName(): string {
        $userName = Str::slug($this->name);

        return "personal-data-{$userName}.zip";
    }

I'm using Laravel v8.20.1

my config/personal-data-export.php file..

<?php

return [
    /*
     * The disk where the exports will be stored by default.
     */
    'disk' => 'personal-data-exports',

    /*
     * The amount of days the exports will be available.
     */
    'delete_after_days' => 5,

    /*
     * Determines whether the user should be logged in to be able
     * to access the export.
     */
    'authentication_required' => false,

    /*
     * The mailable which will be sent to the user when the export
     * has been created.
     */
    'mailable' => \Spatie\PersonalDataExport\Mail\PersonalDataExportCreatedMail::class,

    /*
     * Configure the queue and connection used by `CreatePersonalDataExportJob`
     * which will create the export.
     */
    'job' => [
        'queue' => null,
        'connection' => null,
    ],
];

I found something weird, the files generated inside storage/app/personal-data-exports are valid and correct but when they are downloaded, they become corrupted

Thanks in advance.

rmdir(C:\\Windows\\TEMP\\189362675-0830427001568696710): Directory not empty

while dispatching dispatch(new CreatePersonalDataExportJob($user)); error occurs from line
$temporaryDirectory->delete();

 public function handle()
    {
        $temporaryDirectory = (new TemporaryDirectory())->create();

        $personalDataSelection = $this->selectPersonalData($temporaryDirectory);

        event(new PersonalDataSelected($personalDataSelection, $this->user));

        $zipFilename = $this->zipPersonalData($personalDataSelection, $this->getDisk(), $temporaryDirectory);

        $temporaryDirectory->delete();

        event(new PersonalDataExportCreated($zipFilename, $this->user));

        $this->mailZip($zipFilename);
    }

Class PersonalDataExportController does not exist

The error is debugged and fixed in PR #17

Currently, when trying to download the archive, it creates a App\Http\Controllers\Spatie\PersonalDataExport\Http\Controllers\PersonalDataExportController does not exist error.

Please see more information in the PR.

How to export all fields in a table?

Hi,

Just wanted to know How to export all fields in a table instead of individual fields?

Eg:

public function selectPersonalData(PersonalDataSelection $personalDataSelection) {
    $personalDataSelection
        ->add('user.json', ['*'])
        ->addFile(storage_path("avatars/{$this->id}.jpg")
        ->addFile('other-user-data.xml', 's3'));
}

I tried it but failed.

Thanks,

Corrupted (.pcgz) file is generated when opening the downloaded .zip file

Hello,

After clicking the downloaded .zip file, that file is generated as per the below screenshot..
image

this is the implemented methods in my model class..

    public function selectPersonalData(PersonalDataSelection $personalData): void
    {
        $personalData
            ->add('user.json', [
             'name' => $this->name,
            'email' => $this->email,
            ]);
    }

    public function personalDataExportName(): string {
        $userName = Str::slug($this->name);

        return "personal-data-{$userName}.zip";
    }

I'm using Laravel v8.20.1

Thanks in advance.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.