class-evf-entry-csv-exporter.php
492 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Handles entry CSV export. |
| 4 | * |
| 5 | * @package EverestForms\Export |
| 6 | * @since 1.3.0 |
| 7 | */ |
| 8 | |
| 9 | defined( 'ABSPATH' ) || exit; |
| 10 | |
| 11 | /** |
| 12 | * Include dependencies. |
| 13 | */ |
| 14 | if ( ! class_exists( 'EVF_CSV_Exporter', false ) ) { |
| 15 | require_once EVF_ABSPATH . 'includes/export/abstract-evf-csv-exporter.php'; |
| 16 | } |
| 17 | |
| 18 | /** |
| 19 | * EVF_Entry_CSV_Exporter Class. |
| 20 | */ |
| 21 | class EVF_Entry_CSV_Exporter extends EVF_CSV_Exporter { |
| 22 | |
| 23 | /** |
| 24 | * Form ID. |
| 25 | * |
| 26 | * @var int|mixed |
| 27 | */ |
| 28 | public $form_id; |
| 29 | |
| 30 | /** |
| 31 | * Request Data. |
| 32 | * |
| 33 | * @since 1.8.7 |
| 34 | * @var array |
| 35 | */ |
| 36 | public $request_data; |
| 37 | |
| 38 | /** |
| 39 | * Entry ID. |
| 40 | * |
| 41 | * @var int|mixed |
| 42 | */ |
| 43 | public $entry_id; |
| 44 | |
| 45 | /** |
| 46 | * Type of export used in filter names. |
| 47 | * |
| 48 | * @var string |
| 49 | */ |
| 50 | protected $export_type = 'entry'; |
| 51 | |
| 52 | /** |
| 53 | * Constructor. |
| 54 | * |
| 55 | * @param int $form_id Form ID. |
| 56 | * @param int $entry_id Entry ID. |
| 57 | * @param array $request_data Request Data. |
| 58 | */ |
| 59 | public function __construct( $form_id = '', $entry_id = '', $request_data = array() ) { |
| 60 | $this->form_id = absint( $form_id ); |
| 61 | $this->entry_id = absint( $entry_id ); |
| 62 | $this->request_data = $request_data; |
| 63 | $this->column_names = $this->get_default_column_names(); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Return an array of columns to export. |
| 68 | * |
| 69 | * @return array |
| 70 | */ |
| 71 | public function get_default_column_names() { |
| 72 | $columns = array(); |
| 73 | $form_obj = evf()->form->get( $this->form_id ); |
| 74 | $form_data = ! empty( $form_obj->post_content ) ? evf_decode( $form_obj->post_content ) : ''; |
| 75 | |
| 76 | // Set Entry ID at first. |
| 77 | $columns['entry_id'] = esc_html__( 'ID', 'everest-forms' ); |
| 78 | |
| 79 | // Add whitelisted fields to export columns. |
| 80 | if ( ! empty( $form_data['form_fields'] ) ) { |
| 81 | foreach ( $form_data['form_fields'] as $field ) { |
| 82 | if ( ! in_array( $field['type'], array( 'html', 'title', 'captcha', 'divider' ), true ) ) { |
| 83 | $columns[ $field['id'] ] = evf_clean( $field['label'] ); |
| 84 | } |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | // Set the default columns. |
| 89 | $columns['status'] = esc_html__( 'Status', 'everest-forms' ); |
| 90 | $columns['date_created'] = esc_html__( 'Date Created', 'everest-forms' ); |
| 91 | $columns['date_created_gmt'] = esc_html__( 'Date Created GMT', 'everest-forms' ); |
| 92 | |
| 93 | // If user details are disabled globally discard the IP and UA. |
| 94 | if ( 'yes' !== get_option( 'everest_forms_disable_user_details' ) ) { |
| 95 | $columns['user_device'] = esc_html__( 'User Device', 'everest-forms' ); |
| 96 | $columns['user_ip_address'] = esc_html__( 'User IP Address', 'everest-forms' ); |
| 97 | } |
| 98 | |
| 99 | return apply_filters( "everest_forms_export_{$this->export_type}_default_columns", $columns, $this->request_data ); |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | * Prepare data for export. |
| 104 | * |
| 105 | * @since 1.6.0 |
| 106 | */ |
| 107 | public function prepare_data_to_export() { |
| 108 | $this->row_data = array(); |
| 109 | |
| 110 | if ( $this->entry_id ) { |
| 111 | $entry = evf_get_entry( $this->entry_id ); |
| 112 | $this->row_data[] = $this->generate_row_data( $entry ); |
| 113 | } else { |
| 114 | $entry_ids = evf_search_entries( |
| 115 | array( |
| 116 | 'limit' => -1, |
| 117 | 'order' => 'ASC', |
| 118 | 'form_id' => $this->form_id, |
| 119 | ) |
| 120 | ); |
| 121 | |
| 122 | // Get the entries. |
| 123 | $entries = array_map( 'evf_get_entry', $entry_ids ); |
| 124 | $checked_entry_id = isset( $_REQUEST['entry'] ) ? wp_parse_id_list( wp_unslash( $_REQUEST['entry'] ) ) : array(); // phpcs:ignore WordPress.Security.NonceVerification |
| 125 | foreach ( $entries as $entry ) { |
| 126 | |
| 127 | if ( ! empty( $checked_entry_id ) && ! in_array( absint( $entry->entry_id ), $checked_entry_id, true ) ) { |
| 128 | continue; |
| 129 | } |
| 130 | |
| 131 | $this->row_data[] = $this->generate_row_data( $entry ); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | return $this->row_data; |
| 136 | } |
| 137 | |
| 138 | /** |
| 139 | * Prepare and get quiz report data in CSV format. |
| 140 | */ |
| 141 | public function get_quiz_report() { |
| 142 | $form_data = EVF()->form->get( |
| 143 | absint( $this->form_id ), |
| 144 | array( |
| 145 | 'content_only' => true, |
| 146 | ) |
| 147 | ); |
| 148 | $form_fields = isset( $form_data['form_fields'] ) ? $form_data['form_fields'] : array(); |
| 149 | $entry = evf_get_entry( $this->entry_id ); |
| 150 | $columns = array( 'ID' ); |
| 151 | $row = array( $this->entry_id ); |
| 152 | $total_score = 0; |
| 153 | $respondent_score = 0; |
| 154 | $obtained_score = 0; |
| 155 | $include_all_fields = apply_filters( 'evf_include_all_fields_in_quiz_report_csv', false ); |
| 156 | |
| 157 | // Add form fields in the CSV content. |
| 158 | foreach ( $form_fields as $field_id => $field ) { |
| 159 | $quiz_enabled = isset( $field['quiz_status'] ) && '1' === $field['quiz_status'] ? true : false; |
| 160 | |
| 161 | // Move onto next field if this field has quiz disabled and non-quiz fields are to be excluded. |
| 162 | if ( false === $include_all_fields && false === $quiz_enabled ) { |
| 163 | continue; |
| 164 | } |
| 165 | |
| 166 | $meta_key = isset( $field['meta-key'] ) ? $field['meta-key'] : ''; |
| 167 | $given_answer = isset( $entry->meta[ $meta_key ] ) ? $entry->meta[ $meta_key ] : null; |
| 168 | $correct_answer = isset( $field['correct_answer'] ) ? $field['correct_answer'] : array(); |
| 169 | $field_score = empty( $field['score'] ) ? 0 : $field['score']; |
| 170 | $score = 0; |
| 171 | $total_score += $field_score; |
| 172 | $is_correct = false; |
| 173 | |
| 174 | if ( ! is_null( $given_answer ) ) { |
| 175 | $respondent_score += $field_score; |
| 176 | |
| 177 | if ( ! empty( $correct_answer ) ) { |
| 178 | // Determine if the given answer is correct. |
| 179 | if ( 'select' === $field['type'] ) { |
| 180 | foreach ( $correct_answer as $answer_key => $answer_status ) { |
| 181 | $choice = $field['choices'][ $answer_key ]['label']; |
| 182 | |
| 183 | if ( $given_answer === $choice ) { |
| 184 | $is_correct = true; |
| 185 | break; |
| 186 | } |
| 187 | } |
| 188 | } elseif ( 'radio' === $field['type'] ) { |
| 189 | $correct_answer_key = array_keys( $correct_answer )[0]; |
| 190 | $correct_answer = $field['choices'][ $correct_answer_key ]['label']; |
| 191 | $given_answer = maybe_unserialize( $given_answer )['label']; |
| 192 | $is_correct = ( $given_answer === $correct_answer ); |
| 193 | } elseif ( 'checkbox' === $field['type'] ) { |
| 194 | $given_answer_data = maybe_unserialize( $given_answer )['label']; |
| 195 | $is_correct = true; |
| 196 | $choices = $field['choices']; |
| 197 | $correct_answer_keys = array_keys( $correct_answer ); |
| 198 | $correct_answers = array(); |
| 199 | $given_answers = array(); |
| 200 | |
| 201 | // Prepare list of correct answers. |
| 202 | foreach ( $correct_answer_keys as $correct_answer_key ) { |
| 203 | $correct_answers[] = $choices[ $correct_answer_key ]['label']; |
| 204 | } |
| 205 | |
| 206 | // Prepare list of given answers. |
| 207 | foreach ( $given_answer_data as $given_answer ) { |
| 208 | $given_answers[] = $given_answer; |
| 209 | } |
| 210 | |
| 211 | // See if all the given answers are correct answers. |
| 212 | foreach ( $given_answers as $given_answer ) { |
| 213 | if ( ! in_array( $given_answer, $correct_answers, true ) ) { |
| 214 | $is_correct = false; |
| 215 | break; |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | // Add score if the given answer is correct. |
| 223 | if ( true === $is_correct ) { |
| 224 | $score = $field_score; |
| 225 | $obtained_score += $field_score; |
| 226 | } |
| 227 | |
| 228 | $columns[] = $this->sanitize_csv_cell_data( $field['label'] ); |
| 229 | $row[] = $this->sanitize_csv_cell_data( $score ); |
| 230 | } |
| 231 | |
| 232 | // Add extra columns. |
| 233 | $extra_data = array( |
| 234 | 'Total Score' => $total_score, |
| 235 | 'Respondent Score' => $respondent_score, |
| 236 | 'Obtained Score' => $obtained_score, |
| 237 | ); |
| 238 | foreach ( $extra_data as $key => $value ) { |
| 239 | $columns[] = $this->sanitize_csv_cell_data( $key ); |
| 240 | $row[] = $this->sanitize_csv_cell_data( $value ); |
| 241 | } |
| 242 | |
| 243 | ob_start(); |
| 244 | echo esc_html( implode( ', ', $columns ) ); |
| 245 | echo "\n"; |
| 246 | echo esc_html( implode( ', ', $row ) ); |
| 247 | |
| 248 | return ob_get_clean(); |
| 249 | } |
| 250 | |
| 251 | /** |
| 252 | * Sanitize data for a cell in CSV. |
| 253 | * |
| 254 | * @param string $str Data to sanitize. |
| 255 | */ |
| 256 | public function sanitize_csv_cell_data( $str ) { |
| 257 | $str = (string) $str; |
| 258 | $str = str_replace( '"', '""', $str ); |
| 259 | $str = '"' . $str . '"'; |
| 260 | return $str; |
| 261 | } |
| 262 | |
| 263 | /** |
| 264 | * Take a entry id and generate row data from it for export. |
| 265 | * |
| 266 | * @param object $entry Entry object. |
| 267 | * @return array |
| 268 | */ |
| 269 | protected function generate_row_data( $entry ) { |
| 270 | |
| 271 | $columns = $this->get_column_names(); |
| 272 | $row = array(); |
| 273 | $fields = json_decode( $entry->fields, true ); |
| 274 | foreach ( $columns as $column_id => $column_name ) { |
| 275 | $column_id = strstr( $column_id, ':' ) ? current( explode( ':', $column_id ) ) : $column_id; |
| 276 | $value = ''; |
| 277 | $raw_value = ''; |
| 278 | |
| 279 | if ( isset( $fields[ $column_id ] ) ) { |
| 280 | // Filter for entry meta data. |
| 281 | $field_type = isset( $fields[ $column_id ]['type'] ) ? $fields[ $column_id ]['type'] : ''; |
| 282 | |
| 283 | switch ( $field_type ) { |
| 284 | case 'checkbox': |
| 285 | case 'payment-checkbox': |
| 286 | $value = isset( $fields[ $column_id ]['value']['label'] ) ? $fields[ $column_id ]['value']['label'] : ''; |
| 287 | if ( is_array( $value ) ) { |
| 288 | $value = implode( ',', $value ); |
| 289 | } else { |
| 290 | $value = $value; |
| 291 | } |
| 292 | break; |
| 293 | case 'radio': |
| 294 | case 'payment-multiple': |
| 295 | $value = $fields[ $column_id ]['value']['label']; |
| 296 | break; |
| 297 | case 'select': |
| 298 | $value = $fields[ $column_id ]['value']; |
| 299 | if ( is_array( $value ) ) { |
| 300 | $value = implode( ',', $value ); |
| 301 | } else { |
| 302 | $value = $value; |
| 303 | } |
| 304 | |
| 305 | break; |
| 306 | case 'rating': |
| 307 | $value = ! empty( $fields[ $column_id ]['value']['value'] ) ? $fields[ $column_id ]['value']['value'] : 0; |
| 308 | $number_of_stars = ! empty( $fields[ $column_id ]['number_of_rating'] ) ? $fields[ $column_id ]['number_of_rating'] : 5; |
| 309 | $value = $value . '/' . $number_of_stars; |
| 310 | break; |
| 311 | case 'country': |
| 312 | $value = apply_filters( 'everest_forms_plaintext_field_value', $fields[ $column_id ]['value']['country_code'], $fields[ $column_id ]['value'], $entry, 'email-plain' ); |
| 313 | break; |
| 314 | case 'repeater-fields': |
| 315 | $labels = array(); |
| 316 | $repeater_fields = array(); |
| 317 | |
| 318 | foreach ( $fields[ $column_id ]['value_raw'] as $field_value ) { |
| 319 | foreach ( $field_value as $key => $val ) { |
| 320 | $repeater_field_value = ''; |
| 321 | if ( isset( $repeater_fields[ $val['id'] ] ) ) { |
| 322 | $repeater_field_type = isset( $repeater_fields[ $val['id'] ]['type'] ) ? $repeater_fields[ $val['id'] ]['type'] : ''; |
| 323 | switch ( $repeater_field_type ) { |
| 324 | case 'checkbox': |
| 325 | case 'payment-checkbox': |
| 326 | $value = $val['value']['label']; |
| 327 | $value = implode( ', ', $value ); |
| 328 | $repeater_field_value = implode( ', ', $repeater_fields[ $val['id'] ]['value']['label'] ); |
| 329 | $repeater_field_value .= ', ' . $value; |
| 330 | break; |
| 331 | case 'radio': |
| 332 | case 'payment-multiple': |
| 333 | $repeater_field_value = $repeater_fields[ $val['id'] ]['value']['label']; |
| 334 | $repeater_field_value .= ', ' . $val['value']['label']; |
| 335 | break; |
| 336 | case 'select': |
| 337 | $value = $val['value']; |
| 338 | if ( is_array( $value ) ) { |
| 339 | $value = implode( ',', $value ); |
| 340 | } else { |
| 341 | $value = $value; |
| 342 | } |
| 343 | $repeater_field_value = implode( ', ', $repeater_fields[ $val['id'] ]['value'] ); |
| 344 | $repeater_field_value .= ', ' . $value; |
| 345 | break; |
| 346 | default: |
| 347 | $repeater_field_value = $repeater_fields[ $val['id'] ]['value']; |
| 348 | $repeater_field_value .= ', ' . $val['value']; |
| 349 | break; |
| 350 | } |
| 351 | } else { |
| 352 | $repeater_fields[ $val['id'] ] = $val; |
| 353 | } |
| 354 | $fields[ $key ]['value'] = $repeater_field_value; |
| 355 | $labels [] = isset( $val['name'] ) ? $val['name'] : $val['value']['name']; |
| 356 | |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | $labels = array_unique( $labels ); |
| 361 | $value = ''; |
| 362 | |
| 363 | foreach ( $labels as $val ) { |
| 364 | if ( end( $labels ) === $val ) { |
| 365 | $value .= $val; |
| 366 | } else { |
| 367 | $value .= $val . ' ,'; |
| 368 | } |
| 369 | } |
| 370 | break; |
| 371 | default: |
| 372 | $value = apply_filters( 'everest_forms_html_field_value', $fields[ $column_id ]['value'], $fields[ $column_id ], $entry, 'export-csv' ); |
| 373 | break; |
| 374 | } |
| 375 | } elseif ( is_callable( array( $this, "get_column_value_{$column_id}" ) ) ) { |
| 376 | // Handle special columns which don't map 1:1 to entry data. |
| 377 | $value = $this->{"get_column_value_{$column_id}"}( $entry ); |
| 378 | $raw_value = $value; |
| 379 | } |
| 380 | $column_type = $this->get_entry_type( $column_id, $entry ); |
| 381 | $row[ $column_id ] = apply_filters( 'everest_forms_format_csv_field_data', preg_match( '/textarea/', $column_type ) ? sanitize_textarea_field( $value ) : sanitize_text_field( $value ), $raw_value, $column_id, $column_name, $columns, $entry ); |
| 382 | $row['status'] = ( |
| 383 | isset( $entry->meta['status'] ) && ! empty( $entry->meta['status'] ) |
| 384 | ? $entry->meta['status'] |
| 385 | : ( isset( $row['status'] ) ? $row['status'] : '' ) |
| 386 | ); |
| 387 | } |
| 388 | |
| 389 | return apply_filters( 'everest_forms_entry_export_row_data', $row, $entry, $this->request_data ); |
| 390 | } |
| 391 | |
| 392 | /** |
| 393 | * Get entry type. |
| 394 | * |
| 395 | * @param string $column_id meta key of the column. |
| 396 | * @param object $entry Entry being exported. |
| 397 | * @return string |
| 398 | */ |
| 399 | protected function get_entry_type( $column_id, $entry ) { |
| 400 | $fields = json_decode( $entry->fields, 1 ); |
| 401 | if ( is_null( $fields ) || ! is_array( $fields ) ) { |
| 402 | return false; // Conditional false with fake values. |
| 403 | } |
| 404 | foreach ( $fields as $field ) { |
| 405 | if ( $column_id === $field['id'] ) { |
| 406 | return $field['type']; |
| 407 | } |
| 408 | } |
| 409 | return false; |
| 410 | } |
| 411 | |
| 412 | /** |
| 413 | * Get entry id value. |
| 414 | * |
| 415 | * @param object $entry Entry being exported. |
| 416 | * @return int |
| 417 | */ |
| 418 | protected function get_column_value_entry_id( $entry ) { |
| 419 | return absint( $entry->entry_id ); |
| 420 | } |
| 421 | |
| 422 | /** |
| 423 | * Get entry status value. |
| 424 | * |
| 425 | * @param object $entry Entry being exported. |
| 426 | * @return string |
| 427 | */ |
| 428 | protected function get_column_value_status( $entry ) { |
| 429 | $statuses = evf_get_entry_statuses(); |
| 430 | |
| 431 | if ( isset( $statuses[ $entry->status ] ) ) { |
| 432 | return $statuses[ $entry->status ]; |
| 433 | } |
| 434 | |
| 435 | return $entry->status; |
| 436 | } |
| 437 | |
| 438 | /** |
| 439 | * Get date created value. |
| 440 | * |
| 441 | * @param object $entry Entry being exported. |
| 442 | * @return string |
| 443 | */ |
| 444 | protected function get_column_value_date_created( $entry ) { |
| 445 | $timestamp = false; |
| 446 | |
| 447 | if ( isset( $entry->date_created ) ) { |
| 448 | $timestamp = strtotime( $entry->date_created ); |
| 449 | } |
| 450 | |
| 451 | /* translators: 1: entry date 2: entry time */ |
| 452 | return sprintf( esc_html__( '%1$s %2$s', 'everest-forms' ), date_i18n( evf_date_format(), $timestamp ), date_i18n( evf_time_format(), $timestamp ) ); |
| 453 | } |
| 454 | |
| 455 | /** |
| 456 | * Get GMT date created value. |
| 457 | * |
| 458 | * @param object $entry Entry being exported. |
| 459 | * @return string |
| 460 | */ |
| 461 | protected function get_column_value_date_created_gmt( $entry ) { |
| 462 | $timestamp = false; |
| 463 | |
| 464 | if ( isset( $entry->date_created ) ) { |
| 465 | $timestamp = strtotime( $entry->date_created ) + ( get_option( 'gmt_offset' ) * 3600 ); |
| 466 | } |
| 467 | |
| 468 | /* translators: 1: entry date 2: entry time */ |
| 469 | return sprintf( esc_html__( '%1$s %2$s', 'everest-forms' ), date_i18n( evf_date_format(), $timestamp ), date_i18n( evf_time_format(), $timestamp ) ); |
| 470 | } |
| 471 | |
| 472 | /** |
| 473 | * Get entry user device value. |
| 474 | * |
| 475 | * @param object $entry Entry being exported. |
| 476 | * @return string |
| 477 | */ |
| 478 | protected function get_column_value_user_device( $entry ) { |
| 479 | return sanitize_text_field( $entry->user_device ); |
| 480 | } |
| 481 | |
| 482 | /** |
| 483 | * Get entry user IP address value. |
| 484 | * |
| 485 | * @param object $entry Entry being exported. |
| 486 | * @return string |
| 487 | */ |
| 488 | protected function get_column_value_user_ip_address( $entry ) { |
| 489 | return sanitize_text_field( $entry->user_ip_address ); |
| 490 | } |
| 491 | } |
| 492 |