diff --git a/README.md b/README.md index 5063a80d..93358ea7 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,11 @@ This plugin is meant to assist a Controller, Data Processor, and Data Protection * Re-assignment of user data on erasure requests & pseudonymization of user website data * Data Processor settings and publishing of contact information * Right to access data by admin dashboard with email look up and export -* Right to access data by Data User with front-end requests button & double opt-in confirmation email -* Right to portability & export of data by Admin or Data User in XML or JSON formats -* Encrypted audit logs for the lifetime of Data User compliance activity -* Data User Secret Token for two-factor decryption and recovery of data -* Data breach notification logs and batch email notifications to data users +* Right to access data by Data Subject with front-end requests button & double opt-in confirmation email +* Right to portability & export of data by Admin or Data Subject in XML or JSON formats +* Encrypted audit logs for the lifetime of Data Subject compliance activity +* Data Subject Secret Token for two-factor decryption and recovery of data +* Data breach notification logs and batch email notifications to Data Subjects * Telemetry Tracker for visualizing plugins and website data ## Settings @@ -56,44 +56,44 @@ For optional consents, there's a wrapper function `have_consent( $consent_id )` Consents are logged to the user record for auditing or for access purposes. -## Requests Table & Rights of Data User +## Requests Table & Rights of Data Subject **Right to Erasure Requests** -1. The Data User is able to submit a request to be erased from the site using a shortcode. -1. When a request is made, the Data User will receive an email confirmation to confirm the deletion request. +1. The Data Subject is able to submit a request to be erased from the site using a shortcode. +1. When a request is made, the Data Subject will receive an email confirmation to confirm the deletion request. 1. After email confirmation, the user request is added to the requests table for review by the Administrator. The Administrator can also add a user manually with an email look up and review. - 1. If the Data User has content published on the site for any post types or comments, they will be added to this table. If they do not have any content, they will receive a confirmation of erasure request and be provided a 6 digit Token for safekeeping after erasure in case of recover data needs. + 1. If the Data Subject has content published on the site for any post types or comments, they will be added to this table. If they do not have any content, they will receive a confirmation of erasure request and be provided a 6 digit Token for safekeeping after erasure in case of recover data needs. 1. The requests table allows the Administrator to reassign any content to another user or delete it. - 1. In the event of comments, the Data User’s content would be made anonymous. + 1. In the event of comments, the Data Subject’s content would be made anonymous. 1. Admin can also manually add users to the erasure requests table with a manual email search **Right to Access Data Request & User Data Portability** -1. The Data User can place a request to download their data with the shortcode. +1. The Data Subject can place a request to download their data with the shortcode. 1. After requesting their data, the user will receive a double opt-in confirmation email then the plugin will generate an XML or JSON file, which will be emailed to them for download with an expiration time of 48 hours. **Right to Rectify & Complaint Requests** -1. The Data User can place a request to rectify data or file a complaint with the shortcode. +1. The Data Subject can place a request to rectify data or file a complaint with the shortcode. 1. After making their request, the user will receive a double opt-in confirmation email and then add them to the table for admin to handle the request. ## Tools **Access Data** -The Access Data tool allows the Admin to look up a user email and view the data of a particular user. The Admin can download and export the data in a JSON or XML format and provide to the Data User if manually requested. +The Access Data tool allows the Admin to look up a user email and view the data of a particular user. The Admin can download and export the data in a JSON or XML format and provide to the Data Subject if manually requested. -NOTE: This method should not be used without the Data User confirming their identity. +NOTE: This method should not be used without the Data Subject confirming their identity. **Audit Log** -Everything the Data User does from registration, providing consent to the privacy policy, terms of service and other requests are logged and encrypted in a database. Data breach notifications are also logged to all data users upon confirmation by Controller. +Everything the Data Subject does from registration, providing consent to the privacy policy, terms of service and other requests are logged and encrypted in a database. Data breach notifications are also logged to all Data Subjects upon confirmation by Controller. -1. Using the Data User's email, you can look up and retrieve the user information and display it. -1. If the Data User has been removed from the site, this encrypted log is deleted from the database and saved as an encrypted file inside the plugin folder. +1. Using the Data Subject's email, you can look up and retrieve the user information and display it. +1. If the Data Subject has been removed from the site, this encrypted log is deleted from the database and saved as an encrypted file inside the plugin folder. -If in the future, the Data User makes a complaint or there is a need to recover the data, the user can provide their email address and the 6 digit token they received from the deletion confirmation email to decrypt and retrieve the file. +If in the future, the Data Subject makes a complaint or there is a need to recover the data, the user can provide their email address and the 6 digit token they received from the deletion confirmation email to decrypt and retrieve the file. **Data Breach & Notifications** @@ -147,7 +147,7 @@ For other special categories of personal data, there are more strict regulations #### What are the penalties for non-compliance? Organizations can be fined up to 4% of annual global turnover for breaching GDPR or €20 Million. This is the maximum fine that can be imposed for the most serious infringements. -There is a tiered approach to the fines whereby a company can be fined 2% for not having their records in order (Article 28), not notifying the supervising authority and Data User about a security breach or for investigating and assessing the breach. +There is a tiered approach to the fines whereby a company can be fined 2% for not having their records in order (Article 28), not notifying the supervising authority and Data Subject about a security breach or for investigating and assessing the breach. #### Am I compliant just by activating this plugin? No, this plugin is meant to assist a Controller, Data Processor, and Data Protection Officer (DPO) with efforts to meet the obligations and rights enacted under the GDPR. @@ -157,6 +157,123 @@ Activating this plugin does not guarantee that an organisation is successfully m ## Changelog +##### 2.0.6 +* Fix XML export error. + +##### 2.0.5 +* Fix cookie toggle indicator set to on even if the user had previously untoggled it. +* Other minor fixes to the audit log reconsent. + +##### 2.0.4 +* Adding two missing translation strings +* Removing debug code that I forgot to remove from 2.0.3 +* Adding to audit log when user reconsents. + +##### 2.0.3 +* Fix third party cookies now showing up in the privacy preferences window or the settings page. + +##### 2.0.2 +* Fix reconsent not logging correctly on reconsent +* Fix reconsent bar not showing up. + +##### 2.0.1 +* Removing things that should have been deleted prior to updating to 2.0.0. +* Fix new reconsent bar missing closing div. + +##### 2.0.0 +* Change all requests and privacy preferences window to AJAX to avoid the admin-post hook issue. +* We do not track privacy policy anymore. We can now track any kind of policy that users want. Those have been moved to each consent. +* Including more options. Including "enable/disable" the privacy bar. +* New filters and funtions were included. +* Making the settings a little more accessible. +* Removed the reconsent modal. It was too obtrusive. We switched to a more subtle notification bar. + +##### 1.4.7 +* Fix for users who were complaining about their scroll bars missing if they did not select a privacy policy page. + +##### 1.4.6 +* Change re-consent logic so it doesn't influence SEO with repeated content. + +##### 1.4.5 +* Minor style adjustments +* Body scroll is disabled when modal is active +* Adjusting privacy bar to sit behind re-consent modal + +##### 1.4.4 +* Fix all_cookies field being displayed as text field instead of hidden. + +##### 1.4.3 +* Found one more instance of Telemetry Scanner, changed to Telemetry Tracker. +* Delete cookies when users change their preferences and disable some cookies. +* Changed cookies used field to textarea for easier reading when lots of cookies are set. +* Added a text to the settings page explaining that even if cookies are registered, if the user does not input some text for the privacy banner, it won't show up. +* Adding filters for the admin notification email. [https://gdpr-wp.com/knowledge-base/actions-filters/](https://gdpr-wp.com/knowledge-base/actions-filters/) +* Adding filters for the request forms button label. [https://gdpr-wp.com/knowledge-base/actions-filters/](https://gdpr-wp.com/knowledge-base/actions-filters/) + +##### 1.4.2 +* Fix privacy bar reapearing. Cookie was not set to expire in a year. + +##### 1.4.1 +* Allow links in the consent description in the wp profile page. +* Force tabs to be an array when empty to fix the notices and fatal error in the front end. +* Hide cookies sidebar in the privacy centre window if no cookies were registered. +* Adding a filter so the privacy bar button text can be changed. +* Changing Telemetry Scanner to Telemetry Tracker for consistency across the plugin. +* Translating missing strings. +* Adding options to add or remove consent checkboxes to woocommerce registration form and checkout registration form. + +##### 1.4.0 +* Adding the option to disable the plugin CSS. Be careful when using this option. Make sure you know what you are doing. +* Adding the option to enable or disable the telemetry feature. +* Adding the option to add reCaptcha to the request forms. +* Adding comments to the personal data export. +* Moved privacy bar content field and privacy excerpt field to the general settings tab. +* Removed automatic privacy policy link from the privacy bar. +* We now accept links in the privacy bar content to get around the last change. +* Changed Telemetry cleanup schedule to hourly. +* Forcing the privacy bar to stay on the left to avoid CSS incompatibilities. +* Renaming the tab classes in the admin panel to again avoid incompatibilities. +* Fix privacy preference centre only showing up when cookies were registered. + +##### 1.3.5 +* Fix undefined variable warning. +* Fix WooCommerce and possibly other plugins nonce manipulation for logged out users. For real this time. +* Fix XML export fatal error when meta key starts with a number. + +##### 1.3.4 +* Prefixed all nonce actions. +* Fixed cookies being checked by default when they should have been unchecked. +* Possible fix for strange characters causing XML export to throw an error. +* Fix for WooCommerce nonce manipulation for logged out users that was preventing visitors from updating their privacy preferences. + +##### 1.3.3 +* Fix translation error everybody has been complaining about. + +##### 1.3.2 +* Fix issue with the is_allowed_cookie JS function. + +##### 1.3.1 +* Fix consent syncing when difference comes from database and not the cookie. +* Might allow people to use external services like iubenda. + +##### 1.3.0 +* Added BuddyPress registration form integration. +* Added WooCommerce registration and checkout registration form integration. +* Added admin notifications when a user makes a request that requires interaction. + +##### 1.2.2 +* Adding a couple missing translation strings. +* Wrapping the telemetry post type page in an `if` so people can unregister it if they want to. + +##### 1.2.1 +* After one user reported that their scroll bar disappeared I decided to remove the code that do that when the reconsent modal shows up. This has no impact on anything, but it might fix this user problem. + +##### 1.2.0 +* Fix has_consent and is_allowed_cookie JavaScript functions not being available globally. +* Add a function to get the consent checkbox without echoing them. +* Change how the user deletion request works. We removed the email attachment to avoid being considered spam. The user can now download it immediatelly by clicking on their email link. +* Adding an option for user deletions always be added to the request review table. That will allow you to remove your users from third-party services before removing them from your site. + ##### 1.1.6 * Fix weird javascript issue that was preventing users from using the "Close my account" feature. diff --git a/README.txt b/README.txt index 6c03342d..0aaeeae1 100755 --- a/README.txt +++ b/README.txt @@ -2,10 +2,10 @@ Contributors: fclaussen, matthewfarlymn, trewknowledge Donate link: http://gdpr-wp.com/donate/ Tags: gdpr, compliance, privacy, law, general data protection regulation -Requires at least: 4.0 +Requires at least: 4.7 Requires PHP: 5.6 Tested up to: 4.9 -Stable tag: 1.1.6 +Stable tag: 2.0.6 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -34,11 +34,11 @@ You can send your pull request at [https://github.com/trewknowledge/gdpr](https: * Re-assignment of user data on erasure requests & pseudonymization of user website data * Data Processor settings and publishing of contact information * Right to access data by admin dashboard with email look up and export -* Right to access data by Data User with front-end requests button & double opt-in confirmation email -* Right to portability & export of data by Admin or Data User in XML or JSON formats -* Encrypted audit logs for the lifetime of Data User compliance activity -* Data User Secret Token for two-factor decryption and recovery of data -* Data breach notification logs and batch email notifications to data users +* Right to access data by Data Subject with front-end requests button & double opt-in confirmation email +* Right to portability & export of data by Admin or Data Subject in XML or JSON formats +* Encrypted audit logs for the lifetime of Data Subject compliance activity +* Data Subject Secret Token for two-factor decryption and recovery of data +* Data breach notification logs and batch email notifications to Data Subjects * Telemetry Tracker for visualizing plugins and website data == Settings == @@ -72,28 +72,28 @@ For optional consents, there's a wrapper function `have_consent( $consent_id )` Consents are logged to the user record for auditing or for access purposes. -== Requests Table & Rights of Data User == +== Requests Table & Rights of Data Subject == **Right to Erasure Requests** -1. The Data User is able to submit a request to be erased from the site using a shortcode. -1. When a request is made, the Data User will receive an email confirmation to confirm the deletion request. +1. The Data Subject is able to submit a request to be erased from the site using a shortcode. +1. When a request is made, the Data Subject will receive an email confirmation to confirm the deletion request. 1. After email confirmation, the user request is added to the requests table for review by the Administrator. The Administrator can also add a user manually with an email look up and review. - 1. If the Data User has content published on the site for any post types or comments, they will be added to this table. If they do not have any content, they will receive a confirmation of erasure request and be provided a 6 digit Token for safekeeping after erasure in case of recover data needs. + 1. If the Data Subject has content published on the site for any post types or comments, they will be added to this table. If they do not have any content, they will receive a confirmation of erasure request and be provided a 6 digit Token for safekeeping after erasure in case of recover data needs. 1. The requests table allows the Administrator to reassign any content to another user or delete it. - 1. In the event of comments, the Data User’s content would be made anonymous. + 1. In the event of comments, the Data Subject’s content would be made anonymous. 1. Admin can also manually add users to the erasure requests table with a manual email search **Right to Access Data Request & User Data Portability** -1. The Data User can place a request to download their data with the shortcode. +1. The Data Subject can place a request to download their data with the shortcode. 1. After requesting their data, the user will receive a double opt-in confirmation email then the plugin will generate an XML or JSON file, which will be emailed to them for download with an expiration time of 48 hours. **Right to Rectify & Complaint Requests** -1. The Data User can place a request to rectify data or file a complaint with the shortcode. +1. The Data Subject can place a request to rectify data or file a complaint with the shortcode. 1. After making their request, the user will receive a double opt-in confirmation email and then add them to the table for admin to handle the request. @@ -101,18 +101,18 @@ Consents are logged to the user record for auditing or for access purposes. **Access Data** -The Access Data tool allows the Admin to look up a user email and view the data of a particular user. The Admin can download and export the data in a JSON or XML format and provide to the Data User if manually requested. +The Access Data tool allows the Admin to look up a user email and view the data of a particular user. The Admin can download and export the data in a JSON or XML format and provide to the Data Subject if manually requested. -NOTE: This method should not be used without the Data User confirming their identity. +NOTE: This method should not be used without the Data Subject confirming their identity. **Audit Log** -Everything the Data User does from registration, providing consent to the privacy policy, terms of service and other requests are logged and encrypted in a database. Data breach notifications are also logged to all data users upon confirmation by Controller. +Everything the Data Subject does from registration, providing consent to the privacy policy, terms of service and other requests are logged and encrypted in a database. Data breach notifications are also logged to all Data Subjects upon confirmation by Controller. -1. Using the Data User's email, you can look up and retrieve the user information and display it. -1. If the Data User has been removed from the site, this encrypted log is deleted from the database and saved as an encrypted file inside the plugin folder. +1. Using the Data Subject's email, you can look up and retrieve the user information and display it. +1. If the Data Subject has been removed from the site, this encrypted log is deleted from the database and saved as an encrypted file inside the plugin folder. -If in the future, the Data User makes a complaint or there is a need to recover the data, the user can provide their email address and the 6 digit token they received from the deletion confirmation email to decrypt and retrieve the file. +If in the future, the Data Subject makes a complaint or there is a need to recover the data, the user can provide their email address and the 6 digit token they received from the deletion confirmation email to decrypt and retrieve the file. **Data Breach & Notifications** @@ -184,7 +184,7 @@ For other special categories of personal data, there are more strict regulations Organizations can be fined up to 4% of annual global turnover for breaching GDPR or €20 Million. This is the maximum fine that can be imposed for the most serious infringements. -There is a tiered approach to the fines whereby a company can be fined 2% for not having their records in order (Article 28), not notifying the supervising authority and Data User about a security breach or for investigating and assessing the breach. +There is a tiered approach to the fines whereby a company can be fined 2% for not having their records in order (Article 28), not notifying the supervising authority and Data Subject about a security breach or for investigating and assessing the breach. = Am I compliant just by activating this plugin? = @@ -206,6 +206,123 @@ Activating this plugin does not guarantee that an organisation is successfully m == Changelog == += 2.0.6 = +* Fix XML export error. + += 2.0.5 = +* Fix cookie toggle indicator set to on even if the user had previously untoggled it. +* Other minor fixes to the audit log reconsent. + += 2.0.4 = +* Adding two missing translation strings +* Removing debug code that I forgot to remove from 2.0.3 +* Adding to audit log when user reconsents. + += 2.0.3 = +* Fix third party cookies now showing up in the privacy preferences window or the settings page. + += 2.0.2 = +* Fix reconsent not logging correctly on reconsent +* Fix reconsent bar not showing up. + += 2.0.1 = +* Removing things that should have been deleted prior to updating to 2.0.0. +* Fix new reconsent bar missing closing div. + += 2.0.0 = +* Change all requests and privacy preferences window to AJAX to avoid the admin-post hook issue. +* We do not track privacy policy anymore. We can now track any kind of policy that users want. Those have been moved to each consent. +* Including more options. Including "enable/disable" the privacy bar. +* New filters and funtions were included. +* Making the settings a little more accessible. +* Removed the reconsent modal. It was too obtrusive. We switched to a more subtle notification bar. + += 1.4.7 = +* Fix for users who were complaining about their scroll bars missing if they did not select a privacy policy page. + += 1.4.6 = +* Change re-consent logic so it doesn't influence SEO with repeated content. + += 1.4.5 = +* Minor style adjustments +* Body scroll is disabled when modal is active +* Adjusting privacy bar to sit behind re-consent modal + += 1.4.4 = +* Fix all_cookies field being displayed as text field instead of hidden. + += 1.4.3 = +* Found one more instance of Telemetry Scanner, changed to Telemetry Tracker. +* Delete cookies when users change their preferences and disable some cookies. +* Changed cookies used field to textarea for easier reading when lots of cookies are set. +* Added a text to the settings page explaining that even if cookies are registered, if the user does not input some text for the privacy banner, it won't show up. +* Adding filters for the admin notification email. [https://gdpr-wp.com/knowledge-base/actions-filters/](https://gdpr-wp.com/knowledge-base/actions-filters/) +* Adding filters for the request forms button label. [https://gdpr-wp.com/knowledge-base/actions-filters/](https://gdpr-wp.com/knowledge-base/actions-filters/) + += 1.4.2 = +* Fix privacy bar reapearing. Cookie was not set to expire in a year. + += 1.4.1 = +* Allow links in the consent description in the wp profile page. +* Force tabs to be an array when empty to fix the notices and fatal error in the front end. +* Hide cookies sidebar in the privacy centre window if no cookies were registered. +* Adding a filter so the privacy bar button text can be changed. +* Changing Telemetry Scanner to Telemetry Tracker for consistency across the plugin. +* Translating missing strings. +* Adding options to add or remove consent checkboxes to woocommerce registration form and checkout registration form. + += 1.4.0 = +* Adding the option to disable the plugin CSS. Be careful when using this option. Make sure you know what you are doing. +* Adding the option to enable or disable the telemetry feature. +* Adding the option to add reCaptcha to the request forms. +* Adding comments to the personal data export. +* Moved privacy bar content field and privacy excerpt field to the general settings tab. +* Removed automatic privacy policy link from the privacy bar. +* We now accept links in the privacy bar content to get around the last change. +* Changed Telemetry cleanup schedule to hourly. +* Forcing the privacy bar to stay on the left to avoid CSS incompatibilities. +* Renaming the tab classes in the admin panel to again avoid incompatibilities. +* Fix privacy preference centre only showing up when cookies were registered. + += 1.3.5 = +* Fix undefined variable warning. +* Fix WooCommerce and possibly other plugins nonce manipulation for logged out users. For real this time. +* Fix XML export fatal error when meta key starts with a number. + += 1.3.4 = +* Prefixed all nonce actions. +* Fixed cookies being checked by default when they should have been unchecked. +* Possible fix for strange characters causing XML export to throw an error. +* Fix for WooCommerce nonce manipulation for logged out users that was preventing visitors from updating their privacy preferences. + += 1.3.3 = +* Fix translation error everybody has been complaining about. + += 1.3.2 = +* Fix issue with the is_allowed_cookie JS function. + += 1.3.1 = +* Fix consent syncing when difference comes from database and not the cookie. +* Might allow people to use external services like iubenda. + += 1.3.0 = +* Added BuddyPress registration form integration. +* Added WooCommerce registration and checkout registration form integration. +* Added admin notifications when a user makes a request that requires interaction. + += 1.2.2 = +* Adding a couple missing translation strings. +* Wrapping the telemetry post type page in an `if` so people can unregister it if they want to. + += 1.2.1 = +* After one user reported that their scroll bar disappeared I decided to remove the code that do that when the reconsent modal shows up. This has no impact on anything, but it might fix this user problem. + += 1.2.0 = +* Fix has_consent and is_allowed_cookie JavaScript functions not being available globally. +* Add a function to get the consent checkbox without echoing them. +* Change how the user deletion request works. We removed the email attachment to avoid being considered spam. The user can now download it immediatelly by clicking on their email link. +* Adding an option for user deletions always be added to the request review table. That will allow you to remove your users from third-party services before removing them from your site. + = 1.1.6 = * Fix weird javascript issue that was preventing users from using the "Close my account" feature. @@ -281,6 +398,11 @@ Activating this plugin does not guarantee that an organisation is successfully m == Upgrade Notice == += 2.0.0 = +We have added a few new options which must be reviewed before continuing to use the plugin. +For cookies, we have added a status which allows you to set them as ON, OFF or Required. For consents, we moved the policy selector into each consent. All policies can now be tracked through this. +Please keep in mind the plugin might not work as intended until these settings are reviewed. + = 1.0.0 = This is a major rewrite of the plugin. Things will look different and work differently. We tried to keep most things the same so the impact would be minimal. diff --git a/admin/class-gdpr-admin.php b/admin/class-gdpr-admin.php index f26e8de6..44060000 100755 --- a/admin/class-gdpr-admin.php +++ b/admin/class-gdpr-admin.php @@ -139,7 +139,10 @@ public function add_menu() { $cpt = 'telemetry'; $cpt_obj = get_post_type_object( $cpt ); - add_submenu_page( $parent_slug, $cpt_obj->labels->name, $cpt_obj->labels->menu_name, $capability, $menu_slug ); + if ( $cpt_obj ) { + add_submenu_page( $parent_slug, $cpt_obj->labels->name, $cpt_obj->labels->menu_name, $capability, $menu_slug ); + } + add_action( "load-{$requests_hook}", array( 'GDPR_Help', 'add_requests_help' ) ); add_action( "load-{$tools_hook}", array( 'GDPR_Help', 'add_tools_help' ) ); @@ -148,41 +151,35 @@ public function add_menu() { } /** - * Sanitizing user input on the cookie tabs. + * Sanitizing user input on the cookie categories. * @since 1.0.0 * @author Fernando Claussen * @param array $tabs The cookie tabs. * @return array The sanitized options. */ - public function sanitize_cookie_tabs( $tabs ) { + public function sanitize_cookie_categories( $cookie_categories ) { $output = array(); - if ( ! is_array( $tabs ) ) { - return $tabs; + if ( ! is_array( $cookie_categories ) ) { + return array(); } - foreach ( $tabs as $key => $props ) { - if ( '' === $props['name'] || '' === $props['how_we_use'] ) { - unset( $tabs[ $key ] ); - continue; - } + foreach ( $cookie_categories as $key => $props ) { + $key = sanitize_text_field( $key ); $output[ $key ] = array( - 'name' => sanitize_text_field( wp_unslash( $props['name'] ) ), - 'always_active' => isset( $props['always_active'] ) ? boolval( $props['always_active'] ) : 0, - 'how_we_use' => wp_kses_post( $props['how_we_use'] ), - 'cookies_used' => sanitize_text_field( wp_unslash( $props['cookies_used'] ) ), + 'name' => isset( $props['name'] ) ? sanitize_text_field( $props['name'] ) : '', + 'status' => isset( $props['status'] ) ? sanitize_text_field( $props['status'] ) : '', + 'cookies_used' => isset( $props['cookies_used'] ) ? sanitize_text_field( wp_unslash( $props['cookies_used'] ) ) : '', + 'how_we_use' => isset( $props['how_we_use'] ) ? wp_kses_post( $props['how_we_use'] ) : '', + 'hosts' => array(), ); - if ( isset( $props['hosts'] ) ) { - foreach ( $props['hosts'] as $host_key => $host ) { - if ( empty( $host['name'] ) || empty( $host['cookies_used'] ) || empty( $host['cookies_used'] ) ) { - unset( $props['hosts'][ $host_key ] ); - continue; - } - $output[ $key ]['hosts'][ $host_key ] = array( - 'name' => sanitize_text_field( wp_unslash( $host['name'] ) ), - 'cookies_used' => sanitize_text_field( wp_unslash( $host['cookies_used'] ) ), - 'optout' => esc_url_raw( $host['optout'] ), + foreach ( $props['hosts'] as $domain_key => $domain ) { + $domain_key = sanitize_text_field( $domain_key ); + + $output[ $key ]['hosts'][ $domain_key ] = array( + 'cookies_used' => isset( $domain['cookies_used'] ) ? sanitize_text_field( $domain['cookies_used'] ) : '', + 'optout' => isset( $domain['optout'] ) ? esc_url_raw( $domain['optout'] ) : '', ); } } @@ -197,19 +194,39 @@ public function sanitize_cookie_tabs( $tabs ) { */ public function register_settings() { $settings = array( - 'gdpr_privacy_policy_page' => 'intval', - 'gdpr_cookie_banner_content' => 'sanitize_textarea_field', - 'gdpr_cookie_banner_privacy_policy_link_label' => 'sanitize_text_field', - 'gdpr_cookie_privacy_excerpt' => 'sanitize_textarea_field', - 'gdpr_cookie_popup_content' => array( $this, 'sanitize_cookie_tabs' ), - 'gdpr_email_limit' => 'intval', - 'gdpr_consent_types' => array( $this, 'sanitize_consents' ), + 'gdpr_cookie_banner_content' => array( $this, 'sanitize_with_links' ), + 'gdpr_cookie_privacy_excerpt' => 'sanitize_textarea_field', + 'gdpr_cookie_popup_content' => array( $this, 'sanitize_cookie_categories' ), + 'gdpr_email_limit' => 'intval', + 'gdpr_consent_types' => array( $this, 'sanitize_consents' ), + 'gdpr_deletion_needs_review' => 'boolval', + 'gdpr_disable_css' => 'boolval', + 'gdpr_enable_telemetry_tracker' => 'boolval', + 'gdpr_use_recaptcha' => 'boolval', + 'gdpr_recaptcha_site_key' => 'sanitize_text_field', + 'gdpr_recaptcha_secret_key' => 'sanitize_text_field', + 'gdpr_add_consent_checkboxes_registration' => 'boolval', + 'gdpr_add_consent_checkboxes_checkout' => 'boolval', + 'gdpr_refresh_after_preferences_update' => 'boolval', + 'gdpr_enable_privacy_bar' => 'boolval', + 'gdpr_display_cookie_categories_in_bar' => 'boolval', ); foreach ( $settings as $option_name => $sanitize_callback ) { register_setting( 'gdpr', $option_name, array( 'sanitize_callback' => $sanitize_callback ) ); } } + /** + * Sanitize content but allow links. + * @param string $string The string that will be sanitized. + * @return string Sanitized string. + * @since 1.4.0 + * @author Fernando Claussen + */ + public function sanitize_with_links( $string ) { + return wp_kses( $string, $this->allowed_html ); + } + /** * Sanitize the consents option when saving. * @since 1.0.0 @@ -224,15 +241,15 @@ public function sanitize_consents( $consents ) { } foreach ( $consents as $key => $props ) { - if ( '' === $props['name'] || '' === $props['description'] ) { + if ( '' === $props['name'] ) { unset( $consents[ $key ] ); continue; } $output[ $key ] = array( 'name' => sanitize_text_field( wp_unslash( $props['name'] ) ), - 'required' => isset( $props['required'] ) ? boolval( $props['required'] ) : 0, - 'description' => wp_kses( wp_unslash( $props['description'] ), $this->allowed_html ), - 'registration' => wp_kses( wp_unslash( $props['registration'] ), $this->allowed_html ), + 'policy-page' => isset( $props['policy-page'] ) ? absint( $props['policy-page'] ) : 0, + 'description' => isset( $props['description'] ) ? wp_kses( wp_unslash( $props['description'] ), $this->allowed_html ) : '', + 'registration' => isset( $props['registration'] ) ? wp_kses( wp_unslash( $props['registration'] ), $this->allowed_html ) : '', ); } return $output; @@ -245,14 +262,10 @@ public function sanitize_consents( $consents ) { * @author Fernando Claussen */ public function settings_page_template() { - $privacy_policy_page = get_option( 'gdpr_privacy_policy_page', 0 ); - $tabs = array( - 'general' => esc_html__( 'General', 'gdpr' ), - 'cookies' => esc_html__( 'Cookies', 'gdpr' ), - 'consents' => esc_html__( 'Consent', 'gdpr' ), - ); + $registered_cookies = get_option( 'gdpr_cookie_popup_content', array() ); + $consent_types = get_option( 'gdpr_consent_types', array() ); - $tabs = apply_filters( 'gdpr_settings_pages', $tabs ); + $pages = get_pages(); include_once plugin_dir_path( __FILE__ ) . 'partials/templates/tmpl-cookies.php'; include_once plugin_dir_path( __FILE__ ) . 'partials/templates/tmpl-consents.php'; @@ -319,7 +332,7 @@ public function tools_page_template() { * @author Fernando Claussen */ public function access_data() { - if ( ! isset( $_POST['nonce'], $_POST['email'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'access-data' ) ) { + if ( ! isset( $_POST['nonce'], $_POST['email'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'gdpr-access-data' ) ) { wp_send_json_error(); } @@ -331,6 +344,10 @@ public function access_data() { } $usermeta = GDPR::get_user_meta( $user->ID ); + $comments = get_comments( array( + 'author_email' => $user->user_email, + 'include_unapproved' => true, + ) ); $user_consents = get_user_meta( $user->ID, 'gdpr_consents' ); ob_start(); @@ -375,7 +392,7 @@ public function access_data() { '; if ( ! empty( $user_consents ) ) { - echo '

Consent Given

'; + echo '

' . esc_html__( 'Consent Given', 'gdpr' ) . '

'; echo ' @@ -390,8 +407,50 @@ public function access_data() { echo '
'; } + if ( ! empty( $comments ) ) { + echo '

' . esc_html__( 'Comments', 'gdpr' ) . '

'; + foreach ( $comments as $v ) { + echo ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
' . esc_html__( 'Comment Field', 'gdpr' ) . '' . esc_html__( 'Comment Data', 'gdpr' ) . '
comment_author' . esc_html( $v->comment_author ) . '
comment_author_email' . esc_html( $v->comment_author_email ) . '
comment_author_url' . esc_html( $v->comment_author_url ) . '
comment_author_IP' . esc_html( $v->comment_author_IP ) . '
comment_date' . esc_html( $v->comment_date ) . '
comment_agent' . esc_html( $v->comment_agent ) . '
comment_content' . esc_html( $v->comment_content ) . '

'; + } + } + if ( ! empty( $usermeta ) ) { - echo '

Metadata

'; + echo '

' . esc_html__( 'Metadata', 'gdpr' ) . '

'; echo ' @@ -431,7 +490,7 @@ public function access_data() { * @author Fernando Claussen */ public function audit_log() { - if ( ! isset( $_POST['nonce'], $_POST['email'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'audit-log' ) ) { + if ( ! isset( $_POST['nonce'], $_POST['email'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'gdpr-audit-log' ) ) { wp_send_json_error(); } @@ -451,59 +510,76 @@ public function audit_log() { wp_send_json_success( $log ); } - /** - * Admin notice when the user haven't picked a privacy policy page. - * @since 1.0.0 - * @author Fernando Claussen - */ - public function privacy_policy_page_missing() { - $privacy_page = get_option( 'gdpr_privacy_policy_page', '' ); - if ( ! empty( $privacy_page ) ) { - return; + public function review_settings_after_v2_notice() { + // Check the transient to see if we've just updated the plugin + if ( get_transient( 'gdpr_updated' ) && '2.0.0' === $this->version ) { + ?> +
+

+

+

+

+

+
+ -
-

- -

-

- -

-
- */ - public function privacy_policy_updated_notice() { - $updated = get_option( 'gdpr_privacy_policy_updated' ); - if ( ! $updated ) { + public function policy_updated_notice() { + $policies_updated = get_option( 'gdpr_policies_updated', array() ); + if ( empty( $policies_updated ) ) { return; } + + foreach ( $policies_updated as $key => $policy ) { ?> -
-

- -

-
- +
+ + + + + + + + + +

-
- - + + + + +

*/ public function send_data_breach_confirmation_email() { - if ( ! isset( $_POST['gdpr_data_breach_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST[ 'gdpr_data_breach_nonce' ] ), 'data-breach' ) ) { + if ( ! isset( $_POST['gdpr_data_breach_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST[ 'gdpr_data_breach_nonce' ] ), 'gdpr-data-breach' ) ) { wp_die( esc_html__( 'We could not verify the the security token. Please try again.', 'gdpr' ) ); } @@ -631,7 +707,7 @@ public function registration_errors( $errors, $sanitized_user_login, $user_email } foreach ( $consent_types as $key => $consent ) { - if ( $consent['required'] ) { + if ( $consent['policy-page'] ) { if ( ! isset( $_POST['user_consents'][ $key ] ) ) { $errors->add( 'missing_required_consents', sprintf( '%s: %s %s.', @@ -651,11 +727,16 @@ public function registration_errors( $errors, $sanitized_user_login, $user_email * @author Fernando Claussen */ public function seek_consent() { - if ( ! isset( $_POST['privacy-policy-updated-nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['privacy-policy-updated-nonce'] ), 'seek_consent' ) ) { - wp_die( esc_html__( 'We could not verify the the security token. Please try again.', 'gdpr' ) ); + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'gdpr-seek-consent' ) ) { + wp_send_json_error( esc_html__( 'We could not verify the the security token. Please try again.', 'gdpr' ) ); } - delete_option( 'gdpr_privacy_policy_updated' ); + $policy_id = sanitize_text_field( $_POST['policy_id'] ); + $policy_name = sanitize_text_field( $_POST['policy_name'] ); + $policies_updated = get_option( 'gdpr_policies_updated', array() ); + + unset( $policies_updated[ $policy_id ] ); + update_option( 'gdpr_policies_updated', $policies_updated ); $users = get_users( array( 'fields' => 'all_with_meta' @@ -663,25 +744,14 @@ public function seek_consent() { foreach ( $users as $user ) { $usermeta = get_user_meta( $user->ID, 'gdpr_consents' ); - if ( in_array( 'privacy-policy', $usermeta ) ) { - GDPR_Audit_Log::log( $user->ID, esc_html__( 'Privacy Policy has been updated. Removing the Privacy Policy consent and requesting new consent.', 'gdpr' ) ); - delete_user_meta( $user->ID, 'gdpr_consents', 'privacy-policy' ); + if ( in_array( $policy_id, $usermeta ) ) { + /* translators: 1: The name of the policy that was updated. */ + GDPR_Audit_Log::log( $user->ID, sprintf( esc_html__( '%1$s has been updated. Removing the %1$s consent and requesting new consent.', 'gdpr' ), esc_html( $policy_name ) ) ); + delete_user_meta( $user->ID, 'gdpr_consents', $policy_id ); } } - add_settings_error( 'gdpr', 'resolved', esc_html__( 'Users will have to consent to the updated privacy policy on login.', 'gdpr' ), 'updated' ); - set_transient( 'settings_errors', get_settings_errors(), 30 ); - wp_safe_redirect( - esc_url_raw( - add_query_arg( - array( - 'settings-updated' => true - ), - wp_get_referer() - ) - ) - ); - exit; + wp_send_json_success(); } /** @@ -691,20 +761,29 @@ public function seek_consent() { * @param int $ID The page ID. * @param WP_Post $post The post object. */ - public function privacy_policy_updated( $ID, $post ) { - $privacy_page = (int) get_option( 'gdpr_privacy_policy_page', 0 ); - $ID = (int) $ID; - if ( $ID === $privacy_page ) { - $revisions = wp_get_post_revisions( $ID ); - $revisions = array_filter( $revisions, function( $rev ) { - return strpos( $rev->post_name, 'autosave' ) === false; - }); - - reset( $revisions ); - if ( current( $revisions )->post_content !== $post->post_content ) { - update_option( 'gdpr_privacy_policy_updated', 1 ); + public function policy_updated( $ID, $post ) { + $policies_updated = get_option( 'gdpr_policies_updated', array() ); + $consents = get_option( 'gdpr_consent_types', array() ); + $required_consents = array_filter( $consents, function( $consent ) { + return ! empty( $consent['policy-page'] ); + } ); + + if ( ! empty( $required_consents ) ) { + foreach ( $required_consents as $consent_id => $consent ) { + if ( $ID === $consent['policy-page'] ) { + $revisions = wp_get_post_revisions( $ID ); + $revisions = array_filter( $revisions, function( $rev ) { + return strpos( $rev->post_name, 'autosave' ) === false; + }); + + reset( $revisions ); + if ( current( $revisions )->post_content !== $post->post_content ) { + $policies_updated[ $consent_id ] = $consent['name']; + } + } } } + update_option( 'gdpr_policies_updated', $policies_updated ); } /** @@ -712,12 +791,15 @@ public function privacy_policy_updated( $ID, $post ) { * @since 1.0.0 * @author Fernando Claussen */ - public function ignore_privacy_policy_update() { - if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'ignore_update' ) ) { + public function ignore_policy_update() { + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'gdpr-ignore-update' ) ) { wp_send_json_error( esc_html__( 'We could not verify the the security token. Please try again.', 'gdpr' ) ); } - delete_option( 'gdpr_privacy_policy_updated' ); + $policy = sanitize_text_field( $_POST['policy_id'] ); + $policies_updated = get_option( 'gdpr_policies_updated', array() ); + unset( $policies_updated[ $policy ] ); + update_option( 'gdpr_policies_updated', $policies_updated ); wp_send_json_success(); } @@ -749,7 +831,7 @@ public function edit_user_profile( $user ) { > - + allowed_html ); ?>
@@ -784,4 +866,68 @@ public function user_profile_update( $user_id ) { setcookie( "gdpr[consent_types]", json_encode( $consents ), time() + YEAR_IN_SECONDS, "/" ); } + /** + * Add the consent checkboxes to the checkout page. + * @since 1.3.0 + * @author Fernando Claussen + * @param int $fields The checkout fields. + */ + public function woocommerce_consent_checkboxes( $fields ) { + $consent_types = get_option( 'gdpr_consent_types', array() ); + + foreach ( $consent_types as $key => $consent ) { + $required = ( isset( $consent['policy-page'] ) && $consent['policy-page'] ) ? 'required' : ''; + + $fields['account']['user_consents_' . esc_attr( $key ) ] = array( + 'type' => 'checkbox', + 'label' => wp_kses( $consent['registration'], $this->allowed_html ), + 'required' => $required, + ); + } + return $fields; + } + + /** + * Save the user consent when registering from the checkout page. + * @since 1.3.0 + * @author Fernando Claussen + * @param int $customer_id The user ID. + * @param array $data All data submitted during checkout. + */ + public function woocommerce_checkout_save_consent( $customer_id, $data ) { + $data = array_filter( $data ); + $consent_arr = array_filter( array_keys( $data ), function( $item ) { + return false !== strpos( $item, 'user_consents_' ); + } ); + + foreach ( $consent_arr as $key => $value ) { + $consent = str_replace( 'user_consents_', '', $value ); + add_user_meta( $customer_id, 'gdpr_consents', $consent ); + } + } + + + /** + * Filters the display output of custom columns in the Users list table. + * @since 2.0.0 + * @author Fernando Claussen + * + * @param string $val Custom column output. Default empty. + * @param string $column_name Column name. + * @param int $user_id ID of the currently-listed user. + */ + public function add_consents_to_consents_column( $val, $column_name, $user_id ) { + if ( 'consents' === $column_name ) { + $user_consents = get_user_meta( $user_id, 'gdpr_consents' ); + return implode(', ', $user_consents ); + } + + return $val; + } + + public function add_consents_column_to_user_table( $column_headers ) { + $column_headers['consents'] = esc_html__( 'Consents', 'gdpr' ); + return $column_headers; + } + } diff --git a/admin/class-gdpr-requests-admin.php b/admin/class-gdpr-requests-admin.php index 5c7e569a..848c1aa9 100644 --- a/admin/class-gdpr-requests-admin.php +++ b/admin/class-gdpr-requests-admin.php @@ -26,7 +26,7 @@ class GDPR_Requests_Admin extends GDPR_Requests { * @author Fernando Claussen */ public function add_to_deletion_requests() { - if ( ! isset( $_POST['gdpr_deletion_requests_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['gdpr_deletion_requests_nonce'] ), 'add-to-deletion-requests' ) ) { + if ( ! isset( $_POST['gdpr_deletion_requests_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['gdpr_deletion_requests_nonce'] ), 'gdpr-add-to-deletion-requests' ) ) { wp_die( esc_html__( 'We could not verify the user email or the security token. Please try again.', 'gdpr' ) ); } diff --git a/admin/class-gdpr-telemetry.php b/admin/class-gdpr-telemetry.php index ae6be821..ac9b3cfc 100644 --- a/admin/class-gdpr-telemetry.php +++ b/admin/class-gdpr-telemetry.php @@ -27,10 +27,24 @@ class GDPR_Telemetry { * @author Fernando Claussen */ public function register_post_type() { + $telemetry_enabled = get_option( 'gdpr_enable_telemetry_tracker', false ); + if ( ! $telemetry_enabled ) { + wp_clear_scheduled_hook( 'telemetry_cleanup' ); + return; + } + + if ( ! wp_next_scheduled( 'telemetry_cleanup' ) ) { + wp_schedule_event( + time(), + 'hourly', + 'telemetry_cleanup' + ); + } + register_post_type( 'telemetry', array( - 'label' => 'Telemetry', + 'label' => esc_html__( 'Telemetry', 'gdpr' ), 'labels' => array( 'not_found' => esc_html__( 'No items found. Future connections will be shown at this place.', 'gdpr' ), 'not_found_in_trash' => esc_html__( 'No items found in trash.', 'gdpr' ), @@ -59,6 +73,10 @@ public function register_post_type() { * @since 1.0.0 */ public function log_request( $response, $type, $class, $args, $url ) { + $telemetry_enabled = get_option( 'gdpr_enable_telemetry_tracker', false ); + if ( ! $telemetry_enabled ) { + return; + } /* Only response type */ if ( 'response' !== $type ) { return false; diff --git a/admin/partials/requests.php b/admin/partials/requests.php index da1dbae4..aa39d5ee 100644 --- a/admin/partials/requests.php +++ b/admin/partials/requests.php @@ -28,7 +28,7 @@ -
@@ -85,7 +85,7 @@
-