How to Download, Delete and Anonymize Customer Data in Magento 2 GDPR

Plumrocket GDPR Magento 2 extension allows you to manage any third-party data stored in your Magento database. Use this manual to make your third-party extension GDPR compliant. Contact vendors of the third-party extensions you are using, and ask them if their extensions record any personal customer data in your Magento database. If they collect any personal data, they may become compliant by integrating their plugins with Plumrocket GDPR Extension using the provided API.

Export, Delete or Anonymize Third-Party Data in 2 Simple Steps

  1. Create or edit Dependency Injection (di.xml) file for your third-party plugin.
  2. Create GDPR model in the third-party plugin to manage your customer data.

Both steps are described with examples below.

Step 1. Edit Dependency Injection (di.xml) file

To export or anonymize data, you must first create or edit Dependency Injection XML file (di.xml): Open the file:

/app/code/VendorName/ModuleName/etc/di.xml

and add the code as shown below (download the template here):

<type name="PlumrocketGDPRModelAccountProcessor">
    <arguments>
        <argument name="processors" xsi:type="array">
            <item name="VendorName_ModuleName"
                xsi:type="object">VendorNameModuleNameModelGDPRProcessorName</item>
        </argument>
    </arguments>
</type>
<type name="VendorNameModuleNameModelGDPRProcessorName">
    <arguments>
        <argument name="dataExport" xsi:type="array">
            <item name="db_field_name1" xsi:type="string" translatable="true">
                Field Alias 1</item>
            <item name="db_field_name2" xsi:type="string" translatable="true">
                Field Alias 2</item>
        </argument>

        <argument name="dataAnonymize" xsi:type="array">
            <item name="db_field_name3" xsi:type="null"/>
            <item name="db_field_name4" xsi:type="string">anonymousString</item>
            <item name="db_field_name5" xsi:type="string">anonymousEmail</item>
            <item name="db_field_name6" xsi:type="number">0</item>
       </argument>
    </arguments>
</type>

Where:

“dataExport” argument describes the data that should be exported:

  • Field “db_field_name1” will be named in CSV file as “Field Alias 1”
  • Field “db_field_name2” will be named in CSV file as “Field Alias 2”

“dataAnonymize” argument describes the data that should be anonymized:

  • Field “db_field_name3” will have value “null” after the anonymization process
  • Field “db_field_name4” will be replaced by the value of “anonymousString”. This constant is generated automatically using the following format: [Anonymization Key] + “-” + [Customer Id]
  • Field “db_field_name5” will be replaced by the value of “anonymousEmail”. This constant is generated automatically using the following format: [Anonymization Key] + “-” + “@example.com”;
  • Field “db_field_name6” will have value “0”

Please note: the “Anonymization Key” can be set in Magento System Configuration -> GDPR -> Account Removal Settings -> Anonymization Key

In this first step we can only specify which data to export or anonymize. The process of deleting data will be described in the next step.

Step 2. Create GDPR Model

In the second step we must create GDPR model file that should process all data (export / anonymize / delete) listed in the first step.

Create file:

VendorNameModuleNameModelGDPRProcessorName.php

and add the code as shown below (or download the template here.):

<?php

namespace VendorNameModuleNameModelGDPR;

use PlumrocketGDPRApiDataProcessorInterface;
use PlumrocketGDPRHelperCustomerData;
use MagentoCustomerApiDataCustomerInterface;
use VendorNameModuleNameModelResourceModelDataModelNameCollectionFactory;

/**
 * Class ProcessorName
 * @package VendorNameModuleNameModelGDPR
 */
class ProcessorName implements DataProcessorInterface
{
    /**
     * @var CustomerData
     */
    protected $customerData;
    /**
     * @var CollectionFactory
     */
    private $collectionFactory;
    /**
     * ProcessorName constructor.
     *
     * @param CustomerData $customerData
     * @param CollectionFactory $collectionFactory
     * @param array $dataExport
     * @param array $dataAnonymize
     */
    public function __construct(
        CustomerData $customerData,
        CollectionFactory $collectionFactory,
        array $dataExport = [],
        array $dataAnonymize = []
    ) {
        $this->customerData = $customerData;
        $this->collectionFactory = $collectionFactory;
        $this->dataExport = $dataExport;
        $this->dataAnonymize = $dataAnonymize;
    }

    /**
     * This method is used to export customer data based on arguments defined in di.xml file
     *
     * Expected return structure:
     * array(
     * array('HEADER1', 'HEADER2', 'HEADER3', ...),
     * array('VALUE1', 'VALUE2', 'VALUE3', ...),
     * ...
     * )
     *
     * @param CustomerInterface $customer
     *
     * @return array
     */
    public function export(CustomerInterface $customer)
    {
        $customerId = $customer->getId();
        // Retrieve your customer data from database
        $collection = $this->collectionFactory->create()->addFieldToFilter('customer_id', $customerId);
        $returnData = [];
        $i = 0;

        if (!$collection->getSize()) {
            return null;
        }

        foreach ($this->dataExport as $key => $title) {
            $returnData[$i][] = $title;
        }
        $i++;

        foreach ($collection as $item) {
            $itemData = $item->getData();
            foreach ($this->dataExport as $key => $title) {
                $returnData[$i][] = (isset($itemData[$key]) ? $itemData[$key] : '');
            }
            $i++;
        }

        return $returnData;
    }
    /**
     * Executed upon customer data deletion.
     *
     * @param CustomerInterface $customer
     *
     * @return void
     * @throws Exception
     */
    public function delete(CustomerInterface $customer)
    {
        $customerId = $customer->getId();
        // Retrieve your customer data from database
        $collection = $this->collectionFactory->create()
            ->addFieldToFilter('customer_id', $customerId);
        if (!$collection->getSize()) {
            return;
        }
        $collection->walk('delete');
    }
    /**
     * Executed upon customer data anonymization.
     *
     * @param CustomerInterface $customer
     *
     * @return void
     * @throws Exception
     */
    public function anonymize(CustomerInterface $customer)
    {
        $customerId = $customer->getId();
        // Retrieve your customer data from database
        $collection = $this->collectionFactory->create()
            ->addFieldToFilter('customer_id', ['eq' => $customerId]);

        $dataAnonymized = $this->customerData->getDataAnonymized($this->dataAnonymize, $customerId);
        if (!empty($dataAnonymized) && $collection->getSize()) {
            $collection > setDataToAll($dataAnonymized)->save();
        }
    }
}

Example

This example is based on Free Plumrocket Twitter & Facebook Login Extension for Magento 2. You can download it from our store for your reference.

Step 1. “di.xml” file of Plumrocket Twitter & Facebook Login Extension

In di.xml file of Plumrocket Twitter & Facebook Login we are specifying which data must be exported and anonymized. While exporting you can define user friendly names for your database fields. In this example, database field “type” will be renamed in CSV file to “Network”, “user_id” will become “User Id”, “image” => “Account Photo Url”, “additional” => “Additional Data”.

File path:

/app/code/Plumrocket/SocialLoginFree/etc/di.xml
<type name="PlumrocketGDPRModelAccountProcessor">
    <arguments>
         <argument name="processors" xsi:type="array">
             <item name="Plumrocket_SocialLoginFree" xsi:type="object">
                 PlumrocketSocialLoginFreeModelGDPRProcessorSocialLogin</item>
         </argument>
    </arguments>
</type>

<type name="PlumrocketSocialLoginFreeModelGDPRProcessorSocialLogin">
    <arguments>
        <argument name="dataExport" xsi:type="array">
            <item name="type" xsi:type="string" translatable="true">Network</item>
            <item name="user_id" xsi:type="string" translatable="true">User Id</item>
            <item name="image" xsi:type="string" translatable="true">
                Account Photo Url</item>
            <item name="additional" xsi:type="string" translatable="true">
                Additional Data</item>
        </argument>
        <argument name="dataAnonymize" xsi:type="array">
            <item name="user_id" xsi:type="string">anonymousString</item>
        </argument>
    </arguments>
</type>

Step 2. GDPR Model of Plumrocket Twitter & Facebook Login Extension

Below is a GDPR Model of Plumrocket Twitter & Facebook Login Extension for Magento 2. Please note that in this example we only delete and export data as per customer request. The process of anonymization is not executed unless you uncomment the code in function “delete(CustomerInterface $customer)”.

File Path:

/app/code/Plumrocket/SocialLoginFree/Model/GDPR/ProcessorSocialLogin.php
<?php

/**
 * Plumrocket Inc.
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the End-user License Agreement
 * that is available through the world-wide-web at this URL:
 * http://wiki.plumrocket.net/wiki/EULA
 * If you are unable to obtain it through the world-wide-web, please
 * send an email to support@plumrocket.com so we can send you a copy immediately.
 *
 * @package Plumrocket_SocialLoginFree
 * @copyright Copyright (c) 2018 Plumrocket Inc. (http://www.plumrocket.com)
 * @license http://wiki.plumrocket.net/wiki/EULA End-user License Agreement
 */

namespace PlumrocketSocialLoginFreeModelGDPR;

use PlumrocketGDPRApiDataProcessorInterface;
use PlumrocketGDPRHelperCustomerData;
use PlumrocketSocialLoginFreeModelResourceModelAccountCollectionFactory;
use PlumrocketSocialLoginFreeHelperData;
use MagentoCustomerApiDataCustomerInterface;
use MagentoFrameworkFilesystem;
use MagentoFrameworkFilesystemDriverFile;
use MagentoFrameworkAppFilesystemDirectoryList;

/**
 * Processor SocialLogin.
 */
class ProcessorSocialLogin implements DataProcessorInterface
{

    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;

    /**
     * @var Data
     */
    protected $helper;

    /**
     * @var CustomerData
     */
    protected $customerData;

    /**
     * @var Filesystem
     */
    protected $filesystem;

    /**
     * @var File
     */
    protected $file;

    /**
     * @var array
     */
    protected $dataExport;

    /**
     * @var array
     */
    protected $dataAnonymize;

    /**
     * ProcessorSocialLogin constructor.
     *
     * @param CollectionFactory $collectionFactory
     * @param Data $helper
     * @param array $dataExport
     * @param array $dataAnonymize

     */
    public function __construct(
        CollectionFactory $collectionFactory,
        Data $helper,
        CustomerData $customerData,
        Filesystem $filesystem,
        File $file,
        array $dataExport = [],
        array $dataAnonymize = []

    ) {
        $this->collectionFactory = $collectionFactory;
        $this->helper = $helper;
        $this->customerData = $customerData;
        $this->filesystem = $filesystem;
        $this->file = $file;
        $this->dataExport = $dataExport;
        $this->dataAnonymize = $dataAnonymize;
    }

    /**
     * Executed upon exporting customer data.
     *
     * Expected return structure:
     * array(
     * array('HEADER1', 'HEADER2', 'HEADER3', ...),
     * array('VALUE1', 'VALUE2', 'VALUE3', ...),
     * ...
     * )
     *
     * @param CustomerInterface $customer
     *
     * @return array
     * @throws MagentoFrameworkExceptionNoSuchEntityException
     */
    public function export(CustomerInterface $customer)
    {
        $customerId = $customer->getId();
        $collection = $this->collectionFactory->create()->addFieldToFilter(
            'customer_id', $customerId);
        $returnData = [];
        $i = 0;

        if (!$collection->getSize()) {
            return;
        }

        foreach ($this->dataExport as $key => $title) {
            $returnData[$i][] = $title;
        }
        $i++;

        foreach ($collection as $item) {
            $itemData = $item->getData();
            $itemData['image'] = $this->helper->getPhotoPath(
                false, $customerId, $itemData['type']);

            foreach ($this->dataExport as $key => $title) {
                $returnData[$i][] = (isset($itemData[$key]) ? $itemData[$key] : '');
            }
            $i++;
        }

        return $returnData;
    }

    /**
     * Executed upon customer data deletion.
     *
     * @param CustomerInterface $customer
     *
     * @return void
     * @throws Exception
     */
    public function delete(CustomerInterface $customer)
    {
        // If you wish to anonymize the data instead if deleting it, please uncomment the line below:
        // $this->anonymize($customer);
        // and comment out the rest of the code in this function

        $customerId = $customer->getId();
        $collection = $this->collectionFactory->create()
            ->addFieldToFilter('customer_id', $customerId);
        $mediaRootDir = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)
            ->getAbsolutePath();
        $fileExt = PlumrocketSocialLoginProModelAccount::PHOTO_FILE_EXT;
        $DS = DIRECTORY_SEPARATOR;

        if (!$collection->getSize()) {
            return;
        }

        foreach ($collection as $item) {
            $path = 'pslogin' . $DS . 'photo' . $DS . $item['type'] . $DS .
                $customerId . '.' . $fileExt;
            if ($this->file->isExists($mediaRootDir . $path)) {
                $this->file->deleteFile($mediaRootDir . $path);
            }
        }

        $path = 'pslogin' . $DS . 'photo' . $DS . $customerId . '.' . $fileExt;
        if ($this->file->isExists($mediaRootDir . $path)) {
            $this->file->deleteFile($mediaRootDir . $path);
        }

        $collection->walk('delete');
    }

    /**
     * Executed upon customer data anonymization.
     * This function must be called from the method “delete()” - please read comments above.
     * @param CustomerInterface $customer
     *
     * @return void
     * @throws Exception
     */
    public function anonymize(CustomerInterface $customer)
    {
        $customerId = $customer->getId();
        $collection = $this->collectionFactory->create()->addFieldToFilter(
            'customer_id', ['eq' => $customerId]);
        $dataAnonymized = $this->customerData->getDataAnonymized(
            $this->dataAnonymize, $customerId);
        if (!empty($dataAnonymized) && $collection->getSize()) {
            $collection->setDataToAll($dataAnonymized)->save();
        }
    }
}

Was this article helpful?