├── .babelrc.json ├── .gitattributes ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── Akeeba DataCompliance for Joomla 4.iml ├── aws.xml ├── blade.xml ├── codeStyles │ └── codeStyleConfig.xml ├── composerJson.xml ├── copyright │ ├── DataCompliance.xml │ └── profiles_settings.xml ├── dataSources.xml ├── encodings.xml ├── inspectionProfiles │ └── Project_Default.xml ├── libraries │ └── Generated_files.xml ├── markdown.xml ├── misc.xml ├── modules.xml ├── phing.xml ├── php.xml ├── scopes │ └── Copyright.xml ├── sqldialects.xml ├── vcs.xml ├── watcherTasks.xml └── webResources.xml ├── CHANGELOG ├── LICENSE ├── README.md ├── RELEASENOTES.html ├── assets └── email │ ├── 0_make_lang_strings.php │ ├── admin_admin.html │ ├── admin_lifecycle.html │ ├── admin_user.html │ ├── user_admin.html │ ├── user_lifecycle.html │ ├── user_user.html │ └── user_warnlifecycle.html ├── build.xml ├── build ├── build.properties └── templates │ ├── language │ └── en-GB │ │ └── pkg_datacompliance.sys.ini │ ├── pkg_datacompliance.xml │ └── release.yaml ├── component ├── backend │ ├── access.xml │ ├── assets │ │ └── plugin │ │ │ └── AbstractPlugin.php │ ├── config.xml │ ├── forms │ │ ├── filter_consenttrails.xml │ │ ├── filter_exporttrails.xml │ │ ├── filter_lifecycle.xml │ │ ├── filter_usertrails.xml │ │ └── filter_wipetrails.xml │ ├── language │ │ └── en-GB │ │ │ ├── com_datacompliance.ini │ │ │ └── com_datacompliance.sys.ini │ ├── layouts │ │ └── akeeba │ │ │ └── datacompliance │ │ │ └── common │ │ │ └── user.php │ ├── services │ │ └── provider.php │ ├── sql │ │ ├── install.mysql.utf8.sql │ │ ├── uninstall.mysql.utf8.sql │ │ └── updates │ │ │ └── mysql │ │ │ └── 4.0.0-20210904.sql │ ├── src │ │ ├── AbstractPlugin.php │ │ ├── CliCommand │ │ │ ├── AccountDelete.php │ │ │ ├── LifecycleDelete.php │ │ │ ├── LifecycleNotify.php │ │ │ └── MixIt │ │ │ │ ├── ConfigureIO.php │ │ │ │ ├── MemoryInfo.php │ │ │ │ └── TimeInfo.php │ │ ├── Controller │ │ │ ├── ConsenttrailsController.php │ │ │ ├── ControlpanelController.php │ │ │ ├── EmailtemplatesController.php │ │ │ ├── ExporttrailsController.php │ │ │ ├── LifecycleController.php │ │ │ ├── OptionsController.php │ │ │ ├── UsertrailsController.php │ │ │ └── WipetrailsController.php │ │ ├── Dispatcher │ │ │ └── Dispatcher.php │ │ ├── Extension │ │ │ └── DataComplianceComponent.php │ │ ├── Field │ │ │ └── ArticleField.php │ │ ├── Filter │ │ │ └── .gitinclude │ │ ├── Helper │ │ │ ├── CacheCleaner.php │ │ │ ├── ComponentParams.php │ │ │ ├── Export.php │ │ │ ├── MailTemplateHotFix.php │ │ │ └── TemplateEmails.php │ │ ├── Mixin │ │ │ ├── ControllerCacheTrait.php │ │ │ ├── ControllerEventsTrait.php │ │ │ ├── ControllerRegisterTasksTrait.php │ │ │ ├── ControllerReusableModelsTrait.php │ │ │ ├── RunPluginsTrait.php │ │ │ ├── TableAssertionTrait.php │ │ │ ├── TableColumnAliasTrait.php │ │ │ ├── TableCreateModifyTrait.php │ │ │ ├── TriggerEventTrait.php │ │ │ ├── ViewLoadAnyTemplateTrait.php │ │ │ └── ViewTaskBasedEventsTrait.php │ │ ├── Model │ │ │ ├── ConsenttrailsModel.php │ │ │ ├── ControlpanelModel.php │ │ │ ├── ExportModel.php │ │ │ ├── ExporttrailsModel.php │ │ │ ├── LifecycleModel.php │ │ │ ├── OptionsModel.php │ │ │ ├── StatsModel.php │ │ │ ├── UpgradeModel.php │ │ │ ├── UsertrailsModel.php │ │ │ ├── WipeModel.php │ │ │ └── WipetrailsModel.php │ │ ├── Provider │ │ │ └── RouterFactory.php │ │ ├── Router │ │ │ └── RouterFactory.php │ │ ├── Rule │ │ │ └── .gitinclude │ │ ├── Service │ │ │ └── Html │ │ │ │ └── DataCompliance.php │ │ ├── Table │ │ │ ├── AbstractTable.php │ │ │ ├── ConsenttrailsTable.php │ │ │ ├── ExporttrailsTable.php │ │ │ ├── GetPropertiesAwareTrait.php │ │ │ ├── UsertrailsTable.php │ │ │ └── WipetrailsTable.php │ │ └── View │ │ │ ├── Consenttrails │ │ │ └── HtmlView.php │ │ │ ├── Controlpanel │ │ │ └── HtmlView.php │ │ │ ├── Emailtemplates │ │ │ └── HtmlView.php │ │ │ ├── Exporttrails │ │ │ └── HtmlView.php │ │ │ ├── Lifecycle │ │ │ └── HtmlView.php │ │ │ ├── Options │ │ │ └── HtmlView.php │ │ │ ├── Usertrails │ │ │ └── HtmlView.php │ │ │ └── Wipetrails │ │ │ └── HtmlView.php │ └── tmpl │ │ ├── common │ │ └── errorhandler.php │ │ ├── consenttrails │ │ ├── default.php │ │ └── emptystate.php │ │ ├── controlpanel │ │ ├── default.php │ │ ├── default_icons.php │ │ ├── default_stats.php │ │ └── joomla_eol.php │ │ ├── emailtemplates │ │ └── default.php │ │ ├── exporttrails │ │ ├── default.php │ │ └── emptystate.php │ │ ├── lifecycle │ │ ├── default.php │ │ └── emptystate.php │ │ ├── options │ │ ├── default.php │ │ └── wipe.php │ │ ├── usertrails │ │ ├── default.php │ │ └── emptystate.php │ │ └── wipetrails │ │ ├── default.php │ │ └── emptystate.php ├── datacompliance.xml ├── frontend │ ├── .htaccess │ ├── language │ │ └── en-GB │ │ │ └── com_datacompliance.ini │ ├── src │ │ ├── Controller │ │ │ └── OptionsController.php │ │ ├── Dispatcher │ │ │ └── Dispatcher.php │ │ ├── Model │ │ │ └── OptionsModel.php │ │ ├── Service │ │ │ └── Router.php │ │ └── View │ │ │ └── Options │ │ │ └── HtmlView.php │ ├── tmpl │ │ └── options │ │ │ ├── default.php │ │ │ ├── default.xml │ │ │ └── wipe.php │ └── web.config ├── media │ ├── css │ │ ├── backend.css │ │ ├── backend.css.map │ │ ├── backend.scss │ │ ├── j5.css │ │ ├── j5.css.map │ │ ├── j5.scss │ │ └── sources │ │ │ └── _title_icon.scss │ ├── fonts │ │ └── Akeeba-Products.woff │ ├── joomla.asset.json │ └── js │ │ ├── controlpanel.js │ │ ├── controlpanel.min.js │ │ ├── controlpanel.min.js.map │ │ ├── options.js │ │ ├── options.min.js │ │ └── options.min.js.map └── script.datacompliance.php ├── composer.json ├── composer.lock ├── documentation ├── datacompliance.xml └── images │ ├── control_panel.png │ └── self_service_page.png ├── modules ├── admin │ └── .gitinclude └── site │ └── .gitinclude ├── old └── cli │ └── datacompliance_audit_replay.php └── plugins ├── console └── datacompliance │ ├── .htaccess │ ├── datacompliance.xml │ ├── language │ ├── en-GB │ │ ├── plg_console_datacompliance.ini │ │ └── plg_console_datacompliance.sys.ini │ └── index.html │ ├── services │ └── provider.php │ ├── src │ └── Extension │ │ └── DataCompliance.php │ └── web.config ├── datacompliance ├── ars │ ├── .htaccess │ ├── ars.xml │ ├── language │ │ └── en-GB │ │ │ ├── plg_datacompliance_ars.ini │ │ │ └── plg_datacompliance_ars.sys.ini │ ├── services │ │ └── provider.php │ ├── src │ │ └── Extension │ │ │ └── ARS.php │ └── web.config ├── ats │ ├── .htaccess │ ├── ats.xml │ ├── language │ │ └── en-GB │ │ │ ├── plg_datacompliance_ats.ini │ │ │ └── plg_datacompliance_ats.sys.ini │ ├── services │ │ └── provider.php │ ├── src │ │ └── Extension │ │ │ └── ATS.php │ └── web.config ├── email │ ├── .htaccess │ ├── email.xml │ ├── language │ │ └── en-GB │ │ │ ├── plg_datacompliance_email.ini │ │ │ └── plg_datacompliance_email.sys.ini │ ├── services │ │ └── provider.php │ ├── src │ │ └── Extension │ │ │ └── Email.php │ └── web.config ├── joomla │ ├── .htaccess │ ├── joomla.xml │ ├── language │ │ └── en-GB │ │ │ ├── plg_datacompliance_joomla.ini │ │ │ └── plg_datacompliance_joomla.sys.ini │ ├── services │ │ └── provider.php │ ├── src │ │ └── Extension │ │ │ └── Joomla.php │ └── web.config ├── loginguard │ ├── .htaccess │ ├── language │ │ └── en-GB │ │ │ ├── plg_datacompliance_loginguard.ini │ │ │ └── plg_datacompliance_loginguard.sys.ini │ ├── loginguard.xml │ ├── services │ │ └── provider.php │ ├── src │ │ └── Extension │ │ │ └── LoginGuard.php │ └── web.config └── s3 │ ├── .htaccess │ ├── language │ └── en-GB │ │ ├── plg_datacompliance_s3.ini │ │ └── plg_datacompliance_s3.sys.ini │ ├── s3.xml │ ├── services │ └── provider.php │ ├── src │ └── Extension │ │ └── S3.php │ └── web.config ├── system └── datacompliance │ ├── .htaccess │ ├── datacompliance.xml │ ├── language │ └── en-GB │ │ ├── plg_system_datacompliance.ini │ │ └── plg_system_datacompliance.sys.ini │ ├── services │ └── provider.php │ ├── src │ └── Extension │ │ └── DataCompliance.php │ └── web.config └── user └── datacompliance ├── .htaccess ├── datacompliance.xml ├── datacompliance ├── datacompliance.xml └── list.xml ├── language └── en-GB │ ├── plg_user_datacompliance.ini │ └── plg_user_datacompliance.sys.ini ├── services └── provider.php ├── src ├── Extension │ └── DataCompliance.php └── Field │ └── DatacomplianceField.php └── web.config /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "entry", 7 | "corejs": { 8 | "version": "3.9", 9 | "proposals": true 10 | } 11 | } 12 | ], 13 | [ 14 | "minify", 15 | { 16 | "builtIns": false, 17 | "removeConsole": false 18 | } 19 | ] 20 | ], 21 | "comments": false, 22 | "ignore": [ 23 | "component/media/js/*.min.js" 24 | ] 25 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text eol=lf 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.php text eol=lf 7 | *.phps text eol=lf 8 | *.inc text eol=lf 9 | *.js text eol=lf 10 | *.css text eol=lf 11 | *.ini text eol=lf 12 | *.json text eol=lf 13 | *.htm text eol=lf 14 | *.html text eol=lf 15 | *.xml text eol=lf 16 | *.xslt text eol=lf 17 | *.svg text eol=lf 18 | *.txt text eol=lf 19 | *.md text eol=lf 20 | *.sh text eol=lf 21 | CHANGELOG text eol=lf 22 | README text eol=lf 23 | RELEASENOTES text eol=lf 24 | 25 | # Declare files that will always have CRLF line endings on checkout. 26 | *.sln text eol=crlf 27 | 28 | # Denote all files that are truly binary and should not be modified. 29 | *.acorn binary 30 | *.png binary 31 | *.jpg binary 32 | *.jpeg binary 33 | *.z binary 34 | *.gif binary 35 | *.jpa binary 36 | *.jps binary 37 | *.zip binary 38 | *.dll binary 39 | *.exe binary 40 | *.jar binary 41 | *.phar binary 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Package XML file (copy) 2 | /pkg_*.xml 3 | 4 | ### Node Modules 5 | /node_modules 6 | 7 | ### Component 8 | # Generated files in the component folder 9 | /component/backend/datacompliance.xml 10 | 11 | # Old component being migrated 12 | /old 13 | 14 | ### Release files 15 | /release/* 16 | 17 | ### Composer 18 | /component/backend/vendor 19 | 20 | ### Other generated files 21 | /assets/email/*.ini 22 | .DS_Store -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Akeeba DataCompliance for Joomla 4 -------------------------------------------------------------------------------- /.idea/Akeeba DataCompliance for Joomla 4.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/aws.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/composerJson.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/copyright/DataCompliance.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mysql.8 6 | true 7 | true 8 | $USER_HOME$/Sites/boot4/configuration.php 9 | com.mysql.cj.jdbc.Driver 10 | jdbc:mysql://127.0.0.1:3306/boot4 11 | $ProjectFileDir$ 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/Generated_files.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/markdown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/phing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | $USER_HOME$/.composer/vendor/bin/phing 14 | 15 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | -------------------------------------------------------------------------------- /.idea/scopes/Copyright.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/sqldialects.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/watcherTasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 24 | 25 | 36 | 46 | 47 | -------------------------------------------------------------------------------- /.idea/webResources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /RELEASENOTES.html: -------------------------------------------------------------------------------- 1 |

What's new?

2 | 3 |

4 | Workaround for Joomla! 5.2 broken mail template layout. Joomla! 5.2 introduced the new Mail Template Layout feature. Unfortunately, this feature is breaking URLs in emails by prefixing them with the site's URL even if they are absolute URLs. Something like https://www.akeeba.com will turn into this jumbled mess https://www.example.com/https://www.akeeba.com. This happens to all URLs in anchor (<a>), link, and a few other HTML tags and affects all URLs which come from an email template variable – even those in mail templates of core Joomla! components such as the users component (think account activation, username reminders, password reset, etc). There was simple no QA done before the release of Joomla! 5.2. While we cannot fix Joomla! 5.2 ourselves and we cannot ask our clients to disable the Mail Template Layout feature (it is a nice feature, despite its shoddy implementation), we can work around the problems it brought with it. We added this workaround, as well as a toggle in the component's Options in case you need to disable this workaround, presumably when Joomla finally addresses this issue. 5 |

-------------------------------------------------------------------------------- /assets/email/0_make_lang_strings.php: -------------------------------------------------------------------------------- 1 | loadHTML($html); 11 | 12 | $title = $dom->getElementsByTagName('title')->item(0)->nodeValue; 13 | $bodyNode = $dom->getElementsByTagName('body')->item(0); 14 | $bodyHtml = trim(str_replace(['', ''], ['', ''], $dom->saveHTML($bodyNode))); 15 | $bodyText = str_replace("\n", '\\n', strip_tags(str_replace('', '\n' . str_repeat('-', 70), $bodyHtml))); 16 | $bodyHtml = str_replace("\n", '\\n', $bodyHtml); 17 | 18 | $title = str_replace('"', '\\"', $title); 19 | $bodyHtml = str_replace('"', '\\"', $bodyHtml); 20 | $bodyText = str_replace('"', '\\"', $bodyText); 21 | 22 | $key = strtoupper(basename($file, '.html')); 23 | 24 | echo 'COM_DATACOMPLIANCE_MAIL_' . $key . '_SUBJECT="' . $title . "\"\n"; 25 | echo 'COM_DATACOMPLIANCE_MAIL_' . $key . '_BODY="' . $bodyText . "\"\n"; 26 | echo 'COM_DATACOMPLIANCE_MAIL_' . $key . '_BODY_HTML="' . $bodyHtml . "\"\n"; 27 | } 28 | 29 | ob_start(); 30 | 31 | $di = new DirectoryIterator(__DIR__); 32 | 33 | /** @var DirectoryIterator $file */ 34 | foreach ($di as $file) 35 | { 36 | if (!$file->isFile()) 37 | { 38 | continue; 39 | } 40 | 41 | if ($file->getExtension() != 'html') 42 | { 43 | continue; 44 | } 45 | 46 | parseHtml($file->getPathname()); 47 | 48 | echo "\n"; 49 | } 50 | 51 | $contents = ob_get_clean(); 52 | file_put_contents('en-GB.ini', $contents); 53 | 54 | echo $contents; 55 | -------------------------------------------------------------------------------- /assets/email/admin_admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | An administrator has deleted a user profile on {SITENAME} 6 | 7 | 8 |
9 |
10 |

Hello {NAME},

11 |

An administrator has deleted the user profile of user #{ID} on {sitename}.

12 |

The account deletion was performed in accordance with the provisions of the European Union''s General Data Protection Regulation (GDPR) and / or equivalent laws abroad.

13 |

The exact actions which took place on our system are as follows:

14 |

{ACTIONS}

15 |

Best regards,

16 |

The {sitename} team

17 |
18 |

You are receiving this automatic email message because a user profile on {sitename} has been deleted. Do not reply to this email, it''s sent from an unmonitored email address.

19 |
20 | 21 | -------------------------------------------------------------------------------- /assets/email/admin_lifecycle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Automatic deletion of a user profile on {sitename} 6 | 7 | 8 |
9 |
10 |

Hello {NAME},

11 |

The user profile of user #{ID} has been automatically deleted on {sitename} due to the account life cycle policy as defined by the Data Compliance plugins running on the site. The account was determined to be inactive.

12 |

The account deletion was performed in accordance with the provisions of the European Union's General Data Protection Regulation (GDPR) and / or equivalent laws abroad.

13 |

The exact actions which took place on our system are as follows:

14 |

{ACTIONS}

15 |

Best regards,

16 |

The {sitename} team

17 |
18 |

You are receiving this automatic email message because a user profile on {sitename} has been deleted. Do not reply to this email, it''s sent from an unmonitored email address.

19 |
20 | 21 | -------------------------------------------------------------------------------- /assets/email/admin_user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A user has deleted their user profile on {sitename} 6 | 7 | 8 |
9 |
10 |

Hello {NAME},

11 |

User #{ID} has deleted their user profile on {sitename}.

12 |

The account deletion was performed in accordance with the provisions of the European Union''s General Data Protection Regulation (GDPR) and / or equivalent laws abroad.

13 |

The exact actions which took place on our system are as follows:

14 |

{ACTIONS}

15 |

Best regards,

16 |

The {sitename} team

17 |
18 |

You are receiving this automatic email message because a user profile on {sitename} has been deleted. Do not reply to this email, it''s sent from an unmonitored email address.

19 |
20 | 21 | -------------------------------------------------------------------------------- /assets/email/user_admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | An administrator has deleted your user profile on {sitename} 6 | 7 | 8 |
9 |
10 |

Hello {NAME},

11 |

KEEP THIS EMAIL FOR YOUR RECORDS.

12 |

We would like to notify you that your user profile on {sitename} has been deleted by an administrator. The account deletion was performed in accordance with 13 | the provisions of the European Union''s General Data Protection Regulation (GDPR) and / or equivalent laws abroad.

14 |

The exact actions which took place on our system to delete your user account are as follows:

15 |

{ACTIONS}

16 |

Please note that this email was prepared right before the deletion took place, that''s how we were able to send it and address it to you.

17 |

Now that your account has been deleted from our system you are no longer considered a client (past, current or prospective) of ours. Furthermore, according to the law 18 | (GDPR), it''s as though you have never been our client.

19 |

This is the final communication you are receiving from us notwithstanding any response to any communication that you initiate in the future or have 20 | already initiated outside our web site such as but not limited to email, social media, electronic messaging platforms, letter, telephone, short message service (SMS 21 | a.k.a. "texts") or in person.

22 |

Thank you for having used our services!

23 |

Best regards,

24 |

The {sitename} team

25 |
26 |

You are receiving this automatic email message because your user profile on {sitename} has been deleted. Do not reply to this email, it''s sent from an unmonitored email address.

28 |
29 | 30 | -------------------------------------------------------------------------------- /assets/email/user_user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | You have deleted your user profile on {sitename} 6 | 7 | 8 |
9 |
10 |

Hello {NAME},

11 |

KEEP THIS EMAIL FOR YOUR RECORDS.

12 |

We would like to notify you that your user profile on {sitename} has been deleted per your request. The account deletion was performed in accordance with the provisions 13 | of the European Union's General Data Protection Regulation (GDPR) and / or equivalent laws abroad.

14 |

The exact actions which took place on our system to delete your user account are as follows:

15 |

{ACTIONS}

16 |

Please note that this email was prepared right before the deletion took place, that's how we were able to send it and address it to you.

17 |

Now that your account has been deleted from our system you are no longer considered a client (past, current or prospective) of ours. Furthermore, according to the law 18 | (GDPR), it's as though you have never been our client.

19 |

This is the final communication you are receiving from us notwithstanding any response to any communication that you initiate in the future or have already 20 | initiated outside our web site such as but not limited to email, social media, electronic messaging platforms, letter, telephone, short message service (SMS a.k.a. 21 | "texts") or in person.

22 |

Thank you for having used our services!

23 |

Best regards,

24 |

The {sitename} team

25 |
26 |

You are receiving this automatic email message because your user profile on {sitename} has been deleted. Do not reply to this email, it's sent from an unmonitored email address.

28 |
29 | 30 | -------------------------------------------------------------------------------- /assets/email/user_warnlifecycle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Your user account on {sitename} will be deleted on {DELETEDATE} 6 | 7 | 8 |
9 |
10 |

Hello {NAME},

11 |

PLEASE DO NOT IGNORE THIS EMAIL.

12 |

On May 25th, 2018 the European Union''s General Data Protection Regulation (GDPR) came into effect. According to the GDPR, inactive user accounts and all their personally identifiable information must be permanently deleted. Account inactivity is determined by several factors, such as when was the last time you logged into your account on our site.

13 |

Your user account with the username “{USERNAME}” has been determined to be inactive and will be permanently deleted after {DELETEDATE}.

14 |

If you wish to prevent the deletion of your user account and all of its information you can simply log into our site before {DELETEDATE}.

15 |

If neither of these conditions are met, your account will be deleted on {DELETEDATE}. More specifically, the following actions will take place:

16 |

{ACTIONS}

17 |

After your account will have been deleted:

18 | 23 |

We would like to kindly remind you that deleting your user account is the direct result of a LEGAL REQUIREMENT. We do not do this to inconvenience you. We will delete your information because we are forced to do so by the law (we have no choice). Moreover, it is ILLEGAL for us to keep a copy of your information after deleting it, therefore we will not be able to restore your deleted information even if you ask us.

24 |

Best regards,

25 |

The {sitename} team

26 |
27 |

You are receiving this automatic email message because your user profile on {sitename} has been deleted. Do not reply to this email, it''s sent from an unmonitored email address.

28 |
29 | 30 | -------------------------------------------------------------------------------- /build/build.properties: -------------------------------------------------------------------------------- 1 | ftpdeploy.pattern.core=pkg_datacompliance-*.zip 2 | ftpdeploy.path.core=files/dev/datacompliance 3 | 4 | release.method=yaml 5 | 6 | build.component=datacompliance 7 | build.has_pro=0 8 | -------------------------------------------------------------------------------- /build/templates/language/en-GB/pkg_datacompliance.sys.ini: -------------------------------------------------------------------------------- 1 | PKG_DATACOMPLIANCE="Akeeba Data Compliance package" 2 | PKG_DATACOMPLIANCE_XML_DESCRIPTION="Akeeba Data Compliance. A tool to facilitate GDPR conformance of your Joomla!™ sites." -------------------------------------------------------------------------------- /build/templates/pkg_datacompliance.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | pkg_datacompliance 10 | Nicholas K. Dionysopoulos 11 | ##DATE## 12 | datacompliance 13 | ##VERSION## 14 | https://www.akeeba.com 15 | Akeeba Ltd 16 | https://www.akeeba.com 17 | Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 18 | GNU GPL v3 or later 19 | PKG_DATACOMPLIANCE_XML_DESCRIPTION 20 | true 21 | 22 | 23 | com_datacompliance.zip 24 | 25 | plg_console_datacompliance.zip 26 | 27 | plg_datacompliance_email.zip 28 | plg_datacompliance_joomla.zip 29 | plg_datacompliance_ars.zip 30 | plg_datacompliance_ats.zip 31 | plg_datacompliance_loginguard.zip 32 | plg_datacompliance_s3.zip 33 | 34 | plg_system_datacompliance.zip 35 | 36 | plg_user_datacompliance.zip 37 | 38 | 39 | 40 | en-GB/pkg_datacompliance.sys.ini 41 | 42 | 43 | script.datacompliance.php 44 | 45 | 46 | https://cdn.akeeba.com/updates/pkgdatacompliance.xml 47 | 48 | 49 | -------------------------------------------------------------------------------- /build/templates/release.yaml: -------------------------------------------------------------------------------- 1 | # Basic release information 2 | release: 3 | version: '%%VERSION%%' 4 | date: '%%DATE%%' 5 | category: 56 6 | access: 1 7 | release_notes: '%%DEFAULT_RELEASE_NOTES%%' 8 | changelog: '%%DEFAULT_CHANGELOG%%' 9 | 10 | # Akeeba Release System API connection 11 | api: 12 | type: 'joomla' 13 | endpoint: '%%API.ENDPOINT%%' 14 | connector: '%%API.CONNECTOR%%' 15 | token: '%%API.TOKEN%%' 16 | cacert: '%%CUSTOMCACERT%%' 17 | 18 | steps: [%%RELEASESTEPS%%] 19 | 20 | # File upload connections 21 | connections: 22 | s3: 23 | type: 's3' 24 | access: '%%S3.ACCESS%%' 25 | secret: '%%S3.SECRET%%' 26 | bucket: '%%S3.BUCKET%%' 27 | tls: true 28 | signature: '%%S3.SIGNATURE%%' 29 | region: '%%S3.REGION%%' 30 | directory: 'downloads/datacompliance' 31 | cdnhostname: '%%S3.CDNHOSTNAME%%' 32 | acl: 'public-read' 33 | storage_class: 'STANDARD' 34 | maximum_age: 600 35 | 36 | # Release source files configuration. 37 | files: 38 | - 39 | title: 'Akeeba DataCompliance' 40 | connection: s3 41 | source: '%%RELEASEDIR%%/pkg_datacompliance-*.zip' 42 | access: 1 43 | - 44 | title: 'Documentation (PDF)' 45 | connection: s3 46 | source: '%%RELEASEDIR%%/datacompliance.pdf' 47 | access: 1 48 | 49 | # Update sources 50 | updates: 51 | - 52 | title: 'Akeeba DataCompliance updates' 53 | connection: s3 54 | directory: 'updates' 55 | stream: 42 56 | base_name: 'pkgdatacompliance' 57 | formats: 58 | - 'xml' 59 | -------------------------------------------------------------------------------- /component/backend/access.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 | 10 | 11 |
12 |
13 | -------------------------------------------------------------------------------- /component/backend/assets/plugin/AbstractPlugin.php: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 | 10 | 17 | 18 | 26 | 27 | 28 | 29 | 30 | 39 | 40 | 41 | 42 | 43 | 44 | 51 | 52 |
-------------------------------------------------------------------------------- /component/backend/forms/filter_exporttrails.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 | 10 | 17 | 18 | 26 | 27 | 28 | 29 | 30 | 39 | 40 | 41 | 42 | 43 | 44 | 51 | 52 |
-------------------------------------------------------------------------------- /component/backend/forms/filter_lifecycle.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 | 10 | 17 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 55 | 56 |
-------------------------------------------------------------------------------- /component/backend/forms/filter_usertrails.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 | 10 | 17 | 18 | 19 | 20 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 43 | 44 |
-------------------------------------------------------------------------------- /component/backend/forms/filter_wipetrails.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 | 10 | 17 | 18 | 26 | 27 | 28 | 29 | 30 | 39 | 40 | 41 | 42 | 43 | 44 | 51 | 52 |
-------------------------------------------------------------------------------- /component/backend/language/en-GB/com_datacompliance.sys.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | COM_DATACOMPLIANCE="Akeeba Data Compliance" 6 | COM_DATACOMPLIANCE_XML_DESCRIPTION="A GDRP compliance tool for Joomla! sites" 7 | 8 | COM_DATAWIPE_ACL_VIEW_TRAIL="Audit trails" 9 | COM_DATAWIPE_ACL_VIEW_TRAIL_DESC="View audit trails" 10 | COM_DATAWIPE_ACL_WIPE="Delete profiles" 11 | COM_DATAWIPE_ACL_WIPE_DESC="Delete profiles of other users" 12 | COM_DATAWIPE_ACL_EXPORT="Export profiles" 13 | COM_DATAWIPE_ACL_EXPORT_DESC="Export the personal information profiles of other users" 14 | 15 | COM_DATACOMPLIANCE_MENUMANAGER_VIEW_OPTIONS_LABEL="Data Processing Options" 16 | COM_DATACOMPLIANCE_MENUMANAGER_VIEW_OPTIONS_DESC="Show the Data Processing Options page to the user. They can manage their processing consent and export or delete their profile." 17 | -------------------------------------------------------------------------------- /component/backend/services/provider.php: -------------------------------------------------------------------------------- 1 | registerServiceProvider(new MVCFactory('Akeeba\\Component\\DataCompliance')); 33 | $container->registerServiceProvider(new ComponentDispatcherFactory('Akeeba\\Component\\DataCompliance')); 34 | $container->registerServiceProvider(new RouterFactory('\\Akeeba\\Component\\DataCompliance')); 35 | 36 | $container->set( 37 | ComponentInterface::class, 38 | function (Container $container) { 39 | $component = new DataComplianceComponent($container->get(ComponentDispatcherFactoryInterface::class)); 40 | 41 | $component->setRegistry($container->get(Registry::class)); 42 | $component->setMVCFactory($container->get(MVCFactoryInterface::class)); 43 | $component->setRouterFactory($container->get(RouterFactoryInterface::class)); 44 | 45 | return $component; 46 | } 47 | ); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /component/backend/sql/install.mysql.utf8.sql: -------------------------------------------------------------------------------- 1 | /** 2 | * @package AkeebaDataCompliance 3 | * @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 4 | * @license GNU General Public License version 3, or later 5 | */ 6 | 7 | CREATE TABLE IF NOT EXISTS `#__datacompliance_exporttrails` ( 8 | `datacompliance_exporttrail_id` bigint(20) NOT NULL AUTO_INCREMENT, 9 | `user_id` bigint(20) unsigned NOT NULL, 10 | `created_on` datetime NOT NULL, 11 | `created_by` bigint(20) NOT NULL, 12 | `requester_ip` varchar(255) NOT NULL, 13 | PRIMARY KEY (`datacompliance_exporttrail_id`), 14 | KEY `#__datacompliance_exporttrail_user` (`user_id`) 15 | ) DEFAULT COLLATE utf8mb4_unicode_ci; 16 | 17 | 18 | CREATE TABLE IF NOT EXISTS `#__datacompliance_wipetrails` ( 19 | `datacompliance_wipetrail_id` BIGINT(20) NOT NULL AUTO_INCREMENT, 20 | `user_id` bigint(20) NOT NULL, 21 | `type` enum ('lifecycle','user','admin') NOT NULL DEFAULT 'user', 22 | `created_on` datetime NOT NULL, 23 | `created_by` bigint(20) NOT NULL, 24 | `requester_ip` varchar(255) NOT NULL, 25 | `items` longtext, 26 | PRIMARY KEY (`datacompliance_wipetrail_id`) 27 | ) DEFAULT COLLATE utf8mb4_unicode_ci; 28 | 29 | CREATE TABLE IF NOT EXISTS `#__datacompliance_consenttrails` ( 30 | `created_on` datetime NOT NULL, 31 | `created_by` bigint(20) NOT NULL, 32 | `requester_ip` varchar(255) NOT NULL, 33 | `enabled` int(1) NOT NULL DEFAULT 0, 34 | PRIMARY KEY (`created_by`) 35 | ) DEFAULT COLLATE utf8mb4_unicode_ci; 36 | 37 | 38 | CREATE TABLE IF NOT EXISTS `#__datacompliance_usertrails` ( 39 | `datacompliance_usertrail_id` BIGINT(20) NOT NULL AUTO_INCREMENT, 40 | `user_id` bigint(20) NOT NULL, 41 | `created_on` datetime NOT NULL, 42 | `created_by` bigint(20) NOT NULL, 43 | `requester_ip` varchar(255) NOT NULL, 44 | `items` longtext, 45 | PRIMARY KEY (`datacompliance_usertrail_id`) 46 | ) DEFAULT COLLATE utf8mb4_unicode_ci; 47 | 48 | DROP TABLE IF EXISTS `#__datacompliance_cookietrails`; 49 | 50 | DROP TABLE IF EXISTS `#__datacompliance_emailtemplates`; -------------------------------------------------------------------------------- /component/backend/sql/uninstall.mysql.utf8.sql: -------------------------------------------------------------------------------- 1 | /** 2 | * @package AkeebaDataCompliance 3 | * @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 4 | * @license GNU General Public License version 3, or later 5 | */ 6 | 7 | DROP TABLE IF EXISTS `#__datacompliance_exporttrails`; 8 | DROP TABLE IF EXISTS `#__datacompliance_wipetrails`; 9 | DROP TABLE IF EXISTS `#__datacompliance_consenttrails`; 10 | DROP TABLE IF EXISTS `#__datacompliance_usertrails`; 11 | DROP TABLE IF EXISTS `#__datacompliance_cookietrails`; -------------------------------------------------------------------------------- /component/backend/sql/updates/mysql/4.0.0-20210904.sql: -------------------------------------------------------------------------------- 1 | /** 2 | * @package AkeebaDataCompliance 3 | * @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 4 | * @license GNU General Public License version 3, or later 5 | */ 6 | 7 | # Drop old tables on update 8 | DROP TABLE IF EXISTS `#__datacompliance_emailtemplates`; 9 | DROP TABLE IF EXISTS `#__datacompliance_cookietrails`; 10 | 11 | # Convert all tables to InnoDB 12 | ALTER TABLE `#__datacompliance_exporttrails` ENGINE InnoDB; 13 | ALTER TABLE `#__datacompliance_wipetrails` ENGINE InnoDB; 14 | ALTER TABLE `#__datacompliance_consenttrails` ENGINE InnoDB; 15 | ALTER TABLE `#__datacompliance_usertrails` ENGINE InnoDB; -------------------------------------------------------------------------------- /component/backend/src/AbstractPlugin.php: -------------------------------------------------------------------------------- 1 | autoloadLanguage = true; 42 | 43 | parent::__construct($subject, $config); 44 | } 45 | 46 | /** 47 | * Formats a number of bytes in human readable format 48 | * 49 | * @param int $size The size in bytes to format, e.g. 8254862 50 | * 51 | * @return string The human-readable representation of the byte size, e.g. "7.87 Mb" 52 | * 53 | * @since 3.0.0 54 | */ 55 | protected function formatByteSize(int $size): string 56 | { 57 | $unit = ['b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb']; 58 | 59 | return @round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . ' ' . $unit[$i]; 60 | } 61 | 62 | /** 63 | * Returns the current memory usage, formatted human readable 64 | * 65 | * @return string 66 | */ 67 | protected function memUsage(): string 68 | { 69 | if (function_exists('memory_get_usage')) 70 | { 71 | $size = memory_get_usage(); 72 | 73 | return $this->formatByteSize($size); 74 | } 75 | else 76 | { 77 | return "(unknown)"; 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /component/backend/src/CliCommand/MixIt/ConfigureIO.php: -------------------------------------------------------------------------------- 1 | cliInput = $input; 48 | $this->ioStyle = new SymfonyStyle($input, $output); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /component/backend/src/CliCommand/MixIt/MemoryInfo.php: -------------------------------------------------------------------------------- 1 | [1, 'second'], 52 | 'm' => [60, 'minute'], 53 | 'h' => [60 * 60, 'hour'], 54 | 'd' => [60 * 60 * 24, 'day'], 55 | 'y' => [60 * 60 * 24 * 365, 'year'], 56 | ]; 57 | 58 | if ($measureBy == '') 59 | { 60 | $usemeasure = 's'; 61 | 62 | for ($i = 0; $i < count($calcNum); $i++) 63 | { 64 | if ($clean <= $calcNum[$i][1]) 65 | { 66 | $usemeasure = $calcNum[$i][0]; 67 | $i = count($calcNum); 68 | } 69 | } 70 | } 71 | else 72 | { 73 | $usemeasure = $measureBy; 74 | } 75 | 76 | $datedifference = floor($clean / $calc[$usemeasure][0]); 77 | 78 | if ($autoText == true && ($currentDateTime == time())) 79 | { 80 | if ($raw < 0) 81 | { 82 | $prospect = ' from now'; 83 | } 84 | else 85 | { 86 | $prospect = ' ago'; 87 | } 88 | } 89 | else 90 | { 91 | $prospect = ''; 92 | } 93 | 94 | if ($referenceDateTime != 0) 95 | { 96 | if ($datedifference == 1) 97 | { 98 | return $datedifference . ' ' . $calc[$usemeasure][1] . ' ' . $prospect; 99 | } 100 | else 101 | { 102 | return $datedifference . ' ' . $calc[$usemeasure][1] . 's ' . $prospect; 103 | } 104 | } 105 | else 106 | { 107 | return 'No input time referenced.'; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /component/backend/src/Controller/ConsenttrailsController.php: -------------------------------------------------------------------------------- 1 | unregisterTask('unpublish'); 30 | $this->unregisterTask('archive'); 31 | $this->unregisterTask('trash'); 32 | $this->unregisterTask('report'); 33 | $this->unregisterTask('orderup'); 34 | $this->unregisterTask('orderdown'); 35 | $this->unregisterTask('delete'); 36 | $this->unregisterTask('publish'); 37 | $this->unregisterTask('reorder'); 38 | $this->unregisterTask('saveorder'); 39 | $this->unregisterTask('checkin'); 40 | $this->unregisterTask('saveOrderAjax'); 41 | $this->unregisterTask('runTransition'); 42 | } 43 | 44 | public function getModel($name = 'Consenttrails', $prefix = 'Administrator', $config = ['ignore_request' => true]) 45 | { 46 | return parent::getModel($name, $prefix, $config); 47 | } 48 | } -------------------------------------------------------------------------------- /component/backend/src/Controller/ControlpanelController.php: -------------------------------------------------------------------------------- 1 | registerControllerTasks(); 43 | } 44 | 45 | 46 | /** 47 | * Get user statistics (active / inactive users), used for displaying graphs 48 | * 49 | * @since 1.0.0 50 | * @noinspection PhpPossiblePolymorphicInvocationInspection 51 | */ 52 | public function userstats(): void 53 | { 54 | $stats = $this->getCache()->get(function () { 55 | return $this->getModel()->getUserStats(); 56 | }, [], 'userstats'); 57 | 58 | echo json_encode($stats); 59 | 60 | $this->app->close(); 61 | } 62 | 63 | /** 64 | * Return information about user accounts deleted, used for displaying graphs 65 | * 66 | * @throws Exception 67 | * @since 1.0.0 68 | * 69 | * @noinspection PhpPossiblePolymorphicInvocationInspection 70 | * @noinspection PhpUnused 71 | */ 72 | public function wipedstats(): void 73 | { 74 | $to = clone Factory::getDate(); 75 | $to->setTime(0, 0); 76 | $from = clone Factory::getDate(); 77 | $from->sub(new DateInterval('P1M')); 78 | $from->setTime(0, 0); 79 | $to->setTime(23, 59, 59); 80 | 81 | $stats = $this->getCache()->get(function ($from, $to) { 82 | $statsModel = $this->getModel('Stats', 'Administrator', ['ignore_request' => true]); 83 | 84 | return $statsModel->wipeStats($from, $to); 85 | 86 | }, [$from, $to], 'wipedstats'); 87 | 88 | echo json_encode($stats); 89 | 90 | $this->app->close(); 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /component/backend/src/Controller/EmailtemplatesController.php: -------------------------------------------------------------------------------- 1 | checkToken('get'); 25 | 26 | $returnURL = Route::_('index.php?option=com_datacompliance&view=Emailtemplates', false); 27 | $this->setRedirect($returnURL); 28 | 29 | $affected = TemplateEmails::updateAllTemplates(); 30 | 31 | $message = ($affected > 0) ? 32 | Text::plural('COM_DATACOMPLIANCE_EMAILTEMPLATES_LBL_N_UPDATED', $affected) : 33 | Text::_('COM_DATACOMPLIANCE_EMAILTEMPLATES_ERR_NOUPDATE'); 34 | 35 | $this->setMessage($message, ($affected > 0) ? 'success' : 'warning'); 36 | } 37 | 38 | public function resetEmails($cachable = false, $urlparams = []) 39 | { 40 | $this->checkToken('get'); 41 | 42 | $returnURL = Route::_('index.php?option=com_datacompliance&view=Emailtemplates', false); 43 | $this->setRedirect($returnURL); 44 | 45 | $affected = TemplateEmails::resetAllTemplates(); 46 | 47 | $message = ($affected > 0) ? 48 | Text::plural('COM_DATACOMPLIANCE_EMAILTEMPLATES_LBL_N_RESET', $affected) : 49 | Text::_('COM_DATACOMPLIANCE_EMAILTEMPLATES_ERR_RESET'); 50 | 51 | $this->setMessage($message, ($affected > 0) ? 'success' : 'error'); 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /component/backend/src/Controller/ExporttrailsController.php: -------------------------------------------------------------------------------- 1 | unregisterTask('unpublish'); 30 | $this->unregisterTask('archive'); 31 | $this->unregisterTask('trash'); 32 | $this->unregisterTask('report'); 33 | $this->unregisterTask('orderup'); 34 | $this->unregisterTask('orderdown'); 35 | $this->unregisterTask('delete'); 36 | $this->unregisterTask('publish'); 37 | $this->unregisterTask('reorder'); 38 | $this->unregisterTask('saveorder'); 39 | $this->unregisterTask('checkin'); 40 | $this->unregisterTask('saveOrderAjax'); 41 | $this->unregisterTask('runTransition'); 42 | } 43 | 44 | public function getModel($name = 'Exporttrails', $prefix = 'Administrator', $config = ['ignore_request' => true]) 45 | { 46 | return parent::getModel($name, $prefix, $config); 47 | } 48 | } -------------------------------------------------------------------------------- /component/backend/src/Controller/LifecycleController.php: -------------------------------------------------------------------------------- 1 | unregisterTask('unpublish'); 32 | $this->unregisterTask('archive'); 33 | $this->unregisterTask('trash'); 34 | $this->unregisterTask('report'); 35 | $this->unregisterTask('orderup'); 36 | $this->unregisterTask('orderdown'); 37 | $this->unregisterTask('delete'); 38 | $this->unregisterTask('publish'); 39 | $this->unregisterTask('reorder'); 40 | $this->unregisterTask('saveorder'); 41 | $this->unregisterTask('checkin'); 42 | $this->unregisterTask('saveOrderAjax'); 43 | $this->unregisterTask('runTransition'); 44 | } 45 | 46 | public function display($cachable = false, $urlparams = []) 47 | { 48 | $view = $this->getView(); 49 | $wipeModel = $this->getModel('Wipe'); 50 | $view->setModel($wipeModel, false); 51 | 52 | return parent::display($cachable, $urlparams); 53 | } 54 | 55 | 56 | public function getModel($name = 'Lifecycle', $prefix = 'Administrator', $config = ['ignore_request' => true]) 57 | { 58 | return parent::getModel($name, $prefix, $config); 59 | } 60 | } -------------------------------------------------------------------------------- /component/backend/src/Controller/UsertrailsController.php: -------------------------------------------------------------------------------- 1 | unregisterTask('unpublish'); 30 | $this->unregisterTask('archive'); 31 | $this->unregisterTask('trash'); 32 | $this->unregisterTask('report'); 33 | $this->unregisterTask('orderup'); 34 | $this->unregisterTask('orderdown'); 35 | $this->unregisterTask('delete'); 36 | $this->unregisterTask('publish'); 37 | $this->unregisterTask('reorder'); 38 | $this->unregisterTask('saveorder'); 39 | $this->unregisterTask('checkin'); 40 | $this->unregisterTask('saveOrderAjax'); 41 | $this->unregisterTask('runTransition'); 42 | } 43 | 44 | public function getModel($name = 'Usertrails', $prefix = 'Administrator', $config = ['ignore_request' => true]) 45 | { 46 | return parent::getModel($name, $prefix, $config); 47 | } 48 | } -------------------------------------------------------------------------------- /component/backend/src/Controller/WipetrailsController.php: -------------------------------------------------------------------------------- 1 | unregisterTask('unpublish'); 30 | $this->unregisterTask('archive'); 31 | $this->unregisterTask('trash'); 32 | $this->unregisterTask('report'); 33 | $this->unregisterTask('orderup'); 34 | $this->unregisterTask('orderdown'); 35 | $this->unregisterTask('delete'); 36 | $this->unregisterTask('publish'); 37 | $this->unregisterTask('reorder'); 38 | $this->unregisterTask('saveorder'); 39 | $this->unregisterTask('checkin'); 40 | $this->unregisterTask('saveOrderAjax'); 41 | $this->unregisterTask('runTransition'); 42 | } 43 | 44 | public function getModel($name = 'Wipetrails', $prefix = 'Administrator', $config = ['ignore_request' => true]) 45 | { 46 | return parent::getModel($name, $prefix, $config); 47 | } 48 | } -------------------------------------------------------------------------------- /component/backend/src/Extension/DataComplianceComponent.php: -------------------------------------------------------------------------------- 1 | get(DatabaseInterface::class); 34 | $this->getRegistry()->register('datacompliance', new DataCompliance($dbo)); 35 | 36 | // Make sure the Composer autoloader for our dependencies is loaded 37 | require_once __DIR__ . '/../../vendor/autoload.php'; 38 | } 39 | } -------------------------------------------------------------------------------- /component/backend/src/Field/ArticleField.php: -------------------------------------------------------------------------------- 1 | get(DatabaseInterface::class); 31 | $data = $params->toString('JSON'); 32 | 33 | $sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true)) 34 | ->update($db->qn('#__extensions')) 35 | ->set($db->qn('params') . ' = ' . $db->q($data)) 36 | ->where($db->qn('element') . ' = ' . $db->q('com_datacompliance')) 37 | ->where($db->qn('type') . ' = ' . $db->q('component')); 38 | 39 | $db->setQuery($sql); 40 | 41 | try 42 | { 43 | $db->execute(); 44 | 45 | // The component parameters are cached. We just changed them. Therefore we MUST reset the system cache which holds them. 46 | CacheCleaner::clearCacheGroups(['_system'], [0, 1]); 47 | } 48 | catch (\Exception $e) 49 | { 50 | // Don't sweat if it fails 51 | } 52 | 53 | // Reset ComponentHelper's cache 54 | $refClass = new \ReflectionClass(ComponentHelper::class); 55 | $refProp = $refClass->getProperty('components'); 56 | 57 | $refProp->setAccessible(true); 58 | 59 | if (version_compare(PHP_VERSION, '8.3.0', 'ge')) 60 | { 61 | $components = $refClass->getStaticPropertyValue('components'); 62 | } 63 | else 64 | { 65 | $components = $refProp->getValue(); 66 | } 67 | 68 | $components['com_datacompliance']->params = $params; 69 | 70 | if (version_compare(PHP_VERSION, '8.3.0', 'ge')) 71 | { 72 | $refClass->setStaticPropertyValue('components', $components); 73 | } 74 | else 75 | { 76 | $refProp->setValue($components); 77 | } 78 | 79 | } 80 | } -------------------------------------------------------------------------------- /component/backend/src/Mixin/ControllerCacheTrait.php: -------------------------------------------------------------------------------- 1 | input->get('option', 'com_datacompliance'); 44 | $hash = hash('md5', $group . $handler . $storage); 45 | 46 | if (isset(self::$cacheControllers[$hash])) 47 | { 48 | return self::$cacheControllers[$hash]; 49 | } 50 | 51 | $options = ['defaultgroup' => $group]; 52 | if (isset($storage)) 53 | { 54 | $options['storage'] = $storage; 55 | } 56 | 57 | $cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class) 58 | ->createCacheController($handler, $options); 59 | 60 | self::$cacheControllers[$hash] = $cache; 61 | 62 | return self::$cacheControllers[$hash]; 63 | } 64 | } -------------------------------------------------------------------------------- /component/backend/src/Mixin/ControllerEventsTrait.php: -------------------------------------------------------------------------------- 1 | task = $task; 41 | 42 | $task = strtolower($task); 43 | 44 | if (isset($this->taskMap[$task])) 45 | { 46 | $doTask = $this->taskMap[$task]; 47 | } 48 | elseif (isset($this->taskMap['__default'])) 49 | { 50 | $doTask = $this->taskMap['__default']; 51 | } 52 | else 53 | { 54 | throw new RuntimeException(Text::sprintf('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND', $task), 404); 55 | } 56 | 57 | // Execute onBeforeExecute and onBefore events 58 | $eventName = 'onBefore' . ucfirst($task); 59 | 60 | $this->triggerEvent('onBeforeExecute', [&$task]); 61 | $this->triggerEvent($eventName); 62 | 63 | // The task may have changed, so let's try that once again. 64 | if (isset($this->taskMap[$task])) 65 | { 66 | $doTask = $this->taskMap[$task]; 67 | } 68 | elseif (isset($this->taskMap['__default'])) 69 | { 70 | $doTask = $this->taskMap['__default']; 71 | } 72 | else 73 | { 74 | throw new RuntimeException(Text::sprintf('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND', $task), 404); 75 | } 76 | 77 | // Record the actual task being fired and execute it. 78 | $this->doTask = $doTask; 79 | $result = $this->$doTask(); 80 | 81 | // Execute onAfter and onAfterExecute events 82 | $eventName = 'onAfter' . ucfirst($task); 83 | 84 | $this->triggerEvent($eventName); 85 | $this->triggerEvent('onAfterExecute', [$task]); 86 | 87 | return $result; 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /component/backend/src/Mixin/ControllerRegisterTasksTrait.php: -------------------------------------------------------------------------------- 1 | registerDefaultTask($defaultTask); 35 | 36 | $refObj = new ReflectionObject($this); 37 | 38 | foreach ($refObj->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) 39 | { 40 | if ( 41 | !$refMethod->isUserDefined() || 42 | $refMethod->isStatic() || $refMethod->isAbstract() || $refMethod->isClosure() || 43 | $refMethod->isConstructor() || $refMethod->isDestructor() 44 | 45 | ) 46 | { 47 | continue; 48 | } 49 | 50 | $method = $refMethod->getName(); 51 | 52 | if (substr($method, 0, 1) == '_') 53 | { 54 | continue; 55 | } 56 | 57 | if (substr($method, 0, 8) == 'onBefore') 58 | { 59 | continue; 60 | } 61 | 62 | if (substr($method, 0, 7) == 'onAfter') 63 | { 64 | continue; 65 | } 66 | 67 | $this->registerTask($method, $method); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /component/backend/src/Mixin/TableAssertionTrait.php: -------------------------------------------------------------------------------- 1 | assert(!empty($value), $message); 50 | } 51 | 52 | /** 53 | * Assert that $value is set to one of $validValues or throw a RuntimeException with the $message language string 54 | * 55 | * @param mixed $value The value to check 56 | * @param array $validValues An array of valid values for $value 57 | * @param string $message The language key for the message to throw 58 | * 59 | * @throws RuntimeException 60 | */ 61 | protected function assertInArray($value, array $validValues, $message) 62 | { 63 | $this->assert(in_array($value, $validValues), $message); 64 | } 65 | 66 | /** 67 | * Assert that $value is set to none of $validValues. Otherwise throw a RuntimeException with the $message language 68 | * string. 69 | * 70 | * @param mixed $value The value to check 71 | * @param array $validValues An array of invalid values for $value 72 | * @param string $message The language key for the message to throw 73 | * 74 | * @throws \RuntimeException 75 | */ 76 | protected function assertNotInArray($value, array $validValues, $message) 77 | { 78 | $this->assert(!in_array($value, $validValues, true), $message); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /component/backend/src/Mixin/TableColumnAliasTrait.php: -------------------------------------------------------------------------------- 1 | hasField($name)) 17 | { 18 | $realColumn = $this->getColumnAlias($name); 19 | 20 | return $this->{$realColumn}; 21 | } 22 | 23 | return $this->{$name}; 24 | } 25 | 26 | /** 27 | * Magic setter, is aware of column aliases. 28 | * 29 | * This is required for using Joomla's batch processing to copy / move records of tables which do not have a catid 30 | * column. 31 | * 32 | * @param $name 33 | * @param $value 34 | */ 35 | public function __set($name, $value) 36 | { 37 | if ($this->hasField($name)) 38 | { 39 | $realColumn = $this->getColumnAlias($name); 40 | $this->{$realColumn} = $value; 41 | 42 | return; 43 | } 44 | 45 | $this->{$name} = $value; 46 | } 47 | 48 | 49 | } -------------------------------------------------------------------------------- /component/backend/src/Mixin/TableCreateModifyTrait.php: -------------------------------------------------------------------------------- 1 | updateModified; 23 | } 24 | 25 | public function setUpdateModified(bool $updateModified): void 26 | { 27 | $this->updateModified = $updateModified; 28 | } 29 | 30 | public function getUpdateCreated(): bool 31 | { 32 | return $this->updateCreated; 33 | } 34 | 35 | public function setUpdateCreated(bool $updateCreated): void 36 | { 37 | $this->updateCreated = $updateCreated; 38 | } 39 | 40 | public function onBeforeStore($updateNulls = false) 41 | { 42 | $date = Factory::getDate()->toSql(); 43 | $user = Factory::getApplication()->getIdentity(); 44 | 45 | // Set created date if not set. 46 | if ($this->updateCreated && $this->hasField('created') && !(int) $this->created) 47 | { 48 | $this->created = $date; 49 | } 50 | 51 | if ($this->updateModified && ($this->getId() > 0)) 52 | { 53 | // Existing item 54 | if ($this->hasField('modified_by')) 55 | { 56 | // Set a default value and update it only if we have a valid user object (i.e. we're not under CLI), otherwise the db will complain 57 | $this->modified_by = 0; 58 | 59 | if ($user) 60 | { 61 | $this->modified_by = $user->id; 62 | } 63 | } 64 | if ($this->hasField('modified')) 65 | { 66 | $this->modified = $date; 67 | 68 | } 69 | } 70 | elseif ($this->updateCreated || $this->updateModified) 71 | { 72 | // Field created_by can be set by the user, so we don't touch it if it's set. 73 | if ($this->updateCreated && $this->hasField('created_by') && empty($this->created_by)) 74 | { 75 | // Set a default value and update it only if we have a valid user object (i.e. we're not under CLI), otherwise the db will complain 76 | $this->created_by = 0; 77 | 78 | if ($user) 79 | { 80 | $this->created_by = $user->id; 81 | } 82 | } 83 | 84 | // Set modified to created date if not set 85 | if ($this->updateModified && $this->hasField('modified') && $this->hasField('created') && !(int) $this->modified) 86 | { 87 | $this->modified = $this->created; 88 | } 89 | 90 | // Set modified_by to created_by user if not set 91 | if ($this->updateModified && $this->hasField('modified_by') && $this->hasField('created_by') && empty($this->modified_by)) 92 | { 93 | $this->modified_by = $this->created_by; 94 | } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /component/backend/src/Mixin/TriggerEventTrait.php: -------------------------------------------------------------------------------- 1 | onBeforeSomething(123, 456) 24 | * 2. $this->checkACL('@something') if there is no onBeforeSomething and the event starts with onBefore 25 | * 3. Joomla! plugin event onComFoobarControllerItemBeforeSomething($this, 123, 456) 26 | * 27 | * @param string $event The name of the event, typically named onPredicateVerb e.g. onBeforeKick 28 | * @param array $arguments The arguments to pass to the event handlers 29 | * 30 | * @return bool 31 | */ 32 | protected function triggerEvent(string $event, array $arguments = []): bool 33 | { 34 | // If there is an object method for this event, call it 35 | if (method_exists($this, $event)) 36 | { 37 | /** 38 | * IMPORTANT! We use call_user_func_array() so we can pass arguments by reference. 39 | */ 40 | if (call_user_func_array([$this, $event], $arguments) === false) 41 | { 42 | return false; 43 | } 44 | } 45 | 46 | // All other event handlers live outside this object, therefore they need to be passed a reference to this 47 | // object as the first argument. 48 | array_unshift($arguments, $this); 49 | 50 | // If we have an "on" prefix for the event (e.g. onFooBar) remove it and stash it for later. 51 | $prefix = ''; 52 | 53 | if (substr($event, 0, 2) == 'on') 54 | { 55 | $prefix = 'on'; 56 | $event = substr($event, 2); 57 | } 58 | 59 | // Get the component name and object type from the namespace of the caller 60 | $callers = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS); 61 | $namespaceParts = explode('\\', $callers[1]['class']); 62 | $className = array_pop($namespaceParts); 63 | $objectType = array_pop($namespaceParts); 64 | array_pop($namespaceParts); 65 | $bareComponent = strtolower(array_pop($namespaceParts)); 66 | 67 | // Get the component/model prefix for the event 68 | $prefix .= 'Com' . ucfirst($bareComponent); 69 | $prefix .= ucfirst($className); 70 | 71 | // The event name will be something like onComFoobarItemsControllerBeforeSomething 72 | $event = $prefix . $event; 73 | 74 | // Call the Joomla! plugins 75 | $results = $this->triggerPluginEvent($event, $arguments); 76 | 77 | return !in_array(false, $results, true); 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /component/backend/src/Mixin/ViewTaskBasedEventsTrait.php: -------------------------------------------------------------------------------- 1 | getModel()->getState('task'); 19 | 20 | $eventName = 'onBefore' . ucfirst($task); 21 | $this->triggerEvent($eventName, [&$tpl]); 22 | 23 | parent::display($tpl); 24 | 25 | $eventName = 'onAfter' . ucfirst($task); 26 | $this->triggerEvent($eventName, [&$tpl]); 27 | } 28 | } -------------------------------------------------------------------------------- /component/backend/src/Model/ControlpanelModel.php: -------------------------------------------------------------------------------- 1 | 0, 33 | 'deleted' => 0, 34 | 'expired' => 0, 35 | ]; 36 | 37 | // Total number of users 38 | $db = $this->getDatabase(); 39 | $query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true)) 40 | ->select('COUNT(' . $db->quoteName('id') . ')') 41 | ->from($db->quoteName('#__users')); 42 | $totalUsers = $db->setQuery($query)->loadResult(); 43 | 44 | // Lifecycle (inactive) users 45 | /** @var WipeModel $wipeModel */ 46 | $wipeModel = $this->getMVCFactory()->createModel('Wipe', 'Administrator', ['ignore_request' => true]); 47 | $lifeCycleUsers = $wipeModel->getLifecycleUserIDs(true); 48 | $wipedUsers = $wipeModel->getWipedUserIDs(); 49 | 50 | $ret['deleted'] = count($wipedUsers); 51 | $ret['expired'] = count($lifeCycleUsers); 52 | $ret['active'] = $totalUsers - $ret['expired'] - $ret['deleted']; 53 | 54 | return $ret; 55 | } 56 | 57 | /** 58 | * Update the cached live site's URL for the front-end scheduling feature 59 | * 60 | * @return void 61 | * 62 | * @since 1.0.0 63 | */ 64 | public function updateMagicParameters(): void 65 | { 66 | $cParams = ComponentHelper::getParams('com_datacompliance'); 67 | $cParams->set('siteurl', Uri::root(false)); 68 | 69 | ComponentParams::save($cParams); 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /component/backend/src/Provider/RouterFactory.php: -------------------------------------------------------------------------------- 1 | namespace = $namespace; 39 | } 40 | 41 | /** 42 | * Registers the service provider with a DI container. 43 | * 44 | * @param Container $container The DI container. 45 | * 46 | * @return void 47 | * 48 | * @since 4.0.0 49 | */ 50 | public function register(Container $container) 51 | { 52 | $container->set( 53 | RouterFactoryInterface::class, 54 | function (Container $container) 55 | { 56 | return new \Akeeba\Component\DataCompliance\Administrator\Router\RouterFactory( 57 | $this->namespace, 58 | $container->get(DatabaseInterface::class), 59 | $container->get(MVCFactoryInterface::class) 60 | ); 61 | } 62 | ); 63 | } 64 | } -------------------------------------------------------------------------------- /component/backend/src/Router/RouterFactory.php: -------------------------------------------------------------------------------- 1 | namespace = $namespace; 30 | $this->factory = $factory; 31 | $this->db = $db; 32 | } 33 | 34 | public function createRouter(CMSApplicationInterface $application, AbstractMenu $menu): RouterInterface 35 | { 36 | $className = trim($this->namespace, '\\') . '\\' . ucfirst($application->getName()) . '\\Service\\Router'; 37 | 38 | if (!class_exists($className)) 39 | { 40 | throw new \RuntimeException('No router available for this application.'); 41 | } 42 | 43 | return new $className($application, $menu, $this->db, $this->factory); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /component/backend/src/Rule/.gitinclude: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeeba/com_datacompliance/6ed6e0a91fcd8ca57746e5c0c0432ed1e8a41593/component/backend/src/Rule/.gitinclude -------------------------------------------------------------------------------- /component/backend/src/Service/Html/DataCompliance.php: -------------------------------------------------------------------------------- 1 | setDatabase($db); 27 | } 28 | 29 | public function formatDate(?string $date, ?string $format = null, bool $tzAware = true): string 30 | { 31 | if (empty($date)) 32 | { 33 | return ''; 34 | } 35 | 36 | // Which timezone should I use? 37 | $tz = null; 38 | 39 | if ($tzAware !== false) 40 | { 41 | $userId = is_bool($tzAware) ? null : (int) $tzAware; 42 | 43 | try 44 | { 45 | $tzDefault = Factory::getApplication()->get('offset'); 46 | } 47 | catch (\Exception $e) 48 | { 49 | $tzDefault = new \DateTimeZone('GMT'); 50 | } 51 | 52 | $user = is_null($userId) 53 | ? Factory::getApplication()->getIdentity() 54 | : Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId); 55 | $tz = $user->getParam('timezone', $tzDefault); 56 | } 57 | 58 | $jDate = clone Factory::getDate($date, $tz); 59 | 60 | return $jDate->format($format ?: 'Y-m-d H:i T', true); 61 | } 62 | 63 | public static function booleanList(string $name, bool $value, string $label, ?string $id = null) 64 | { 65 | return (new FileLayout('joomla.form.field.radio.switcher'))->render([ 66 | 'id' => $id ?: $value, 67 | 'name' => $name, 68 | 'label' => $label, 69 | 'value' => $value ? 1 : 0, 70 | 'onchange' => '', 71 | 'dataAttribute' => '', 72 | 'readonly' => false, 73 | 'disabled' => false, 74 | 'class' => 'form-control', 75 | 'options' => [ 76 | HTMLHelper::_('select.option', '0', Text::_('JNO')), 77 | HTMLHelper::_('select.option', '1', Text::_('JYES')), 78 | ], 79 | ]); 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /component/backend/src/Table/ExporttrailsTable.php: -------------------------------------------------------------------------------- 1 | _supportNullValue = false; 37 | $this->setColumnAlias('created', 'created_on'); 38 | $this->setColumnAlias('id', 'datacompliance_exporttrail_id'); 39 | 40 | parent::__construct('#__datacompliance_exporttrails', 'datacompliance_exporttrail_id', $db, $dispatcher); 41 | } 42 | 43 | protected function onBeforeCheck() 44 | { 45 | if (empty($this->user_id)) 46 | { 47 | throw new \RuntimeException("Export audit trail: cannot have an empty user ID"); 48 | } 49 | 50 | $this->requester_ip = $this->requester_ip ?: (IpHelper::getIp() ?: '(CLI)'); 51 | } 52 | } -------------------------------------------------------------------------------- /component/backend/src/Table/GetPropertiesAwareTrait.php: -------------------------------------------------------------------------------- 1 | !empty($x) && !is_numeric($x) && ord(substr($x, 0, 1)) !== 0, ARRAY_FILTER_USE_KEY); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /component/backend/src/Table/UsertrailsTable.php: -------------------------------------------------------------------------------- 1 | _supportNullValue = false; 41 | $this->setColumnAlias('created', 'created_on'); 42 | $this->setColumnAlias('id', 'datacompliance_usertrail_id'); 43 | 44 | parent::__construct('#__datacompliance_usertrails', 'datacompliance_usertrail_id', $db, $dispatcher); 45 | 46 | $this->items = []; 47 | } 48 | 49 | public function onAfterReset() 50 | { 51 | $this->items = []; 52 | } 53 | 54 | protected function onBeforeStore(&$updateNulls) 55 | { 56 | $this->onBeforeStoreCreateModifyAware($updateNulls); 57 | 58 | if (is_array($this->items) || is_object($this->items)) 59 | { 60 | $this->items = json_encode($this->items); 61 | } 62 | } 63 | 64 | protected function onAfterStore(&$result, $updateNulls) 65 | { 66 | if (!is_array($this->items)) 67 | { 68 | $this->items = @json_decode($this->items ?: '{}', true) ?? []; 69 | } 70 | } 71 | 72 | protected function onBeforeBind(&$src, &$ignore = []) 73 | { 74 | $src = (array)$src; 75 | 76 | if (!is_array($src['items'] ?? '')) 77 | { 78 | $this->items = @json_decode($src['params'] ?: '{}', true) ?? []; 79 | } 80 | } 81 | 82 | protected function onBeforeCheck() 83 | { 84 | if (empty($this->user_id)) 85 | { 86 | throw new \RuntimeException("Data wipe audit trail: cannot have an empty user ID"); 87 | } 88 | 89 | $this->requester_ip = $this->requester_ip ?: (IpHelper::getIp() ?: '(CLI)'); 90 | 91 | if (empty($this->items)) 92 | { 93 | $this->items = []; 94 | } 95 | } 96 | 97 | 98 | } -------------------------------------------------------------------------------- /component/backend/src/View/Consenttrails/HtmlView.php: -------------------------------------------------------------------------------- 1 | getModel(); 78 | $this->items = $model->getItems(); 79 | $this->pagination = $model->getPagination(); 80 | $this->state = $model->getState(); 81 | $this->filterForm = $model->getFilterForm(); 82 | $this->activeFilters = $model->getActiveFilters(); 83 | $this->isEmptyState = $this->get('IsEmptyState'); 84 | 85 | // Check for errors. 86 | if (count($errors = $this->get('Errors'))) 87 | { 88 | throw new GenericDataException(implode("\n", $errors), 500); 89 | } 90 | 91 | if (!\count($this->items) && $this->isEmptyState) 92 | { 93 | $this->setLayout('emptystate'); 94 | } 95 | 96 | ToolbarHelper::title(Text::_('COM_DATACOMPLIANCE_TITLE_CONSENTTRAILS'), 'datacompliance'); 97 | ToolbarHelper::back('COM_DATACOMPLIANCE_TITLE_DASHBOARD_SHORT', 'index.php?option=com_datacompliance'); 98 | 99 | parent::display($tpl); 100 | } 101 | 102 | 103 | } -------------------------------------------------------------------------------- /component/backend/src/View/Controlpanel/HtmlView.php: -------------------------------------------------------------------------------- 1 | document->getWebAssetManager() 25 | ->useScript('com_datacompliance.controlpanel') 26 | ->useScript('com_datacompliance.chart_moment_adapter'); 27 | 28 | $this->document->addScriptOptions( 29 | 'com_datacompliance.controlpanel.userGraphsUrl', 30 | Route::_('index.php?option=com_datacompliance&task=controlpanel.userstats', false, Route::TLS_IGNORE, true) 31 | ); 32 | $this->document->addScriptOptions( 33 | 'com_datacompliance.controlpanel.wipedGraphsUrl', 34 | Route::_('index.php?option=com_datacompliance&task=controlpanel.wipedstats', false, Route::TLS_IGNORE, true) 35 | ); 36 | 37 | Text::script('COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_INACTIVE'); 38 | Text::script('COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_ACTIVE'); 39 | Text::script('COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_DELETED'); 40 | Text::script('COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_USER'); 41 | Text::script('COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_ADMIN'); 42 | Text::script('COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_LIFECYCLE'); 43 | 44 | ToolbarHelper::title(Text::_('COM_DATACOMPLIANCE_TITLE_DASHBOARD'), 'datacompliance'); 45 | ToolbarHelper::preferences('com_datacompliance'); 46 | 47 | parent::display($tpl); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /component/backend/src/View/Emailtemplates/HtmlView.php: -------------------------------------------------------------------------------- 1 | getModel(); 78 | $this->items = $model->getItems(); 79 | $this->pagination = $model->getPagination(); 80 | $this->state = $model->getState(); 81 | $this->filterForm = $model->getFilterForm(); 82 | $this->activeFilters = $model->getActiveFilters(); 83 | $this->isEmptyState = $this->get('IsEmptyState'); 84 | 85 | // Check for errors. 86 | if (count($errors = $this->get('Errors'))) 87 | { 88 | throw new GenericDataException(implode("\n", $errors), 500); 89 | } 90 | 91 | if (!\count($this->items) && $this->isEmptyState) 92 | { 93 | $this->setLayout('emptystate'); 94 | } 95 | 96 | ToolbarHelper::title(Text::_('COM_DATACOMPLIANCE_EXPORTTRAILS'), 'datacompliance'); 97 | ToolbarHelper::back('COM_DATACOMPLIANCE_TITLE_DASHBOARD_SHORT', 'index.php?option=com_datacompliance'); 98 | 99 | parent::display($tpl); 100 | } 101 | 102 | 103 | } -------------------------------------------------------------------------------- /component/backend/src/View/Lifecycle/HtmlView.php: -------------------------------------------------------------------------------- 1 | getModel(); 78 | $this->items = $model->getItems(); 79 | $this->pagination = $model->getPagination(); 80 | $this->state = $model->getState(); 81 | $this->filterForm = $model->getFilterForm(); 82 | $this->activeFilters = $model->getActiveFilters(); 83 | $this->isEmptyState = $this->get('IsEmptyState'); 84 | 85 | // Check for errors. 86 | if (count($errors = $this->get('Errors'))) 87 | { 88 | throw new GenericDataException(implode("\n", $errors), 500); 89 | } 90 | 91 | if (!\count($this->items) && $this->isEmptyState) 92 | { 93 | $this->setLayout('emptystate'); 94 | } 95 | 96 | ToolbarHelper::title(Text::_('COM_DATACOMPLIANCE_TITLE_LIFECYCLE'), 'datacompliance'); 97 | ToolbarHelper::back('COM_DATACOMPLIANCE_TITLE_DASHBOARD_SHORT', 'index.php?option=com_datacompliance'); 98 | 99 | parent::display($tpl); 100 | } 101 | 102 | 103 | } -------------------------------------------------------------------------------- /component/backend/src/View/Usertrails/HtmlView.php: -------------------------------------------------------------------------------- 1 | getModel(); 78 | $this->items = $model->getItems(); 79 | $this->pagination = $model->getPagination(); 80 | $this->state = $model->getState(); 81 | $this->filterForm = $model->getFilterForm(); 82 | $this->activeFilters = $model->getActiveFilters(); 83 | $this->isEmptyState = $this->get('IsEmptyState'); 84 | 85 | // Check for errors. 86 | if (count($errors = $this->get('Errors'))) 87 | { 88 | throw new GenericDataException(implode("\n", $errors), 500); 89 | } 90 | 91 | if (!\count($this->items) && $this->isEmptyState) 92 | { 93 | $this->setLayout('emptystate'); 94 | } 95 | 96 | ToolbarHelper::title(Text::_('COM_DATACOMPLIANCE_USERTRAILS'), 'datacompliance'); 97 | ToolbarHelper::back('COM_DATACOMPLIANCE_TITLE_DASHBOARD_SHORT', 'index.php?option=com_datacompliance'); 98 | 99 | parent::display($tpl); 100 | } 101 | 102 | 103 | } -------------------------------------------------------------------------------- /component/backend/src/View/Wipetrails/HtmlView.php: -------------------------------------------------------------------------------- 1 | getModel(); 78 | $this->items = $model->getItems(); 79 | $this->pagination = $model->getPagination(); 80 | $this->state = $model->getState(); 81 | $this->filterForm = $model->getFilterForm(); 82 | $this->activeFilters = $model->getActiveFilters(); 83 | $this->isEmptyState = $this->get('IsEmptyState'); 84 | 85 | // Check for errors. 86 | if (count($errors = $this->get('Errors'))) 87 | { 88 | throw new GenericDataException(implode("\n", $errors), 500); 89 | } 90 | 91 | if (!\count($this->items) && $this->isEmptyState) 92 | { 93 | $this->setLayout('emptystate'); 94 | } 95 | 96 | ToolbarHelper::title(Text::_('COM_DATACOMPLIANCE_WIPETRAILS'), 'datacompliance'); 97 | ToolbarHelper::back('COM_DATACOMPLIANCE_TITLE_DASHBOARD_SHORT', 'index.php?option=com_datacompliance'); 98 | 99 | parent::display($tpl); 100 | } 101 | 102 | 103 | } -------------------------------------------------------------------------------- /component/backend/tmpl/consenttrails/emptystate.php: -------------------------------------------------------------------------------- 1 | 'COM_DATACOMPLIANCE_CONSENTTRAILS', 14 | 'formURL' => 'index.php?option=com_datacompliance&view=consenttrails', 15 | //'helpURL' => '', 16 | 'icon' => 'fa fa-check-square', 17 | //'createURL' => '', 18 | ]; 19 | 20 | echo LayoutHelper::render('joomla.content.emptystate', $displayData); 21 | -------------------------------------------------------------------------------- /component/backend/tmpl/controlpanel/default.php: -------------------------------------------------------------------------------- 1 | loadAnyTemplate('Controlpanel/joomla_eol'); 15 | } 16 | 17 | ?> 18 | 19 |
20 |
21 |
22 | loadTemplate('icons') ?> 23 |
24 |
25 | loadTemplate('stats') ?> 26 |
27 |
28 |
-------------------------------------------------------------------------------- /component/backend/tmpl/controlpanel/default_stats.php: -------------------------------------------------------------------------------- 1 | 14 | 15 |
16 |

17 | 18 |

19 | 20 |
21 | 22 |
23 |
24 | 25 |
26 |

27 | 28 |

29 | 30 |
31 | 32 |
33 |
34 | 35 | -------------------------------------------------------------------------------- /component/backend/tmpl/controlpanel/joomla_eol.php: -------------------------------------------------------------------------------- 1 | 12 | 1760475600): ?> 13 |
14 | Joomla! 4 has reached End of Service 15 |

16 | Joomla! 4 became End of Service on October 15th, 2025. 17 |

18 |

19 | Our software for Joomla! 4 is also End of Life. We will no longer provide any updates or support. 20 |

21 |

22 | Kindly note that we started showing these notices since October 15th, 2023 — two years before the planned End of Life of our software for Joomla! 4. 23 |

24 |
25 | 1728939600): ?> 26 |
27 | Joomla! 4 is approaching End of Service 28 |

29 | Joomla! 4 is currently in security–only maintenance. It will become End of Service on October 15th, 2025. 30 |

31 |

32 | Our software for Joomla! 4 is also in security-only maintenance. We only provide security updates and limited support for it until October 15th, 2025. 33 |

34 |

35 | You need to update your site to Joomla! 5 as soon as possible. We will not provide any updates or support after October 15th, 2025. Moreover, we do not guarantee an update path to Joomla! 5 and beyond will exist after October 15th, 2025. 36 |

37 |
38 | 1697317200): ?> 39 |
40 | Joomla! 4 is approaching Security Maintenance 41 |

42 | Joomla! 4 will enter security–only maintenance on October 15th, 2024. It will become End of Service on October 15th, 2025. 43 |

44 |

45 | Will provide full support and updates for our Joomla! 4 software until October 15th, 2024. From then until October 15th, 2025 we will only provide security updates and limited support. We will not provide any updates or support after October 15th, 2025. 46 |

47 |

48 | We urge you to upgrade your site to Joomla! 5 before October 15th, 2024. Please note that we do not guarantee an update path to Joomla! 5 and beyond after October 15th, 2025. 49 |

50 |
51 | 52 | -------------------------------------------------------------------------------- /component/backend/tmpl/emailtemplates/default.php: -------------------------------------------------------------------------------- 1 | getFormToken(); 17 | ?> 18 | 19 |
20 |

21 | 22 |

23 |
24 |

25 | 26 |

27 |

28 | 30 | 31 | 32 | 33 |

34 |
35 |
36 | 37 |
38 |

39 | 40 |

41 |
42 |
43 |
44 | 54 |
55 | 56 |
57 |
58 |
59 | 69 |
70 | 71 |
72 |
73 |
74 |
75 |
-------------------------------------------------------------------------------- /component/backend/tmpl/exporttrails/emptystate.php: -------------------------------------------------------------------------------- 1 | 'COM_DATACOMPLIANCE_EXPORTTRAILS', 14 | 'formURL' => 'index.php?option=com_datacompliance&view=exporttrails', 15 | //'helpURL' => '', 16 | 'icon' => 'fa fa-file-export', 17 | //'createURL' => '', 18 | ]; 19 | 20 | echo LayoutHelper::render('joomla.content.emptystate', $displayData); 21 | -------------------------------------------------------------------------------- /component/backend/tmpl/lifecycle/emptystate.php: -------------------------------------------------------------------------------- 1 | 'COM_DATACOMPLIANCE_LIFECYCLE', 14 | 'formURL' => 'index.php?option=com_datacompliance&view=lifecycle', 15 | //'helpURL' => '', 16 | 'icon' => 'fa fa-user-clock', 17 | //'createURL' => '', 18 | ]; 19 | 20 | echo LayoutHelper::render('joomla.content.emptystate', $displayData); 21 | -------------------------------------------------------------------------------- /component/backend/tmpl/usertrails/emptystate.php: -------------------------------------------------------------------------------- 1 | 'COM_DATACOMPLIANCE_USERTRAILS', 14 | 'formURL' => 'index.php?option=com_datacompliance&view=usertrails', 15 | //'helpURL' => '', 16 | 'icon' => 'fa fa-users', 17 | //'createURL' => '', 18 | ]; 19 | 20 | echo LayoutHelper::render('joomla.content.emptystate', $displayData); 21 | -------------------------------------------------------------------------------- /component/backend/tmpl/wipetrails/emptystate.php: -------------------------------------------------------------------------------- 1 | 'COM_DATACOMPLIANCE_WIPETRAILS', 14 | 'formURL' => 'index.php?option=com_datacompliance&view=wipetrails', 15 | //'helpURL' => '', 16 | 'icon' => 'fa fa-user-minus', 17 | //'createURL' => '', 18 | ]; 19 | 20 | echo LayoutHelper::render('joomla.content.emptystate', $displayData); 21 | -------------------------------------------------------------------------------- /component/datacompliance.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | COM_DATACOMPLIANCE 8 | 2025-02-10 9 | Nicholas K. Dionysopoulos 10 | nicholas@akeeba.com 11 | https://www.akeeba.com 12 | Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 13 | This component in released under the GNU/GPL v3 or later license 14 | 3.2.3 15 | COM_DATACOMPLIANCE_XML_DESCRIPTION 16 | Akeeba\Component\DataCompliance 17 | 18 | 19 | 20 | language 21 | src 22 | tmpl 23 | 24 | .htaccess 25 | web.config 26 | 27 | 28 | 29 | 30 | en-GB/com_datacompliance.ini 31 | 32 | 33 | 34 | 35 | 36 | COM_DATACOMPLIANCE 37 | 38 | 39 | 40 | assets 41 | forms 42 | language 43 | layouts 44 | services 45 | sql 46 | src 47 | tmpl 48 | vendor 49 | 50 | access.xml 51 | config.xml 52 | 53 | 54 | 55 | 56 | en-GB/com_datacompliance.ini 57 | en-GB/com_datacompliance.sys.ini 58 | 59 | 60 | 61 | 62 | 63 | 64 | css 65 | js 66 | 67 | joomla.asset.json 68 | 69 | 70 | 71 | 72 | sql/install.mysql.utf8.sql 73 | 74 | 75 | 76 | 77 | 78 | sql/uninstall.mysql.utf8.sql 79 | 80 | 81 | 82 | 83 | 84 | sql/updates/mysql 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /component/frontend/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Order deny,allow 3 | Deny from all 4 | 5 | 6 | 7 | Require all denied 8 | 9 | 10 | -------------------------------------------------------------------------------- /component/frontend/src/Controller/OptionsController.php: -------------------------------------------------------------------------------- 1 | setDatabase($db); 33 | $this->setMVCFactory($factory); 34 | 35 | $this->registerView(new RouterViewConfiguration('options')); 36 | 37 | parent::__construct($app, $menu); 38 | 39 | $this->attachRule(new MenuRules($this)); 40 | $this->attachRule(new StandardRules($this)); 41 | $this->attachRule(new NomenuRules($this)); 42 | } 43 | 44 | public function build(&$query) 45 | { 46 | $query['view'] = strtolower($query['view'] ?? 'options'); 47 | 48 | $segments = parent::build($query); 49 | 50 | $task = strtolower($query['task'] ?? 'options'); 51 | 52 | if (in_array($task, ['export', 'wipe'])) 53 | { 54 | $segments[] = $task; 55 | unset($query['task']); 56 | } 57 | 58 | return $segments; 59 | } 60 | 61 | public function parse(&$segments) 62 | { 63 | $query = parent::parse($segments); 64 | 65 | $lastSegment = count($segments) ? array_pop($segments) : null; 66 | 67 | if (empty($lastSegment)) 68 | { 69 | return $query; 70 | } 71 | 72 | if (in_array($lastSegment, ['export', 'wipe'])) 73 | { 74 | $query['view'] = 'options'; 75 | $query['task'] = $lastSegment; 76 | 77 | return $query; 78 | } 79 | 80 | $segments[] = $lastSegment; 81 | 82 | return $query; 83 | } 84 | 85 | 86 | } -------------------------------------------------------------------------------- /component/frontend/src/View/Options/HtmlView.php: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | COM_DATACOMPLIANCE_MENUMANAGER_VIEW_OPTIONS_LABEL 15 | COM_DATACOMPLIANCE_MENUMANAGER_VIEW_OPTIONS_DESC 16 | 17 | 18 | -------------------------------------------------------------------------------- /component/frontend/web.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /component/media/css/backend.css: -------------------------------------------------------------------------------- 1 | /*!* 2 | * @package AkeebaDataCompliance 3 | * @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 4 | * @license GNU General Public License version 3, or later 5 | *//*!* 6 | * @package AkeebaDataCompliance 7 | * @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 8 | * @license GNU General Public License version 3, or later 9 | */@font-face{font-family:"Akeeba Products";src:url("../fonts/Akeeba-Products.woff");font-display:swap}span.icon-datacompliance:before,span.icon-datacompliance-j4:before{font-family:"Akeeba Products" !important;font-style:normal !important;font-weight:normal !important;line-height:1 !important;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;position:relative;content:"O"}/*# sourceMappingURL=backend.css.map */ 10 | -------------------------------------------------------------------------------- /component/media/css/backend.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["backend.scss","sources/_title_icon.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GCMA,WACE,8BACA,yCACA,kBAKA,mEACE,yCACA,6BACA,8BACA,yBACA,mCACA,kCAEA,qBACA,kBAEA","file":"backend.css"} -------------------------------------------------------------------------------- /component/media/css/backend.scss: -------------------------------------------------------------------------------- 1 | /*!* 2 | * @package AkeebaDataCompliance 3 | * @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 4 | * @license GNU General Public License version 3, or later 5 | */ 6 | 7 | @import 'sources/title_icon.scss'; 8 | 9 | -------------------------------------------------------------------------------- /component/media/css/j5.css: -------------------------------------------------------------------------------- 1 | /*!* 2 | * @package DocImport 3 | * @copyright Copyright (c)2011-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 4 | * @license GNU General Public License version 3, or later 5 | */@media screen{section#content main .alert[class*=alert-] a[class*=btn-]{color:var(--btn-color)}section#content main .alert[class*=alert-] a.btn-primary{color:var(--btn-primary-color)}section#content main .alert[class*=alert-] a.btn-secondary{color:var(--btn-secondary-color)}section#content main .alert[class*=alert-] a[class*=btn-outline-]{color:var(--btn-color)}section#content main .alert[class*=alert-] a.btn:hover{color:var(--btn-hover-color)}}/*# sourceMappingURL=j5.css.map */ 6 | -------------------------------------------------------------------------------- /component/media/css/j5.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["j5.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA;AAAA,GAMA,cAII,iFAEA,wFACA,4FAEA,yFAEA","file":"j5.css"} -------------------------------------------------------------------------------- /component/media/css/j5.scss: -------------------------------------------------------------------------------- 1 | /*!* 2 | * @package DocImport 3 | * @copyright Copyright (c)2011-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 4 | * @license GNU General Public License version 3, or later 5 | */ 6 | 7 | @media screen 8 | { 9 | // Fix button colors in alert DIVs 10 | section#content main .alert[class*="alert-"] a { 11 | &[class*='btn-'] { color: var(--btn-color); } 12 | 13 | &.btn-primary { color: var(--btn-primary-color); } 14 | &.btn-secondary { color: var(--btn-secondary-color); } 15 | 16 | &[class*='btn-outline-'] { color: var(--btn-color); } 17 | 18 | &.btn:hover { color: var(--btn-hover-color); } 19 | } 20 | } -------------------------------------------------------------------------------- /component/media/css/sources/_title_icon.scss: -------------------------------------------------------------------------------- 1 | /*!* 2 | * @package AkeebaDataCompliance 3 | * @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 4 | * @license GNU General Public License version 3, or later 5 | */ 6 | 7 | @font-face { 8 | font-family: "Akeeba Products"; 9 | src: url("../fonts/Akeeba-Products.woff"); 10 | font-display: swap; 11 | } 12 | 13 | span.icon-datacompliance, 14 | span.icon-datacompliance-j4, { 15 | &:before{ 16 | font-family: 'Akeeba Products' !important; 17 | font-style: normal !important; 18 | font-weight: normal !important; 19 | line-height: 1 !important; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | 23 | display: inline-block; 24 | position: relative; 25 | 26 | content:'\004F'; 27 | } 28 | } -------------------------------------------------------------------------------- /component/media/fonts/Akeeba-Products.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeeba/com_datacompliance/6ed6e0a91fcd8ca57746e5c0c0432ed1e8a41593/component/media/fonts/Akeeba-Products.woff -------------------------------------------------------------------------------- /component/media/joomla.asset.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json", 3 | "name": "com_datacompliance", 4 | "version": "3.2.3", 5 | "description": "Akeeba DataCompliance", 6 | "license": "GPL-3.0-or-later", 7 | "assets": [ 8 | { 9 | "name": "com_datacompliance.backend", 10 | "description": "Backend CSS", 11 | "type": "style", 12 | "uri": "com_datacompliance/backend.css" 13 | }, 14 | 15 | { 16 | "name": "com_datacompliance.j5", 17 | "description": "Backend CSS for Joomla! 5", 18 | "type": "style", 19 | "uri": "com_datacompliance/j5.css" 20 | }, 21 | 22 | { 23 | "name": "com_datacompliance.controlpanel", 24 | "description": "Control Panel JavaScript", 25 | "type": "script", 26 | "uri": "com_datacompliance/controlpanel.min.js", 27 | "dependencies": [ 28 | "core" 29 | ], 30 | "attributes": { 31 | "defer": true 32 | } 33 | }, 34 | 35 | { 36 | "name": "com_datacompliance.options", 37 | "description": "Data Options JavaScript", 38 | "type": "script", 39 | "uri": "com_datacompliance/options.min.js", 40 | "dependencies": [ 41 | "core" 42 | ], 43 | "attributes": { 44 | "defer": true 45 | } 46 | }, 47 | 48 | { 49 | "name": "com_datacompliance.chart", 50 | "description": "Charts.js — renders charts and graphs", 51 | "type": "script", 52 | "uri": "https://cdn.jsdelivr.net/npm/chart.js@3.2.1/dist/chart.min.js", 53 | "attributes": { 54 | "defer": true 55 | } 56 | }, 57 | { 58 | "name": "com_datacompliance.chart_moment_adapter", 59 | "description": "Moment adapter for Charts.js", 60 | "type": "script", 61 | "dependencies": [ 62 | "com_datacompliance.chart", 63 | "com_datacompliance.moment" 64 | ], 65 | "uri": "https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@0.1.1", 66 | "attributes": { 67 | "defer": true 68 | } 69 | }, 70 | { 71 | "name": "com_datacompliance.moment", 72 | "description": "Moment — handles date conversions in JavaScript", 73 | "type": "script", 74 | "uri": "https://cdn.jsdelivr.net/npm/moment@2.27.0", 75 | "attributes": { 76 | "defer": true 77 | } 78 | }, 79 | 80 | { 81 | "name": "com_datacompliance.backend", 82 | "type": "preset", 83 | "dependencies": [ 84 | "com_datacompliance.backend#style" 85 | ] 86 | } 87 | ] 88 | } -------------------------------------------------------------------------------- /component/media/js/controlpanel.min.js: -------------------------------------------------------------------------------- 1 | "use strict";if("undefined"==typeof akeeba)var akeeba={};"undefined"==typeof akeeba.DataCompliance&&(akeeba.DataCompliance={}),akeeba.DataCompliance.ControlPanel={},akeeba.DataCompliance.ControlPanel.loadUserGraphs=function(){var a=Joomla.getOptions("com_datacompliance.controlpanel.userGraphsUrl");Joomla.request({url:a,method:"GET",perform:!0,onSuccess:function(a){var b=JSON.parse(a),c=document.getElementById("adcExpiredUsers").getContext("2d");new Chart(c,{type:"doughnut",data:{labels:[Joomla.Text._("COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_INACTIVE"),Joomla.Text._("COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_ACTIVE"),Joomla.Text._("COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_DELETED")],datasets:[{data:[b.expired,b.active,b.deleted],backgroundColor:["#ff0000","#009900","#666666"],borderWidth:6}]},options:{cutout:"50%",plugins:{legend:{display:!1}}}})}})},akeeba.DataCompliance.ControlPanel.loadWipedGraphs=function(){var a=Joomla.getOptions("com_datacompliance.controlpanel.wipedGraphsUrl");Joomla.request({url:a,method:"GET",perform:!0,onSuccess:function(a){var b=JSON.parse(a),c=document.getElementById("adcWipedUsers").getContext("2d");new Chart(c,{type:"bar",data:{datasets:[{label:Joomla.Text._("COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_USER"),backgroundColor:"#009900",data:b.user},{label:Joomla.Text._("COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_ADMIN"),backgroundColor:"#ff0000",data:b.admin},{label:Joomla.Text._("COM_DATACOMPLIANCE_CONTROLPANEL_LBL_CHART_LIFECYCLE"),backgroundColor:"#666666",data:b.lifecycle}]},options:{scales:{x:{stacked:!0,type:"time",time:{unit:"day"},distribution:"linear"},y:{type:"linear",stacked:!0,ticks:{callback:function(a){return""+a}}}},plugins:{legend:{display:!0,position:"right"}}}})}})},akeeba.DataCompliance.ControlPanel.loadUserGraphs(),akeeba.DataCompliance.ControlPanel.loadWipedGraphs(); 2 | //# sourceMappingURL=controlpanel.min.js.map -------------------------------------------------------------------------------- /component/media/js/options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @package AkeebaDataCompliance 3 | * @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 4 | * @license GNU General Public License version 3, or later 5 | */ 6 | "use strict"; 7 | 8 | document.querySelectorAll('a.akeebaDataComplianceArticleToggle').forEach(function(element) { 9 | element.addEventListener('click', function(event) { 10 | event.preventDefault(); 11 | 12 | const elTarget = document.getElementById('datacompliance-article'); 13 | 14 | if (elTarget.style.display === "none") 15 | { 16 | elTarget.style.display = "block"; 17 | 18 | return false; 19 | } 20 | 21 | elTarget.style.display = "none"; 22 | 23 | return false; 24 | }) 25 | }); -------------------------------------------------------------------------------- /component/media/js/options.min.js: -------------------------------------------------------------------------------- 1 | "use strict";document.querySelectorAll("a.akeebaDataComplianceArticleToggle").forEach(function(a){a.addEventListener("click",function(a){a.preventDefault();var b=document.getElementById("datacompliance-article");return"none"===b.style.display?(b.style.display="block",!1):(b.style.display="none",!1)})}); 2 | //# sourceMappingURL=options.min.js.map -------------------------------------------------------------------------------- /component/media/js/options.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"options.min.js","names":["document","querySelectorAll","forEach","element","addEventListener","event","preventDefault","elTarget","getElementById","style","display"],"sources":["options.js"],"sourcesContent":["/**\n * @package AkeebaDataCompliance\n * @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd\n * @license GNU General Public License version 3, or later\n */\n\"use strict\";\n\ndocument.querySelectorAll('a.akeebaDataComplianceArticleToggle').forEach(function(element) {\n element.addEventListener('click', function(event) {\n event.preventDefault();\n\n const elTarget = document.getElementById('datacompliance-article');\n\n if (elTarget.style.display === \"none\")\n {\n elTarget.style.display = \"block\";\n\n return false;\n }\n\n elTarget.style.display = \"none\";\n\n return false;\n })\n});"],"mappings":"AAKA,YAAY,CAEZA,QAAQ,CAACC,gBAAgB,CAAC,qCAAqC,CAAC,CAACC,OAAO,CAAC,SAASC,CAAO,CAAE,CACxFA,CAAO,CAACC,gBAAgB,CAAC,OAAO,CAAE,SAASC,CAAK,CAAE,CAC9CA,CAAK,CAACC,cAAc,CAAC,CAAC,CAEtB,GAAM,CAAAC,CAAQ,CAAGP,QAAQ,CAACQ,cAAc,CAAC,wBAAwB,CAAC,CAAC,MAEpC,MAAM,GAAjCD,CAAQ,CAACE,KAAK,CAACC,OAAkB,EAEjCH,CAAQ,CAACE,KAAK,CAACC,OAAO,CAAG,OAAO,MAKpCH,CAAQ,CAACE,KAAK,CAACC,OAAO,CAAG,MAAM,IAGnC,CAAC,CACJ,CAAC,CAAC","ignoreList":[]} -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "akeeba/com_datacompliance", 3 | "description": "Akeeba DataCompliance for Joomla", 4 | "config": { 5 | "vendor-dir": "component/backend/vendor", 6 | "platform": { 7 | "php": "7.4.0" 8 | } 9 | }, 10 | "require": { 11 | "php": "^7.4.0||^8.0.0", 12 | "akeeba/s3": "dev-development", 13 | "ext-simplexml": "*", 14 | "ext-dom": "*", 15 | "ext-json": "*" 16 | } 17 | } -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "03733880220dac2f3e15bd3a16d83013", 8 | "packages": [ 9 | { 10 | "name": "akeeba/s3", 11 | "version": "dev-development", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/akeeba/s3.git", 15 | "reference": "f4f20122476b27ffa243f8a49aeaf7813cbc8932" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/akeeba/s3/zipball/f4f20122476b27ffa243f8a49aeaf7813cbc8932", 20 | "reference": "f4f20122476b27ffa243f8a49aeaf7813cbc8932", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-curl": "*", 25 | "ext-simplexml": "*", 26 | "php": ">=7.1.0 <8.4" 27 | }, 28 | "default-branch": true, 29 | "type": "library", 30 | "autoload": { 31 | "files": [ 32 | "src/aliasing.php" 33 | ], 34 | "psr-4": { 35 | "Akeeba\\S3\\": "src" 36 | } 37 | }, 38 | "notification-url": "https://packagist.org/downloads/", 39 | "license": [ 40 | "GPL-3.0-or-later" 41 | ], 42 | "authors": [ 43 | { 44 | "name": "Nicholas K. Dionysopoulos", 45 | "email": "nicholas_NO_SPAM_PLEASE@akeeba.com", 46 | "homepage": "http://www.dionysopoulos.me", 47 | "role": "Lead Developer" 48 | } 49 | ], 50 | "description": "A compact, dependency-less Amazon S3 API client implementing the most commonly used features", 51 | "homepage": "https://github.com/akeeba/s3", 52 | "keywords": [ 53 | "s3" 54 | ], 55 | "support": { 56 | "issues": "https://github.com/akeeba/s3/issues", 57 | "source": "https://github.com/akeeba/s3/tree/development" 58 | }, 59 | "time": "2025-01-06T14:29:07+00:00" 60 | } 61 | ], 62 | "packages-dev": [], 63 | "aliases": [], 64 | "minimum-stability": "stable", 65 | "stability-flags": { 66 | "akeeba/s3": 20 67 | }, 68 | "prefer-stable": false, 69 | "prefer-lowest": false, 70 | "platform": { 71 | "php": "^7.4.0||^8.0.0", 72 | "ext-simplexml": "*", 73 | "ext-dom": "*", 74 | "ext-json": "*" 75 | }, 76 | "platform-dev": {}, 77 | "platform-overrides": { 78 | "php": "7.4.0" 79 | }, 80 | "plugin-api-version": "2.6.0" 81 | } 82 | -------------------------------------------------------------------------------- /documentation/images/control_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeeba/com_datacompliance/6ed6e0a91fcd8ca57746e5c0c0432ed1e8a41593/documentation/images/control_panel.png -------------------------------------------------------------------------------- /documentation/images/self_service_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeeba/com_datacompliance/6ed6e0a91fcd8ca57746e5c0c0432ed1e8a41593/documentation/images/self_service_page.png -------------------------------------------------------------------------------- /modules/admin/.gitinclude: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeeba/com_datacompliance/6ed6e0a91fcd8ca57746e5c0c0432ed1e8a41593/modules/admin/.gitinclude -------------------------------------------------------------------------------- /modules/site/.gitinclude: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeeba/com_datacompliance/6ed6e0a91fcd8ca57746e5c0c0432ed1e8a41593/modules/site/.gitinclude -------------------------------------------------------------------------------- /plugins/console/datacompliance/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Order deny,allow 3 | Deny from all 4 | 5 | 6 | 7 | Require all denied 8 | 9 | 10 | -------------------------------------------------------------------------------- /plugins/console/datacompliance/datacompliance.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | PLG_CONSOLE_DATACOMPLIANCE 10 | 3.2.3 11 | 2025-02-10 12 | Nicholas K. Dionysopoulos 13 | nicholas@dionysopoulos.me 14 | https://www.akeeba.com 15 | Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 16 | GNU General Public License version 3, or later 17 | PLG_CONSOLE_DATACOMPLIANCE_XML_DESCRIPTION 18 | Akeeba\Plugin\Console\DataCompliance 19 | 20 | services 21 | src 22 | 23 | .htaccess 24 | web.config 25 | 26 | 27 | en-GB/plg_console_datacompliance.ini 28 | en-GB/plg_console_datacompliance.sys.ini 29 | 30 | -------------------------------------------------------------------------------- /plugins/console/datacompliance/language/en-GB/plg_console_datacompliance.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeeba/com_datacompliance/6ed6e0a91fcd8ca57746e5c0c0432ed1e8a41593/plugins/console/datacompliance/language/en-GB/plg_console_datacompliance.ini -------------------------------------------------------------------------------- /plugins/console/datacompliance/language/en-GB/plg_console_datacompliance.sys.ini: -------------------------------------------------------------------------------- 1 | PLG_CONSOLE_DATACOMPLIANCE="Console - Data Compliance" 2 | PLG_CONSOLE_DATACOMPLIANCE_XML_DESCRIPTION="Adds Akeeba Data Compliance commands to the Joomla console application (cli/joomla.php)." -------------------------------------------------------------------------------- /plugins/console/datacompliance/language/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /plugins/console/datacompliance/services/provider.php: -------------------------------------------------------------------------------- 1 | registerServiceProvider(new MVCFactory('Akeeba\\Component\\DataCompliance')); 40 | 41 | $container->set( 42 | PluginInterface::class, 43 | function (Container $container) { 44 | $config = (array) PluginHelper::getPlugin('console', 'datacompliance'); 45 | $subject = $container->get(DispatcherInterface::class); 46 | $mvcFactory = $container->get(MVCFactoryInterface::class); 47 | 48 | $plugin = new DataCompliance($subject, $config, $mvcFactory); 49 | 50 | $plugin->setApplication(Factory::getApplication()); 51 | $plugin->setDatabase($container->get(DatabaseInterface::class)); 52 | 53 | return $plugin; 54 | } 55 | ); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /plugins/console/datacompliance/web.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /plugins/datacompliance/ars/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Order deny,allow 3 | Deny from all 4 | 5 | 6 | 7 | Require all denied 8 | 9 | 10 | -------------------------------------------------------------------------------- /plugins/datacompliance/ars/ars.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | PLG_DATACOMPLIANCE_ARS 10 | Nicholas K. Dionysopoulos 11 | nicholas@akeeba.com 12 | https://www.akeeba.com 13 | Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 14 | GNU General Public License version 3, or later 15 | 2025-02-10 16 | 3.2.3 17 | PLG_DATACOMPLIANCE_ARS_DESCRIPTION 18 | Akeeba\Plugin\DataCompliance\ARS 19 | 20 | 21 | language 22 | services 23 | src 24 | 25 | .htaccess 26 | web.config 27 | 28 | 29 | 30 | en-GB/plg_datacompliance_ars.ini 31 | en-GB/plg_datacompliance_ars.sys.ini 32 | 33 | 34 | -------------------------------------------------------------------------------- /plugins/datacompliance/ars/language/en-GB/plg_datacompliance_ars.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_DATACOMPLIANCE_ARS="Data Compliance – Akeeba Release System" 6 | PLG_DATACOMPLIANCE_ARS_DESCRIPTION="GDPR Data Compliance plugin for Akeeba Release System" 7 | PLG_DATACOMPLIANCE_ARS_DOMAINNAME="Akeeba Release System" 8 | 9 | PLG_DATACOMPLIANCE_ARS_ACTIONS_1="Download log entries linked to your account wil be deleted. Clarification: any download attempts you performed without being logged into our site and / or using a valid Download ID are NOT deleted as they cannot be automatically linked to your user account and we have no technical means to make that connection either." -------------------------------------------------------------------------------- /plugins/datacompliance/ars/language/en-GB/plg_datacompliance_ars.sys.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_DATACOMPLIANCE_ARS="Data Compliance – Akeeba Release System" 6 | PLG_DATACOMPLIANCE_ARS_DESCRIPTION="GDPR Data Compliance plugin for Akeeba Release System" -------------------------------------------------------------------------------- /plugins/datacompliance/ars/services/provider.php: -------------------------------------------------------------------------------- 1 | registerServiceProvider($mvcFactory); 34 | 35 | $container->set( 36 | PluginInterface::class, 37 | function (Container $container) { 38 | $config = (array) PluginHelper::getPlugin('datacompliance', 'ars'); 39 | $dispatcher = $container->get(DispatcherInterface::class); 40 | 41 | $plugin = new ARS( 42 | $dispatcher, $config, new \Joomla\CMS\MVC\Factory\MVCFactory('Akeeba\\Component\\DataCompliance') 43 | ); 44 | 45 | $plugin->setApplication(Factory::getApplication()); 46 | $plugin->setDatabase($container->get(DatabaseInterface::class)); 47 | 48 | return $plugin; 49 | } 50 | ); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /plugins/datacompliance/ars/web.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /plugins/datacompliance/ats/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Order deny,allow 3 | Deny from all 4 | 5 | 6 | 7 | Require all denied 8 | 9 | 10 | -------------------------------------------------------------------------------- /plugins/datacompliance/ats/ats.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | PLG_DATACOMPLIANCE_ATS 10 | Nicholas K. Dionysopoulos 11 | nicholas@akeeba.com 12 | https://www.akeeba.com 13 | Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 14 | GNU General Public License version 3, or later 15 | 2025-02-10 16 | 3.2.3 17 | PLG_DATACOMPLIANCE_ATS_DESCRIPTION 18 | Akeeba\Plugin\DataCompliance\ATS 19 | 20 | language 21 | services 22 | src 23 | 24 | .htaccess 25 | web.config 26 | 27 | 28 | 29 | en-GB/plg_datacompliance_ats.ini 30 | en-GB/plg_datacompliance_ats.sys.ini 31 | 32 | 33 | -------------------------------------------------------------------------------- /plugins/datacompliance/ats/language/en-GB/plg_datacompliance_ats.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_DATACOMPLIANCE_ATS="Data Compliance – Akeeba Ticket System" 6 | PLG_DATACOMPLIANCE_ATS_DESCRIPTION="GDPR Data Compliance plugin for Akeeba Ticket System" 7 | PLG_DATACOMPLIANCE_ATS_DOMAINNAME="Akeeba Ticket System" 8 | 9 | PLG_DATACOMPLIANCE_ATS_ACTIONS_1="All of your private and public tickets will be deleted, including their posts and attachments." -------------------------------------------------------------------------------- /plugins/datacompliance/ats/language/en-GB/plg_datacompliance_ats.sys.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_DATACOMPLIANCE_ATS="Data Compliance – Akeeba Ticket System" 6 | PLG_DATACOMPLIANCE_ATS_DESCRIPTION="GDPR Data Compliance plugin for Akeeba Ticket System" -------------------------------------------------------------------------------- /plugins/datacompliance/ats/services/provider.php: -------------------------------------------------------------------------------- 1 | registerServiceProvider($mvcFactory); 34 | 35 | $container->set( 36 | PluginInterface::class, 37 | function (Container $container) { 38 | $config = (array) PluginHelper::getPlugin('datacompliance', 'ats'); 39 | $dispatcher = $container->get(DispatcherInterface::class); 40 | 41 | $plugin = new ATS( 42 | $dispatcher, $config, new \Joomla\CMS\MVC\Factory\MVCFactory('Akeeba\\Component\\DataCompliance') 43 | ); 44 | 45 | $plugin->setApplication(Factory::getApplication()); 46 | $plugin->setDatabase($container->get(DatabaseInterface::class)); 47 | 48 | return $plugin; 49 | } 50 | ); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /plugins/datacompliance/ats/web.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /plugins/datacompliance/email/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Order deny,allow 3 | Deny from all 4 | 5 | 6 | 7 | Require all denied 8 | 9 | 10 | -------------------------------------------------------------------------------- /plugins/datacompliance/email/email.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | PLG_DATACOMPLIANCE_EMAIL 10 | Nicholas K. Dionysopoulos 11 | nicholas@akeeba.com 12 | https://www.akeeba.com 13 | Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 14 | GNU General Public License version 3, or later 15 | 2025-02-10 16 | 3.2.3 17 | PLG_DATACOMPLIANCE_EMAIL_DESCRIPTION 18 | Akeeba\Plugin\DataCompliance\Email 19 | 20 | 21 | language 22 | services 23 | src 24 | 25 | .htaccess 26 | web.config 27 | 28 | 29 | 30 | en-GB/plg_datacompliance_email.ini 31 | en-GB/plg_datacompliance_email.sys.ini 32 | 33 | 34 | 35 | 36 |
37 | 43 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 64 |
65 |
66 |
67 | 68 |
69 | -------------------------------------------------------------------------------- /plugins/datacompliance/email/language/en-GB/plg_datacompliance_email.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_DATACOMPLIANCE_EMAIL="Data Compliance - Send emails on account deletion" 6 | PLG_DATACOMPLIANCE_EMAIL_DESCRIPTION="Send an email to the user and / or the administrator of the site whenever we are deleting a user account. IMPORTANT! You MUST publish this plugin BEFORE the “Data Compliance - Joomla! Core User Data” plugin. Otherwise this plugin will not be able to find the user's email address since it will have been deleted before this plugin runs!" 7 | 8 | PLG_DATACOMPLIANCE_EMAIL_USERS_LABEL="Email users" 9 | PLG_DATACOMPLIANCE_EMAIL_USERS_DESC="Email the user when they delete their user account, letting them know what steps we took to ensure their personal information is properly deleted." 10 | 11 | PLG_DATACOMPLIANCE_EMAIL_ADMINS_LABEL="Email administrators" 12 | PLG_DATACOMPLIANCE_EMAIL_ADMINS_DESC="Email the administrators of the site (see below) whenever a user deletes their account." 13 | 14 | PLG_DATACOMPLIANCE_JOOMLA_ADMINEMAILS_LABEL="Administrator emails" 15 | PLG_DATACOMPLIANCE_JOOMLA_ADMINEMAILS_DESC="Enter the emails of administrators, one address per line. The email addresses MUST belong to active Super User accounts on this site, otherwise they are ignored. If you leave this empty all Super Users of the site will be emailed when the Email administrators option above is enabled." -------------------------------------------------------------------------------- /plugins/datacompliance/email/language/en-GB/plg_datacompliance_email.sys.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_DATACOMPLIANCE_EMAIL="Data Compliance - Send emails on account deletion" 6 | PLG_DATACOMPLIANCE_EMAIL_DESCRIPTION="Send an email to the user and / or the administrator of the site whenever we are deleting a user account." 7 | -------------------------------------------------------------------------------- /plugins/datacompliance/email/services/provider.php: -------------------------------------------------------------------------------- 1 | registerServiceProvider($mvcFactory); 34 | 35 | $container->set( 36 | PluginInterface::class, 37 | function (Container $container) { 38 | $config = (array) PluginHelper::getPlugin('datacompliance', 'email'); 39 | $dispatcher = $container->get(DispatcherInterface::class); 40 | 41 | $plugin = new Email( 42 | $dispatcher, $config, new \Joomla\CMS\MVC\Factory\MVCFactory('Akeeba\\Component\\DataCompliance') 43 | ); 44 | 45 | $plugin->setApplication(Factory::getApplication()); 46 | $plugin->setDatabase($container->get(DatabaseInterface::class)); 47 | 48 | return $plugin; 49 | } 50 | ); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /plugins/datacompliance/email/web.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /plugins/datacompliance/joomla/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Order deny,allow 3 | Deny from all 4 | 5 | 6 | 7 | Require all denied 8 | 9 | 10 | -------------------------------------------------------------------------------- /plugins/datacompliance/joomla/language/en-GB/plg_datacompliance_joomla.sys.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_DATACOMPLIANCE_JOOMLA="Data Compliance – Joomla! Core User Data" 6 | PLG_DATACOMPLIANCE_JOOMLA_DESCRIPTION="GDPR Data Compliance plugin for Joomla! Core User Data" -------------------------------------------------------------------------------- /plugins/datacompliance/joomla/services/provider.php: -------------------------------------------------------------------------------- 1 | registerServiceProvider($mvcFactory); 34 | 35 | $container->set( 36 | PluginInterface::class, 37 | function (Container $container) { 38 | $config = (array) PluginHelper::getPlugin('datacompliance', 'joomla'); 39 | $dispatcher = $container->get(DispatcherInterface::class); 40 | 41 | $plugin = new Joomla( 42 | $dispatcher, $config, new \Joomla\CMS\MVC\Factory\MVCFactory('Akeeba\\Component\\DataCompliance') 43 | ); 44 | 45 | $plugin->setApplication(Factory::getApplication()); 46 | $plugin->setDatabase($container->get(DatabaseInterface::class)); 47 | 48 | return $plugin; 49 | } 50 | ); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /plugins/datacompliance/joomla/web.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /plugins/datacompliance/loginguard/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Order deny,allow 3 | Deny from all 4 | 5 | 6 | 7 | Require all denied 8 | 9 | 10 | -------------------------------------------------------------------------------- /plugins/datacompliance/loginguard/language/en-GB/plg_datacompliance_loginguard.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_DATACOMPLIANCE_LOGINGUARD="Data Compliance – Akeeba LoginGuard" 6 | PLG_DATACOMPLIANCE_LOGINGUARD_DESCRIPTION="GDPR Data Compliance plugin for Akeeba LoginGuard" 7 | PLG_DATACOMPLIANCE_LOGINGUARD_DOMAINNAME="Akeeba LoginGuard" 8 | 9 | PLG_DATACOMPLIANCE_LOGINGUARD_ACTIONS_1="Your Two Step Verification / Two Factor Authentication preferences will be deleted." -------------------------------------------------------------------------------- /plugins/datacompliance/loginguard/language/en-GB/plg_datacompliance_loginguard.sys.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_DATACOMPLIANCE_LOGINGUARD="Data Compliance – Akeeba LoginGuard" 6 | PLG_DATACOMPLIANCE_LOGINGUARD_DESCRIPTION="GDPR Data Compliance plugin for Akeeba LoginGuard" -------------------------------------------------------------------------------- /plugins/datacompliance/loginguard/loginguard.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | PLG_DATACOMPLIANCE_LOGINGUARD 10 | Nicholas K. Dionysopoulos 11 | nicholas@akeeba.com 12 | https://www.akeeba.com 13 | Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 14 | GNU General Public License version 3, or later 15 | 2025-02-10 16 | 3.2.3 17 | PLG_DATACOMPLIANCE_LOGINGUARD_DESCRIPTION 18 | Akeeba\Plugin\DataCompliance\LoginGuard 19 | 20 | 21 | language 22 | services 23 | src 24 | 25 | .htaccess 26 | web.config 27 | 28 | 29 | 30 | en-GB/plg_datacompliance_loginguard.ini 31 | en-GB/plg_datacompliance_loginguard.sys.ini 32 | 33 | 34 | -------------------------------------------------------------------------------- /plugins/datacompliance/loginguard/services/provider.php: -------------------------------------------------------------------------------- 1 | registerServiceProvider($mvcFactory); 34 | 35 | $container->set( 36 | PluginInterface::class, 37 | function (Container $container) { 38 | $params = (array) PluginHelper::getPlugin('datacompliance', 'loginguard'); 39 | $dispatcher = $container->get(DispatcherInterface::class); 40 | 41 | $plugin = new LoginGuard( 42 | $dispatcher, $params, new \Joomla\CMS\MVC\Factory\MVCFactory('Akeeba\\Component\\DataCompliance') 43 | ); 44 | 45 | $plugin->setApplication(Factory::getApplication()); 46 | $plugin->setDatabase($container->get(DatabaseInterface::class)); 47 | 48 | return $plugin; 49 | } 50 | ); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /plugins/datacompliance/loginguard/web.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /plugins/datacompliance/s3/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Order deny,allow 3 | Deny from all 4 | 5 | 6 | 7 | Require all denied 8 | 9 | 10 | -------------------------------------------------------------------------------- /plugins/datacompliance/s3/language/en-GB/plg_datacompliance_s3.sys.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_DATACOMPLIANCE_S3="Data Compliance - Upload user deletion audit trail to S3-compatible storage" 6 | PLG_DATACOMPLIANCE_S3_DESCRIPTION="GDPR Data Compliance plugin to automatically upload the user deletion audit trail records as JSON files to Amazon S3 and other S3-compatible storage providers." 7 | -------------------------------------------------------------------------------- /plugins/datacompliance/s3/services/provider.php: -------------------------------------------------------------------------------- 1 | registerServiceProvider($mvcFactory); 34 | 35 | $container->set( 36 | PluginInterface::class, 37 | function (Container $container) { 38 | $params = (array) PluginHelper::getPlugin('datacompliance', 's3'); 39 | $dispatcher = $container->get(DispatcherInterface::class); 40 | 41 | $plugin = new S3( 42 | $dispatcher, $params, new \Joomla\CMS\MVC\Factory\MVCFactory('Akeeba\\Component\\DataCompliance') 43 | ); 44 | 45 | $plugin->setApplication(Factory::getApplication()); 46 | $plugin->setDatabase($container->get(DatabaseInterface::class)); 47 | 48 | return $plugin; 49 | } 50 | ); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /plugins/datacompliance/s3/web.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /plugins/system/datacompliance/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Order deny,allow 3 | Deny from all 4 | 5 | 6 | 7 | Require all denied 8 | 9 | 10 | -------------------------------------------------------------------------------- /plugins/system/datacompliance/datacompliance.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | PLG_SYSTEM_DATACOMPLIANCE 10 | Nicholas K. Dionysopoulos 11 | nicholas@akeeba.com 12 | https://www.akeeba.com 13 | Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 14 | GNU General Public License version 3, or later 15 | 2025-02-10 16 | 3.2.3 17 | PLG_SYSTEM_DATACOMPLIANCE_DESCRIPTION 18 | Akeeba\Plugin\System\DataCompliance 19 | 20 | 21 | services 22 | src 23 | 24 | .htaccess 25 | web.config 26 | 27 | 28 | 29 | en-GB/plg_system_datacompliance.ini 30 | en-GB/plg_system_datacompliance.sys.ini 31 | 32 | 33 | 34 | 35 |
36 | 42 | 43 |
44 |
45 |
46 |
47 | -------------------------------------------------------------------------------- /plugins/system/datacompliance/language/en-GB/plg_system_datacompliance.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_SYSTEM_DATACOMPLIANCE="System – Data Compliance" 6 | PLG_SYSTEM_DATACOMPLIANCE_DESCRIPTION="Shows the captive login page for the Akeeba Data Compliance component for GDPR compliance" 7 | 8 | PLG_SYSTEM_DATACOMPLIANCE_EXEMPT_LABEL="Exempt components / views / tasks" 9 | PLG_SYSTEM_DATACOMPLIANCE_EXEMPT_DESC="Combinations of component, view and task which are exempt from the captive login. Comma or newline separated entries. Each entry is in the format component.view.task. Use * in any position to match any value." 10 | 11 | PLG_SYSTEM_DATACOMPLIANCE_MSG_MUSTACCEPT="You must provide your consent below to continue using our site." -------------------------------------------------------------------------------- /plugins/system/datacompliance/language/en-GB/plg_system_datacompliance.sys.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_SYSTEM_DATACOMPLIANCE="System – Data Compliance" 6 | PLG_SYSTEM_DATACOMPLIANCE_DESCRIPTION="Shows the captive login page for the Akeeba Data Compliance component for GDPR compliance" 7 | -------------------------------------------------------------------------------- /plugins/system/datacompliance/services/provider.php: -------------------------------------------------------------------------------- 1 | registerServiceProvider($mvcFactory); 35 | 36 | $container->set( 37 | PluginInterface::class, 38 | function (Container $container) { 39 | $params = (array) PluginHelper::getPlugin('system', 'datacompliance'); 40 | $dispatcher = $container->get(DispatcherInterface::class); 41 | 42 | $plugin = new DataCompliance( 43 | $dispatcher, $params, new \Joomla\CMS\MVC\Factory\MVCFactory('Akeeba\\Component\\DataCompliance') 44 | ); 45 | 46 | $plugin->setApplication(Factory::getApplication()); 47 | $plugin->setDatabase($container->get(DatabaseInterface::class)); 48 | 49 | return $plugin; 50 | } 51 | ); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /plugins/system/datacompliance/web.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /plugins/user/datacompliance/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Order deny,allow 3 | Deny from all 4 | 5 | 6 | 7 | Require all denied 8 | 9 | 10 | -------------------------------------------------------------------------------- /plugins/user/datacompliance/datacompliance.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | PLG_USER_DATACOMPLIANCE 10 | Nicholas K. Dionysopoulos 11 | nicholas@akeeba.com 12 | https://www.akeeba.com 13 | Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 14 | GNU General Public License version 3, or later 15 | 2025-02-10 16 | 3.2.3 17 | PLG_USER_DATACOMPLIANCE_DESCRIPTION 18 | Akeeba\Plugin\User\DataCompliance 19 | 20 | 21 | datacompliance 22 | language 23 | services 24 | src 25 | 26 | .htaccess 27 | web.config 28 | 29 | 30 | 31 | en-GB/plg_user_datacompliance.ini 32 | en-GB/plg_user_datacompliance.sys.ini 33 | 34 | 35 | -------------------------------------------------------------------------------- /plugins/user/datacompliance/datacompliance/datacompliance.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 | 10 |
13 | 22 |
23 |
24 |
-------------------------------------------------------------------------------- /plugins/user/datacompliance/datacompliance/list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 | 10 |
13 | 21 |
22 |
23 |
-------------------------------------------------------------------------------- /plugins/user/datacompliance/language/en-GB/plg_user_datacompliance.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_USER_DATACOMPLIANCE="User – Data Compliance" 6 | PLG_USER_DATACOMPLIANCE_DESCRIPTION="Records user profile changes with the Akeeba Data Compliance component for GDPR compliance. Also displays the special user field which links to the Data Compliance self–service page for Personally Identifiable Information." 7 | 8 | PLG_USER_DATACOMPLIANCE_HEADER="Personal Data Options" 9 | PLG_USER_DATACOMPLIANCE_FIELD_LABEL="Your Personal Data Options" 10 | PLG_USER_DATACOMPLIANCE_FIELD_DESC="Click this link to go into a page where you can can give / withdraw your consent to your personal data being processed, as well as export your personal data or delete your profile with us. When you use this button any other changes you have made to your user profile without previously saving them will be lost." 11 | PLG_USER_DATACOMPLIANCE_FIELD_INFO="Manage your personal data options" 12 | PLG_USER_DATACOMPLIANCE_FIELD_INFO_ADMIN="Manage the user's data options" 13 | 14 | PLG_USER_DATACOMPLIANCE_FIELD_HASCONSENT_LABEL="Consent to personal data processing" 15 | 16 | PLG_USER_DATACOMPLIANCE_ERR_NOUSER="No such user." 17 | PLG_USER_DATACOMPLIANCE_ERR_NOCOMPONENT="The Akeeba Data Compliance component has not been installed or is currently deactivated." -------------------------------------------------------------------------------- /plugins/user/datacompliance/language/en-GB/plg_user_datacompliance.sys.ini: -------------------------------------------------------------------------------- 1 | ;; @package AkeebaDataCompliance 2 | ;; @copyright Copyright (c)2018-2025 Nicholas K. Dionysopoulos / Akeeba Ltd 3 | ;; @license GNU General Public License version 3, or later 4 | 5 | PLG_USER_DATACOMPLIANCE="User – Data Compliance" 6 | PLG_USER_DATACOMPLIANCE_DESCRIPTION="Records user profile changes with the Akeeba Data Compliance component for GDPR compliance. Also displays the special user field which links to the Data Compliance self–service page for Personally Identifiable Information." 7 | -------------------------------------------------------------------------------- /plugins/user/datacompliance/services/provider.php: -------------------------------------------------------------------------------- 1 | registerServiceProvider($mvcFactory); 34 | 35 | $container->set( 36 | PluginInterface::class, 37 | function (Container $container) { 38 | $params = (array) PluginHelper::getPlugin('user', 'datacompliance'); 39 | $dispatcher = $container->get(DispatcherInterface::class); 40 | 41 | $plugin = new DataCompliance( 42 | $dispatcher, $params, new \Joomla\CMS\MVC\Factory\MVCFactory('Akeeba\\Component\\DataCompliance') 43 | ); 44 | 45 | $plugin->setApplication(Factory::getApplication()); 46 | $plugin->setDatabase($container->get(DatabaseInterface::class)); 47 | 48 | return $plugin; 49 | } 50 | ); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /plugins/user/datacompliance/src/Field/DatacomplianceField.php: -------------------------------------------------------------------------------- 1 | form->getData()->get('id', null); 31 | 32 | if (is_null($user_id)) 33 | { 34 | return Text::_('PLG_USER_DATACOMPLIANCE_ERR_NOUSER'); 35 | } 36 | 37 | /** 38 | * Why not use HMVC to display the Options page inside the user form just like we do with LoginGuard? Our page 39 | * has a FORM element. This is rendered inside Joomla's own FORM element, since all user fields are form fields, 40 | * right? However, Joomla seems to have some magic JavaScript which removes the nested form elements (since 41 | * you can't normally have nested form elements), moving their contents one level up. This of course completely 42 | * breaks the behaviour of our software. So instead I have to add a link to the actual page. Too tired to battle 43 | * with this. 44 | */ 45 | 46 | $url = Route::_('index.php?option=com_datacompliance&view=options&user_id=' . $user_id); 47 | $user = Factory::getApplication()->getIdentity() ?? new User(); 48 | $isAdmin = $user_id != $user->id; 49 | $key = $isAdmin ? 'PLG_USER_DATACOMPLIANCE_FIELD_INFO_ADMIN' : 'PLG_USER_DATACOMPLIANCE_FIELD_INFO'; 50 | $labelText = Text::_($key); 51 | $html = <<< HTML 52 | $labelText 53 | 54 | HTML; 55 | 56 | // Display the content 57 | return $html; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /plugins/user/datacompliance/web.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------