1: <?php
2:
3: if ( ! class_exists( 'GFForms' ) ) {
4: die();
5: }
6:
7: 8: 9: 10: 11: 12: 13:
14: class GF_Personal_Data {
15:
16: 17: 18: 19: 20: 21: 22:
23: private static $_form;
24:
25: 26: 27: 28: 29: 30: 31:
32: private static $_forms;
33:
34: 35: 36: 37: 38: 39: 40:
41: public static function form_settings( $form_id ) {
42:
43: $form = self::get_form( $form_id );
44:
45: $form_personal_data_settings = rgar( $form, 'personalData' );
46:
47: $action_url = admin_url( sprintf( 'admin.php?page=gf_edit_forms&view=settings&subview=personal-data&id=%d', $form_id ) );
48:
49: $enabled = (bool) rgars( $form_personal_data_settings, 'exportingAndErasing/enabled' );
50:
51: $prevent_ip = (bool) rgar( $form_personal_data_settings, 'preventIP' );
52:
53: $retention_policy = rgars( $form_personal_data_settings, 'retention/policy' );
54:
55: if ( empty( $retention_policy ) ) {
56: $retention_policy = 'retain';
57: }
58:
59: $retention_days = rgars( $form_personal_data_settings, 'retention/retain_entries_days' );
60:
61: if ( empty( $retention_days ) ) {
62: $retention_days = 1;
63: }
64:
65: ?>
66: <h3><span><i class="fa fa-lock"></i> <?php esc_html_e( 'Personal Data', 'gravityforms' ); ?>
67: </h3>
68: <div class="gform_panel gform_panel_form_settings" id="gf_personal_data_settings">
69: <form action="<?php esc_url( $action_url ); ?>" method="POST">
70: <?php wp_nonce_field( 'gravityforms_personal_data' ); ?>
71: <table class="gforms_form_settings" cellspacing="0" cellpadding="0">
72: <tr>
73: <td colspan="2">
74: <h4 class="gf_settings_subgroup_title"><?php esc_html_e( 'General Settings', 'gravityforms' ); ?></h4>
75: </td>
76: </tr>
77: <tr>
78: <th>
79: <?php esc_html_e( 'IP Addresses', 'gravityforms' ); ?>
80: <?php gform_tooltip( 'personal_data_prevent_ip' ); ?>
81: </th>
82: <td>
83: <label for="gf_personal_data_prevent_ip">
84: <input
85: id="gf_personal_data_prevent_ip"
86: type="checkbox"
87: name="prevent_ip"
88: <?php checked( true, $prevent_ip ); ?>
89: />
90: <?php esc_html_e( 'Prevent the storage of IP addresses during form submission.', 'gravityforms' ); ?>
91: </label>
92: </td>
93: </tr>
94: <tr>
95: <th>
96: <?php esc_html_e( 'Retention Policy', 'gravityforms' ); ?>
97: <?php gform_tooltip( 'personal_data_retention_policy' ); ?>
98: </th>
99: <td>
100: <label for="gf_personal_data_retention_do_not_delete">
101: <input
102: id="gf_personal_data_retention_do_not_delete"
103: type="radio"
104: name="retention"
105: value="retain"
106: <?php checked( 'retain', $retention_policy ); ?>
107: />
108: <?php esc_html_e( 'Retain entries indefinitely', 'gravityforms' ); ?>
109: </label>
110: <br/>
111: <label for="gf_personal_data_retention_trash">
112: <input
113: id="gf_personal_data_retention_trash"
114: type="radio"
115: name="retention"
116: value="trash"
117: <?php checked( 'trash', $retention_policy ); ?>
118: />
119: <?php esc_html_e( 'Trash entries automatically', 'gravityforms' ); ?>
120: </label>
121: <br/>
122: <label for="gf_personal_data_retention_delete">
123: <input
124: id="gf_personal_data_retention_delete"
125: type="radio"
126: name="retention"
127: value="delete"
128: <?php checked( 'delete', $retention_policy ); ?>
129: />
130: <?php esc_html_e( 'Delete entries permanently automatically', 'gravityforms' ); ?>
131: </label>
132: <div id="gf_personal_data_retain_entries_days_container" style="<?php echo( $retention_policy !== 'retain' ? '' : 'display:none' ) ?>">
133: <label for="gf_personal_data_retain_entries_days_container">
134: <?php esc_html_e( 'Number of days to retain entries before trashing/deleting:', 'gravityforms' ); ?>
135: <input
136: id="gf_personal_data_retain_entries_days"
137: type="text"
138: class="small-text"
139: name="retain_entries_days"
140: value="<?php echo absint( $retention_days ); ?>"
141: />
142: </label>
143: </div>
144: </td>
145: </tr>
146: <tr>
147: <td colspan="2">
148: <h4 class="gf_settings_subgroup_title"><?php esc_html_e( 'Exporting and Erasing Data', 'gravityforms' ); ?></h4>
149: </td>
150: </tr>
151: <?php
152: $identification_field_choices = array();
153:
154: $email_fields = GFAPI::get_fields_by_type( $form, 'email' );
155:
156: foreach ( $email_fields as $email_field ) {
157: $identification_field_choices[ (string) $email_field->id ] = $email_field->label;
158: }
159:
160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171:
172: $identification_field_choices = gf_apply_filters( array( 'gform_personal_data_identification_fields', $form['id'] ), $identification_field_choices, $form );
173:
174: $no_id = empty( $identification_field_choices );
175:
176: $lookup_field = rgars( $form, 'personalData/exportingAndErasing/identificationField' );
177:
178: if ( $no_id ) {
179: if ( $lookup_field == 'created_by' ) {
180:
181: $no_id = false;
182:
183: $identification_field_choices = array(
184: 'created_by' => __( 'Created By', 'gravityforms' ),
185: );
186:
187: } elseif ( $selected_field = GFFormsModel::get_field( $form, $lookup_field ) ) {
188:
189: $no_id = false;
190:
191: $selected_field->set_context_property( 'use_admin_label', true );
192:
193: $choice_label = GFFormsModel::get_label( $selected_field );
194:
195: $identification_field_choices = array(
196: $lookup_field => $choice_label,
197: );
198:
199: } else {
200: $enabled = false;
201: }
202: }
203: ?>
204: <tr>
205: <th>
206: <?php esc_html_e( 'Enable', 'gravityforms' ); ?>
207: <?php gform_tooltip( 'personal_data_enable' ); ?>
208: </th>
209: <td>
210: <label for="gf_personal_data_enable">
211: <input
212: id="gf_personal_data_enable"
213: type="checkbox"
214: name="exporting_and_erasing_enabled"
215: <?php checked( true, $enabled ); ?>
216: <?php disabled( true, $no_id ); ?>
217: />
218: <?php esc_html_e( 'Enable integration with the WordPress tools for exporting and erasing personal data.', 'gravityforms' ); ?>
219: </label>
220: </td>
221: </tr>
222: <?php
223:
224: if ( ! $no_id ) {
225: ?>
226: <tr class="gf_personal_data_settings" style="<?php echo( $enabled ? '' : 'display:none;' ); ?>">
227: <th>
228: <?php esc_html_e( 'Identification Field', 'gravityforms' ); ?>
229: <?php gform_tooltip( 'personal_data_identification' ); ?>
230: </th>
231: <td>
232: <select name="identification_field">
233: <?php
234: echo sprintf( '<option value="">%s</option>', esc_html__( 'Select a Field', 'gravityforms' ) );
235: foreach ( $identification_field_choices as $id => $label ) {
236: $selected = selected( $id, $lookup_field, false );
237: echo sprintf( '<option %s value="%s">%s</option>', $selected, $id, $label );
238: }
239: ?>
240: </select>
241: </td>
242: </tr>
243:
244: <tr class="gf_personal_data_settings" style="<?php echo( $enabled ? '' : 'display:none;' ); ?>">
245: <th>
246: <?php esc_html_e( 'Personal Data', 'gravityforms' ); ?>
247: <?php gform_tooltip( 'personal_data_field_settings' ); ?>
248:
249: </th>
250: <td>
251: <table id="gf_personal_data_field_settings">
252: <thead>
253: <tr>
254: <th class="gf_personal_data_field_label_title"><?php esc_html_e( 'Fields', 'gravityforms' ); ?></th>
255: <th class="gf_personal_data_cb_title"><?php esc_html_e( 'Export', 'gravityforms' ); ?></th>
256: <th class="gf_personal_data_cb_title"><?php esc_html_e( 'Erase', 'gravityforms' ); ?></th>
257: </tr>
258: </thead>
259: <tbody>
260: <tr>
261: <td>
262: <?php esc_html_e( 'Select/Deselect All', 'gravityforms' ); ?>
263: </td>
264: <td class="gf_personal_data_cb_cell">
265: <input
266: id="gf_personal_data_export_all"
267: type="checkbox"
268: />
269: </td>
270: <td class="gf_personal_data_cb_cell">
271: <input
272: id="gf_personal_data_erase_all"
273: type="checkbox"
274: />
275: </td>
276: </tr>
277: <?php
278:
279: $columns = self::get_columns();
280:
281: $all_column_settings = rgars( $form_personal_data_settings, 'exportingAndErasing/columns' );
282:
283: foreach ( $columns as $key => $label ) {
284: $column_settings = rgar( $all_column_settings, $key );
285: ?>
286: <tr>
287: <td>
288: <?php echo esc_html( $label ); ?>
289: </td>
290: <td class="gf_personal_data_cb_cell">
291: <input
292: class="gf_personal_data_cb_export"
293: type="checkbox"
294: name="export_fields[<?php echo esc_attr( $key ); ?>]"
295: <?php checked( true, (bool) rgar( $column_settings, 'export' ) ); ?>
296: />
297: </td>
298: <td class="gf_personal_data_cb_cell">
299: <input
300: class="gf_personal_data_cb_erase"
301: type="checkbox"
302: name="erase_fields[<?php echo esc_attr( $key ); ?>]"
303: <?php checked( true, (bool) rgar( $column_settings, 'erase' ) ); ?>
304: />
305: </td>
306: </tr>
307: <?php
308: }
309:
310:
311: $fields = $form['fields'];
312:
313: foreach ( $fields as $field ) {
314:
315: if ( $field->displayOnly ) {
316:
317: continue;
318: }
319:
320: $field->set_context_property( 'use_admin_label', true );
321:
322: ?>
323: <tr>
324: <td>
325: <?php echo esc_html( GFFormsModel::get_label( $field ) ); ?>
326: </td>
327: <td class="gf_personal_data_cb_cell">
328: <input
329: class="gf_personal_data_cb_export"
330: type="checkbox"
331: name="export_fields[<?php echo absint( $field->id ); ?>]"
332: <?php checked( true, (bool) $field->personalDataExport ); ?>
333: />
334: </td>
335: <td class="gf_personal_data_cb_cell">
336: <input
337: class="gf_personal_data_cb_erase"
338: type="checkbox"
339: name="erase_fields[<?php echo absint( $field->id ); ?>]"
340: <?php checked( true, (bool) $field->personalDataErase ); ?>
341: />
342: </td>
343: </tr>
344: <?php
345: }
346:
347: $custom_items = self::get_custom_items( $form );
348:
349: if ( ! empty( $custom_items ) ) {
350:
351: ?>
352: <tr>
353: <th class="gf_personal_data_field_label_title" colspan="3">
354: <?php esc_html_e( 'Other Data', 'gravityforms' ) ?>
355: </th>
356: </tr>
357: <?php
358:
359: $custom_items_settings = rgars( $form_personal_data_settings, 'exportingAndErasing/custom' );
360:
361: foreach ( $custom_items as $key => $custom_item_details ) {
362: $custom_settings = rgar( $custom_items_settings, $key );
363: $label = rgar( $custom_item_details, 'label' );
364: ?>
365: <tr>
366: <td>
367: <?php echo esc_html( $label ); ?>
368: </td>
369: <td class="gf_personal_data_cb_cell">
370: <?php
371: if ( isset( $custom_item_details['exporter_callback'] ) && is_callable( $custom_item_details['exporter_callback'] ) ) {
372: ?>
373: <input
374: class="gf_personal_data_cb_export"
375: type="checkbox"
376: name="export_fields[<?php echo esc_attr( $key ); ?>]"
377: <?php checked( true, (bool) rgar( $custom_settings, 'export' ) ); ?>
378: />
379: <?php
380: }
381: ?>
382: </td>
383: <td class="gf_personal_data_cb_cell">
384: <?php
385: if ( isset( $custom_item_details['eraser_callback'] ) && is_callable( $custom_item_details['eraser_callback'] ) ) {
386: ?>
387: <input
388: class="gf_personal_data_cb_erase"
389: type="checkbox"
390: name="erase_fields[<?php echo esc_attr( $key ); ?>]"
391: <?php checked( true, (bool) rgar( $custom_settings, 'erase' ) ); ?>
392: />
393: <?php
394: }
395: ?>
396: </td>
397: </tr>
398: <?php
399: }
400: }
401:
402: ?>
403: </tbody>
404: </table>
405: </td>
406: </tr>
407:
408: <?php
409: } else {
410: ?>
411: <tr>
412: <th></th>
413: <td>
414: <div class="alert_red" style="padding:15px;">
415: <?php esc_html_e( 'You must add an email address field to the form in order to enable this setting.', 'gravityforms' ); ?>
416: </div>
417: </td>
418: </tr>
419: <?php
420: }
421: ?>
422:
423: </table>
424: <input
425: class="button-primary"
426: type="submit"
427: name="save_personal_data_settings"
428: value="<?php esc_attr_e( 'Save', 'gravityforms' ); ?>"
429: />
430: </form>
431: </div>
432: <script>
433: jQuery(document).ready(function ($) {
434: $('#gf_personal_data_enable').change(function () {
435: if ($(this).is(":checked")) {
436: $('.gf_personal_data_settings').show();
437: } else {
438: $('.gf_personal_data_settings').hide();
439: }
440: });
441: $('#gf_personal_data_export_all').change(function () {
442: if ($(this).is(":checked")) {
443: $('.gf_personal_data_cb_export').prop('checked', true);
444: } else {
445: $('.gf_personal_data_cb_export').prop('checked', false);
446: }
447: });
448: $('#gf_personal_data_erase_all').change(function () {
449: if ($(this).is(":checked")) {
450: $('.gf_personal_data_cb_erase').prop('checked', true);
451: } else {
452: $('.gf_personal_data_cb_erase').prop('checked', false);
453: }
454: });
455: $("input[name='lookup']").change(function () {
456: if ($(this).val() == 'identifying_email_field') {
457: $('#gf_personal_data_email_field_select').fadeIn();
458: } else {
459: $('#gf_personal_data_email_field_select').hide();
460: }
461: });
462: $("input[name='retention']").change(function () {
463: if ($(this).val() == 'retain') {
464: $('#gf_personal_data_retain_entries_days_container').hide();
465: } else {
466: alert( <?php echo json_encode( __( 'Warning: this will affect all entries that are older than the number of days specified.', 'gravityforms' ) ) ?> );
467: $('#gf_personal_data_retain_entries_days_container').fadeIn();
468: }
469: });
470:
471: });
472: </script>
473: <?php
474: }
475:
476: 477: 478: 479: 480: 481: 482:
483: public static function process_form_settings( $form_id ) {
484: check_admin_referer( 'gravityforms_personal_data' );
485:
486: $form = self::get_form( $form_id );
487:
488: $posted_export_fields = rgpost( 'export_fields' );
489:
490: $posted_erase_fields = rgpost( 'erase_fields' );
491:
492: $columns = self::get_columns();
493:
494: if ( ! isset( $form['personalData'] ) ) {
495: $form['personalData'] = array();
496: }
497:
498: $form_personal_data_settings = $form['personalData'];
499:
500: $form_personal_data_settings['preventIP'] = (bool) rgpost( 'prevent_ip' );
501:
502: $retention_policy = rgpost( 'retention' );
503:
504:
505: $retention_policy = in_array( $retention_policy, array(
506: 'retain',
507: 'trash',
508: 'delete',
509: ) ) ? $retention_policy : 'retain';
510:
511: $retain_entries_days = absint( rgpost( 'retain_entries_days' ) );
512:
513: if ( empty( $retain_entries_days ) ) {
514:
515: $retain_entries_days = 1;
516: }
517:
518:
519: $form_personal_data_settings['retention'] = array(
520: 'policy' => $retention_policy,
521: 'retain_entries_days' => $retain_entries_days,
522: );
523:
524: if ( ! isset( $form_personal_data_settings['exportingAndErasing'] ) ) {
525: $form_personal_data_settings['exportingAndErasing'] = array();
526: }
527:
528: $exporting_and_erasing = $form_personal_data_settings['exportingAndErasing'];
529:
530: if ( ! isset( $exporting_and_erasing['columns'] ) ) {
531: $exporting_and_erasing['columns'] = array();
532: }
533:
534: $exporting_and_erasing['enabled'] = (bool) rgpost( 'exporting_and_erasing_enabled' );
535:
536: if ( ! $exporting_and_erasing['enabled'] ) {
537: $exporting_and_erasing['identificationField'] = '';
538: } else {
539: $exporting_and_erasing['identificationField'] = sanitize_text_field( rgpost( 'identification_field' ) );
540: }
541:
542: $all_column_settings = $exporting_and_erasing['columns'];
543:
544: foreach ( $columns as $key => $label ) {
545: $column_settings = array(
546: 'export' => (bool) rgar( $posted_export_fields, $key ),
547: 'erase' => (bool) rgar( $posted_erase_fields, $key ),
548: );
549:
550: $all_column_settings[ $key ] = $column_settings;
551: }
552:
553: $exporting_and_erasing['columns'] = $all_column_settings;
554:
555:
556:
557: $fields = $form['fields'];
558:
559: foreach ( $fields as &$field ) {
560: $field->personalDataExport = (bool) rgar( $posted_export_fields, $field->id );
561: $field->personalDataErase = (bool) rgar( $posted_erase_fields, $field->id );
562: }
563:
564: $custom_items = self::get_custom_items( $form );
565:
566: if ( ! empty( $custom_items ) ) {
567: $custom_items_settings = rgar( $exporting_and_erasing, 'custom' ) ? $exporting_and_erasing['custom'] : array();
568:
569: foreach ( array_keys( $custom_items ) as $custom_item_key ) {
570: $custom_item_settings = array(
571: 'export' => (bool) rgar( $posted_export_fields, $custom_item_key ),
572: 'erase' => (bool) rgar( $posted_erase_fields, $custom_item_key ),
573: );
574:
575: $custom_items_settings[ $custom_item_key ] = $custom_item_settings;
576: }
577:
578: $exporting_and_erasing['custom'] = $custom_items_settings;
579: }
580:
581: $form_personal_data_settings['exportingAndErasing'] = $exporting_and_erasing;
582:
583: $form['personalData'] = $form_personal_data_settings;
584:
585: GFAPI::update_form( $form );
586: self::$_form = $form;
587: ?>
588: <div class="updated below-h2" id="after_update_dialog">
589: <p>
590: <strong><?php _e( 'Personal data settings updated successfully.', 'gravityforms' ); ?></strong>
591: </p>
592: </div>
593: <?php
594: }
595:
596: 597: 598: 599: 600: 601: 602: 603: 604:
605: public static function get_form( $form_id ) {
606: if ( empty( self::$_form ) ) {
607: self::$_form = GFAPI::get_form( $form_id );
608: }
609:
610: return self::$_form;
611: }
612:
613: 614: 615: 616: 617: 618: 619:
620: public static function get_columns() {
621: $columns = array(
622: 'ip' => esc_html__( 'IP Address', 'gravityforms' ),
623: 'source_url' => esc_html__( 'Embed URL', 'gravityforms' ),
624: 'user_agent' => esc_html__( 'Browser details', 'gravityforms' ),
625: );
626:
627: return $columns;
628: }
629:
630: 631: 632: 633: 634: 635: 636: 637: 638:
639: public static function get_custom_items( $form ) {
640:
641: $custom_items = array();
642:
643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675:
676: $custom_items = apply_filters( 'gform_personal_data', $custom_items, $form );
677:
678: return $custom_items;
679: }
680:
681: 682: 683: 684: 685: 686: 687:
688: public static function get_forms() {
689:
690: if ( is_null( self::$_forms ) ) {
691: $form_ids = GFFormsModel::get_form_ids();
692:
693: if ( empty( $form_ids ) ) {
694: return array(
695: 'data' => array(),
696: 'done' => true,
697: );
698: }
699:
700: $forms_by_id = GFFormsModel::get_form_meta_by_id( $form_ids );
701:
702: self::$_forms = array();
703: foreach ( $forms_by_id as $form ) {
704: self::$_forms[ $form['id'] ] = $form;
705: }
706: }
707:
708: return self::$_forms;
709: }
710:
711: 712: 713: 714: 715: 716: 717: 718: 719: 720: 721:
722: public static function get_entries( $email_address, $page = 1, $limit = 50 ) {
723:
724: $user = get_user_by( 'email', $email_address );
725:
726: $forms = self::get_forms();
727:
728: $form_ids = array();
729:
730: $query = new GF_Query();
731:
732: $conditions = array();
733:
734: foreach ( $forms as $form ) {
735:
736: if ( ! rgars( $form, 'personalData/exportingAndErasing/enabled' ) ) {
737: continue;
738: }
739:
740: $form_ids[] = $form['id'];
741:
742: $identification_field = rgars( $form, 'personalData/exportingAndErasing/identificationField' );
743:
744: $field = GFAPI::get_field( $form, $identification_field );
745:
746: if ( $field && $field->get_input_type() == 'email' ) {
747:
748: $conditions[] = new GF_Query_Condition(
749: new GF_Query_Column( $identification_field, $form['id'] ),
750: GF_Query_Condition::EQ,
751: new GF_Query_Literal( $email_address )
752: );
753:
754: } else {
755:
756: if ( ! $field && $identification_field != 'created_by' ) {
757: continue;
758: }
759:
760: if ( ! $user ) {
761: continue;
762: }
763:
764: $conditions[] = new GF_Query_Condition(
765: new GF_Query_Column( $identification_field, $form['id'] ),
766: GF_Query_Condition::EQ,
767: new GF_Query_Literal( $user->ID )
768: );
769: }
770: }
771:
772: if ( empty( $conditions ) ) {
773: return array();
774: }
775:
776: $all_conditions = call_user_func_array( array( 'GF_Query_Condition', '_or' ), $conditions );
777:
778: $entries = $query->from( $form_ids )->where( $all_conditions )->limit( $limit )->page( $page )->get();
779:
780: return $entries;
781: }
782:
783: 784: 785: 786: 787: 788: 789: 790: 791: 792:
793: public static function data_exporter( $email_address, $page = 1 ) {
794:
795: $export_items = array(
796: 'done' => true,
797: );
798:
799: $export_data = array();
800:
801: if ( $page == 1 ) {
802: $export_data = self::get_draft_submissions_export_items( $email_address );
803: }
804:
805: $export_items['data'] = $export_data;
806:
807: $limit = 50;
808:
809: $columns = self::get_columns();
810:
811: $forms = self::get_forms();
812:
813: $entries = self::get_entries( $email_address, $page, $limit );
814:
815: if ( empty( $entries ) ) {
816: return $export_items;
817: }
818:
819: foreach ( $entries as $entry ) {
820:
821: $data = array();
822:
823: $form_id = $entry['form_id'];
824:
825: $form = $forms[ $form_id ];
826:
827: $item_id = "gf-entry-{$entry['id']}";
828:
829: $group_id = 'gravityforms-entries';
830:
831: $group_label = __( 'Forms', 'gravityforms' );
832:
833: $columns_settings = rgars( $forms, $form_id . '/personalData/exportingAndErasing/columns' );
834:
835: if ( is_array( $columns_settings ) ) {
836: foreach ( $columns_settings as $column_key => $column_settings ) {
837: if ( rgar( $column_settings, 'export' ) ) {
838: $data[] = array(
839: 'name' => $columns[ $column_key ],
840: 'value' => $entry[ $column_key ],
841: );
842: }
843: }
844: }
845:
846: foreach ( $form['fields'] as $field ) {
847:
848: if ( $field->personalDataExport ) {
849: $value = GFFormsModel::get_lead_field_value( $entry, $field );
850: $data[] = array(
851: 'name' => $field->get_field_label( false, $value ),
852: 'value' => $field->get_value_entry_detail( $value, rgar( $entry, 'currency' ), true, 'text' ),
853: );
854: }
855: }
856:
857: $custom_items = self::get_custom_items( $form );
858:
859: if ( ! empty( $custom_items ) ) {
860: $all_custom_settings = rgars( $forms, $form_id . '/personalData/exportingAndErasing/custom' );
861: foreach ( $custom_items as $custom_item_key => $custom_item_details ) {
862: $custom_settings = rgar( $all_custom_settings, $custom_item_key );
863: if ( rgars( $custom_settings, 'export' ) && isset( $custom_item_details['exporter_callback'] ) && is_callable( $custom_item_details['exporter_callback'] ) ) {
864: $data[] = call_user_func( $custom_item_details['exporter_callback'], $form, $entry );
865: }
866: }
867: }
868:
869: if ( ! empty( $data ) ) {
870: $export_data[] = array(
871: 'group_id' => $group_id,
872: 'group_label' => $group_label,
873: 'item_id' => $item_id,
874: 'data' => $data,
875: );
876: }
877: }
878:
879: $done = count( $entries ) < $limit;
880:
881: $export_items = array(
882: 'data' => $export_data,
883: 'done' => $done,
884: );
885:
886: return $export_items;
887: }
888:
889: 890: 891: 892: 893: 894: 895: 896: 897:
898: public static function get_draft_submissions_export_items( $email_address ) {
899: $export_items = array();
900:
901: $forms = self::get_forms();
902:
903: $columns = self::get_columns();
904:
905: $draft_submissions = self::get_draft_submissions( $email_address );
906:
907: foreach ( $draft_submissions as $i => $draft_submission ) {
908: $data = array();
909:
910: $form_id = $draft_submission['form_id'];
911:
912: $form = $forms[ $form_id ];
913:
914: $submission_json = $draft_submission['submission'];
915:
916: $submission = json_decode( $submission_json, true );
917:
918: $entry = $submission['partial_entry'];
919:
920: $item_id = "gf-draft-submission-{$i}";
921:
922: $group_id = 'gravityforms-draft-submissions';
923:
924: $group_label = __( 'Draft Forms (Save and Continue Later)', 'gravityforms' );
925:
926: $columns_settings = rgars( $forms, $form_id . '/personalData/exportingAndErasing/columns' );
927:
928: if ( is_array( $columns_settings ) ) {
929: foreach ( $columns_settings as $column_key => $column_settings ) {
930: if ( rgar( $column_settings, 'export' ) && isset( $draft_submission[ $column_key ] ) ) {
931: $data[] = array(
932: 'name' => $columns[ $column_key ],
933: 'value' => $draft_submission[ $column_key ],
934: );
935: }
936: }
937: }
938:
939: foreach ( $form['fields'] as $field ) {
940:
941: if ( $field->personalDataExport ) {
942: $value = GFFormsModel::get_lead_field_value( $entry, $field );
943: $data[] = array(
944: 'name' => $field->get_field_label( false, $value ),
945: 'value' => $field->get_value_entry_detail( $value, rgar( $entry, 'currency' ), true, 'text' ),
946: );
947: }
948: }
949:
950: if ( ! empty( $data ) ) {
951: $export_items[] = array(
952: 'group_id' => $group_id,
953: 'group_label' => $group_label,
954: 'item_id' => $item_id,
955: 'data' => $data,
956: );
957: }
958: }
959:
960: return $export_items;
961: }
962:
963: 964: 965: 966: 967: 968: 969: 970: 971: 972:
973: public static function data_eraser( $email_address, $page = 1 ) {
974:
975: $limit = 50;
976:
977: $items_removed = $page == 1 ? self::erase_draft_submissions_data( $email_address ) : false;
978:
979: $forms = self::get_forms();
980:
981: $entries = self::get_entries( $email_address, $page, $limit );
982:
983: foreach ( $entries as $entry ) {
984:
985: $form_id = $entry['form_id'];
986:
987: $form = $forms[ $form_id ];
988:
989: $columns_settings = rgars( $forms, $form_id . '/personalData/exportingAndErasing/columns' );
990:
991: if ( is_array( $columns_settings ) ) {
992: foreach ( $columns_settings as $column_key => $column_settings ) {
993: if ( rgar( $column_settings, 'erase' ) ) {
994: GFAPI::update_entry_property( $entry['id'], $column_key, '' );
995: $items_removed = true;
996: }
997: }
998: }
999:
1000: $has_product_field = false;
1001:
1002: foreach ( $form['fields'] as $field ) {
1003:
1004:
1005: if ( $field->personalDataErase ) {
1006:
1007: $input_type = $field->get_input_type();
1008:
1009: if ( $input_type == 'fileupload' ) {
1010: GFFormsModel::delete_files( $entry['id'] );
1011: GFAPI::update_entry_field( $entry['id'], $field->id, '' );
1012: continue;
1013: }
1014:
1015: if ( $field->type == 'product' ) {
1016: $has_product_field = true;
1017: }
1018:
1019: $value = GFFormsModel::get_lead_field_value( $entry, $field );
1020:
1021: if ( is_array( $value ) ) {
1022: self::erase_field_values( $value, $entry['id'], $field->id );
1023: $items_removed = true;
1024: } else {
1025: switch ( $input_type ) {
1026: case 'email':
1027: $anonymous = 'deleted@site.invalid';
1028: break;
1029: case 'website':
1030: $anonymous = 'https://site.invalid';
1031: break;
1032: case 'date':
1033: $anonymous = '0000-00-00';
1034: break;
1035: case 'text':
1036: case 'textarea':
1037:
1038: $anonymous = __( '[deleted]' );
1039: break;
1040: default:
1041: $anonymous = '';
1042: }
1043: GFAPI::update_entry_field( $entry['id'], $field->id, $anonymous );
1044: $items_removed = true;
1045: }
1046: }
1047: }
1048:
1049: if ( $has_product_field ) {
1050: GFFormsModel::refresh_product_cache( $form, $entry );
1051: }
1052:
1053: $custom_items = self::get_custom_items( $form );
1054:
1055: if ( ! empty( $custom_items ) ) {
1056: $all_custom_settings = rgars( $forms, $form_id . '/personalData/exportingAndErasing/custom' );
1057: foreach ( $custom_items as $custom_item_key => $custom_item_details ) {
1058: $custom_settings = rgar( $all_custom_settings, $custom_item_key );
1059: if ( rgars( $custom_settings, 'erase' ) && isset( $custom_item_details['eraser_callback'] ) && is_callable( $custom_item_details['eraser_callback'] ) ) {
1060: call_user_func( $custom_item_details['eraser_callback'], $form, $entry );
1061: $items_removed = true;
1062: }
1063: }
1064: }
1065: }
1066:
1067: $done = count( $entries ) < $limit;
1068:
1069: return array(
1070: 'items_removed' => $items_removed,
1071: 'items_retained' => false,
1072: 'messages' => array(),
1073: 'done' => $done,
1074: );
1075: }
1076:
1077: public static function erase_field_values( $value, $entry_id, $input_id, $item_index = '' ) {
1078: if ( is_array( $value ) ) {
1079: $i = 0;
1080: foreach ( $value as $key => $val ) {
1081: if ( is_array( $val ) ) {
1082: foreach ( $val as $k => $v ) {
1083: $new_index = $item_index . '_' . $i;
1084: self::erase_field_values( $v, $entry_id, $k, $new_index );
1085: }
1086: $i++;
1087: } else {
1088: GFAPI::update_entry_field( $entry_id, $key, '', $item_index );
1089: }
1090: }
1091: } else {
1092: GFAPI::update_entry_field( $entry_id, $input_id, '', $item_index );
1093: }
1094:
1095: }
1096:
1097: 1098: 1099: 1100: 1101: 1102: 1103: 1104: 1105:
1106: public static function get_draft_submissions( $email_address ) {
1107:
1108: $draft_submissions = GFFormsModel::get_draft_submissions();
1109:
1110: if ( empty( $draft_submissions ) ) {
1111: return array();
1112: }
1113:
1114: $user = get_user_by( 'email', $email_address );
1115:
1116: $return = array();
1117:
1118: $forms = self::get_forms();
1119:
1120: foreach ( $draft_submissions as $i => $draft_submission ) {
1121:
1122: $form_id = $draft_submission['form_id'];
1123:
1124: $form = $forms[ $form_id ];
1125:
1126: if ( ! rgars( $form, 'personalData/exportingAndErasing/enabled' ) ) {
1127: continue;
1128: }
1129:
1130: $submission_json = $draft_submission['submission'];
1131:
1132: $submission = json_decode( $submission_json, true );
1133:
1134: $entry = $submission['partial_entry'];
1135:
1136: $identification_field = rgars( $form, 'personalData/exportingAndErasing/identificationField' );
1137:
1138: $field = GFAPI::get_field( $form, $identification_field );
1139:
1140: if ( ( $field && $field->get_input_type() == 'email' && $entry[ (string) $identification_field ] === $email_address )
1141: || ( $user && $user->ID == rgar( $entry, $identification_field ) )
1142: ) {
1143: $return[] = $draft_submission;
1144: }
1145: }
1146:
1147: return $return;
1148: }
1149:
1150: 1151: 1152: 1153: 1154: 1155: 1156: 1157: 1158:
1159: public static function erase_draft_submissions_data( $email_address ) {
1160: $items_removed = false;
1161:
1162: $forms = self::get_forms();
1163:
1164: $draft_entries = self::get_draft_submissions( $email_address );
1165:
1166: foreach ( $draft_entries as $draft_entry ) {
1167:
1168: $entry_dirty = false;
1169:
1170: $form_id = $draft_entry['form_id'];
1171:
1172: $resume_token = $draft_entry['uuid'];
1173:
1174: $date_created = $draft_entry['date_created'];
1175:
1176: $form = $forms[ $form_id ];
1177:
1178: $columns_settings = rgars( $forms, $form_id . '/personalData/exportingAndErasing/columns' );
1179:
1180: $submission_json = $draft_entry['submission'];
1181:
1182: $submission = json_decode( $submission_json, true );
1183:
1184: $entry = $submission['partial_entry'];
1185:
1186: $submitted_values = $submission['submitted_values'];
1187:
1188: if ( is_array( $columns_settings ) ) {
1189: foreach ( $columns_settings as $column_key => $column_settings ) {
1190: if ( rgar( $column_settings, 'erase' ) ) {
1191: if ( isset( $draft_entry[ $column_key ] ) ) {
1192: $draft_entry[ $column_key ] = '';
1193: }
1194:
1195: if ( isset( $entry[ $column_key ] ) ) {
1196: $entry[ $column_key ] = '';
1197: }
1198:
1199: $entry_dirty = true;
1200: }
1201: }
1202: }
1203:
1204: foreach ( $form['fields'] as $field ) {
1205:
1206:
1207: if ( $field->personalDataErase ) {
1208:
1209: $input_type = $field->get_input_type();
1210:
1211: $value = GFFormsModel::get_lead_field_value( $entry, $field );
1212:
1213: if ( is_array( $value ) ) {
1214: foreach ( $value as $k => $v ) {
1215: $entry[ $k ] = '';
1216: $submitted_values[ $field->id ][ $k ] = '';
1217: }
1218: $entry_dirty = true;
1219: } else {
1220: switch ( $input_type ) {
1221: case 'email':
1222: $anonymous = 'deleted@site.invalid';
1223: break;
1224: case 'website':
1225: $anonymous = 'https://site.invalid';
1226: break;
1227: case 'date':
1228: $anonymous = '0000-00-00';
1229: break;
1230: case 'text':
1231: case 'textarea':
1232:
1233: $anonymous = __( '[deleted]', 'gravityforms' );
1234: break;
1235: default:
1236: $anonymous = '';
1237: }
1238: $submitted_values[ (string) $field->id ] = $anonymous;
1239: $entry[ (string) $field->id ] = $anonymous;
1240: $entry_dirty = true;
1241: }
1242: }
1243: }
1244:
1245: $custom_items = self::get_custom_items( $form );
1246:
1247: if ( ! empty( $custom_items ) ) {
1248: $all_custom_settings = rgars( $forms, $form_id . '/personalData/exportingAndErasing/custom' );
1249: foreach ( $custom_items as $custom_item_key => $custom_item_details ) {
1250: $custom_settings = rgar( $all_custom_settings, $custom_item_key );
1251: if ( rgars( $custom_settings, 'erase' ) && isset( $custom_item_details['eraser_callback'] ) && is_callable( $custom_item_details['eraser_callback'] ) ) {
1252: call_user_func( $custom_item_details['eraser_callback'], $form, $entry );
1253: $items_removed = true;
1254: }
1255: }
1256: }
1257:
1258: if ( $entry_dirty ) {
1259: $submission['submitted_values'] = $submitted_values;
1260: $submission['partial_entry'] = $entry;
1261: $submission_json = json_encode( $submission );
1262: GFFormsModel::update_draft_submission( $resume_token, $form, $date_created, $draft_entry['ip'], $draft_entry['source_url'], $submission_json );
1263: $items_removed = true;
1264: }
1265: }
1266:
1267:
1268: return $items_removed;
1269: }
1270:
1271: 1272: 1273: 1274: 1275:
1276: public static function cron_task() {
1277:
1278: self::log_debug( __METHOD__ . '(): starting personal data cron task' );
1279:
1280: $forms = self::get_forms();
1281:
1282: $trash_form_ids = array();
1283: $trash_conditions = array();
1284:
1285: $delete_form_ids = array();
1286: $delete_conditions = array();
1287:
1288: foreach ( $forms as $form ) {
1289:
1290: $retention_policy = rgars( $form, 'personalData/retention/policy', 'retain' );
1291:
1292: if ( $retention_policy == 'retain' ) {
1293: continue;
1294: }
1295:
1296: $form_conditions = array();
1297:
1298: $retention_days = rgars( $form, 'personalData/retention/retain_entries_days' );
1299:
1300: $delete_timestamp = time() - ( DAY_IN_SECONDS * $retention_days );
1301:
1302: $delete_date = date( 'Y-m-d H:i:s', $delete_timestamp );
1303:
1304: $form_conditions[] = new GF_Query_Condition(
1305: new GF_Query_Column( 'date_created' ),
1306: GF_Query_Condition::LT,
1307: new GF_Query_Literal( $delete_date )
1308: );
1309:
1310: $form_conditions[] = new GF_Query_Condition(
1311: new GF_Query_Column( 'form_id' ),
1312: GF_Query_Condition::EQ,
1313: new GF_Query_Literal( $form['id'] )
1314: );
1315:
1316: if ( ! empty( $form_conditions ) ) {
1317: if ( $retention_policy == 'trash' ) {
1318: $trash_form_ids[] = $form['id'];
1319: $trash_conditions[] = call_user_func_array( array(
1320: 'GF_Query_Condition',
1321: '_and',
1322: ), $form_conditions );
1323: } elseif ( $retention_policy == 'delete' ) {
1324: $delete_form_ids[] = $form['id'];
1325: $delete_conditions[] = call_user_func_array( array(
1326: 'GF_Query_Condition',
1327: '_and',
1328: ), $form_conditions );
1329: }
1330: }
1331: }
1332:
1333: if ( ! empty( $trash_conditions ) ) {
1334:
1335: $query = new GF_Query();
1336:
1337: $all_trash_conditions = array();
1338:
1339: $all_trash_conditions[] = call_user_func_array( array( 'GF_Query_Condition', '_or' ), $trash_conditions );
1340:
1341: $all_trash_conditions[] = new GF_Query_Condition(
1342: new GF_Query_Column( 'status' ),
1343: GF_Query_Condition::NEQ,
1344: new GF_Query_Literal( 'trash' )
1345: );
1346:
1347: $all_trash_conditions = call_user_func_array( array( 'GF_Query_Condition', '_and' ), $all_trash_conditions );
1348:
1349: $entry_ids = $query->from( $trash_form_ids )->where( $all_trash_conditions )->get_ids();
1350:
1351: self::log_debug( __METHOD__ . '(): trashing entries: ' . join( ', ', $entry_ids ) );
1352:
1353: foreach ( $entry_ids as $entry_id ) {
1354: GFAPI::update_entry_property( $entry_id, 'status', 'trash' );
1355: }
1356: }
1357:
1358: if ( ! empty( $delete_conditions ) ) {
1359:
1360: $query = new GF_Query();
1361:
1362: $all_delete_conditions = call_user_func_array( array( 'GF_Query_Condition', '_or' ), $delete_conditions );
1363:
1364: $entry_ids = $query->from( $delete_form_ids )->where( $all_delete_conditions )->get_ids();
1365:
1366: self::log_debug( __METHOD__ . '(): deleting entries: ' . join( ', ', $entry_ids ) );
1367:
1368: 1369: 1370: 1371: 1372: 1373: 1374: 1375:
1376: $entry_ids = apply_filters( 'gform_entry_ids_automatic_deletion', $entry_ids );
1377:
1378: foreach ( $entry_ids as $entry_id ) {
1379: GFAPI::delete_entry( $entry_id );
1380: }
1381: }
1382:
1383: self::log_debug( __METHOD__ . '(): done' );
1384:
1385: }
1386:
1387: 1388: 1389: 1390: 1391: 1392: 1393:
1394: public static function log_debug( $message ) {
1395: GFCommon::log_debug( $message );
1396: }
1397:
1398: 1399: 1400: 1401: 1402:
1403: public static function flush_current_forms() {
1404: self::$_forms = null;
1405: }
1406: }
1407: