Developer Notes
Module Architecture
The GDPR Compliance Suite consists of 21 independent modules organized into two main categories:
- 15 Core Modules - Essential GDPR functionality for all Magento installations
- 6 Hyvä Compatibility Modules - Native Hyvä theme integration
Module Dependencies
GdprAdmin (base - required by all)
├── GdprConsent
│ └── GdprConsentHyva
├── GdprCookie
│ ├── GdprCookieHyva
│ └── GdprCookieTemplates
├── GdprDataSubject
│ ├── GdprDataSubjectHyva
│ └── GdprAutomation
├── GdprPrivacyCenter
│ └── GdprPrivacyCenterHyva
├── GdprPolicy
│ └── GdprPolicyHyva
├── GdprAnalytics
├── GdprGtm
├── GdprMarketing
├── GdprApi
├── GdprGeo
├── GdprIntegration
└── GdprFrontend
└── GdprFrontendHyva
GdprConsent Module
Database Schema
Table: qoliber_gdpr_consent_definition
Stores consent type definitions.
| Column | Type | Description |
|---|---|---|
consent_id |
INT UNSIGNED | Primary key |
code |
VARCHAR(100) | Unique consent code |
title |
VARCHAR(255) | Consent title |
description |
TEXT | Consent description |
checkbox_text |
TEXT | Checkbox label text |
display_type |
VARCHAR(50) | Display type (text, cms_page_link) |
link_url |
VARCHAR(500) | Link URL (optional) |
cms_page_id |
INT UNSIGNED | CMS page ID (optional) |
link_target |
VARCHAR(20) | Link target (_self, _blank, modal) |
is_required |
BOOLEAN | Is consent required |
is_enabled |
BOOLEAN | Is consent enabled |
form_locations |
TEXT | Form locations (JSON array) |
sort_order |
INT UNSIGNED | Display sort order |
consent_type |
VARCHAR(50) | Consent type (checkbox, implicit) |
validation_rules |
TEXT | Validation rules (JSON) |
created_at |
TIMESTAMP | Creation timestamp |
updated_at |
TIMESTAMP | Update timestamp |
Indexes:
- PRIMARY KEY (consent_id)
- UNIQUE KEY (code)
- INDEX (is_enabled)
- INDEX (sort_order)
Table: qoliber_gdpr_consent_store_label
Multi-language support for consent labels.
| Column | Type | Description |
|---|---|---|
label_id |
INT UNSIGNED | Primary key |
consent_id |
INT UNSIGNED | Foreign key to consent_definition |
store_id |
SMALLINT UNSIGNED | Foreign key to store |
title |
VARCHAR(255) | Store-specific title |
description |
TEXT | Store-specific description |
checkbox_text |
TEXT | Store-specific checkbox text |
Foreign Keys:
- consent_id → qoliber_gdpr_consent_definition.consent_id (CASCADE)
- store_id → store.store_id (CASCADE)
Indexes:
- UNIQUE KEY (consent_id, store_id)
Table: qoliber_gdpr_consent_customer_log
Logs all consent actions by customers.
| Column | Type | Description |
|---|---|---|
log_id |
INT UNSIGNED | Primary key |
consent_id |
INT UNSIGNED | Foreign key to consent_definition |
consent_code |
VARCHAR(100) | Consent code (preserved if definition deleted) |
consent_snapshot |
TEXT | Full consent data snapshot (JSON) |
customer_id |
INT UNSIGNED | Customer ID (null for guests) |
guest_email |
VARCHAR(255) | Guest email |
form_location |
VARCHAR(100) | Form location identifier |
is_accepted |
BOOLEAN | Acceptance status |
ip_address |
VARCHAR(45) | IP address |
user_agent |
TEXT | Browser user agent |
store_id |
SMALLINT UNSIGNED | Store ID |
accepted_at |
TIMESTAMP | When consent was given |
revoked_at |
TIMESTAMP | When consent was revoked |
order_entity_id |
INT UNSIGNED | Associated order ID |
additional_data |
TEXT | Additional data (JSON) |
Foreign Keys:
- consent_id → qoliber_gdpr_consent_definition.consent_id (SET NULL)
- customer_id → customer_entity.entity_id (SET NULL)
- store_id → store.store_id (CASCADE)
- order_entity_id → sales_order.entity_id (SET NULL)
Indexes:
- INDEX (customer_id)
- INDEX (guest_email)
- INDEX (consent_code)
- INDEX (accepted_at)
- INDEX (order_entity_id)
Preferences
<!-- Repository Interface -->
<preference for="Qoliber\GdprConsent\Api\ConsentDefinitionRepositoryInterface"
type="Qoliber\GdprConsent\Model\ConsentDefinitionRepository"/>
<!-- Data Interface -->
<preference for="Qoliber\GdprConsent\Api\Data\ConsentDefinitionInterface"
type="Qoliber\GdprConsent\Model\ConsentDefinition"/>
<!-- Search Results -->
<preference for="Qoliber\GdprConsent\Api\Data\ConsentDefinitionSearchResultsInterface"
type="Qoliber\GdprConsent\Model\ConsentDefinitionSearchResults"/>
<!-- Consent Log Interfaces -->
<preference for="Qoliber\GdprConsent\Api\ConsentLogRepositoryInterface"
type="Qoliber\GdprConsent\Model\ConsentLogRepository"/>
<preference for="Qoliber\GdprConsent\Api\Data\ConsentLogInterface"
type="Qoliber\GdprConsent\Model\ConsentLog"/>
Virtual Types
<!-- Grid Collection for Admin Listing -->
<virtualType name="Qoliber\GdprConsent\Model\ResourceModel\ConsentDefinition\Grid\Collection"
type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
<arguments>
<argument name="mainTable" xsi:type="string">qoliber_gdpr_consent_definition</argument>
<argument name="resourceModel" xsi:type="string">Qoliber\GdprConsent\Model\ResourceModel\ConsentDefinition</argument>
</arguments>
</virtualType>
<!-- Consent Log Grid Collection -->
<virtualType name="Qoliber\GdprConsent\Model\ResourceModel\ConsentCustomerLog\Grid\Collection"
type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
<arguments>
<argument name="mainTable" xsi:type="string">qoliber_gdpr_consent_customer_log</argument>
<argument name="resourceModel" xsi:type="string">Qoliber\GdprConsent\Model\ResourceModel\ConsentCustomerLog</argument>
</arguments>
</virtualType>
Plugins
None. The module uses event observers and service classes.
Console Commands
Command: gdpr:consent:list
Class: Qoliber\GdprConsent\Console\Command\ListConsentsCommand
Purpose: List all consent definitions
GdprCookie Module
Database Schema
Table: qoliber_gdpr_cookie_category
Stores cookie categories (Essential, Analytics, Marketing, Functional).
| Column | Type | Description |
|---|---|---|
category_id |
INT UNSIGNED | Primary key |
code |
VARCHAR(100) | Unique category code |
name |
VARCHAR(255) | Category name |
description |
TEXT | Category description |
is_required |
BOOLEAN | Is category essential |
is_enabled |
BOOLEAN | Is category enabled |
sort_order |
INT UNSIGNED | Display order |
created_at |
TIMESTAMP | Creation timestamp |
updated_at |
TIMESTAMP | Update timestamp |
Table: qoliber_gdpr_cookie_detail
Stores individual cookie information.
| Column | Type | Description |
|---|---|---|
cookie_id |
INT UNSIGNED | Primary key |
category_id |
INT UNSIGNED | Foreign key to cookie_category |
name |
VARCHAR(255) | Cookie name |
provider |
VARCHAR(255) | Cookie provider/service |
purpose |
TEXT | Cookie purpose description |
expiry |
VARCHAR(100) | Cookie expiry period |
type |
VARCHAR(50) | Cookie type (session, persistent) |
created_at |
TIMESTAMP | Creation timestamp |
updated_at |
TIMESTAMP | Update timestamp |
Foreign Keys:
- category_id → qoliber_gdpr_cookie_category.category_id (CASCADE)
Preferences
<preference for="Qoliber\GdprCookie\Api\CookieCategoryRepositoryInterface"
type="Qoliber\GdprCookie\Model\CookieCategoryRepository"/>
<preference for="Qoliber\GdprCookie\Api\Data\CookieCategoryInterface"
type="Qoliber\GdprCookie\Model\CookieCategory"/>
<preference for="Qoliber\GdprCookie\Api\CookieDetailRepositoryInterface"
type="Qoliber\GdprCookie\Model\CookieDetailRepository"/>
<preference for="Qoliber\GdprCookie\Api\Data\CookieDetailInterface"
type="Qoliber\GdprCookie\Model\CookieDetail"/>
GdprDataSubject Module
Database Schema
Table: qoliber_gdpr_requests
Unified table for all GDPR data requests.
| Column | Type | Description |
|---|---|---|
request_id |
INT UNSIGNED | Primary key |
customer_id |
INT UNSIGNED | Customer ID (null for guests) |
customer_email |
VARCHAR(255) | Customer email |
request_type |
VARCHAR(50) | Request type (fetch_data, anonymize, delete, rectification) |
request_source |
VARCHAR(50) | Request source (customer, guest, admin) |
status |
VARCHAR(50) | Status (pending, processing, completed, failed, expired) |
request_data |
TEXT | Request data (JSON) |
response_data |
TEXT | Response data (JSON) |
is_blocked |
BOOLEAN | Whether request is blocked |
blocked_reason |
VARCHAR(255) | Reason for blocking |
blocked_at |
TIMESTAMP | When blocked |
blocked_by |
VARCHAR(255) | Admin who blocked |
token |
VARCHAR(255) | Verification token for guests |
ip_address |
VARCHAR(45) | IP address |
user_agent |
TEXT | User agent |
admin_user |
VARCHAR(255) | Admin user (if admin action) |
store_id |
INT UNSIGNED | Store ID |
email_sent_at |
TIMESTAMP | Email sent timestamp |
accessed_at |
TIMESTAMP | When data was accessed |
verified_at |
TIMESTAMP | Email verification timestamp |
reviewed_at |
TIMESTAMP | When reviewed by admin |
reviewed_by |
VARCHAR(255) | Admin who reviewed |
created_at |
TIMESTAMP | Creation timestamp |
updated_at |
TIMESTAMP | Update timestamp |
completed_at |
TIMESTAMP | Completion timestamp |
expires_at |
TIMESTAMP | Expiration timestamp |
Indexes:
- INDEX (customer_id)
- INDEX (customer_email)
- INDEX (request_type)
- INDEX (status)
- INDEX (token)
- INDEX (created_at)
- INDEX (expires_at)
Table: qoliber_gdpr_request_notes
Internal notes for GDPR requests.
| Column | Type | Description |
|---|---|---|
note_id |
INT UNSIGNED | Primary key |
request_id |
INT UNSIGNED | Foreign key to gdpr_requests |
note_type |
VARCHAR(50) | Note type (internal, customer, system, compliance) |
note_content |
TEXT | Note content |
created_by |
VARCHAR(255) | Admin username |
created_at |
TIMESTAMP | Creation timestamp |
Foreign Keys:
- request_id → qoliber_gdpr_requests.request_id (CASCADE)
Table: qoliber_gdpr_email_queue
Email queue for asynchronous email processing.
| Column | Type | Description |
|---|---|---|
queue_id |
INT UNSIGNED | Primary key |
request_id |
INT UNSIGNED | Foreign key to gdpr_requests |
email_type |
VARCHAR(50) | Email template type |
recipient_email |
VARCHAR(255) | Recipient email |
recipient_name |
VARCHAR(255) | Recipient name |
status |
VARCHAR(50) | Status (pending, processing, sent, failed) |
template_vars |
TEXT | Template variables (JSON) |
retry_count |
INT UNSIGNED | Number of retry attempts |
error_message |
TEXT | Error message if failed |
scheduled_at |
TIMESTAMP | When scheduled |
sent_at |
TIMESTAMP | When sent |
created_at |
TIMESTAMP | Creation timestamp |
Foreign Keys:
- request_id → qoliber_gdpr_requests.request_id (CASCADE)
Indexes:
- INDEX (status)
- INDEX (scheduled_at)
GdprPolicy Module
Database Schema
Table: qoliber_gdpr_privacy_policy
Privacy policy versions.
| Column | Type | Description |
|---|---|---|
policy_id |
INT UNSIGNED | Primary key |
version |
VARCHAR(50) | Policy version number |
title |
VARCHAR(255) | Policy title |
content |
TEXT | Policy content (HTML) |
is_active |
BOOLEAN | Is this the active version |
effective_date |
DATE | When policy becomes effective |
store_id |
SMALLINT UNSIGNED | Store ID |
created_at |
TIMESTAMP | Creation timestamp |
updated_at |
TIMESTAMP | Update timestamp |
Table: qoliber_gdpr_policy_acceptance
Customer policy acceptance log.
| Column | Type | Description |
|---|---|---|
acceptance_id |
INT UNSIGNED | Primary key |
policy_id |
INT UNSIGNED | Foreign key to privacy_policy |
customer_id |
INT UNSIGNED | Customer ID |
accepted_at |
TIMESTAMP | Acceptance timestamp |
ip_address |
VARCHAR(45) | IP address |
user_agent |
TEXT | User agent |
Events
The GDPR suite dispatches the following events for customization:
Consent Events
Event: gdpr_consent_before_save
Parameters:
- consent - ConsentDefinition model
- customer_id - Customer ID
Event: gdpr_consent_after_save
Parameters:
- consent - ConsentDefinition model
- is_accepted - Boolean
Event: gdpr_consent_withdrawn
Parameters:
- consent_identifier - Consent code
- customer_id - Customer ID
Data Request Events
Event: gdpr_data_request_submitted
Parameters:
- request - Request model
- type - Request type (export, delete, rectify)
Event: gdpr_data_request_approved
Parameters:
- request - Request model
Event: gdpr_data_request_completed
Parameters:
- request - Request model
- result - Result data
Event: gdpr_customer_anonymized
Parameters:
- customer_id - Customer ID
- anonymization_data - Anonymization details
Cookie Events
Event: gdpr_cookie_consent_saved
Parameters:
- categories - Array of accepted categories
- customer_id - Customer ID (if logged in)
Routers
Frontend Router: GdprDataSubject
Router ID: gdpr
Area: Frontend
Purpose: Handle GDPR data subject request URLs
Routes:
- /gdpr/customer/* - Logged-in customer requests
- /gdpr/guest/* - Guest user requests
- /gdpr/data-request/* - Request submission and status
Console Commands
GdprConsent
# List all consent definitions
php bin/magento gdpr:consent:list
# Export consent logs
php bin/magento gdpr:consent:export --from=2024-01-01 --to=2024-12-31
GdprDataSubject
# Export customer data
php bin/magento gdpr:export:customer <customer_id>
# Anonymize customer
php bin/magento gdpr:anonymize <customer_id>
# Delete customer (hard delete)
php bin/magento gdpr:delete:customer <customer_id>
# List pending requests
php bin/magento gdpr:requests:list --status=pending
GdprAutomation
# Run anonymization manually
php bin/magento gdpr:anonymize:run
# Check accounts eligible for anonymization
php bin/magento gdpr:anonymize:check --dry-run
Cron Jobs
GdprAutomation
Cron Group: default
Job: gdpr_anonymize_inactive_accounts
Schedule: Daily at 2:00 AM
Class: Qoliber\GdprAutomation\Cron\AnonymizeInactiveAccounts
Purpose: Automatically anonymize inactive customer accounts
Job: gdpr_cleanup_old_logs
Schedule: Weekly (Sunday 3:00 AM)
Class: Qoliber\GdprAutomation\Cron\CleanupOldLogs
Purpose: Clean up old consent logs and request data
GdprDataSubject
Job: gdpr_process_email_queue
Schedule: Every 5 minutes
Class: Qoliber\GdprDataSubject\Cron\ProcessEmailQueue
Purpose: Process queued GDPR notification emails
Job: gdpr_expire_old_requests
Schedule: Daily at 4:00 AM
Class: Qoliber\GdprDataSubject\Cron\ExpireOldRequests
Purpose: Mark expired requests as expired
API Endpoints
REST API
Currently implemented endpoints:
GdprPolicy - Policy Consent API
POST /rest/V1/gdpr/policy/consent/log
Authentication: Anonymous
Purpose: Log policy consent acceptance
GET /rest/V1/gdpr/policy/consent/customer/:customerId/type/:policyType
Authentication: Self (customer)
Purpose: Get latest consent for customer
Note: customerId is automatically set from customer token
GET /rest/V1/gdpr/policy/consent/check
Authentication: Self (customer)
Purpose: Check if customer has accepted current policy version
GET /rest/V1/gdpr/policy/consent/:consentId
Authentication: Admin ACL (Qoliber_GdprPolicy::policy_consent_view)
Purpose: Get specific consent record
GET /rest/V1/gdpr/policy/consent/search
Authentication: Admin ACL (Qoliber_GdprPolicy::policy_consent_view)
Purpose: Search consent records
Future API Development
Additional API endpoints are planned for future releases to support: - Consent tracking and management - Data export/deletion requests - Cookie consent preferences - Customer privacy data access
Extending the Module
Adding Custom Consent Renderer
- Create renderer class implementing
ConsentRendererInterface - Register in
di.xml:
<type name="Qoliber\GdprConsent\Model\ConsentRendererPool">
<arguments>
<argument name="renderers" xsi:type="array">
<item name="custom_type" xsi:type="object">Vendor\Module\Model\Renderer\CustomRenderer</item>
</argument>
</arguments>
</type>
Adding Custom Form Location
Add to di.xml:
<type name="Qoliber\GdprConsent\Model\Source\FormLocation">
<arguments>
<argument name="formLocations" xsi:type="array">
<item name="custom_form" xsi:type="array">
<item name="label" xsi:type="string">Custom Form</item>
<item name="sort_order" xsi:type="number">50</item>
</item>
</argument>
</arguments>
</type>
Adding Custom Export Data
Implement ExportDataProviderInterface:
<?php
namespace Vendor\Module\Model\Export;
use Qoliber\GdprDataSubject\Api\ExportDataProviderInterface;
class CustomDataProvider implements ExportDataProviderInterface
{
public function getData(int $customerId): array
{
return [
'section_name' => 'Custom Data',
'data' => $this->getCustomerCustomData($customerId)
];
}
public function getSectionName(): string
{
return 'custom_section';
}
}
Register in di.xml:
<type name="Qoliber\GdprDataSubject\Model\Export\DataProviderPool">
<arguments>
<argument name="providers" xsi:type="array">
<item name="custom" xsi:type="object">Vendor\Module\Model\Export\CustomDataProvider</item>
</argument>
</arguments>
</type>
Testing
Unit Tests
php bin/magento dev:tests:run unit Qoliber_GdprConsent
php bin/magento dev:tests:run unit Qoliber_GdprDataSubject
Integration Tests
Functional Tests (MFTF)
Performance Optimization
Database Indexes
All critical tables have appropriate indexes. For large installations, consider:
-- Add composite index for frequent queries
ALTER TABLE qoliber_gdpr_consent_customer_log
ADD INDEX idx_customer_consent_date (customer_id, consent_code, accepted_at);
-- Add index for admin reporting
ALTER TABLE qoliber_gdpr_requests
ADD INDEX idx_type_status_created (request_type, status, created_at);
Caching
Consent definitions are cached automatically. Cache key format:
Clear cache:
Security Considerations
- Always validate customer ownership before processing data requests
- Use tokenized URLs for guest data access
- Rate limit API endpoints to prevent abuse
- Encrypt sensitive data in database where applicable
- Audit log all admin actions related to GDPR
- Implement proper ACL for admin users
Support & Resources
- Documentation: https://docs.qoliber.com
- Support Email: [email protected]
- GitHub Issues: (if applicable)
- Developer Slack: (if applicable)