<?php if ( ! defined( 'ABSPATH' ) ) exit;
class NF_Admin_UserDataRequests {
protected $ignored_field_types = array (
protected $request_email;
public function __construct() {
add_filter( 'wp_privacy_personal_data_exporters', array(
$this, 'plugin_register_exporters' ) );
add_filter( 'wp_privacy_personal_data_erasers', array(
$this, 'plugin_register_erasers' ) );
* Register exporter for Plugin user data.
* @param array $exporters
function plugin_register_exporters( $exporters = array() ) {
'exporter_friendly_name' => esc_html__( 'Ninja Forms Submission Data', 'ninja-forms' ),
'callback' => array( $this, 'plugin_user_data_exporter' ),
* Register eraser for Plugin user data.
function plugin_register_erasers( $erasers = array() ) {
'eraser_friendly_name' => esc_html__( 'Ninja Forms Submissions Data', 'ninja-forms' ),
'callback' => array( $this, 'plugin_user_data_eraser' ),
* Adds Ninja Forms Submission data to the default HTML export file that
* WordPress creates on converted request
function plugin_user_data_exporter( $email_address, $page = 1 ) {
$this->user = get_user_by( 'email', $email_address );
$this->request_email = $email_address;
if( $this->user && $this->user->ID ) {
$item_id = "ninja-forms-" . $this->user->ID;
$item_id = "ninja-forms";
$group_id = 'ninja-forms';
$group_label = esc_html__( 'Ninja Forms Submission Data', 'ninja-forms' );
$subs = $this->get_related_subs( $email_address );
// get the field values from postmeta
$sub_meta = get_post_meta( $sub->ID );
// make sure we have a form submission
if ( isset( $sub_meta[ '_form_id' ] ) ) {
$form = Ninja_Forms()->form( $sub_meta[ '_form_id' ][ 0 ] )
$fields = Ninja_Forms()->form( $sub_meta[ '_form_id' ][ 0 ] )
foreach ( $fields as $field_id => $field ) {
// we don't care about submit, hr, divider, html fields
if ( ! in_array( $field->get_setting( 'type' ),
$this->ignored_field_types ) ) {
// make sure there is a value
if ( isset( $sub_meta[ '_field_' . $field_id ] ) ) {
//multi-value fields may need to be unserialized
if( in_array( $field->get_setting( 'type' ),
array( 'listcheckbox', 'listmultiselect' ) ) ){
//implode the unserialized array
$value = implode( ',', maybe_unserialize(
$sub_meta[ '_field_' . $field_id ][ 0 ] ) );
$value = $sub_meta[ '_field_' . $field_id ][ 0 ];
// Add label/value pairs to data array
'name' => $field->get_setting( 'label' ),
// Add this group of items to the exporters data array.
'group_id' => $group_id . '-' . $sub->ID,
'group_label' => $group_label . '-' .
$form->get_setting( 'title' ),
'item_id' => $item_id . '-' . $sub->ID,
// Returns an array of exported items for this pass, but also a boolean whether this exporter is finished.
//If not it will be called again with $page increased by 1.
* Eraser for Plugin user data. This will completely erase all Ninja Form
* submission data for the user when converted by the admin.
function plugin_user_data_eraser( $email_address, $page = 1 ) {
if ( empty( $email_address ) ) {
'items_removed' => false,
'items_retained' => false,
$this->user = get_user_by( 'email', $email_address );
$this->request_email = $email_address;
if (!isset($_REQUEST['id']) || empty($_REQUEST['id'])) {
$request_id = absint($_REQUEST[ 'id' ]);
$make_anonymous = get_post_meta( $request_id, 'nf_anonymize_data',
$subs = $this->get_related_subs( $email_address );
if( 0 < sizeof( $subs ) ) {
if( '1' != $make_anonymous ) {
$this->delete_submissions( $subs );
$this->anonymize_submissions( $subs, $email_address );
* Returns an array of exported items for this pass, but also a boolean
* whether this exporter is finished.
* If not it will be called again with $page increased by 1.
'items_removed' => $items_removed,
'items_retained' => $items_retained,
* Retrieve all submissions related(by author id or email address) to the
private function get_related_subs( $email_address ) {
// array if subs where user is author
$logged_in_subs = array();
if ( $this->user && $this->user->ID ) {
// get submission ids the old-fashioned way if user is author
$logged_in_subs = get_posts(
'author' => $this->user->ID,
// get submission ids where email address is a field value
$anon_sub_ids = $this->get_subs_by_email( $email_address );
// merge anonymous and author submissions ids and get unique
$sub_ids = array_unique( array_merge( $logged_in_subs, $anon_sub_ids ) );
// return empty array if $sub_ids is empty
if( 1 > count( $sub_ids ) ) {
// get post objects related to the email address
'include' => implode(',', $sub_ids),
* Get submission ids where the submission has the give email address as
private function get_subs_by_email( $email_address ) {
// query to find any submission with our requester's email as value
$anon_subs_query = "SELECT DISTINCT(m.post_id) FROM `" . $wpdb->prefix
JOIN `" . $wpdb->prefix . "posts` p ON p.id = m.post_id
WHERE m.meta_value = '" . $email_address . "'
AND p.post_type = 'nf_sub'";
$anon_subs = $wpdb->get_results( $anon_subs_query );
// let's get the integer value of those submission ids
if( 0 < sizeof( $anon_subs ) ) {
foreach( $anon_subs as $sub ) {
$sub_id_array[] = intval( $sub->post_id );
private function delete_submissions( $subs ) {
if( 0 < sizeof( $subs ) ) {
// iterate and delete the submissions
wp_delete_post( $sub->ID, true );
* This will (redact) personal data and anonymize submissions
private function anonymize_submissions( $subs ) {
$form_id_array = array();
if( 0 < sizeof( $subs ) ) {
foreach( $subs as $sub ) {
$form_id = get_post_meta( $sub->ID, '_form_id', true );
$form = Ninja_Forms()->form( $form_id );
* Do we have a use, if so does the post(submission) author
* match the user. If so, then anonymize
if( $this->user && $this->user->ID
&& $sub->post_author == $this->user->ID ) {
* Otherwise, does the submitter email for the submission
* equal the email for the request
$form_submitter_email = '';
if( in_array( $form_id, array_keys( $form_id_array ) ) ) {
* if we already have the submitter field key, no
* need to iterate over the actions again
$submitter_field = $form_id_array[ $form_id ];
$actions = $form->get_actions();
if ( 0 < sizeof( $actions ) ) {
foreach ( $actions as $action ) {
// we only care about the save action
if ( 'save' == $action->get_setting( 'type' )
&& null != $action->get_setting( 'submitter_email' )
&& '' != $action->get_setting( 'submitter_email' ) ) {
// get the submitter field
$submitter_field = $action->get_setting( 'submitter_email' );
* Add the form id and submitter field to
* this array so we don't have to load
* the form again if we have multiple
* submissions for the same form
$form_id_array[ $form_id ] = $submitter_field;
* If the submitter field is not empty, then let's
* get the value given in the form submission for
if ( '' != $submitter_field ) {
$fields = $form->get_fields();
foreach ( $fields as $field ) {
$key = $field->get_setting( 'key' );
// we only care about email fields
if ( 'email' == $field->get_setting( 'type' )
&& $submitter_field == $key ) {
// if we have a match, get the value
$form_submitter_email = get_post_meta(
'_field_' . $field->get_id(),
// if form submitter email matches requester's email
if( $form_submitter_email === $this->request_email ) {
// anonymize the actual submitted for values
$this->anonymize_fields($sub, $form->get_fields() );
* This will anonymize personally identifiable fields and anonymize
* submissions submitted by the user with the provided email address
private function anonymize_fields( $sub, $fields ) {
foreach( $fields as $field ) {
$type = $field->get_setting( 'type' );
// ignore fields that aren't saved
if( ! in_array( $type, $this->ignored_field_types ) ) {
$is_personal = $field->get_setting( 'personally_identifiable' );
* If this is personally identifiable, redact it
if( null != $is_personal && '1' == $is_personal ) {
$field_id = $field->get_id();
// make sure we have that field saved.
$field_value = get_post_meta(
if( '' != $field_value ) {
// Remove the author id if the the email address belongs to the author
if( $this->user && $this->user->ID &&
$this->user->ID == $sub->post_author ) {