| @@ -155,8 +155,13 @@ | ||
| 155 | 155 | 'label' => __( 'Donor Comment', 'suredonation' ), |
| 156 | 156 | 'aliases' => [ 'donor comment', 'comment', 'donor_comment' ], |
| 157 | 157 | 'required' => false, |
| 158 | 158 | ], |
| 159 | + 'donor_comment_status' => [ | |
| 160 | + 'label' => __( 'Comment Status', 'suredonation' ), | |
| 161 | + 'aliases' => [ 'comment status', 'donor comment status', 'donor_comment_status' ], | |
| 162 | + 'required' => false, | |
| 163 | + ], | |
| 159 | 164 | 'ip_address' => [ |
| 160 | 165 | 'label' => __( 'IP Address', 'suredonation' ), |
| 161 | 166 | 'aliases' => [ 'ip address', 'ip', 'ip_address' ], |
| 162 | 167 | 'required' => false, |
| @@ -434,8 +439,9 @@ | ||
| 434 | 439 | __( 'Subscription Status', 'suredonation' ), |
| 435 | 440 | __( 'Parent Subscription ID', 'suredonation' ), |
| 436 | 441 | __( 'Anonymous', 'suredonation' ), |
| 437 | 442 | __( 'Donor Comment', 'suredonation' ), |
| 443 | + __( 'Comment Status', 'suredonation' ), | |
| 438 | 444 | __( 'IP Address', 'suredonation' ), |
| 439 | 445 | __( 'Date', 'suredonation' ), |
| 440 | 446 | __( 'Import Source', 'suredonation' ), |
| 441 | 447 | __( 'Import Source ID', 'suredonation' ), |
| @@ -442,12 +448,43 @@ | ||
| 442 | 448 | ]; |
| 443 | 449 | } |
| 444 | 450 | |
| 445 | 451 | /** |
| 452 | + * The trailing export column a donation's custom fields spill into once | |
| 453 | + * they exceed the exporter's per-export column cap. | |
| 454 | + * | |
| 455 | + * Single source of truth shared with Import_Export_Api::export_donations(), | |
| 456 | + * which writes this same label as a plain JSON object of label => value. | |
| 457 | + * Kept separate from standard_donation_export_labels() (which the importer | |
| 458 | + * treats as columns to skip entirely) because this one instead needs its | |
| 459 | + * own value decoded and expanded — see extract_custom_fields(). | |
| 460 | + * | |
| 461 | + * Deliberately untranslated: this same string is written on export and | |
| 462 | + * matched on import (extract_custom_fields()), so an export made under one | |
| 463 | + * site locale would fail to match on import under another, silently | |
| 464 | + * storing the JSON blob as one literal field instead of expanding it — | |
| 465 | + * the exact corruption this column exists to prevent. Interchange data, | |
| 466 | + * not display copy, same reasoning as the anonymous yes/no export tokens. | |
| 467 | + * | |
| 468 | + * @return string Column label. | |
| 469 | + * @since 1.6.1 | |
| 470 | + */ | |
| 471 | + public static function other_fields_export_label() { | |
| 472 | + return 'Other Fields'; | |
| 473 | + } | |
| 474 | + | |
| 475 | + /** | |
| 446 | 476 | * Pull submitted custom form-field values out of a donation row: any column |
| 447 | 477 | * whose header is not a standard export column is treated as a custom field |
| 448 | 478 | * (these are the per-form fields flattened into trailing export columns). |
| 449 | 479 | * |
| 480 | + * The "Other Fields" column is the one exception: it is itself a JSON object | |
| 481 | + * of label => value for whatever didn't fit in the export's own column cap, | |
| 482 | + * so its cell is decoded and expanded into individual fields rather than | |
| 483 | + * stored verbatim as one field literally named "Other Fields" — otherwise a | |
| 484 | + * re-import of a capped export would silently corrupt donation_data with a | |
| 485 | + * single bogus field holding a raw JSON blob. | |
| 486 | + * | |
| 450 | 487 | * @param array<int, string> $headers CSV header cells. |
| 451 | 488 | * @param array<int, string> $row Raw row cells (column order). |
| 452 | 489 | * @return array<string, array{label: string, value: string}> Custom fields keyed by slug. |
| 453 | 490 | * @since 1.3.0 |
| @@ -459,8 +496,10 @@ | ||
| 459 | 496 | }, |
| 460 | 497 | self::standard_donation_export_labels() |
| 461 | 498 | ); |
| 462 | 499 | |
| 500 | + $other_fields_label = strtolower( trim( self::other_fields_export_label() ) ); | |
| 501 | + | |
| 463 | 502 | $fields = []; |
| 464 | 503 | foreach ( $headers as $index => $header ) { |
| 465 | 504 | $label = trim( (string) $header ); |
| 466 | 505 | if ( '' === $label || in_array( strtolower( $label ), $standard, true ) ) { |
| @@ -465,12 +504,35 @@ | ||
| 465 | 504 | $label = trim( (string) $header ); |
| 466 | 505 | if ( '' === $label || in_array( strtolower( $label ), $standard, true ) ) { |
| 467 | 506 | continue; |
| 468 | 507 | } |
| 508 | + | |
| 469 | 509 | $value = isset( $row[ $index ] ) ? (string) $row[ $index ] : ''; |
| 470 | 510 | if ( '' === $value ) { |
| 471 | 511 | continue; |
| 472 | 512 | } |
| 513 | + | |
| 514 | + if ( strtolower( $label ) === $other_fields_label ) { | |
| 515 | + $overflow = json_decode( $value, true ); | |
| 516 | + if ( is_array( $overflow ) ) { | |
| 517 | + foreach ( $overflow as $overflow_label => $overflow_value ) { | |
| 518 | + $overflow_label = trim( (string) $overflow_label ); | |
| 519 | + if ( '' === $overflow_label || ! is_scalar( $overflow_value ) ) { | |
| 520 | + continue; | |
| 521 | + } | |
| 522 | + $overflow_key = sanitize_key( $overflow_label ); | |
| 523 | + if ( '' === $overflow_key ) { | |
| 524 | + continue; | |
| 525 | + } | |
| 526 | + $fields[ $overflow_key ] = [ | |
| 527 | + 'label' => sanitize_text_field( $overflow_label ), | |
| 528 | + 'value' => sanitize_text_field( (string) $overflow_value ), | |
| 529 | + ]; | |
| 530 | + } | |
| 531 | + } | |
| 532 | + continue; | |
| 533 | + } | |
| 534 | + | |
| 473 | 535 | $key = sanitize_key( $label ); |
| 474 | 536 | if ( '' === $key ) { |
| 475 | 537 | $key = 'field_' . (int) $index; |
| 476 | 538 | } |