Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5] add2scheduler-privacyconsents #2946

Closed
jgerman-bot opened this issue Sep 4, 2023 · 0 comments · Fixed by #2951
Closed

[5] add2scheduler-privacyconsents #2946

jgerman-bot opened this issue Sep 4, 2023 · 0 comments · Fixed by #2951

Comments

@jgerman-bot
Copy link

New language relevant PR in upstream repo: joomla/joomla-cms#40553 Here are the upstream changes:

Click to expand the diff!
diff --git a/administrator/components/com_admin/script.php b/administrator/components/com_admin/script.php
index 1eefc688d162..768a04aa6df0 100644
--- a/administrator/components/com_admin/script.php
+++ b/administrator/components/com_admin/script.php
@@ -2346,11 +2346,86 @@ public function postflight($action, $installer)
             return false;
         }
 
+        if (!$this->migratePrivacyconsentConfiguration()) {
+            return false;
+        }
+
         $this->setGuidedToursUid();
 
         return true;
     }
 
+    /**
+     * Migrate privacyconsents system plugin configuration
+     *
+     * @return  boolean  True on success
+     *
+     * @since   5.0.0
+     */
+    private function migratePrivacyconsentConfiguration(): bool
+    {
+        $db = Factory::getDbo();
+
+        try {
+            // Get the PrivacyConsent system plugin's parameters
+            $row = $db->setQuery(
+                $db->getQuery(true)
+                    ->select($db->quotename('enabled'), $db->quoteName('params'))
+                    ->from($db->quoteName('#__extensions'))
+                    ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
+                    ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
+                    ->where($db->quoteName('element') . ' = ' . $db->quote('privacyconsent'))
+            )->loadObject();
+        } catch (Exception $e) {
+            echo Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()) . '<br>';
+
+            return false;
+        }
+
+        // If not existing or disbled there is nothing to migrate
+        if (!$row || !$row->enabled) {
+            return true;
+        }
+
+        $params = new Registry($row->params);
+
+        // If consent expiration was disbled there is nothing to migrate
+        if (!$params->get('enabled', 0)) {
+            return true;
+        }
+
+        /** @var SchedulerComponent $component */
+        $component = Factory::getApplication()->bootComponent('com_scheduler');
+
+        /** @var TaskModel $model */
+        $model = $component->getMVCFactory()->createModel('Task', 'Administrator', ['ignore_request' => true]);
+        $task  = [
+            'title'           => 'PrivacyConsent',
+            'type'            => 'privacy.consent',
+            'execution_rules' => [
+                'rule-type'     => 'interval-days',
+                'interval-days' => $params->get('cachetimeout', 30),
+                'exec-time'     => gmdate('H:i', $params->get('lastrun', time())),
+                'exec-day'      => gmdate('d'),
+            ],
+            'state'  => 1,
+            'params' => [
+                'consentexpiration' => $params->get('consentexpiration', 360),
+                'remind'            => $params->get('remind', 30),
+            ],
+        ];
+
+        try {
+            $model->save($task);
+        } catch (Exception $e) {
+            echo Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()) . '<br>';
+
+            return false;
+        }
+
+        return true;
+    }
+
     /**
      * Migrate TinyMCE editor plugin configuration
      *
diff --git a/administrator/components/com_admin/sql/updates/mysql/5.0.0-2023-09-02.sql b/administrator/components/com_admin/sql/updates/mysql/5.0.0-2023-09-02.sql
index 5552693d22c2..27c020cd2fb5 100644
--- a/administrator/components/com_admin/sql/updates/mysql/5.0.0-2023-09-02.sql
+++ b/administrator/components/com_admin/sql/updates/mysql/5.0.0-2023-09-02.sql
@@ -1,9 +1,12 @@
 INSERT INTO `#__extensions` (`name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `locked`, `manifest_cache`, `params`, `custom_data`, `checked_out`, `checked_out_time`, `ordering`, `state`) VALUES
+('plg_task_privacyconsent', 'plugin', 'privacyconsent', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
 ('plg_task_rotatelogs', 'plugin', 'rotatelogs', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
 ('plg_task_updatenotification', 'plugin', 'updatenotification', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0);
 
 INSERT INTO `#__mail_templates` (`template_id`, `extension`, `language`, `subject`, `body`, `htmlbody`, `attachments`, `params`) VALUES
+('plg_task_privacyconsent.request.reminder', 'plg_task_privacyconsent', '', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_SUBJECT', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_BODY', '', '', '{"tags":["sitename","url","tokenurl","formurl","token"]}'),
 ('plg_task_updatenotification.mail', 'plg_task_updatenotification', '', 'PLG_TASK_UPDATENOTIFICATION_EMAIL_SUBJECT', 'PLG_TASK_UPDATENOTIFICATION_EMAIL_BODY', '', '', '{"tags":["newversion","curversion","sitename","url","link","releasenews"]}');
 
-DELETE FROM `#__mail_templates` WHERE `template_id` = 'plg_system_updatenotification.mail';
+DELETE FROM `#__mail_templates` WHERE `template_id` IN ('plg_system_privacyconsent.request.reminder', 'plg_system_updatenotification.mail');
+
 DELETE FROM `#__postinstall_messages` WHERE `condition_file` = 'site://plugins/system/updatenotification/postinstall/updatecachetime.php';
diff --git a/administrator/components/com_admin/sql/updates/postgresql/5.0.0-2023-09-02.sql b/administrator/components/com_admin/sql/updates/postgresql/5.0.0-2023-09-02.sql
index fc99290a3d62..931f4acb15b6 100644
--- a/administrator/components/com_admin/sql/updates/postgresql/5.0.0-2023-09-02.sql
+++ b/administrator/components/com_admin/sql/updates/postgresql/5.0.0-2023-09-02.sql
@@ -1,9 +1,12 @@
 INSERT INTO "#__extensions" ("name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "locked", "manifest_cache", "params", "custom_data", "checked_out", "checked_out_time", "ordering", "state") VALUES
+('plg_task_privacyconsent', 'plugin', 'privacyconsent', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
 ('plg_task_rotatelogs', 'plugin', 'rotatelogs', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0),
 ('plg_task_updatenotification', 'plugin', 'updatenotification', 'task', 0, 1, 1, 0, 1, '', '{}', '', NULL, NULL, 0, 0);
 
 INSERT INTO "#__mail_templates" ("template_id", "extension", "language", "subject", "body", "htmlbody", "attachments", "params") VALUES
+('plg_task_privacyconsent.request.reminder', 'plg_task_privacyconsent', '', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_SUBJECT', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_BODY', '', '', '{"tags":["sitename","url","tokenurl","formurl","token"]}'),
 ('plg_task_updatenotification.mail', 'plg_task_updatenotification', '', 'PLG_TASK_UPDATENOTIFICATION_EMAIL_SUBJECT', 'PLG_TASK_UPDATENOTIFICATION_EMAIL_BODY', '', '', '{"tags":["newversion","curversion","sitename","url","link","releasenews"]}');
 
-DELETE FROM "#__mail_templates" WHERE "template_id" = 'plg_system_updatenotification.mail';
+DELETE FROM "#__mail_templates" WHERE "template_id" IN ('plg_system_privacyconsent.request.reminder', 'plg_system_updatenotification.mail');
+
 DELETE FROM "#__postinstall_messages" WHERE "condition_file" = 'site://plugins/system/updatenotification/postinstall/updatecachetime.php';
diff --git a/administrator/language/en-GB/plg_system_privacyconsent.ini b/administrator/language/en-GB/plg_system_privacyconsent.ini
index 30ef0294554d..2abb7a81a4ec 100644
--- a/administrator/language/en-GB/plg_system_privacyconsent.ini
+++ b/administrator/language/en-GB/plg_system_privacyconsent.ini
@@ -5,18 +5,11 @@
 
 PLG_SYSTEM_PRIVACYCONSENT="System - Privacy Consent"
 PLG_SYSTEM_PRIVACYCONSENT_BODY="<p>The user consented to storing their user information using the IP address <strong>%s</strong></p><p>The user agent string of the user's browser was:<br>%s</p><p>This information was automatically recorded when the user submitted their details on the website and checked the confirm box</p>"
-PLG_SYSTEM_PRIVACYCONSENT_CACHETIMEOUT_DESC="How often the check is performed."
-PLG_SYSTEM_PRIVACYCONSENT_CACHETIMEOUT_LABEL="Periodic check (days)"
 PLG_SYSTEM_PRIVACYCONSENT_CONSENT="User <a href='{accountlink}'>{username}</a> consented to the privacy policy."
-PLG_SYSTEM_PRIVACYCONSENT_CONSENTEXPIRATION_DESC="Number of days after which the privacy consent shall expire."
-PLG_SYSTEM_PRIVACYCONSENT_CONSENTEXPIRATION_LABEL="Expiration"
 PLG_SYSTEM_PRIVACYCONSENT_EMAIL_REMIND_BODY="Your Privacy Consent given at {URL} will expire in few days, you can renew the privacy consent for this website.\n\nIn order to do this, you can complete one of the following tasks:\n\n1. Visit the following URL: {TOKENURL}\n\n2. Copy your token from this email, visit the referenced URL, and paste your token into the form.\nURL: {FORMURL}\nToken: {TOKEN}\n\nPlease note that this token is only valid for this account."
 PLG_SYSTEM_PRIVACYCONSENT_EMAIL_REMIND_SUBJECT="Privacy Consent at {SITENAME}"
-PLG_SYSTEM_PRIVACYCONSENT_EXPIRATION_FIELDSET_LABEL="Expiration"
 PLG_SYSTEM_PRIVACYCONSENT_FIELD_ARTICLE_DESC="Select the article from the list or create a new one."
 PLG_SYSTEM_PRIVACYCONSENT_FIELD_ARTICLE_LABEL="Privacy Article"
-PLG_SYSTEM_PRIVACYCONSENT_FIELD_ENABLED_DESC="When enabled it performs checks for consent expiration."
-PLG_SYSTEM_PRIVACYCONSENT_FIELD_ENABLED_LABEL="Enable"
 PLG_SYSTEM_PRIVACYCONSENT_FIELD_ERROR="Agreement to the site's Privacy Policy is required."
 PLG_SYSTEM_PRIVACYCONSENT_FIELD_LABEL="Privacy Policy"
 PLG_SYSTEM_PRIVACYCONSENT_FIELD_MENU_ITEM_LABEL="Privacy Menu Item"
@@ -36,7 +29,16 @@ PLG_SYSTEM_PRIVACYCONSENT_OPTION_DO_NOT_AGREE="I do not agree"
 PLG_SYSTEM_PRIVACYCONSENT_REDIRECT_MESSAGE_DEFAULT="Please confirm that you consent to this website storing your information by agreeing to the privacy policy."
 PLG_SYSTEM_PRIVACYCONSENT_REDIRECT_MESSAGE_DESC="Custom message to be displayed on redirect. If left blank then the default message will be used."
 PLG_SYSTEM_PRIVACYCONSENT_REDIRECT_MESSAGE_LABEL="Redirect Message"
-PLG_SYSTEM_PRIVACYCONSENT_REMINDBEFORE_DESC="Number of days to send a reminder before the expiration of the privacy consent."
-PLG_SYSTEM_PRIVACYCONSENT_REMINDBEFORE_LABEL="Remind"
 PLG_SYSTEM_PRIVACYCONSENT_SUBJECT="Privacy Policy"
 PLG_SYSTEM_PRIVACYCONSENT_XML_DESCRIPTION="Basic plugin to request user's consent to the site's privacy policy. Existing users who have not consented yet will be redirected on login to update their profile."
+
+; All the following strings are deprecated and will be removed with 6.0
+PLG_SYSTEM_PRIVACYCONSENT_CACHETIMEOUT_DESC="How often the check is performed."
+PLG_SYSTEM_PRIVACYCONSENT_CACHETIMEOUT_LABEL="Periodic check (days)"
+PLG_SYSTEM_PRIVACYCONSENT_CONSENTEXPIRATION_DESC="Number of days after which the privacy consent shall expire."
+PLG_SYSTEM_PRIVACYCONSENT_CONSENTEXPIRATION_LABEL="Expiration"
+PLG_SYSTEM_PRIVACYCONSENT_EXPIRATION_FIELDSET_LABEL="Expiration"
+PLG_SYSTEM_PRIVACYCONSENT_FIELD_ENABLED_DESC="When enabled it performs checks for consent expiration."
+PLG_SYSTEM_PRIVACYCONSENT_FIELD_ENABLED_LABEL="Enable"
+PLG_SYSTEM_PRIVACYCONSENT_REMINDBEFORE_DESC="Number of days to send a reminder before the expiration of the privacy consent."
+PLG_SYSTEM_PRIVACYCONSENT_REMINDBEFORE_LABEL="Remind"
diff --git a/administrator/language/en-GB/plg_task_privacyconsent.ini b/administrator/language/en-GB/plg_task_privacyconsent.ini
new file mode 100644
index 000000000000..5baf97d04fae
--- /dev/null
+++ b/administrator/language/en-GB/plg_task_privacyconsent.ini
@@ -0,0 +1,47 @@
+; Joomla! Project
+; (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
+; License GNU General Public License version 2 or later; see LICENSE.txt
+; Note : All ini files need to be saved as UTF-8
+
+PLG_TASK_PRIVACYCONSENT="Task - Privacy Consents"
+PLG_TASK_PRIVACYCONSENT_CONSENTEXPIRATIONDAYS_DESC="Number of days after which the privacy consent shall expire."
+PLG_TASK_PRIVACYCONSENT_CONSENTEXPIRATIONDAYS_LABEL="Expiration"
+PLG_TASK_PRIVACYCONSENT_INVALIDATE_TITLE ="Expiration of privacy consents"
+PLG_TASK_PRIVACYCONSENT_INVALIDATE_DESC ="Manage the expiration of privacy consents"
+PLG_TASK_PRIVACYCONSENT_REMIND_DESC="Manage the remind of expiration of privacy consents"
+PLG_TASK_PRIVACYCONSENT_REMIND_TITLE="Remind" 
+PLG_TASK_PRIVACYCONSENT_BODY="<p>The user consented to storing their user information using the IP address <strong>%s</strong></p><p>The user agent string of the user's browser was:<br>%s</p><p>This information was automatically recorded when the user submitted their details on the website and checked the confirm box</p>"
+PLG_TASK_PRIVACYCONSENT_CACHETIMEOUT_DESC="How often the check is performed."
+PLG_TASK_PRIVACYCONSENT_CACHETIMEOUT_LABEL="Periodic check (days)"
+PLG_TASK_PRIVACYCONSENT_CONSENT="User <a href='{accountlink}'>{username}</a> consented to the privacy policy."
+
+PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_BODY="Your Privacy Consent given at {URL} will expire in few days, you can renew the privacy consent for this website.\n\nIn order to do this, you can complete one of the following tasks:\n\n1. Visit the following URL: {TOKENURL}\n\n2. Copy your token from this email, visit the referenced URL, and paste your token into the form.\nURL: {FORMURL}\nToken: {TOKEN}\n\nPlease note that this token is only valid for this account."
+PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_SUBJECT="Privacy Consent at {SITENAME}"
+PLG_TASK_PRIVACYCONSENT_EXPIRATION_FIELDSET_LABEL="Expiration"
+PLG_TASK_PRIVACYCONSENT_FIELD_ARTICLE_DESC="Select the article from the list or create a new one."
+PLG_TASK_PRIVACYCONSENT_FIELD_ARTICLE_LABEL="Privacy Article"
+PLG_TASK_PRIVACYCONSENT_FIELD_ENABLED_DESC="When enabled it performs checks for consent expiration."
+PLG_TASK_PRIVACYCONSENT_FIELD_ENABLED_LABEL="Enable"
+PLG_TASK_PRIVACYCONSENT_FIELD_ERROR="Agreement to the site's Privacy Policy is required."
+PLG_TASK_PRIVACYCONSENT_FIELD_LABEL="Privacy Policy"
+PLG_TASK_PRIVACYCONSENT_FIELD_MENU_ITEM_LABEL="Privacy Menu Item"
+PLG_TASK_PRIVACYCONSENT_FIELD_TYPE_ARTICLE="Article"
+PLG_TASK_PRIVACYCONSENT_FIELD_TYPE_LABEL="Privacy Type"
+PLG_TASK_PRIVACYCONSENT_FIELD_TYPE_MENU_ITEM="Menu Item"
+PLG_TASK_PRIVACYCONSENT_LABEL="Website Privacy"
+PLG_TASK_PRIVACYCONSENT_MAIL_REQUEST_REMINDER_DESC="Reminder to renew the privacy consent for this website."
+PLG_TASK_PRIVACYCONSENT_MAIL_REQUEST_REMINDER_TITLE="System - Privacy Consent: Renew Consent"
+PLG_TASK_PRIVACYCONSENT_NOTE_FIELD_DEFAULT="By signing up to this website and agreeing to the Privacy Policy you agree to this website storing your information."
+PLG_TASK_PRIVACYCONSENT_NOTE_FIELD_DESC="A summary of the site's privacy policy. If left blank then the default message will be used."
+PLG_TASK_PRIVACYCONSENT_NOTE_FIELD_LABEL="Short Privacy Policy"
+PLG_TASK_PRIVACYCONSENT_NOTIFICATION_USER_PRIVACY_EXPIRED_MESSAGE="Privacy consent has expired for %1$s."
+PLG_TASK_PRIVACYCONSENT_NOTIFICATION_USER_PRIVACY_EXPIRED_SUBJECT="Privacy Consent Expired"
+PLG_TASK_PRIVACYCONSENT_OPTION_AGREE="I agree"
+PLG_TASK_PRIVACYCONSENT_OPTION_DO_NOT_AGREE="I do not agree"
+PLG_TASK_PRIVACYCONSENT_REDIRECT_MESSAGE_DEFAULT="Please confirm that you consent to this website storing your information by agreeing to the privacy policy."
+PLG_TASK_PRIVACYCONSENT_REDIRECT_MESSAGE_DESC="Custom message to be displayed on redirect. If left blank then the default message will be used."
+PLG_TASK_PRIVACYCONSENT_REDIRECT_MESSAGE_LABEL="Redirect Message"
+PLG_TASK_PRIVACYCONSENT_REMINDBEFORE_DESC="Number of days to send a reminder before the expiration of the privacy consent."
+PLG_TASK_PRIVACYCONSENT_REMINDBEFORE_LABEL="Remind"
+PLG_TASK_PRIVACYCONSENT_SUBJECT="Privacy Policy"
+PLG_TASK_PRIVACYCONSENT_XML_DESCRIPTION="Task for remind expired consents and delete expired consents"
diff --git a/administrator/language/en-GB/plg_task_privacyconsent.sys.ini b/administrator/language/en-GB/plg_task_privacyconsent.sys.ini
new file mode 100644
index 000000000000..4893e9d29203
--- /dev/null
+++ b/administrator/language/en-GB/plg_task_privacyconsent.sys.ini
@@ -0,0 +1,7 @@
+; Joomla! Project
+; (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
+; License GNU General Public License version 2 or later; see LICENSE.txt
+; Note : All ini files need to be saved as UTF-8
+
+PLG_TASK_PRIVACYCONSENT="Task - Privacy Consents"
+PLG_TASK_PRIVACYCONSENT_XML_DESCRIPTION="Task for remind expired consents and delete expired consents."
diff --git a/installation/sql/mysql/base.sql b/installation/sql/mysql/base.sql
index d5ed86c6dd52..0c0b6a33700a 100644
--- a/installation/sql/mysql/base.sql
+++ b/installation/sql/mysql/base.sql
@@ -366,9 +366,10 @@ INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`,
 (0, 'plg_task_checkfiles', 'plugin', 'checkfiles', 'task', 0, 1, 1, 0, 1, '', '{}', '', 1, 0),
 (0, 'plg_task_globalcheckin', 'plugin', 'globalcheckin', 'task', 0, 1, 1, 0, 0, '', '{}', '', 2, 0),
 (0, 'plg_task_requests', 'plugin', 'requests', 'task', 0, 1, 1, 0, 1, '', '{}', '', 3, 0),
-(0, 'plg_task_rotatelogs', 'plugin', 'rotatelogs', 'task', 0, 1, 1, 0, 1, '', '{}', '', 4, 0),
-(0, 'plg_task_sitestatus', 'plugin', 'sitestatus', 'task', 0, 1, 1, 0, 1, '', '{}', '', 5, 0),
-(0, 'plg_task_updatenotification', 'plugin', 'updatenotification', 'task', 0, 1, 1, 0, 1, '', '{}', '', 6, 0),
+(0, 'plg_task_privacyconsent', 'plugin', 'privacyconsent', 'task', 0, 1, 1, 0, 1, '', '{}', '', 4, 0),
+(0, 'plg_task_rotatelogs', 'plugin', 'rotatelogs', 'task', 0, 1, 1, 0, 1, '', '{}', '', 5, 0),
+(0, 'plg_task_sitestatus', 'plugin', 'sitestatus', 'task', 0, 1, 1, 0, 1, '', '{}', '', 6, 0),
+(0, 'plg_task_updatenotification', 'plugin', 'updatenotification', 'task', 0, 1, 1, 0, 1, '', '{}', '', 7, 0),
 (0, 'plg_multifactorauth_totp', 'plugin', 'totp', 'multifactorauth', 0, 1, 1, 0, 1, '', '', '', 1, 0),
 (0, 'plg_multifactorauth_yubikey', 'plugin', 'yubikey', 'multifactorauth', 0, 1, 1, 0, 1, '', '', '', 2, 0),
 (0, 'plg_multifactorauth_webauthn', 'plugin', 'webauthn', 'multifactorauth', 0, 1, 1, 0, 1, '', '', '', 3, 0),
diff --git a/installation/sql/mysql/supports.sql b/installation/sql/mysql/supports.sql
index f7252db7465a..2829bad359b0 100644
--- a/installation/sql/mysql/supports.sql
+++ b/installation/sql/mysql/supports.sql
@@ -431,7 +431,7 @@ INSERT INTO `#__mail_templates` (`template_id`, `extension`, `language`, `subjec
 ('com_users.registration.admin.new_notification', 'com_users', '', 'COM_USERS_EMAIL_ACCOUNT_DETAILS', 'COM_USERS_EMAIL_REGISTERED_NOTIFICATION_TO_ADMIN_BODY', '', '', '{"tags":["name","sitename","siteurl","username"]}'),
 ('com_users.registration.user.admin_activated', 'com_users', '', 'COM_USERS_EMAIL_ACTIVATED_BY_ADMIN_ACTIVATION_SUBJECT', 'COM_USERS_EMAIL_ACTIVATED_BY_ADMIN_ACTIVATION_BODY', '', '', '{"tags":["name","sitename","siteurl","username"]}'),
 ('com_users.registration.admin.verification_request', 'com_users', '', 'COM_USERS_EMAIL_ACTIVATE_WITH_ADMIN_ACTIVATION_SUBJECT', 'COM_USERS_EMAIL_ACTIVATE_WITH_ADMIN_ACTIVATION_BODY', '', '', '{"tags":["name","sitename","email","username","activate"]}'),
-('plg_system_privacyconsent.request.reminder', 'plg_system_privacyconsent', '', 'PLG_SYSTEM_PRIVACYCONSENT_EMAIL_REMIND_SUBJECT', 'PLG_SYSTEM_PRIVACYCONSENT_EMAIL_REMIND_BODY', '', '', '{"tags":["sitename","url","tokenurl","formurl","token"]}'),
+('plg_task_privacyconsent.request.reminder', 'plg_task_privacyconsent', '', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_SUBJECT', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_BODY', '', '', '{"tags":["sitename","url","tokenurl","formurl","token"]}'),
 ('com_messages.new_message', 'com_messages', '', 'COM_MESSAGES_NEW_MESSAGE', 'COM_MESSAGES_NEW_MESSAGE_BODY', '', '', '{"tags":["subject","message","fromname","sitename","siteurl","fromemail","toname","toemail"]}'),
 ('plg_system_tasknotification.failure_mail', 'plg_system_tasknotification', '', 'PLG_SYSTEM_TASK_NOTIFICATION_FAILURE_MAIL_SUBJECT', 'PLG_SYSTEM_TASK_NOTIFICATION_FAILURE_MAIL_BODY', '', '', '{"tags": ["task_id", "task_title", "exit_code", "exec_data_time", "task_output"]}'),
 ('plg_system_tasknotification.fatal_recovery_mail', 'plg_system_tasknotification', '', 'PLG_SYSTEM_TASK_NOTIFICATION_FATAL_MAIL_SUBJECT', 'PLG_SYSTEM_TASK_NOTIFICATION_FATAL_MAIL_BODY', '', '', '{"tags": ["task_id", "task_title"]}'),
diff --git a/installation/sql/postgresql/base.sql b/installation/sql/postgresql/base.sql
index 58314488cbde..883ad28bf8ff 100644
--- a/installation/sql/postgresql/base.sql
+++ b/installation/sql/postgresql/base.sql
@@ -372,9 +372,10 @@ INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder",
 (0, 'plg_task_checkfiles', 'plugin', 'checkfiles', 'task', 0, 1, 1, 0, 1, '', '{}', '', 1, 0),
 (0, 'plg_task_globalcheckin', 'plugin', 'globalcheckin', 'task', 0, 1, 1, 0, 0, '', '{}', '', 2, 0),
 (0, 'plg_task_requests', 'plugin', 'requests', 'task', 0, 1, 1, 0, 1, '', '{}', '', 3, 0),
-(0, 'plg_task_rotatelogs', 'plugin', 'rotatelogs', 'task', 0, 1, 1, 0, 1, '', '{}', '', 4, 0),
-(0, 'plg_task_sitestatus', 'plugin', 'sitestatus', 'task', 0, 1, 1, 0, 1, '', '{}', '', 5, 0),
-(0, 'plg_task_updatenotification', 'plugin', 'updatenotification', 'task', 0, 1, 1, 0, 1, '', '{}', '', 6, 0),
+(0, 'plg_task_privacyconsent', 'plugin', 'privacyconsent', 'task', 0, 1, 1, 0, 1, '', '{}', '', 4, 0),
+(0, 'plg_task_rotatelogs', 'plugin', 'rotatelogs', 'task', 0, 1, 1, 0, 1, '', '{}', '', 5, 0),
+(0, 'plg_task_sitestatus', 'plugin', 'sitestatus', 'task', 0, 1, 1, 0, 1, '', '{}', '', 6, 0),
+(0, 'plg_task_updatenotification', 'plugin', 'updatenotification', 'task', 0, 1, 1, 0, 1, '', '{}', '', 7, 0),
 (0, 'plg_multifactorauth_totp', 'plugin', 'totp', 'multifactorauth', 0, 1, 1, 0, 1, '', '', '', 1, 0),
 (0, 'plg_multifactorauth_yubikey', 'plugin', 'yubikey', 'multifactorauth', 0, 1, 1, 0, 1, '', '', '', 2, 0),
 (0, 'plg_multifactorauth_webauthn', 'plugin', 'webauthn', 'multifactorauth', 0, 1, 1, 0, 1, '', '', '', 3, 0),
diff --git a/installation/sql/postgresql/supports.sql b/installation/sql/postgresql/supports.sql
index 1ad64316c3c2..fb6571f6f951 100644
--- a/installation/sql/postgresql/supports.sql
+++ b/installation/sql/postgresql/supports.sql
@@ -442,7 +442,7 @@ INSERT INTO "#__mail_templates" ("template_id", "extension", "language", "subjec
 ('com_users.registration.admin.new_notification', 'com_users', '', 'COM_USERS_EMAIL_ACCOUNT_DETAILS', 'COM_USERS_EMAIL_REGISTERED_NOTIFICATION_TO_ADMIN_BODY', '', '', '{"tags":["name","sitename","siteurl","username"]}'),
 ('com_users.registration.user.admin_activated', 'com_users', '', 'COM_USERS_EMAIL_ACTIVATED_BY_ADMIN_ACTIVATION_SUBJECT', 'COM_USERS_EMAIL_ACTIVATED_BY_ADMIN_ACTIVATION_BODY', '', '', '{"tags":["name","sitename","siteurl","username"]}'),
 ('com_users.registration.admin.verification_request', 'com_users', '', 'COM_USERS_EMAIL_ACTIVATE_WITH_ADMIN_ACTIVATION_SUBJECT', 'COM_USERS_EMAIL_ACTIVATE_WITH_ADMIN_ACTIVATION_BODY', '', '', '{"tags":["name","sitename","email","username","activate"]}'),
-('plg_system_privacyconsent.request.reminder', 'plg_system_privacyconsent', '', 'PLG_SYSTEM_PRIVACYCONSENT_EMAIL_REMIND_SUBJECT', 'PLG_SYSTEM_PRIVACYCONSENT_EMAIL_REMIND_BODY', '', '', '{"tags":["sitename","url","tokenurl","formurl","token"]}'),
+('plg_task_privacyconsent.request.reminder', 'plg_task_privacyconsent', '', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_SUBJECT', 'PLG_TASK_PRIVACYCONSENT_EMAIL_REMIND_BODY', '', '', '{"tags":["sitename","url","tokenurl","formurl","token"]}'),
 ('com_messages.new_message', 'com_messages', '', 'COM_MESSAGES_NEW_MESSAGE', 'COM_MESSAGES_NEW_MESSAGE_BODY', '', '', '{"tags":["subject","message","fromname","sitename","siteurl","fromemail","toname","toemail"]}'),
 ('plg_system_tasknotification.failure_mail', 'plg_system_tasknotification', '', 'PLG_SYSTEM_TASK_NOTIFICATION_FAILURE_MAIL_SUBJECT', 'PLG_SYSTEM_TASK_NOTIFICATION_FAILURE_MAIL_BODY', '', '', '{"tags": ["task_id", "task_title", "exit_code", "exec_data_time", "task_output"]}'),
 ('plg_system_tasknotification.fatal_recovery_mail', 'plg_system_tasknotification', '', 'PLG_SYSTEM_TASK_NOTIFICATION_FATAL_MAIL_SUBJECT', 'PLG_SYSTEM_TASK_NOTIFICATION_FATAL_MAIL_BODY', '', '', '{"tags": ["task_id", "task_title"]}'),
diff --git a/libraries/src/Extension/ExtensionHelper.php b/libraries/src/Extension/ExtensionHelper.php
index e52390c4cb9a..bde784432c9d 100644
--- a/libraries/src/Extension/ExtensionHelper.php
+++ b/libraries/src/Extension/ExtensionHelper.php
@@ -319,6 +319,7 @@ class ExtensionHelper
         // Core plugin extensions - task scheduler
         ['plugin', 'checkfiles', 'task', 0],
         ['plugin', 'globalcheckin', 'task', 0],
+        ['plugin', 'privacyconsent', 'task', 0],
         ['plugin', 'requests', 'task', 0],
         ['plugin', 'rotatelogs', 'task', 0],
         ['plugin', 'sitestatus', 'task', 0],
diff --git a/plugins/system/privacyconsent/privacyconsent.xml b/plugins/system/privacyconsent/privacyconsent.xml
index 9f7d31f02082..6c47d949ef51 100644
--- a/plugins/system/privacyconsent/privacyconsent.xml
+++ b/plugins/system/privacyconsent/privacyconsent.xml
@@ -78,65 +78,6 @@
 					filter="html"
 				/>
 			</fieldset>
-			<fieldset
-				name="expiration"
-				label="PLG_SYSTEM_PRIVACYCONSENT_EXPIRATION_FIELDSET_LABEL"
-			>
-				<field
-					name="enabled"
-					type="radio"
-					label="PLG_SYSTEM_PRIVACYCONSENT_FIELD_ENABLED_LABEL"
-					description="PLG_SYSTEM_PRIVACYCONSENT_FIELD_ENABLED_DESC"
-					layout="joomla.form.field.radio.switcher"
-					default="0"
-					filter="integer"
-					>
-					<option value="0">JNO</option>
-					<option value="1">JYES</option>
-				</field>
-				<field
-					name="cachetimeout"
-					type="integer"
-					label="PLG_SYSTEM_PRIVACYCONSENT_CACHETIMEOUT_LABEL"
-					description="PLG_SYSTEM_PRIVACYCONSENT_CACHETIMEOUT_DESC"
-					first="0"
-					last="120"
-					step="1"
-					default="30"
-					filter="int"
-					validate="number"
-				/>
-				<field
-					name="consentexpiration"
-					type="integer"
-					label="PLG_SYSTEM_PRIVACYCONSENT_CONSENTEXPIRATION_LABEL"
-					description="PLG_SYSTEM_PRIVACYCONSENT_CONSENTEXPIRATION_DESC"
-					first="180"
-					last="720"
-					step="30"
-					default="360"
-					filter="int"
-					validate="number"
-				/>
-				<field
-					name="remind"
-					type="integer"
-					label="PLG_SYSTEM_PRIVACYCONSENT_REMINDBEFORE_LABEL"
-					description="PLG_SYSTEM_PRIVACYCONSENT_REMINDBEFORE_DESC"
-					first="0"
-					last="120"
-					step="1"
-					default="30"
-					filter="int"
-					validate="number"
-				/>
-				<field
-					name="lastrun"
-					type="hidden"
-					default="0"
-					filter="integer"
-				/>
-			</fieldset>
 		</fields>
 	</config>
 </extension>
diff --git a/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php b/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php
index 3376b260f548..639aa20cff5a 100644
--- a/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php
+++ b/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php
@@ -10,28 +10,18 @@
 
 namespace Joomla\Plugin\System\PrivacyConsent\Extension;
 
-use Joomla\CMS\Application\ApplicationHelper;
-use Joomla\CMS\Cache\Cache;
 use Joomla\CMS\Event\Privacy\CheckPrivacyPolicyPublishedEvent;
 use Joomla\CMS\Factory;
 use Joomla\CMS\Form\Form;
 use Joomla\CMS\Form\FormHelper;
 use Joomla\CMS\Language\Associations;
 use Joomla\CMS\Language\Text;
-use Joomla\CMS\Mail\Exception\MailDisabledException;
-use Joomla\CMS\Mail\MailTemplate;
 use Joomla\CMS\Plugin\CMSPlugin;
 use Joomla\CMS\Router\Route;
-use Joomla\CMS\Uri\Uri;
-use Joomla\CMS\User\UserFactoryAwareTrait;
-use Joomla\CMS\User\UserHelper;
 use Joomla\Component\Actionlogs\Administrator\Model\ActionlogModel;
-use Joomla\Component\Messages\Administrator\Model\MessageModel;
 use Joomla\Database\DatabaseAwareTrait;
-use Joomla\Database\Exception\ExecutionFailureException;
 use Joomla\Database\ParameterType;
 use Joomla\Utilities\ArrayHelper;
-use PHPMailer\PHPMailer\Exception as phpmailerException;
 
 // phpcs:disable PSR1.Files.SideEffects
 \defined('_JEXEC') or die;
@@ -450,252 +440,4 @@ private function getPrivacyItemId()
 
         return $itemId;
     }
-
-    /**
-     * The privacy consent expiration check code is triggered after the page has fully rendered.
-     *
-     * @return  void
-     *
-     * @since   3.9.0
-     */
-    public function onAfterRender()
-    {
-        if (!$this->params->get('enabled', 0)) {
-            return;
-        }
-
-        $cacheTimeout = (int) $this->params->get('cachetimeout', 30);
-        $cacheTimeout = 24 * 3600 * $cacheTimeout;
-
-        // Do we need to run? Compare the last run timestamp stored in the plugin's options with the current
-        // timestamp. If the difference is greater than the cache timeout we shall not execute again.
-        $now  = time();
-        $last = (int) $this->params->get('lastrun', 0);
-
-        if ((abs($now - $last) < $cacheTimeout)) {
-            return;
-        }
-
-        // Update last run status
-        $this->params->set('lastrun', $now);
-
-        $paramsJson = $this->params->toString('JSON');
-        $db         = $this->getDatabase();
-        $query      = $db->getQuery(true)
-            ->update($db->quoteName('#__extensions'))
-            ->set($db->quoteName('params') . ' = :params')
-            ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
-            ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
-            ->where($db->quoteName('element') . ' = ' . $db->quote('privacyconsent'))
-            ->bind(':params', $paramsJson);
-
-        try {
-            // Lock the tables to prevent multiple plugin executions causing a race condition
-            $db->lockTable('#__extensions');
-        } catch (\Exception $e) {
-            // If we can't lock the tables it's too risky to continue execution
-            return;
-        }
-
-        try {
-            // Update the plugin parameters
-            $result = $db->setQuery($query)->execute();
-            $this->clearCacheGroups(['com_plugins'], [0, 1]);
-        } catch (\Exception $exc) {
-            // If we failed to execute
-            $db->unlockTables();
-            $result = false;
-        }
-
-        try {
-            // Unlock the tables after writing
-            $db->unlockTables();
-        } catch (\Exception $e) {
-            // If we can't lock the tables assume we have somehow failed
-            $result = false;
-        }
-
-        // Stop on failure
-        if (!$result) {
-            return;
-        }
-
-        // Delete the expired privacy consents
-        $this->invalidateExpiredConsents();
-
-        // Remind for privacy consents near to expire
-        $this->remindExpiringConsents();
-    }
-
-    /**
-     * Method to send the remind for privacy consents renew
-     *
-     * @return  integer
-     *
-     * @since   3.9.0
-     */
-    private function remindExpiringConsents()
-    {
-        // Load the parameters.
-        $expire   = (int) $this->params->get('consentexpiration', 365);
-        $remind   = (int) $this->params->get('remind', 30);
-        $now      = Factory::getDate()->toSql();
-        $period   = '-' . ($expire - $remind);
-        $db       = $this->getDatabase();
-        $query    = $db->getQuery(true);
-
-        $query->select($db->quoteName(['r.id', 'r.user_id', 'u.email']))
-            ->from($db->quoteName('#__privacy_consents', 'r'))
-            ->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('r.user_id'))
-            ->where($db->quoteName('subject') . ' = ' . $db->quote('PLG_SYSTEM_PRIVACYCONSENT_SUBJECT'))
-            ->where($db->quoteName('remind') . ' = 0')
-            ->where($query->dateAdd($db->quote($now), $period, 'DAY') . ' > ' . $db->quoteName('created'));
-
-        try {
-            $users = $db->setQuery($query)->loadObjectList();
-        } catch (ExecutionFailureException $exception) {
-            return false;
-        }
-
-        $app      = $this->getApplication();
-        $linkMode = $app->get('force_ssl', 0) == 2 ? Route::TLS_FORCE : Route::TLS_IGNORE;
-
-        foreach ($users as $user) {
-            $token       = ApplicationHelper::getHash(UserHelper::genRandomPassword());
-            $hashedToken = UserHelper::hashPassword($token);
-
-            // The mail
-            try {
-                $templateData = [
-                    'sitename' => $app->get('sitename'),
-                    'url'      => Uri::root(),
-                    'tokenurl' => Route::link('site', 'index.php?option=com_privacy&view=remind&remind_token=' . $token, false, $linkMode, true),
-                    'formurl'  => Route::link('site', 'index.php?option=com_privacy&view=remind', false, $linkMode, true),
-                    'token'    => $token,
-                ];
-
-                $mailer = new MailTemplate('plg_system_privacyconsent.request.reminder', $app->getLanguage()->getTag());
-                $mailer->addTemplateData($templateData);
-                $mailer->addRecipient($user->email);
-
-                $mailResult = $mailer->send();
-
-                if ($mailResult === false) {
-                    return false;
-                }
-
-                $userId = (int) $user->id;
-
-                // Update the privacy_consents item to not send the reminder again
-                $query->clear()
-                    ->update($db->quoteName('#__privacy_consents'))
-                    ->set($db->quoteName('remind') . ' = 1')
-                    ->set($db->quoteName('token') . ' = :token')
-                    ->where($db->quoteName('id') . ' = :userid')
-                    ->bind(':token', $hashedToken)
-                    ->bind(':userid', $userId, ParameterType::INTEGER);
-                $db->setQuery($query);
-
-                try {
-                    $db->execute();
-                } catch (\RuntimeException $e) {
-                    return false;
-                }
-            } catch (MailDisabledException | phpmailerException $exception) {
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Method to delete the expired privacy consents
-     *
-     * @return  boolean
-     *
-     * @since   3.9.0
-     */
-    private function invalidateExpiredConsents()
-    {
-        // Load the parameters.
-        $expire = (int) $this->params->get('consentexpiration', 365);
-        $now    = Factory::getDate()->toSql();
-        $period = '-' . $expire;
-        $db     = $this->getDatabase();
-        $query  = $db->getQuery(true);
-
-        $query->select($db->quoteName(['id', 'user_id']))
-            ->from($db->quoteName('#__privacy_consents'))
-            ->where($query->dateAdd($db->quote($now), $period, 'DAY') . ' > ' . $db->quoteName('created'))
-            ->where($db->quoteName('subject') . ' = ' . $db->quote('PLG_SYSTEM_PRIVACYCONSENT_SUBJECT'))
-            ->where($db->quoteName('state') . ' = 1');
-
-        $db->setQuery($query);
-
-        try {
-            $users = $db->loadObjectList();
-        } catch (\RuntimeException $e) {
-            return false;
-        }
-
-        // Do not process further if no expired consents found
-        if (empty($users)) {
-            return true;
-        }
-
-        // Push a notification to the site's super users
-        /** @var MessageModel $messageModel */
-        $messageModel = $this->getApplication()->bootComponent('com_messages')->getMVCFactory()->createModel('Message', 'Administrator');
-
-        foreach ($users as $user) {
-            $userId = (int) $user->id;
-            $query  = $db->getQuery(true)
-                ->update($db->quoteName('#__privacy_consents'))
-                ->set($db->quoteName('state') . ' = 0')
-                ->where($db->quoteName('id') . ' = :userid')
-                ->bind(':userid', $userId, ParameterType::INTEGER);
-            $db->setQuery($query);
-
-            try {
-                $db->execute();
-            } catch (\RuntimeException $e) {
-                return false;
-            }
-
-            $messageModel->notifySuperUsers(
-                $this->getApplication()->getLanguage()->_('PLG_SYSTEM_PRIVACYCONSENT_NOTIFICATION_USER_PRIVACY_EXPIRED_SUBJECT'),
-                Text::sprintf('PLG_SYSTEM_PRIVACYCONSENT_NOTIFICATION_USER_PRIVACY_EXPIRED_MESSAGE', $this->getUserFactory()->loadUserById($user->user_id)->username)
-            );
-        }
-
-        return true;
-    }
-    /**
-     * Clears cache groups. We use it to clear the plugins cache after we update the last run timestamp.
-     *
-     * @param   array  $clearGroups   The cache groups to clean
-     * @param   array  $cacheClients  The cache clients (site, admin) to clean
-     *
-     * @return  void
-     *
-     * @since    3.9.0
-     */
-    private function clearCacheGroups(array $clearGroups, array $cacheClients = [0, 1])
-    {
-        foreach ($clearGroups as $group) {
-            foreach ($cacheClients as $client_id) {
-                try {
-                    $options = [
-                        'defaultgroup' => $group,
-                        'cachebase'    => $client_id ? JPATH_ADMINISTRATOR . '/cache' :
-                            $this->getApplication()->get('cache_path', JPATH_SITE . '/cache'),
-                    ];
-
-                    $cache = Cache::getInstance('callback', $options);
-                    $cache->clean();
-                } catch (\Exception $e) {
-                    // Ignore it
-                }
-            }
-        }
-    }
 }
diff --git a/plugins/task/privacyconsent/forms/privacyconsentForm.xml b/plugins/task/privacyconsent/forms/privacyconsentForm.xml
new file mode 100644
index 000000000000..dd6f12b71ff6
--- /dev/null
+++ b/plugins/task/privacyconsent/forms/privacyconsentForm.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form>
+	<fields name="params">
+		<fieldset name="task_params">
+			<field
+					name="consentexpiration"
+					type="integer"
+					label="PLG_TASK_PRIVACYCONSENT_CONSENTEXPIRATIONDAYS_LABEL"
+					description="PLG_TASK_PRIVACYCONSENT_CONSENTEXPIRATIONDAYS_DESC"
+					first="0"
+					last="720"
+					step="30"
+					default="360"
+					filter="int"
+					validate="number"
+				/>
+				<field
+					name="remind"
+					type="integer"
+					label="PLG_TASK_PRIVACYCONSENT_REMINDBEFORE_LABEL"
+					description="PLG_TASK_PRIVACYCONSENT_REMINDBEFORE_DESC"
+					first="0"
+					last="120"
+					step="1"
+					default="30"
+					filter="int"
+					validate="number"
+				/>
+		</fieldset>
+	</fields>
+</form>
diff --git a/plugins/task/privacyconsent/privacyconsent.xml b/plugins/task/privacyconsent/privacyconsent.xml
new file mode 100644
index 000000000000..c47fd75c4747
--- /dev/null
+++ b/plugins/task/privacyconsent/privacyconsent.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<extension type="plugin" group="task" method="upgrade">
+	<name>plg_task_privacyconsent</name>
+	<author>Joomla! Project</author>
+	<creationDate>2023-07</creationDate>
+	<copyright>(C) 2023 Open Source Matters, Inc.</copyright>
+	<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
+	<authorEmail>admin@joomla.org</authorEmail>
+	<authorUrl>www.joomla.org</authorUrl>
+	<version>5.0.0</version>
+	<description>PLG_TASK_PRIVACYCONSENT_XML_DESCRIPTION</description>
+	<namespace path="src">Joomla\Plugin\Task\PrivacyConsent</namespace>
+	<files>
+		<folder>forms</folder>
+		<folder plugin="privacyconsent">services</folder>
+		<folder>src</folder>
+	</files>
+	<languages>
+		<language tag="en-GB">language/en-GB/plg_task_privacyconsent.ini</language>
+		<language tag="en-GB">language/en-GB/plg_task_privacyconsent.sys.ini</language>
+	</languages>
+</extension>
diff --git a/plugins/task/privacyconsent/services/provider.php b/plugins/task/privacyconsent/services/provider.php
new file mode 100644
index 000000000000..4bd20fa1c74c
--- /dev/null
+++ b/plugins/task/privacyconsent/services/provider.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @package     Joomla.Plugin
+ * @subpackage  Task.PrivacyConsent
+ *
+ * @copyright   (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license     GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Extension\PluginInterface;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Plugin\PluginHelper;
+use Joomla\CMS\User\UserFactoryInterface;
+use Joomla\Database\DatabaseInterface;
+use Joomla\DI\Container;
+use Joomla\DI\ServiceProviderInterface;
+use Joomla\Event\DispatcherInterface;
+use Joomla\Plugin\Task\PrivacyConsent\Extension\PrivacyConsent;
+
+return new class () implements ServiceProviderInterface {
+    /**
+     * Registers the service provider with a DI container.
+     *
+     * @param   Container  $container  The DI container.
+     *
+     * @return  void
+     *
+     * @since   __DEPLOY_VERSION__
+     */
+    public function register(Container $container)
+    {
+        $container->set(
+            PluginInterface::class,
+            function (Container $container) {
+                $plugin = new PrivacyConsent(
+                    $container->get(DispatcherInterface::class),
+                    (array) PluginHelper::getPlugin('task', 'privacyconsent')
+                );
+                $plugin->setApplication(Factory::getApplication());
+                $plugin->setDatabase($container->get(DatabaseInterface::class));
+                $plugin->setUserFactory($container->get(UserFactoryInterface::class));
+
+                return $plugin;
+            }
+        );
+    }
+};
diff --git a/plugins/task/privacyconsent/src/Extension/PrivacyConsent.php b/plugins/task/privacyconsent/src/Extension/PrivacyConsent.php
new file mode 100644
index 000000000000..d79a595e4ee8
--- /dev/null
+++ b/plugins/task/privacyconsent/src/Extension/PrivacyConsent.php
@@ -0,0 +1,261 @@
+<?php
+
+/**
+ * @package     Joomla.Plugin
+ * @subpackage  Task.PrivacyConsent
+ *
+ * @copyright   (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license     GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Plugin\Task\PrivacyConsent\Extension;
+
+use Joomla\CMS\Application\ApplicationHelper;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\Mail\Exception\MailDisabledException;
+use Joomla\CMS\Mail\MailTemplate;
+use Joomla\CMS\Plugin\CMSPlugin;
+use Joomla\CMS\Router\Route;
+use Joomla\CMS\Uri\Uri;
+use Joomla\CMS\User\UserFactoryAwareTrait;
+use Joomla\CMS\User\UserHelper;
+use Joomla\Component\Scheduler\Administrator\Event\ExecuteTaskEvent;
+use Joomla\Component\Scheduler\Administrator\Task\Status;
+use Joomla\Component\Scheduler\Administrator\Task\Task;
+use Joomla\Component\Scheduler\Administrator\Traits\TaskPluginTrait;
+use Joomla\Database\DatabaseAwareTrait;
+use Joomla\Database\ParameterType;
+use Joomla\Event\SubscriberInterface;
+use PHPMailer\PHPMailer\Exception as phpmailerException;
+
+// phpcs:disable PSR1.Files.SideEffects
+\defined('_JEXEC') or die;
+// phpcs:enable PSR1.Files.SideEffects
+
+/**
+ * A task plugin. Offers 2 task routines Invalidate Expired Consents and Remind Expired Consents
+ * {@see ExecuteTaskEvent}.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+final class PrivacyConsent extends CMSPlugin implements SubscriberInterface
+{
+    use DatabaseAwareTrait;
+    use TaskPluginTrait;
+    use UserFactoryAwareTrait;
+
+    /**
+     * @var string[]
+     * @since __DEPLOY_VERSION__
+     */
+    private const TASKS_MAP = [
+        'privacy.consent' => [
+            'langConstPrefix' => 'PLG_TASK_PRIVACYCONSENT_INVALIDATE',
+            'method'          => 'privacyConsents',
+            'form'            => 'privacyconsentForm',
+        ],
+    ];
+
+    /**
+     * @var boolean
+     *
+     * @since __DEPLOY_VERSION__
+     */
+    protected $autoloadLanguage = true;
+
+    /**
+     * @inheritDoc
+     *
+     * @return string[]
+     *
+     * @since __DEPLOY_VERSION__
+     */
+    public static function getSubscribedEvents(): array
+    {
+        return [
+            'onTaskOptionsList'    => 'advertiseRoutines',
+            'onExecuteTask'        => 'standardRoutineHandler',
+            'onContentPrepareForm' => 'enhanceTaskItemForm',
+        ];
+    }
+
+    /**
+     * Method to send the remind for privacy consents renew.
+     *
+     * @param   ExecuteTaskEvent  $event  The `onExecuteTask` event.
+     *
+     * @return integer  The routine exit code.
+     *
+     * @since  __DEPLOY_VERSION__
+     * @throws \Exception
+     */
+    private function privacyConsents(ExecuteTaskEvent $event): int
+    {
+        // Load the parameters.
+        $expire = (int) $event->getArgument('params')->consentexpiration ?? 365;
+        $remind = (int) $event->getArgument('params')->remind ?? 30;
+
+        if (
+            $this->invalidateExpiredConsents($expire) === Status::OK
+            && $this->remindExpiringConsents($expire, $remind) === Status::OK
+        ) {
+            return Status::OK;
+        }
+
+        return Status::KNOCKOUT;
+    }
+
+    /**
+     * Method to send the remind for privacy consents renew.
+     *
+     * @param   integer    $expire
+     * @param   integer    $remind
+     *
+     * @return integer  The routine exit code.
+     *
+     * @since  __DEPLOY_VERSION__
+     * @throws \Exception
+     */
+    private function remindExpiringConsents($expire, $remind): int
+    {
+        $now      = Factory::getDate()->toSql();
+        $period   = '-' . ($expire - $remind);
+        $db       = $this->getDatabase();
+        $query    = $db->getQuery(true);
+
+        $query->select($db->quoteName(['r.id', 'r.user_id', 'u.email']))
+            ->from($db->quoteName('#__privacy_consents', 'r'))
+            ->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('r.user_id'))
+            ->where($db->quoteName('subject') . ' = ' . $db->quote('PLG_TASK_PRIVACYCONSENT_SUBJECT'))
+            ->where($db->quoteName('remind') . ' = 0')
+            ->where($query->dateAdd($db->quote($now), $period, 'DAY') . ' > ' . $db->quoteName('created'));
+
+        try {
+            $users = $db->setQuery($query)->loadObjectList();
+        } catch (\RuntimeException $exception) {
+            return Status::KNOCKOUT;
+        }
+
+        // Do not process further if no expired consents found
+        if (empty($users)) {
+            return Status::OK;
+        }
+
+        $app      = $this->getApplication();
+        $linkMode = $app->get('force_ssl', 0) == 2 ? Route::TLS_FORCE : Route::TLS_IGNORE;
+
+        foreach ($users as $user) {
+            $token       = ApplicationHelper::getHash(UserHelper::genRandomPassword());
+            $hashedToken = UserHelper::hashPassword($token);
+
+            // The mail
+            try {
+                $templateData = [
+                    'sitename' => $app->get('sitename'),
+                    'url'      => Uri::root(),
+                    'tokenurl' => Route::link('site', 'index.php?option=com_privacy&view=remind&remind_token=' . $token, false, $linkMode, true),
+                    'formurl'  => Route::link('site', 'index.php?option=com_privacy&view=remind', false, $linkMode, true),
+                    'token'    => $token,
+                ];
+
+                $mailer = new MailTemplate('plg_task_privacyconsent.request.reminder', $app->getLanguage()->getTag());
+                $mailer->addTemplateData($templateData);
+                $mailer->addRecipient($user->email);
+
+                $mailResult = $mailer->send();
+
+                if ($mailResult === false) {
+                    return Status::KNOCKOUT;
+                }
+
+                $userId = (int) $user->id;
+
+                // Update the privacy_consents item to not send the reminder again
+                $query->clear()
+                    ->update($db->quoteName('#__privacy_consents'))
+                    ->set($db->quoteName('remind') . ' = 1')
+                    ->set($db->quoteName('token') . ' = :token')
+                    ->where($db->quoteName('id') . ' = :userid')
+                    ->bind(':token', $hashedToken)
+                    ->bind(':userid', $userId, ParameterType::INTEGER);
+                $db->setQuery($query);
+
+                try {
+                    $db->execute();
+                } catch (\RuntimeException $e) {
+                    return Status::KNOCKOUT;
+                }
+            } catch (MailDisabledException | phpmailerException $exception) {
+                return Status::KNOCKOUT;
+            }
+        }
+        $this->logTask('Remind end');
+
+        return Status::OK;
+    }
+
+    /**
+     * Method to delete the expired privacy consents.
+     *
+     * @param   integer    $expire
+     *
+     * @return integer  The routine exit code.
+     *
+     * @since  __DEPLOY_VERSION__
+     * @throws \Exception
+     */
+    private function invalidateExpiredConsents($expire): int
+    {
+        $now    = Factory::getDate()->toSql();
+        $period = '-' . $expire;
+        $db     = $this->getDatabase();
+        $query  = $db->getQuery(true);
+
+        $query->select($db->quoteName(['id', 'user_id']))
+            ->from($db->quoteName('#__privacy_consents'))
+            ->where($query->dateAdd($db->quote($now), $period, 'DAY') . ' > ' . $db->quoteName('created'))
+            ->where($db->quoteName('subject') . ' = ' . $db->quote('PLG_TASK_PRIVACYCONSENT_SUBJECT'))
+            ->where($db->quoteName('state') . ' = 1');
+
+        $db->setQuery($query);
+
+        try {
+            $users = $db->loadObjectList();
+        } catch (\RuntimeException $e) {
+            return Status::KNOCKOUT;
+        }
+
+        // Do not process further if no expired consents found
+        if (empty($users)) {
+            return Status::OK;
+        }
+
+        // Push a notification to the site's super users
+        /** @var MessageModel $messageModel */
+        $messageModel = $this->getApplication()->bootComponent('com_messages')->getMVCFactory()->createModel('Message', 'Administrator');
+
+        foreach ($users as $user) {
+            $userId = (int) $user->id;
+            $query  = $db->getQuery(true)
+                ->update($db->quoteName('#__privacy_consents'))
+                ->set($db->quoteName('state') . ' = 0')
+                ->where($db->quoteName('id') . ' = :userid')
+                ->bind(':userid', $userId, ParameterType::INTEGER);
+            $db->setQuery($query);
+
+            try {
+                $db->execute();
+            } catch (\RuntimeException $e) {
+                return Status::KNOCKOUT;
+            }
+
+            $messageModel->notifySuperUsers(
+                Text::_('PLG_TASK_PRIVACYCONSENT_NOTIFICATION_USER_PRIVACY_EXPIRED_SUBJECT'),
+                Text::sprintf('PLG_TASK_PRIVACYCONSENT_NOTIFICATION_USER_PRIVACY_EXPIRED_MESSAGE', $this->getUserFactory()->loadUserById($user->user_id)->username)
+            );
+        }
+
+        return Status::OK;
+    }
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

4 participants