diff --git a/dt-posts/dt-posts-endpoints.php b/dt-posts/dt-posts-endpoints.php index 3170de5048..91f3b29a9a 100644 --- a/dt-posts/dt-posts-endpoints.php +++ b/dt-posts/dt-posts-endpoints.php @@ -590,8 +590,10 @@ public function create_post( WP_REST_Request $request ) { $get_params = $request->get_query_params(); $silent = isset( $get_params['silent'] ) && $get_params['silent'] === 'true'; $check_dups = ! empty( $get_params['check_for_duplicates'] ) ? explode( ',', $get_params['check_for_duplicates'] ) : []; + $overwrite_existing_fields = !empty( $get_params['overwrite_existing_fields'] ); $post = DT_Posts::create_post( $url_params['post_type'], $fields, $silent, true, [ - 'check_for_duplicates' => $check_dups + 'check_for_duplicates' => $check_dups, + 'do_not_overwrite_existing_fields' => !$overwrite_existing_fields ] ); return $post; } @@ -607,7 +609,10 @@ public function update_post( WP_REST_Request $request ){ $url_params = $request->get_url_params(); $get_params = $request->get_query_params(); $silent = isset( $get_params['silent'] ) && $get_params['silent'] === 'true'; - return DT_Posts::update_post( $url_params['post_type'], $url_params['id'], $fields, $silent ); + $overwrite_existing_fields = !empty( $get_params['overwrite_existing_fields'] ); + return DT_Posts::update_post( $url_params['post_type'], $url_params['id'], $fields, $silent, true, [ + 'do_not_overwrite_existing_fields' => !$overwrite_existing_fields + ] ); } public function delete_post( WP_REST_Request $request ){ diff --git a/dt-posts/dt-posts.php b/dt-posts/dt-posts.php index 6cc9ab19a8..1d15409c50 100644 --- a/dt-posts/dt-posts.php +++ b/dt-posts/dt-posts.php @@ -84,15 +84,16 @@ public static function create_post( string $post_type, array $fields, bool $sile if ( isset( $args['check_for_duplicates'] ) && is_array( $args['check_for_duplicates'] ) && ! empty( $args['check_for_duplicates'] ) ) { $duplicate_post_ids = apply_filters( 'dt_create_check_for_duplicate_posts', [], $post_type, $fields, $args['check_for_duplicates'], $check_permissions ); if ( ! empty( $duplicate_post_ids ) && count( $duplicate_post_ids ) > 0 ) { + $duplicate_post_id = $duplicate_post_ids[0]; $name = $fields['name'] ?? $fields['title']; $fields['notes'] = isset( $fields['notes'] ) ? $fields['notes'] : []; //No need to update title or name. - unset( $fields['title'], $fields['name'] ); + unset( $fields['title'], $fields['name'], $fields['ID'] ); //update most recently created matched post. - $updated_post = self::update_post( $post_type, $duplicate_post_ids[0], $fields, $silent, false ); + $updated_post = self::update_post( $post_type, $duplicate_post_id, $fields, $silent, false, $args ); if ( is_wp_error( $updated_post ) ){ return $updated_post; } @@ -391,7 +392,7 @@ public static function create_post( string $post_type, array $fields, bool $sile * * @return array|WP_Error */ - public static function update_post( string $post_type, int $post_id, array $fields, bool $silent = false, bool $check_permissions = true ){ + public static function update_post( string $post_type, int $post_id, array $fields, bool $silent = false, bool $check_permissions = true, $args = [] ){ $post_types = self::get_post_types(); if ( !in_array( $post_type, $post_types ) ){ return new WP_Error( __FUNCTION__, 'Post type does not exist', [ 'status' => 403 ] ); @@ -415,6 +416,15 @@ public static function update_post( string $post_type, int $post_id, array $fiel return new WP_Error( __FUNCTION__, 'post does not exist', [ 'status' => 404 ] ); } + /** + * If field overwrite has been disabled for existing fields, then ensure + * to have them removed from importing fields. + */ + + if ( isset( $args['do_not_overwrite_existing_fields'] ) && $args['do_not_overwrite_existing_fields'] ) { + $fields = self::dt_ignore_duplicated_post_fields( $fields, $post_type, $post_id ); + } + $existing_post = self::get_post( $post_type, $post_id, false, false ); //get extra fields and defaults $fields = apply_filters( 'dt_post_update_fields', $fields, $post_type, $post_id, $existing_post ); diff --git a/dt-posts/posts.php b/dt-posts/posts.php index 389ce1192c..3e4350558d 100644 --- a/dt-posts/posts.php +++ b/dt-posts/posts.php @@ -3145,6 +3145,125 @@ public static function geolocate_addresses( $post_id, $post_type, $field_key, $a return false; } + + + /** + * Remove duplicated field values from specified post record. + * + * @param $fields + * @param $post_type + * @param $post_id + * @return array + */ + public static function dt_ignore_duplicated_post_fields( $fields, $post_type, $post_id ) { + $updated_fields = []; + $existing_fields = DT_Posts::get_post( $post_type, $post_id, true, false ); + if ( empty( $existing_fields ) || is_wp_error( $existing_fields ) ){ + return $fields; + } + $field_settings = DT_Posts::get_post_field_settings( $post_type ); + foreach ( $fields as $field_key => $field_value ) { + if ( !isset( $existing_fields[ $field_key ], $field_settings[ $field_key ]['type'] ) ){ + $updated_fields[ $field_key ] = $field_value; + continue; + } + + $field_type = $field_settings[ $field_key ]['type']; + switch ( $field_type ) { + case 'text': + case 'textarea': + case 'number': + case 'boolean': + if ( empty( $existing_fields[ $field_key ] ) ) { + $updated_fields[ $field_key ] = $field_value; + } + break; + case 'date': + case 'key_select': + $key = 'key'; + if ( $field_type == 'date' ) { + $key = 'formatted'; + } + if ( !( isset( $existing_fields[ $field_key ][ $key ] ) && $existing_fields[ $field_key ][ $key ] == $field_value ) ) { + $updated_fields[ $field_key ] = $field_value; + } + break; + case 'tags': + case 'multi_select': + $values = []; + foreach ( $field_value['values'] ?? [] as $value ) { + if ( !( isset( $value['value'] ) && in_array( $value['value'], $existing_fields[ $field_key ] ) ) ) { + $values[] = $value; + } + } + if ( !empty( $values ) ) { + $updated_fields[ $field_key ] = [ + 'values' => $values, + ]; + } + break; + case 'location': + case 'location_meta': + $values = []; + foreach ( $field_value['values'] ?? [] as $value ) { + $key = 'label'; + $found = array_filter( $existing_fields[ $field_key ], function ( $option ) use ( $value, $key ) { + $hit = isset( $option[$key], $value[$key] ) && $option[$key] == $value[$key]; + + if ( !$hit && isset( $option['matched_search'], $value[$key] ) && $option['matched_search'] == $value[$key] ) { + $hit = true; + } + + return $hit; + } ); + if ( empty( $found ) || count( $found ) == 0 ) { + $values[] = $value; + } + } + if ( !empty( $values ) ) { + $updated_fields[ $field_key ] = [ + 'values' => $values, + ]; + } + break; + case 'communication_channel': + $values = []; + $existing_field_values = $existing_fields[ $field_key ]; + + foreach ( $field_value ?? [] as $value ) { + $key = 'value'; + $found = array_values( array_filter( $value, function ( $option ) use ( $existing_field_values, $key ) { + + $hit = false; + foreach ( $existing_field_values as $existing_field_value ) { + if ( !$hit && isset( $option ) && $option != $existing_field_value[$key] ) { + $hit = true; + } + } + return $hit; + } ) ); + if ( !empty( $found ) ) { + foreach ( $found as $found_value ) { + $values[] = [ + 'value' => $found_value['value'] + ]; + } + } + } + if ( !empty( $values ) ) { + $updated_fields[ $field_key ] = [ + 'values' => $values, + ]; + } + break; + default: + $updated_fields[ $field_key ] = $field_value; + break; + } + } + + return $updated_fields; + } } /** diff --git a/tests/dt-posts/dt-posts/unit-test-create-post.php b/tests/dt-posts/dt-posts/unit-test-create-post.php index a137209a63..47373dd328 100755 --- a/tests/dt-posts/dt-posts/unit-test-create-post.php +++ b/tests/dt-posts/dt-posts/unit-test-create-post.php @@ -17,6 +17,7 @@ class DT_Posts_DT_Posts_Create_Post extends WP_UnitTestCase { 'contact_phone' => [ 'values' => [ [ 'value' => '798456780' ] ] ], 'contact_email' => [ 'values' => [ [ 'value' => 'bob@example.com' ] ] ], 'tags' => [ 'values' => [ [ 'value' => 'tag1' ] ] ], + 'quick_button_contact_established' => '1' ]; public $sample_group = [ @@ -170,4 +171,342 @@ public function test_custom_number_field_min_max_error() { $contact4 = DT_Posts::create_post( 'contacts', [ 'name' => 'one', 'number_test' => '' ], true, false ); $this->assertNotWPError( $contact4 ); } + + /** + * @testdox do_not_overwrite_existing_fields: create with duplicate detection + */ + public function test_do_not_overwrite_existing_fields_create() { + $base_user = get_option( 'dt_base_user' ); + + // Create initial contact + $initial_fields = $this->sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['name'] = 'John Doe'; + $initial_fields['contact_phone'] = [ 'values' => [ [ 'value' => '123-456-7890' ] ] ]; + $initial_fields['overall_status'] = 'active'; + $initial_fields['nickname'] = 'Johnny'; + + $initial_contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $initial_contact ); + + // Try to create duplicate with do_not_overwrite_existing_fields = true + $duplicate_fields = [ + 'assigned_to' => $base_user, + 'name' => 'John Doe', + 'contact_phone' => [ 'values' => [ [ 'value' => '123-456-7890' ] ] ], + 'overall_status' => 'paused', // Different value + 'nickname' => 'John', // Different value + 'contact_email' => [ 'values' => [ [ 'value' => 'john@example.com' ] ] ] // New field + ]; + + $result = DT_Posts::create_post('contacts', $duplicate_fields, true, false, [ + 'check_for_duplicates' => [ 'contact_phone' ], + 'do_not_overwrite_existing_fields' => true + ]); + + $this->assertNotWPError( $result ); + // Should still only have one contact_phone entry. + $this->assertSame( 1, count( $result['contact_phone'] ) ); + // Should update to new values, as they are different. + $this->assertSame( 'paused', $result['overall_status']['key'] ); + $this->assertSame( 'Johnny', $result['nickname'] ); + // Should add new fields. + $this->assertSame( 'john@example.com', $result['contact_email'][1]['value'] ); + // Extra assertion sanity checks. + $this->assertContains( 'tag1', $result['tags'] ); + $this->assertSame( 1, count( $result['location_grid'] ) ); + $this->assertSame( 2, count( $result['milestones'] ) ); + $this->assertSame( true, $result['requires_update'] ); + } + + /** + * @testdox do_not_overwrite_existing_fields: create with overwrite enabled + */ + public function test_overwrite_existing_fields_create() { + $base_user = get_option( 'dt_base_user' ); + + // Create initial contact + $initial_fields = $this->sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['name'] = 'John Doe'; + $initial_fields['contact_phone'] = [ 'values' => [ [ 'value' => '987-654-3210' ] ] ]; + $initial_fields['overall_status'] = 'active'; + $initial_fields['nickname'] = 'Janie'; + + $initial_contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $initial_contact ); + + // Try to create duplicate with do_not_overwrite_existing_fields = false + $duplicate_fields = [ + 'assigned_to' => $base_user, + 'name' => 'Jane Doe', + 'contact_phone' => [ 'values' => [ [ 'value' => '987-654-3210' ] ] ], + 'overall_status' => 'paused', // Different value + 'nickname' => 'Jane', // Different value + 'contact_email' => [ 'values' => [ [ 'value' => 'jane@example.com' ] ] ], // New field + 'milestones' => [ 'values' => [ [ 'value' => 'milestone_has_bible' ], [ 'value' => 'mature_christian' ] ] ] + ]; + + $result = DT_Posts::create_post('contacts', $duplicate_fields, true, false, [ + 'check_for_duplicates' => [ 'contact_phone' ], + 'do_not_overwrite_existing_fields' => false + ]); + + $this->assertNotWPError( $result ); + // Should update existing fields with new values + $this->assertSame( 'paused', $result['overall_status']['key'] ); + $this->assertSame( 'Jane', $result['nickname'] ); + // Should add new fields + $this->assertSame( 'jane@example.com', $result['contact_email'][1]['value'] ); + // Extra assertion sanity checks. + $this->assertSame( 1, $result['quick_button_contact_established'] ); + } + + /** + * @testdox do_not_overwrite_existing_fields: multi-select fields + */ + public function test_do_not_overwrite_multi_select_fields_create() { + $base_user = get_option( 'dt_base_user' ); + + // Create initial contact with tags + $initial_fields = $this->sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['name'] = 'Multi Test'; + $initial_fields['contact_phone'] = [ 'values' => [ [ 'value' => '555-0001' ] ] ]; + $initial_fields['tags'] = [ 'values' => [ [ 'value' => 'existing_tag' ] ] ]; + + $initial_contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $initial_contact ); + + // Try to create duplicate with additional tags + $duplicate_fields = [ + 'assigned_to' => $base_user, + 'name' => 'Multi Test', + 'contact_phone' => [ 'values' => [ [ 'value' => '555-0001' ] ] ], + 'tags' => [ 'values' => [ [ 'value' => 'existing_tag' ], [ 'value' => 'new_tag' ] ] ], + 'baptism_date' => '2025-01-01' + ]; + + $result = DT_Posts::create_post('contacts', $duplicate_fields, true, false, [ + 'check_for_duplicates' => [ 'contact_phone' ], + 'do_not_overwrite_existing_fields' => true + ]); + + $this->assertNotWPError( $result ); + // Should only add new tags, not duplicate existing ones + $this->assertContains( 'existing_tag', $result['tags'] ); + $this->assertContains( 'new_tag', $result['tags'] ); + $this->assertSame( 2, count( $result['tags'] ) ); + // Extra assertion sanity checks. + $this->assertSame( $duplicate_fields['baptism_date'], $result['baptism_date']['formatted'] ); + } + + public function test_do_not_overwrite_text_fields_create() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = $this->sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['contact_phone'] = [ 'values' => [ [ 'value' => '123-456-7890' ] ] ]; + $initial_fields['nickname'] = 'Johnny'; + + $initial_contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $initial_contact ); + + $duplicate_fields = [ + 'assigned_to' => $base_user, + 'name' => 'John Doe', + 'contact_phone' => [ 'values' => [ [ 'value' => '123-456-7890' ] ] ], + 'nickname' => 'John', // Different value + ]; + + $result = DT_Posts::create_post('contacts', $duplicate_fields, true, false, [ + 'check_for_duplicates' => [ 'contact_phone' ], + 'do_not_overwrite_existing_fields' => true + ]); + $this->assertNotWPError( $result ); + + $this->assertSame( 'Johnny', $result['nickname'] ); + } + + public function test_do_not_overwrite_number_fields_create() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = $this->sample_contact; + $initial_fields['assigned_to'] = $base_user; + + $initial_contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $initial_contact ); + + $duplicate_fields = [ + 'assigned_to' => $base_user, + 'title' => $initial_fields['title'], + 'contact_phone' => $initial_fields['contact_phone'], + 'quick_button_contact_established' => 1 + ]; + + $result = DT_Posts::create_post('contacts', $duplicate_fields, true, false, [ + 'check_for_duplicates' => [ 'contact_phone' ], + 'do_not_overwrite_existing_fields' => true + ]); + $this->assertNotWPError( $result ); + + $this->assertSame( $duplicate_fields['quick_button_contact_established'], $result['quick_button_contact_established'] ); + } + + public function test_do_not_overwrite_boolean_fields_create() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = $this->sample_contact; + $initial_fields['requires_update'] = true; + $initial_fields['assigned_to'] = $base_user; + + $initial_contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $initial_contact ); + + $duplicate_fields = [ + 'assigned_to' => $base_user, + 'title' => $initial_fields['title'], + 'contact_phone' => $initial_fields['contact_phone'], + 'requires_update' => false + ]; + + $result = DT_Posts::create_post('contacts', $duplicate_fields, true, false, [ + 'check_for_duplicates' => [ 'contact_phone' ], + 'do_not_overwrite_existing_fields' => false + ]); + $this->assertNotWPError( $result ); + + $this->assertSame( true, empty( $result['requires_update'] ) ); + } + + public function test_do_not_overwrite_date_fields_create() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = $this->sample_contact; + $initial_fields['assigned_to'] = $base_user; + + $initial_contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $initial_contact ); + + $duplicate_fields = [ + 'assigned_to' => $base_user, + 'name' => 'Frank', + 'contact_phone' => $initial_fields['contact_phone'], + 'baptism_date' => '2025-01-01' + ]; + + $result = DT_Posts::create_post('contacts', $duplicate_fields, true, false, [ + 'check_for_duplicates' => [ 'contact_phone' ], + 'do_not_overwrite_existing_fields' => true + ]); + $this->assertNotWPError( $result ); + + $this->assertSame( $duplicate_fields['baptism_date'], $result['baptism_date']['formatted'] ); + } + + public function test_do_not_overwrite_key_select_fields_create() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = $this->sample_contact; + $initial_fields['overall_status'] = 'active'; + $initial_fields['assigned_to'] = $base_user; + + $initial_contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $initial_contact ); + + $duplicate_fields = [ + 'assigned_to' => $base_user, + 'name' => 'John Doe', + 'contact_phone' => $initial_fields['contact_phone'], + 'overall_status' => 'paused', // Different value + ]; + + $result = DT_Posts::create_post('contacts', $duplicate_fields, true, false, [ + 'check_for_duplicates' => [ 'contact_phone' ], + 'do_not_overwrite_existing_fields' => true + ]); + $this->assertNotWPError( $result ); + + $this->assertSame( 'paused', $result['overall_status']['key'] ); + } + + public function test_do_not_overwrite_tags_fields_create() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = $this->sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['tags'] = [ 'values' => [ [ 'value' => 'existing_tag' ] ] ]; + + $initial_contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $initial_contact ); + + $duplicate_fields = [ + 'assigned_to' => $base_user, + 'name' => 'Tags Test', + 'contact_phone' => $initial_fields['contact_phone'], + 'tags' => [ 'values' => [ [ 'value' => 'existing_tag' ], [ 'value' => 'new_tag' ] ] ] + ]; + + $result = DT_Posts::create_post('contacts', $duplicate_fields, true, false, [ + 'check_for_duplicates' => [ 'contact_phone' ], + 'do_not_overwrite_existing_fields' => true + ]); + $this->assertNotWPError( $result ); + + $this->assertSame( 2, count( $result['tags'] ) ); + $this->assertContains( 'existing_tag', $result['tags'] ); + $this->assertContains( 'new_tag', $result['tags'] ); + } + + public function test_do_not_overwrite_location_fields_create() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = $this->sample_contact; + $initial_fields['assigned_to'] = $base_user; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + $duplicate_fields = [ + 'assigned_to' => $base_user, + 'title' => $initial_fields['title'], + 'contact_phone' => $initial_fields['contact_phone'], + 'location_grid' => [ 'values' => [ [ 'value' => '100089589' ] ] ] + ]; + + $result = DT_Posts::create_post('contacts', $duplicate_fields, true, false, [ + 'check_for_duplicates' => [ 'contact_phone' ], + 'do_not_overwrite_existing_fields' => false + ]); + $this->assertNotWPError( $result ); + + $this->assertSame( 1, count( $result['location_grid'] ) ); + } + + public function test_do_not_overwrite_comms_channel_fields_create() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = $this->sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['contact_phone'] = [ 'values' => [ [ 'value' => '123-456-7890' ] ] ]; + + $initial_contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $initial_contact ); + + $duplicate_fields = [ + 'assigned_to' => $base_user, + 'name' => 'John Doe', + 'contact_phone' => [ 'values' => [ [ 'value' => '123-456-7890' ] ] ], + 'contact_email' => [ 'values' => [ [ 'value' => 'john@example.com' ] ] ] + ]; + + $result = DT_Posts::create_post('contacts', $duplicate_fields, true, false, [ + 'check_for_duplicates' => [ 'contact_phone' ], + 'do_not_overwrite_existing_fields' => true + ]); + $this->assertNotWPError( $result ); + + $this->assertSame( 1, count( $result['contact_phone'] ) ); + $this->assertSame( 'john@example.com', $result['contact_email'][1]['value'] ); + } } diff --git a/tests/dt-posts/dt-posts/unit-test-update-post.php b/tests/dt-posts/dt-posts/unit-test-update-post.php index d8ebca789d..34d7bf3813 100755 --- a/tests/dt-posts/dt-posts/unit-test-update-post.php +++ b/tests/dt-posts/dt-posts/unit-test-update-post.php @@ -18,12 +18,14 @@ class DT_Posts_DT_Posts_Update_Post extends WP_UnitTestCase { 'contact_phone' => [ 'values' => [ [ 'value' => '798456780' ] ] ], 'contact_email' => [ 'values' => [ [ 'value' => 'bob@example.com' ] ] ], 'tags' => [ 'values' => [ [ 'value' => 'tag1' ], [ 'value' => 'tagToDelete' ] ] ], + 'quick_button_contact_established' => '1' ]; public static $contact = null; public static function setupBeforeClass(): void { //setup custom fields for each field type and custom tile. $user_id = wp_create_user( 'dispatcher1', 'test', 'test2@example.com' ); + update_option( 'dt_base_user', $user_id, false ); wp_set_current_user( $user_id ); $current_user = wp_get_current_user(); $current_user->set_role( 'dispatcher' ); @@ -192,4 +194,354 @@ public function test_dt_private_fields(){ $this->assertArrayNotHasKey( 'tags_test_private', $contact2 ); $this->assertArrayNotHasKey( 'number_test_private', $contact2 ); } + + /** + * @testdox do_not_overwrite_existing_fields: update with protection enabled + */ + public function test_do_not_overwrite_existing_fields_update() { + $base_user = get_option( 'dt_base_user' ); + + // Create a contact with initial values + $initial_fields = self::$sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['name'] = 'Update Test'; + $initial_fields['nickname'] = 'Original Nick'; + $initial_fields['overall_status'] = 'active'; + $initial_fields['contact_phone'] = [ 'values' => [ [ 'value' => '111-222-3333' ] ] ]; + $initial_fields['tags'] = [ 'values' => [ [ 'value' => 'updating_tag' ] ] ]; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + // Update with do_not_overwrite_existing_fields = true + $update_fields = [ + 'assigned_to' => $base_user, + 'overall_status' => 'paused', // Try to change existing field + 'nickname' => 'New Nick', // Try to change existing field + 'contact_email' => [ 'values' => [ [ 'value' => 'test@example.com' ] ] ], // Add new field + 'tags' => [ 'values' => [ [ 'value' => 'updated_tag' ] ] ] + ]; + + $result = DT_Posts::update_post('contacts', $contact['ID'], $update_fields, true, false, [ + 'do_not_overwrite_existing_fields' => true + ]); + + $this->assertNotWPError( $result ); + // Should still only have one contact_phone entry. + $this->assertSame( 1, count( $result['contact_phone'] ) ); + // Should update to new values, as they are different. + $this->assertSame( 'paused', $result['overall_status']['key'] ); + $this->assertSame( 'Original Nick', $result['nickname'] ); + // Should add new fields + $this->assertSame( 'test@example.com', $result['contact_email'][1]['value'] ); + // Extra assertion sanity checks. + $this->assertSame( 2, count( $result['tags'] ) ); + $this->assertSame( $initial_fields['baptism_date'], $result['baptism_date']['formatted'] ); + } + + /** + * @testdox do_not_overwrite_existing_fields: update with protection disabled + */ + public function test_overwrite_existing_fields_update() { + $base_user = get_option( 'dt_base_user' ); + + // Create a contact with initial values + $initial_fields = self::$sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['name'] = 'Overwrite Test'; + $initial_fields['nickname'] = 'Original Nick'; + $initial_fields['overall_status'] = 'active'; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + // Update with do_not_overwrite_existing_fields = false (default behavior) + $update_fields = [ + 'assigned_to' => $base_user, + 'overall_status' => 'paused', + 'nickname' => 'New Nick', + 'contact_email' => [ 'values' => [ [ 'value' => 'overwrite@example.com' ] ] ], + 'location_grid' => [ 'values' => [ [ 'value' => '100089589' ] ] ] + ]; + + $result = DT_Posts::update_post('contacts', $contact['ID'], $update_fields, true, false, [ + 'do_not_overwrite_existing_fields' => false + ]); + + $this->assertNotWPError( $result ); + // Should update existing fields with new values + $this->assertSame( 'paused', $result['overall_status']['key'] ); + $this->assertSame( 'New Nick', $result['nickname'] ); + // Should add new fields + $this->assertSame( 'overwrite@example.com', $result['contact_email'][1]['value'] ); + // Extra assertion sanity checks. + $this->assertSame( 1, count( $result['location_grid'] ) ); + } + + /** + * @testdox do_not_overwrite_existing_fields: empty vs non-empty fields + */ + public function test_do_not_overwrite_empty_fields_update() { + $base_user = get_option( 'dt_base_user' ); + + // Create a contact with some empty fields + $initial_fields = self::$sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['name'] = 'Overwrite Test'; + $initial_fields['nickname'] = null; + $initial_fields['overall_status'] = 'active'; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + // Update with do_not_overwrite_existing_fields = true + $update_fields = [ + 'assigned_to' => $base_user, + 'overall_status' => 'paused', // Try to change existing field + 'nickname' => 'Should Be Added' // Add to empty field + ]; + + $result = DT_Posts::update_post('contacts', $contact['ID'], $update_fields, true, false, [ + 'do_not_overwrite_existing_fields' => true + ]); + + $this->assertNotWPError( $result ); + // Should update to new values, as they are different and not duplicates. + $this->assertSame( 'paused', $result['overall_status']['key'] ); + // Should add value to empty field + $this->assertSame( 'Should Be Added', $result['nickname'] ); + } + + /** + * @testdox do_not_overwrite_existing_fields: communication channel fields + */ + public function test_do_not_overwrite_communication_channels_update() { + $base_user = get_option( 'dt_base_user' ); + + // Create a contact with phone number + $initial_fields = self::$sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['name'] = 'Communication Test'; + $initial_fields['contact_phone'] = [ 'values' => [ [ 'value' => '444-555-6666' ] ] ]; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + // Update with additional phone and email + $update_fields = [ + 'assigned_to' => $base_user, + 'contact_phone' => [ 'values' => [ [ 'value' => '444-555-6666' ], [ 'value' => '777-888-9999' ] ] ], + 'contact_email' => [ 'values' => [ [ 'value' => 'comm@example.com' ] ] ], + 'baptism_date' => $initial_fields['baptism_date'] + ]; + + $result = DT_Posts::update_post('contacts', $contact['ID'], $update_fields, true, false, [ + 'do_not_overwrite_existing_fields' => true + ]); + + $this->assertNotWPError( $result ); + // Should keep existing phone and add new one + $phone_values = array_column( $result['contact_phone'], 'value' ); + $this->assertContains( '444-555-6666', $phone_values ); + $this->assertContains( '777-888-9999', $phone_values ); + // Should add new email + $this->assertSame( 'comm@example.com', $result['contact_email'][1]['value'] ); + // Extra assertion sanity checks. + $this->assertSame( 2, count( $result['contact_email'] ) ); + $this->assertSame( 2, count( $result['milestones'] ) ); + $this->assertSame( $initial_fields['baptism_date'], $result['baptism_date']['formatted'] ); + } + + /** + * @testdox do_not_overwrite_existing_fields: date fields + */ + public function test_do_not_overwrite_date_fields_update() { + $base_user = get_option( 'dt_base_user' ); + + // Create a contact with baptism date + $initial_fields = self::$sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['name'] = 'Date Test'; + $initial_fields['baptism_date'] = '2020-01-01'; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + // Try to update baptism date + $update_fields = [ + 'assigned_to' => $base_user, + 'baptism_date' => '2023-12-25', + 'tags' => [ 'values' => [ [ 'value' => 'tag1' ], [ 'value' => 'tag2' ] ] ], + 'milestones' => [ 'values' => [ [ 'value' => 'mature_christian' ] ] ] + ]; + + $result = DT_Posts::update_post('contacts', $contact['ID'], $update_fields, true, false, [ + 'do_not_overwrite_existing_fields' => true + ]); + + $this->assertNotWPError( $result ); + // Should update to new values, as they are different and not duplicates. + $this->assertSame( $update_fields['baptism_date'], $result['baptism_date']['formatted'] ); + // Extra assertion sanity checks. + $this->assertContains( 'tag2', $result['tags'] ); + $this->assertSame( 3, count( $result['tags'] ) ); + $this->assertSame( 2, count( $result['milestones'] ) ); + } + + public function test_do_not_overwrite_text_fields_update() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = self::$sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['contact_phone'] = [ 'values' => [ [ 'value' => '123-456-7890' ] ] ]; + $initial_fields['nickname'] = 'Johnny'; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + $update_fields = [ + 'assigned_to' => $base_user, + 'name' => 'John Doe', + 'contact_phone' => [ 'values' => [ [ 'value' => '123-456-7890' ] ] ], + 'nickname' => 'John', // Different value + ]; + + $result = DT_Posts::update_post('contacts', $contact['ID'], $update_fields, true, false, [ + 'do_not_overwrite_existing_fields' => true + ]); + + $this->assertNotWPError( $result ); + + $this->assertSame( 'Johnny', $result['nickname'] ); + } + + public function test_do_not_overwrite_number_fields_update() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = self::$sample_contact; + $initial_fields['assigned_to'] = $base_user; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + $update_fields = [ + 'assigned_to' => $base_user, + 'title' => $initial_fields['title'], + 'contact_phone' => $initial_fields['contact_phone'], + 'quick_button_contact_established' => 1 + ]; + + $result = DT_Posts::update_post('contacts', $contact['ID'], $update_fields, true, false, [ + 'do_not_overwrite_existing_fields' => true + ]); + + $this->assertNotWPError( $result ); + + $this->assertSame( $update_fields['quick_button_contact_established'], $result['quick_button_contact_established'] ); + } + + public function test_do_not_overwrite_boolean_fields_update() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = self::$sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['requires_update'] = true; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + $update_fields = [ + 'assigned_to' => $base_user, + 'title' => $initial_fields['title'], + 'contact_phone' => $initial_fields['contact_phone'], + 'requires_update' => false + ]; + + $result = DT_Posts::update_post('contacts', $contact['ID'], $update_fields, true, false, [ + 'do_not_overwrite_existing_fields' => true + ]); + + $this->assertNotWPError( $result ); + + $this->assertSame( false, empty( $result['requires_update'] ) ); + } + + public function test_do_not_overwrite_key_select_fields_update() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = self::$sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['overall_status'] = 'active'; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + $update_fields = [ + 'assigned_to' => $base_user, + 'name' => 'John Doe', + 'contact_phone' => $initial_fields['contact_phone'], + 'overall_status' => 'paused', // Different value + ]; + + $result = DT_Posts::update_post('contacts', $contact['ID'], $update_fields, true, false, [ + 'do_not_overwrite_existing_fields' => true + ]); + + $this->assertNotWPError( $result ); + + $this->assertSame( 'paused', $result['overall_status']['key'] ); + } + + public function test_do_not_overwrite_tags_fields_update() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = self::$sample_contact; + $initial_fields['assigned_to'] = $base_user; + $initial_fields['tags'] = [ 'values' => [ [ 'value' => 'existing_tag' ] ] ]; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + $update_fields = [ + 'assigned_to' => $base_user, + 'name' => 'Tags Test', + 'contact_phone' => $initial_fields['contact_phone'], + 'tags' => [ 'values' => [ [ 'value' => 'existing_tag' ], [ 'value' => 'new_tag' ] ] ] + ]; + + $result = DT_Posts::update_post('contacts', $contact['ID'], $update_fields, true, false, [ + 'do_not_overwrite_existing_fields' => true + ]); + + $this->assertNotWPError( $result ); + + $this->assertSame( 2, count( $result['tags'] ) ); + $this->assertContains( 'existing_tag', $result['tags'] ); + $this->assertContains( 'new_tag', $result['tags'] ); + } + + public function test_do_not_overwrite_location_fields_update() { + $base_user = get_option( 'dt_base_user' ); + + $initial_fields = self::$sample_contact; + $initial_fields['assigned_to'] = $base_user; + + $contact = DT_Posts::create_post( 'contacts', $initial_fields, true, false ); + $this->assertNotWPError( $contact ); + + $update_fields = [ + 'assigned_to' => $base_user, + 'title' => $initial_fields['title'], + 'contact_phone' => $initial_fields['contact_phone'], + 'location_grid' => [ 'values' => [ [ 'value' => '100089589' ] ] ] + ]; + + $result = DT_Posts::update_post('contacts', $contact['ID'], $update_fields, true, false, [ + 'do_not_overwrite_existing_fields' => false + ]); + + $this->assertNotWPError( $result ); + + $this->assertSame( 1, count( $result['location_grid'] ) ); + } }