| @@ -448,12 +448,43 @@ | ||
| 448 | 448 | ]; |
| 449 | 449 | } |
| 450 | 450 | |
| 451 | 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 | + /** | |
| 452 | 476 | * Pull submitted custom form-field values out of a donation row: any column |
| 453 | 477 | * whose header is not a standard export column is treated as a custom field |
| 454 | 478 | * (these are the per-form fields flattened into trailing export columns). |
| 455 | 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 | + * | |
| 456 | 487 | * @param array<int, string> $headers CSV header cells. |
| 457 | 488 | * @param array<int, string> $row Raw row cells (column order). |
| 458 | 489 | * @return array<string, array{label: string, value: string}> Custom fields keyed by slug. |
| 459 | 490 | * @since 1.3.0 |
| @@ -465,8 +496,10 @@ | ||
| 465 | 496 | }, |
| 466 | 497 | self::standard_donation_export_labels() |
| 467 | 498 | ); |
| 468 | 499 | |
| 500 | + $other_fields_label = strtolower( trim( self::other_fields_export_label() ) ); | |
| 501 | + | |
| 469 | 502 | $fields = []; |
| 470 | 503 | foreach ( $headers as $index => $header ) { |
| 471 | 504 | $label = trim( (string) $header ); |
| 472 | 505 | if ( '' === $label || in_array( strtolower( $label ), $standard, true ) ) { |
| @@ -471,12 +504,35 @@ | ||
| 471 | 504 | $label = trim( (string) $header ); |
| 472 | 505 | if ( '' === $label || in_array( strtolower( $label ), $standard, true ) ) { |
| 473 | 506 | continue; |
| 474 | 507 | } |
| 508 | + | |
| 475 | 509 | $value = isset( $row[ $index ] ) ? (string) $row[ $index ] : ''; |
| 476 | 510 | if ( '' === $value ) { |
| 477 | 511 | continue; |
| 478 | 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 | + | |
| 479 | 535 | $key = sanitize_key( $label ); |
| 480 | 536 | if ( '' === $key ) { |
| 481 | 537 | $key = 'field_' . (int) $index; |
| 482 | 538 | } |