UserDataRequests.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. <?php if ( ! defined( 'ABSPATH' ) ) exit;
  2. class NF_Admin_UserDataRequests {
  3. /**
  4. * @var array
  5. */
  6. protected $ignored_field_types = array (
  7. 'html',
  8. 'submit',
  9. 'hr',
  10. 'recaptcha',
  11. 'spam',
  12. 'creditcard',
  13. 'creditcardcvc',
  14. 'creditcardexpiration',
  15. 'creditcardfullname',
  16. 'creditcardnumber',
  17. 'creditcardzip'
  18. );
  19. /**
  20. * @var WP_User
  21. */
  22. protected $user;
  23. /**
  24. * @var string
  25. */
  26. protected $request_email;
  27. /** Class constructor */
  28. public function __construct() {
  29. add_filter( 'wp_privacy_personal_data_exporters', array(
  30. $this, 'plugin_register_exporters' ) );
  31. add_filter( 'wp_privacy_personal_data_erasers', array(
  32. $this, 'plugin_register_erasers' ) );
  33. }
  34. /**
  35. * Register exporter for Plugin user data.
  36. *
  37. * @param array $exporters
  38. *
  39. * @return array
  40. */
  41. function plugin_register_exporters( $exporters = array() ) {
  42. $exporters[] = array(
  43. 'exporter_friendly_name' => __( 'Ninja Forms Submission Data', 'ninja-forms' ),
  44. 'callback' => array( $this, 'plugin_user_data_exporter' ),
  45. );
  46. return $exporters;
  47. }
  48. /**
  49. * Register eraser for Plugin user data.
  50. *
  51. * @param array $erasers
  52. *
  53. * @return array
  54. */
  55. function plugin_register_erasers( $erasers = array() ) {
  56. $erasers[] = array(
  57. 'eraser_friendly_name' => __( 'Ninja Forms Submissions Data', 'ninja-forms' ),
  58. 'callback' => array( $this, 'plugin_user_data_eraser' ),
  59. );
  60. return $erasers;
  61. }
  62. /**
  63. * Adds Ninja Forms Submission data to the default HTML export file that
  64. * WordPress creates on converted request
  65. *
  66. * @param $email_address
  67. * @param int $page
  68. *
  69. * @return array
  70. */
  71. function plugin_user_data_exporter( $email_address, $page = 1 ) {
  72. $export_items = array();
  73. // get the user
  74. $this->user = get_user_by( 'email', $email_address );
  75. $this->request_email = $email_address;
  76. if( $this->user && $this->user->ID ) {
  77. $item_id = "ninja-forms-" . $this->user->ID;
  78. } else {
  79. $item_id = "ninja-forms";
  80. }
  81. $group_id = 'ninja-forms';
  82. $group_label = __( 'Ninja Forms Submission Data', 'ninja-forms' );
  83. $subs = $this->get_related_subs( $email_address );
  84. foreach($subs as $sub) {
  85. $data = array();
  86. // get the field values from postmeta
  87. $sub_meta = get_post_meta( $sub->ID );
  88. // make sure we have a form submission
  89. if ( isset( $sub_meta[ '_form_id' ] ) ) {
  90. $form = Ninja_Forms()->form( $sub_meta[ '_form_id' ][ 0 ] )
  91. ->get();
  92. $fields = Ninja_Forms()->form( $sub_meta[ '_form_id' ][ 0 ] )
  93. ->get_fields();
  94. foreach ( $fields as $field_id => $field ) {
  95. // we don't care about submit, hr, divider, html fields
  96. if ( ! in_array( $field->get_setting( 'type' ),
  97. $this->ignored_field_types ) ) {
  98. // make sure there is a value
  99. if ( isset( $sub_meta[ '_field_' . $field_id ] ) ) {
  100. //multi-value fields may need to be unserialized
  101. if( in_array( $field->get_setting( 'type' ),
  102. array( 'listcheckbox', 'listmultiselect' ) ) ){
  103. //implode the unserialized array
  104. $value = implode( ',', maybe_unserialize(
  105. $sub_meta[ '_field_' . $field_id ][ 0 ] ) );
  106. } else {
  107. $value = $sub_meta[ '_field_' . $field_id ][ 0 ];
  108. }
  109. // Add label/value pairs to data array
  110. $data[] = array(
  111. 'name' => $field->get_setting( 'label' ),
  112. 'value' => $value
  113. );
  114. }
  115. }
  116. }
  117. // Add this group of items to the exporters data array.
  118. $export_items[] = array(
  119. 'group_id' => $group_id . '-' . $sub->ID,
  120. 'group_label' => $group_label . '-' .
  121. $form->get_setting( 'title' ),
  122. 'item_id' => $item_id . '-' . $sub->ID,
  123. 'data' => $data,
  124. );
  125. }
  126. }
  127. // Returns an array of exported items for this pass, but also a boolean whether this exporter is finished.
  128. //If not it will be called again with $page increased by 1.
  129. return array(
  130. 'data' => $export_items,
  131. 'done' => true,
  132. );
  133. }
  134. /**
  135. * Eraser for Plugin user data. This will completely erase all Ninja Form
  136. * submission data for the user when converted by the admin.
  137. *
  138. * @param $email_address
  139. * @param int $page
  140. *
  141. * @return array
  142. */
  143. function plugin_user_data_eraser( $email_address, $page = 1 ) {
  144. if ( empty( $email_address ) ) {
  145. return array(
  146. 'items_removed' => false,
  147. 'items_retained' => false,
  148. 'messages' => array(),
  149. 'done' => true,
  150. );
  151. }
  152. // get the user
  153. $this->user = get_user_by( 'email', $email_address );
  154. $this->request_email = $email_address;
  155. $request_id = $_REQUEST[ 'id' ];
  156. $make_anonymous = get_post_meta( $request_id, 'nf_anonymize_data',
  157. true);
  158. $messages = array();
  159. $items_removed = false;
  160. $items_retained = false;
  161. $subs = $this->get_related_subs( $email_address );
  162. if( 0 < sizeof( $subs ) ) {
  163. $items_removed = true;
  164. }
  165. if( '1' != $make_anonymous ) {
  166. $this->delete_submissions( $subs );
  167. $items_removed = true;
  168. } else {
  169. $this->anonymize_submissions( $subs, $email_address );
  170. }
  171. /**
  172. * Returns an array of exported items for this pass, but also a boolean
  173. * whether this exporter is finished.
  174. * If not it will be called again with $page increased by 1.
  175. * */
  176. return array(
  177. 'items_removed' => $items_removed,
  178. 'items_retained' => $items_retained,
  179. 'messages' => $messages,
  180. 'done' => true,
  181. );
  182. }
  183. /**
  184. * Retrieve all submissions related(by author id or email address) to the
  185. * given email address
  186. *
  187. * @param $email_address
  188. *
  189. * @return array
  190. */
  191. private function get_related_subs( $email_address ) {
  192. // array if subs where user is author
  193. $logged_in_subs = array();
  194. if ( $this->user && $this->user->ID ) {
  195. // get submission ids the old-fashioned way if user is author
  196. $logged_in_subs = get_posts(
  197. array(
  198. 'author' => $this->user->ID,
  199. 'post_type' => 'nf_sub',
  200. 'posts_per_page' => - 1,
  201. 'fields' => 'ids'
  202. )
  203. );
  204. }
  205. // get submission ids where email address is a field value
  206. $anon_sub_ids = $this->get_subs_by_email( $email_address );
  207. // merge anonymous and author submissions ids and get unique
  208. $sub_ids = array_unique( array_merge( $logged_in_subs, $anon_sub_ids ) );
  209. // return empty array if $sub_ids is empty
  210. if( 1 > count( $sub_ids ) ) {
  211. return array();
  212. }
  213. // get post objects related to the email address
  214. return get_posts(
  215. array(
  216. 'include' => implode(',', $sub_ids),
  217. 'post_type' => 'nf_sub',
  218. 'posts_per_page' => -1,
  219. )
  220. );
  221. }
  222. /**
  223. * Get submission ids where the submission has the give email address as
  224. * data
  225. *
  226. * @param $email_address
  227. *
  228. * @return array
  229. */
  230. private function get_subs_by_email( $email_address ) {
  231. global $wpdb;
  232. // query to find any submission with our requester's email as value
  233. $anon_subs_query = "SELECT DISTINCT(m.post_id) FROM `" . $wpdb->prefix
  234. . "postmeta` m
  235. JOIN `" . $wpdb->prefix . "posts` p ON p.id = m.post_id
  236. WHERE m.meta_value = '" . $email_address . "'
  237. AND p.post_type = 'nf_sub'";
  238. $anon_subs = $wpdb->get_results( $anon_subs_query );
  239. $sub_id_array = array();
  240. // let's get the integer value of those submission ids
  241. if( 0 < sizeof( $anon_subs ) ) {
  242. foreach( $anon_subs as $sub ) {
  243. $sub_id_array[] = intval( $sub->post_id );
  244. }
  245. }
  246. return $sub_id_array;
  247. }
  248. /**
  249. * Delete Submissions
  250. *
  251. * @param $subs
  252. */
  253. private function delete_submissions( $subs ) {
  254. if( 0 < sizeof( $subs ) ) {
  255. // iterate and delete the submissions
  256. foreach($subs as $sub) {
  257. wp_delete_post( $sub->ID, true );
  258. }
  259. }
  260. }
  261. /**
  262. * This will (redact) personal data and anonymize submissions
  263. *
  264. * @param $subs
  265. */
  266. private function anonymize_submissions( $subs ) {
  267. $form_id_array = array();
  268. $submitter_field = '';
  269. if( 0 < sizeof( $subs ) ) {
  270. $anonymize_data = false;
  271. foreach( $subs as $sub ) {
  272. // get the form id
  273. $form_id = get_post_meta( $sub->ID, '_form_id', true );
  274. $form = Ninja_Forms()->form( $form_id );
  275. /*
  276. * Do we have a use, if so does the post(submission) author
  277. * match the user. If so, then anonymize
  278. */
  279. if( $this->user && $this->user->ID
  280. && $sub->post_author == $this->user->ID ) {
  281. $anonymize_data = true;
  282. } else {
  283. /*
  284. * Otherwise, does the submitter email for the submission
  285. * equal the email for the request
  286. */
  287. $form_submitter_email = '';
  288. if( in_array( $form_id, array_keys( $form_id_array ) ) ) {
  289. /*
  290. * if we already have the submitter field key, no
  291. * need to iterate over the actions again
  292. */
  293. $submitter_field = $form_id_array[ $form_id ];
  294. } else {
  295. $actions = $form->get_actions();
  296. if ( 0 < sizeof( $actions ) ) {
  297. foreach ( $actions as $action ) {
  298. // we only care about the save action
  299. if ( 'save' == $action->get_setting( 'type' )
  300. && null != $action->get_setting( 'submitter_email' )
  301. && '' != $action->get_setting( 'submitter_email' ) ) {
  302. // get the submitter field
  303. $submitter_field = $action->get_setting( 'submitter_email' );
  304. /*
  305. * Add the form id and submitter field to
  306. * this array so we don't have to load
  307. * the form again if we have multiple
  308. * submissions for the same form
  309. */
  310. $form_id_array[ $form_id ] = $submitter_field;
  311. break;
  312. }
  313. }
  314. }
  315. }
  316. /*
  317. * If the submitter field is not empty, then let's
  318. * get the value given in the form submission for
  319. * that field
  320. */
  321. if ( '' != $submitter_field ) {
  322. $fields = $form->get_fields();
  323. foreach ( $fields as $field ) {
  324. $key = $field->get_setting( 'key' );
  325. // we only care about email fields
  326. if ( 'email' == $field->get_setting( 'type' )
  327. && $submitter_field == $key ) {
  328. // if we have a match, get the value
  329. $form_submitter_email = get_post_meta(
  330. $sub->ID,
  331. '_field_' . $field->get_id(),
  332. true );
  333. break;
  334. }
  335. }
  336. }
  337. // if form submitter email matches requester's email
  338. if( $form_submitter_email === $this->request_email ) {
  339. $anonymize_data = true;
  340. }
  341. }
  342. if( $anonymize_data ) {
  343. // anonymize the actual submitted for values
  344. $this->anonymize_fields($sub, $form->get_fields() );
  345. }
  346. }
  347. }
  348. }
  349. /**
  350. * This will anonymize personally identifiable fields and anonymize
  351. * submissions submitted by the user with the provided email address
  352. *
  353. * @param $sub
  354. * @param $fields
  355. */
  356. private function anonymize_fields( $sub, $fields ) {
  357. foreach( $fields as $field ) {
  358. $type = $field->get_setting( 'type' );
  359. // ignore fields that aren't saved
  360. if( ! in_array( $type, $this->ignored_field_types ) ) {
  361. $is_personal = $field->get_setting( 'personally_identifiable' );
  362. /**
  363. * If this is personally identifiable, redact it
  364. */
  365. if( null != $is_personal && '1' == $is_personal ) {
  366. $field_id = $field->get_id();
  367. // make sure we have that field saved.
  368. $field_value = get_post_meta(
  369. $sub->ID,
  370. '_field_' . $field_id,
  371. true
  372. );
  373. if( '' != $field_value ) {
  374. update_post_meta(
  375. $sub->ID,
  376. '_field_' . $field_id,
  377. '(redacted)'
  378. );
  379. }
  380. }
  381. }
  382. }
  383. // Remove the author id if the the email address belongs to the author
  384. if( $this->user && $this->user->ID &&
  385. $this->user->ID == $sub->post_author ) {
  386. wp_update_post(
  387. array(
  388. 'ID' => $sub->ID,
  389. 'post_author' => 0
  390. )
  391. );
  392. }
  393. }
  394. }