PluginProbe
The GDPR Framework By Data443 / trunk
The GDPR Framework By Data443 vtrunk
2.5.0 2.4.0 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.3 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.39 1.0.4 1.0.40 1.0.41 1.0.42 1.0.43 1.0.44 1.0.45 1.0.46 All 41 releases
gdpr-framework / src / Components / PrivacyToolsPage / PrivacyToolsPageController.php

PrivacyToolsPageController.php in The GDPR Framework By Data443 trunk, at src/Components/PrivacyToolsPage/PrivacyToolsPageController.php

499 lines 16.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Codelight\GDPR\Components\PrivacyToolsPage;
4
5 if ( ! defined( 'ABSPATH' ) ) exit;
6
7 use Codelight\GDPR\DataSubject\DataSubject;
8 use Codelight\GDPR\DataSubject\DataSubjectAuthenticator;
9 use Codelight\GDPR\DataSubject\DataSubjectIdentificator;
10 use Codelight\GDPR\DataSubject\DataSubjectManager;
11 use Codelight\GDPR\DataSubject\DataExporter;
12 use Codelight\GDPR\Components\Consent\UserConsentModel;
13
14 /**
15 * Handle the data page on front-end
16 *
17 * Class DataPageController
18 *
19 * @package Codelight\GDPR\Components\DataPage
20 */
21 class PrivacyToolsPageController {
22
23 /* @var DataSubjectAuthenticator */
24 protected $dataSubjectAuthenticator;
25
26 /* @var DataSubjectIdentificator */
27 protected $dataSubjectIdentificator;
28
29 /* @var DataSubjectManager */
30 protected $dataSubjectManager;
31
32 protected $UserConsentModel;
33
34 protected $dataExporter;
35
36 /**
37 * DataPageController constructor.
38 *
39 * @param DataSubjectIdentificator $dataSubjectIdentificator
40 * @param DataSubjectManager $dataSubjectManager
41 */
42 public function __construct(
43 DataSubjectAuthenticator $dataSubjectAuthenticator,
44 DataSubjectIdentificator $dataSubjectIdentificator,
45 DataSubjectManager $dataSubjectManager,
46 DataExporter $dataExporter,
47 UserConsentModel $UserConsentModel
48 ) {
49 $this->dataSubjectAuthenticator = $dataSubjectAuthenticator;
50 $this->dataSubjectIdentificator = $dataSubjectIdentificator;
51 $this->dataSubjectManager = $dataSubjectManager;
52 $this->dataExporter = $dataExporter;
53
54 $this->UserConsentModel = $UserConsentModel;
55
56 if ( ! gdpr( 'options' )->get( 'enable' ) ) {
57 return;
58 }
59
60 $this->setup();
61 }
62
63 protected function setup() {
64 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue' ) );
65 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_donotsell' ) );
66
67 // Listen to 'identify' action and send an email
68 add_action( 'gdpr/frontend/action/identify', array( $this, 'sendIdentificationEmail' ) );
69
70 add_action( 'gdpr/frontend/privacy-tools-page/content', array( $this, 'renderConsentForm' ), 10, 2 );
71 add_action( 'gdpr/frontend/privacy-tools-page/content', array( $this, 'renderExportForm' ), 20, 2 );
72 add_action( 'gdpr/frontend/privacy-tools-page/content', array( $this, 'renderDeleteForm' ), 30, 2 );
73 add_action( 'gdpr/frontend/privacy-tools-page/content', array( $this, 'renderDoNotSellForm' ), 40, 2 );
74
75 add_action( 'gdpr/frontend/privacy-tools-page/action/withdraw_consent', array( $this, 'withdrawConsent' ), 10, 2 );
76 add_action( 'gdpr/frontend/privacy-tools-page/action/export', array( $this, 'export' ), 10, 2 );
77 add_action( 'gdpr/frontend/privacy-tools-page/action/forget', array( $this, 'forget' ), 10, 2 );
78 add_action( 'wp_ajax_donot_sell_save_post', array( $this, 'donot_sell_save_post' ) );
79 add_action( 'wp_ajax_nopriv_donot_sell_save_post', array( $this, 'donot_sell_save_post' ) );
80 add_action( 'wp_ajax_nopriv_validation_privacysafe', array( $this, 'validation_privacysafe' ) );
81 }
82
83 public function enqueue_donotsell() {
84 global $gdpr;
85 wp_enqueue_script(
86 'donot-sell-form',
87 $gdpr->PluginUrl . 'assets/js/gdpr-donotsell.js',
88 array( 'jquery' ),
89 GDPR_FRAMEWORK_VERSION,
90 true
91 );
92 wp_localize_script(
93 'donot-sell-form',
94 'localized_donot_sell_form',
95 array(
96 'admin_donot_sell_ajax_url' => admin_url( 'admin-ajax.php' ),
97 )
98 );
99 }
100
101 public function enqueue() {
102 global $gdpr;
103 if ( ! gdpr( 'options' )->get( 'enable_stylesheet' ) || ! is_page( gdpr( 'options' )->get( 'tools_page' ) ) ) {
104 return;
105 }
106
107 wp_enqueue_style(
108 'gdpr-framework-privacy-tools',
109 $gdpr->PluginUrl . 'assets/css/privacy-tools.css'
110 );
111
112 }
113
114 public function validation_privacysafe() {
115 return true;
116 exit;
117 }
118
119 /**
120 * If the given email address exists as a data subject, send an authentication email to that address
121 */
122 public function sendIdentificationEmail() {
123 // Additional safety check
124 if ( ! is_email( $_REQUEST['email'] ) ) {
125 $this->redirect( array( 'gdpr_notice' => 'invalid_email' ) );
126 } else {
127 $requested_email = sanitize_email( $_REQUEST['email'] );
128 }
129
130 if ( $this->dataSubjectIdentificator->isDataSubject( $requested_email ) ) {
131 $this->dataSubjectIdentificator->sendIdentificationEmail( $requested_email );
132 } else {
133 $user = get_user_by( 'email', $requested_email );
134 if (empty($user)) {
135 $this->redirect( array( 'gdpr_notice' => 'unregistered_user' ) );
136 } else {
137 $this->dataSubjectIdentificator->sendNoDataFoundEmail( $requested_email );
138 }
139 }
140
141 $this->redirect( array( 'gdpr_notice' => 'email_sent' ) );
142 }
143
144 /**
145 * Render the page contents.
146 * This is only called via the shortcode.
147 */
148 public function render() {
149 $dataSubject = $this->dataSubjectAuthenticator->authenticate();
150 $this->renderNotices();
151
152 if ( $dataSubject ) {
153 $this->renderPrivacyTools( $dataSubject );
154 } else {
155 $this->renderIdentificationForm();
156 }
157 }
158
159 /**
160 * Display notices to the user.
161 * The contents of the notices are currently hardcoded inside the template.
162 */
163 protected function renderNotices() {
164 if ( ! isset( $_REQUEST['gdpr_notice'] ) ) {
165 return;
166 }
167
168 echo gdpr( 'view' )->render( 'privacy-tools/notices' );
169 }
170
171 /**
172 * Render the contents of the identification form
173 */
174 protected function renderIdentificationForm() {
175 $nonce = wp_create_nonce( 'gdpr/frontend/action/identify' );
176 // FRAM-144 Fix reference of an undefined variable 'notices'
177 if (!isset($notices)) {
178 $notices = "NOTICES PLACEHOLDER";
179 }
180 echo gdpr( 'view' )->render( 'privacy-tools/form-identify', compact( 'nonce', 'notices' ) );
181 }
182
183 /**
184 * Render the contents of the Privacy Tools page
185 *
186 * @param DataSubject $dataSubject
187 */
188 protected function renderPrivacyTools( DataSubject $dataSubject ) {
189 $email = $dataSubject->getEmail();
190 echo gdpr( 'view' )->render( 'privacy-tools/privacy-tools', compact( 'dataSubject', 'email' ) );
191 }
192
193 /**
194 * Render the form that allows withdrawing consent
195 *
196 * @param DataSubject $dataSubject
197 */
198 public function renderConsentForm( DataSubject $dataSubject ) {
199 $consentData = $dataSubject->getVisibleConsentData();
200 if ( $consentData ) {
201 foreach ( $consentData as &$item ) {
202 $item['withdraw_url'] = add_query_arg(
203 array(
204 'gdpr_action' => 'withdraw_consent',
205 'gdpr_nonce' => wp_create_nonce( 'gdpr/frontend/privacy-tools-page/action/withdraw_consent' ),
206 'email' => $dataSubject->getEmail(),
207 'consent' => $item['slug'],
208 )
209 );
210 }
211 }
212
213 $info = gdpr( 'options' )->get( 'consent_info' );
214
215 if (empty($info)) {
216 $consentInfo = "";
217 } else {
218 $consentInfo = wpautop( $info );
219 }
220
221 echo gdpr( 'view' )->render(
222 'privacy-tools/form-consent',
223 compact( 'consentData', 'consentInfo' )
224 );
225 }
226
227 /**
228 * Render the form that allows the data subject to export their data
229 *
230 * @param DataSubject $dataSubject
231 */
232 public function renderExportForm( DataSubject $dataSubject ) {
233 $email = $dataSubject->getEmail();
234 $nonce = wp_create_nonce( 'gdpr/frontend/privacy-tools-page/action/export' );
235
236 echo gdpr( 'view' )->render(
237 'privacy-tools/form-export',
238 compact( 'email', 'nonce' )
239 );
240 }
241
242 /**
243 * Render the form that allows the data subject to delete their data
244 *
245 * @param DataSubject $dataSubject
246 */
247 public function renderDeleteForm( DataSubject $dataSubject ) {
248 // Let's not allow admins to delete themselves
249 if ( current_user_can( 'manage_options' ) ) {
250 echo gdpr( 'view' )->render( 'privacy-tools/notice-admin-role' );
251 return;
252 }
253 $email = $dataSubject->getEmail();
254 $gdpr_user = get_user_by( 'email', $email );
255 if ( isset( $gdpr_user->data->ID ) ) {
256 if ( user_can( $gdpr_user->data->ID, 'manage_options' ) ) {
257 echo gdpr( 'view' )->render( 'privacy-tools/notice-admin-role' );
258 return;
259 }
260 }
261 $action = 'forget';
262 $nonce = wp_create_nonce( 'gdpr/frontend/privacy-tools-page/action/forget' );
263 $user = wp_get_current_user();
264 echo gdpr( 'view' )->render(
265 'privacy-tools/form-delete',
266 compact( 'action', 'email', 'nonce' )
267 );
268 }
269
270 /**
271 * Render the "Do Not Sell My Data" (CCPA) request form as a section of the
272 * Privacy Tools page, alongside the consent/export/delete sections. This is
273 * the same form the [gdpr_do_not_sell_form] shortcode renders, prefilled
274 * from the authenticated data subject. Submitting it hits
275 * donot_sell_save_post(), which only records the request when the submitted
276 * email matches the verified identity (see that method for details).
277 *
278 * @param DataSubject $dataSubject
279 */
280 public function renderDoNotSellForm( DataSubject $dataSubject ) {
281 global $gdpr;
282
283 $defaultConsentTypes = $gdpr->Consent->getbySlugConsent( 'do-not-sell-info' );
284
285 $first_name = '';
286 $last_name = '';
287 $user_email = $dataSubject->getEmail();
288
289 $user = get_user_by( 'email', $user_email );
290 if ( $user ) {
291 $metaFirstName = get_user_meta( $user->ID, 'first_name', true );
292 $first_name = ( '' !== $metaFirstName ) ? $metaFirstName : $user->user_nicename;
293 $last_name = get_user_meta( $user->ID, 'last_name', true );
294 }
295
296 echo gdpr( 'view' )->render(
297 'privacy-tools/donotsell',
298 compact( 'defaultConsentTypes', 'first_name', 'last_name', 'user_email' )
299 );
300 }
301
302 /**
303 * Withdraw the consent
304 *
305 * @param DataSubject $dataSubject
306 */
307 public function withdrawConsent( DataSubject $dataSubject ) {
308 $consent = sanitize_key( $_REQUEST['consent'] );
309 $dataSubject->withdrawConsent( $consent );
310 $this->redirect( array( 'gdpr_notice' => 'consent_withdrawn' ) );
311 }
312
313 /**
314 * Trigger the export action.
315 *
316 * @param DataSubject $dataSubject
317 */
318 public function export( DataSubject $dataSubject ) {
319 $format = sanitize_key( $_REQUEST['gdpr_format'] );
320 $data = $dataSubject->export( $format );
321
322 if ( ! is_null( $data ) ) {
323 // If there is data, download it
324 $this->dataExporter->export( $data, $dataSubject, $format );
325 } else {
326 // If there's no data, then show notification that your request has been sent.
327 $this->redirect( array( 'gdpr_notice' => 'request_sent' ) );
328 }
329 }
330
331 /**
332 * Trigger the forget action.
333 *
334 * @param DataSubject $dataSubject
335 */
336 public function forget( DataSubject $dataSubject ) {
337 $deleted = $dataSubject->forget();
338
339 if ( $deleted ) {
340 $this->dataSubjectAuthenticator->deleteSession();
341 $this->redirect( array( 'gdpr_notice' => 'data_deleted' ) );
342 } else {
343 // If request was sent to admin, then show notification
344 $this->redirect( array( 'gdpr_notice' => 'request_sent' ) );
345 }
346
347 }
348
349 /**
350 * Redirect the visitor to an appropriate location
351 *
352 * @param array $args
353 * @param null $baseUrl
354 */
355 protected function redirect( $args = array(), $baseUrl = null ) {
356 if ( ! $baseUrl ) {
357 // If custom tools page URL is set
358 if ( gdpr( 'options' )->get( 'custom_tools_page' ) ) {
359 $privacyToolsUrl = gdpr( 'options' )->get( 'custom_tools_page' );
360 $baseUrl = apply_filters( 'redirect_after_gdpr_submit', $privacyToolsUrl );
361 } else {
362 $privacyToolsUrl = gdpr( 'options' )->get( 'tools_page' );
363 $baseUrl = $privacyToolsUrl ? get_permalink( $privacyToolsUrl ) : home_url();
364 $baseUrl = apply_filters( 'redirect_after_gdpr_submit', $baseUrl );
365 }
366 // Avoid infinite loop redirect
367
368 }
369
370 wp_safe_redirect( add_query_arg( $args, $baseUrl ) );
371 exit;
372 }
373
374 public function gdpr_get_formatted_billing_name_and_address( $user_id ) {
375 $address = get_user_meta( $user_id, 'billing_address_1', true ) . ' ';
376 $address .= get_user_meta( $user_id, 'billing_address_2', true ) . ' ';
377 $address .= get_user_meta( $user_id, 'billing_city', true ) . ' ';
378 $address .= get_user_meta( $user_id, 'billing_state', true ) . ' ';
379 $address .= get_user_meta( $user_id, 'billing_postcode', true ) . ' ';
380 $address .= get_user_meta( $user_id, 'billing_country', true ) . ' ';
381 return $address;
382 }
383 public function donot_sell_save_post() {
384 $r = array();
385
386 if ( empty( $_POST['form_data'] ) ) {
387 $r['error'] = __( 'Missing form data.', 'gdpr-framework' );
388 echo json_encode( $r );
389 exit;
390 }
391
392 parse_str( wp_unslash( $_POST['form_data'] ), $form_data );
393
394 // Security fix (SECURITY-AUDIT.md Finding 6): this endpoint is
395 // wp_ajax_nopriv_* (anonymous, by design) but previously had no
396 // nonce at all, letting anyone script arbitrary submissions. The
397 // nonce is rendered into the real form in
398 // views/privacy-tools/donotsell.php. Note: the nonce is CSRF
399 // protection ONLY -- see the identity check below.
400 if ( empty( $form_data['_wpnonce'] ) || ! wp_verify_nonce( $form_data['_wpnonce'], 'gdpr_donot_sell' ) ) {
401 $r['error'] = __( 'Security check failed, please refresh the page and try again.', 'gdpr-framework' );
402 echo json_encode( $r );
403 exit;
404 }
405
406 // Security fix: a valid nonce proves the request is not a blind CSRF,
407 // but it does NOT prove the submitter controls the email address --
408 // the form and its nonce are intentionally exposed to anonymous
409 // visitors. Previously anyone could therefore script two
410 // wp_gdpr_consent rows ('do-not-sell-info' and 'receive-communications')
411 // for an arbitrary victim's address. Treat CSRF protection and
412 // identity verification as separate controls: never mutate consent
413 // for an email we have not verified the submitter owns.
414 $submitted_email = isset( $form_data['donotsell_email'] )
415 ? sanitize_email( $form_data['donotsell_email'] )
416 : '';
417
418 if ( ! $submitted_email || ! is_email( $submitted_email ) ) {
419 $r['error'] = __( 'Please enter a valid email address.', 'gdpr-framework' );
420 echo json_encode( $r );
421 exit;
422 }
423
424 // Derive a server-verified identity. authenticate() returns a
425 // DataSubject for a logged-in user, or for a visitor who has completed
426 // the plugin's signed, expiring email-identification link (the
427 // gdpr_key cookie validated against its stored token); otherwise false.
428 $dataSubject = $this->dataSubjectAuthenticator->authenticate();
429 $verified_email = $dataSubject ? sanitize_email( $dataSubject->getEmail() ) : '';
430
431 $email_matches = $verified_email
432 && hash_equals( strtolower( $verified_email ), strtolower( $submitted_email ) );
433
434 if ( ! $email_matches ) {
435 // The submitter has not proven ownership of this address. Send the
436 // plugin's signed, expiring identification link and record nothing
437 // until they complete it and resubmit.
438 $this->dataSubjectIdentificator->sendIdentificationEmail( $submitted_email );
439
440 $r['verification_required'] = true;
441 $r['error'] = __( 'Please check your email and verify the address before completing this request.', 'gdpr-framework' );
442 echo json_encode( $r );
443 exit;
444 }
445
446 // From this point forward, use only the server-verified email.
447 $email = $verified_email;
448
449 $authorname = '';
450 if ( is_user_logged_in() ) {
451 $current_user = wp_get_current_user();
452 $authorname = esc_html( $current_user->user_login );
453 }
454
455 $postarr = array(
456 'ID' => '', // If ID stays empty the post will be created.
457 'post_author' => $authorname,
458 'post_title' => $email,
459 'post_status' => 'publish',
460 'post_type' => 'donotsellrequests',
461 );
462 $new_post = wp_insert_post(
463 $postarr,
464 true
465 );
466
467 // Post was not created/updated, so let's output the error message.
468 if ( is_wp_error( $new_post ) ) {
469 $r['error'] = $new_post->get_error_message();
470
471 echo json_encode( $r );
472
473 exit;
474 }
475
476 $post_id = intval( $new_post );
477 if ( $post_id ) {
478 add_post_meta( $post_id, 'donotsell_first_name', isset( $form_data['donotsell_first_name'] ) ? sanitize_text_field( $form_data['donotsell_first_name'] ) : '' );
479 add_post_meta( $post_id, 'donotsell_last_name', isset( $form_data['donotsell_last_name'] ) ? sanitize_text_field( $form_data['donotsell_last_name'] ) : '' );
480 add_post_meta( $post_id, 'donotsell_consent', isset( $form_data['donotsell_consent'] ) ? sanitize_text_field( $form_data['donotsell_consent'] ) : '' );
481 }
482
483 if ( ! empty( $form_data['donotsell_consent'] ) ) {
484 $this->UserConsentModel->give( $email, 'do-not-sell-info', null );
485 $this->UserConsentModel->give( $email, 'receive-communications', null );
486 }
487
488 // Gets post info in array format as it's easier to debug via console if needed.
489 $post_array = get_post( $post_id, ARRAY_A );
490
491 if ( $post_array ) {
492 $r['donotsellrequests'] = $post_array;
493 }
494
495 echo json_encode( $r );
496 exit;
497 }
498 }
499