Passed
Pull Request — master (#339)
by Jonathan
08:20
created

Object_Sync_Sf_Mapping::get_fieldmaps()   C

Complexity

Conditions 12
Paths 13

Size

Total Lines 52
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 40
c 0
b 0
f 0
nc 13
nop 3
dl 0
loc 52
rs 6.9666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Map objects and records between WordPress and Salesforce
4
 *
5
 * @class   Object_Sync_Sf_Mapping
6
 * @package Object_Sync_Salesforce
7
 */
8
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * Object_Sync_Sf_Mapping class.
13
 */
14
class Object_Sync_Sf_Mapping {
15
16
	/**
17
	 * Current version of the plugin
18
	 *
19
	 * @var string
20
	 */
21
	public $version;
22
23
	/**
24
	 * The user's installed version of this plugin's database setup
25
	 *
26
	 * @var string
27
	 */
28
	public $user_installed_version;
29
30
	/**
31
	 * The main plugin file
32
	 *
33
	 * @var string
34
	 */
35
	public $file;
36
37
	/**
38
	 * Global object of `$wpdb`, the WordPress database
39
	 *
40
	 * @var object
41
	 */
42
	public $wpdb;
43
44
	/**
45
	 * The plugin's slug so we can include it when necessary
46
	 *
47
	 * @var string
48
	 */
49
	public $slug;
50
51
	/**
52
	 * The plugin's prefix when saving options to the database
53
	 *
54
	 * @var string
55
	 */
56
	public $option_prefix;
57
58
	/**
59
	 * Object_Sync_Sf_Logging class
60
	 *
61
	 * @var object
62
	 */
63
	public $logging;
64
65
	/**
66
	 * The database table for fieldmaps
67
	 *
68
	 * @var string
69
	 */
70
	public $fieldmap_table;
71
72
	/**
73
	 * The database table for object maps
74
	 *
75
	 * @var string
76
	 */
77
	public $object_map_table;
78
79
	/**
80
	 * Bitmap value for when sync is off
81
	 *
82
	 * @var string
83
	 */
84
	public $sync_off;
85
86
	/**
87
	 * Bitmap value for when sync is is on for WordPress create events
88
	 *
89
	 * @var string
90
	 */
91
	public $sync_wordpress_create;
92
93
	/**
94
	 * Bitmap value for when sync is is on for WordPress update events
95
	 *
96
	 * @var string
97
	 */
98
	public $sync_wordpress_update;
99
100
	/**
101
	 * Bitmap value for when sync is is on for WordPress delete events
102
	 *
103
	 * @var string
104
	 */
105
	public $sync_wordpress_delete;
106
107
	/**
108
	 * Bitmap value for when sync is is on for Salesforce create events
109
	 *
110
	 * @var string
111
	 */
112
	public $sync_sf_create;
113
114
	/**
115
	 * Bitmap value for when sync is is on for Salesforce update events
116
	 *
117
	 * @var string
118
	 */
119
	public $sync_sf_update;
120
121
	/**
122
	 * Bitmap value for when sync is is on for Salesforce delete events
123
	 *
124
	 * @var string
125
	 */
126
	public $sync_sf_delete;
127
128
	/**
129
	 * Which events are run by WordPress
130
	 *
131
	 * @var string
132
	 */
133
	public $wordpress_events;
134
135
	/**
136
	 * Which events are run by Salesforce
137
	 *
138
	 * @var string
139
	 */
140
	public $salesforce_events;
141
142
	/**
143
	 * The direction from WordPress to Salesforce
144
	 *
145
	 * @var string
146
	 */
147
	public $direction_wordpress_sf;
148
149
	/**
150
	 * The direction from Salesforce to WordPress
151
	 *
152
	 * @var string
153
	 */
154
	public $direction_sf_wordpress;
155
156
	/**
157
	 * The direction to sync both ways
158
	 *
159
	 * @var string
160
	 */
161
	public $direction_sync;
162
163
	/**
164
	 * WordPress directions, including sync
165
	 *
166
	 * @var string
167
	 */
168
	public $direction_wordpress;
169
170
	/**
171
	 * Salesforce directions, including sync
172
	 *
173
	 * @var string
174
	 */
175
	public $direction_salesforce;
176
177
	/**
178
	 * Default record type when using a Salesforce object that has a default or Master record type
179
	 *
180
	 * @var string
181
	 */
182
	public $salesforce_default_record_type;
183
184
	/**
185
	 * Delimiter for arrays coming from Salesforce
186
	 *
187
	 * @var string
188
	 */
189
	public $array_delimiter;
190
191
	/**
192
	 * Data in Salesforce that is stored as an array
193
	 *
194
	 * @var string
195
	 */
196
	public $array_types_from_salesforce;
197
198
	/**
199
	 * Data in Salesforce that is stored as a date
200
	 *
201
	 * @var string
202
	 */
203
	public $date_types_from_salesforce;
204
205
	/**
206
	 * Data in Salesforce that is stored as an integer
207
	 *
208
	 * @var string
209
	 */
210
	public $int_types_from_salesforce;
211
212
	/**
213
	 * How long can a mapping field be
214
	 *
215
	 * @var int
216
	 */
217
	public $name_length;
218
219
	/**
220
	 * Status flag for success
221
	 *
222
	 * @var int
223
	 */
224
	public $status_success;
225
226
	/**
227
	 * Status flag for error
228
	 *
229
	 * @var int
230
	 */
231
	public $status_error;
232
233
	/**
234
	 * Option value for whether the plugin is in debug mode
235
	 *
236
	 * @var string
237
	 */
238
	public $debug;
239
240
	/**
241
	 * Constructor for mapping class
242
	 */
243
	public function __construct() {
244
		$this->version                = object_sync_for_salesforce()->version;
245
		$this->user_installed_version = object_sync_for_salesforce()->user_installed_version;
246
		$this->file                   = object_sync_for_salesforce()->file;
247
		$this->wpdb                   = object_sync_for_salesforce()->wpdb;
248
		$this->slug                   = object_sync_for_salesforce()->slug;
249
		$this->option_prefix          = object_sync_for_salesforce()->option_prefix;
250
251
		$this->logging = object_sync_for_salesforce()->logging;
252
253
		$this->fieldmap_table   = $this->wpdb->prefix . 'object_sync_sf_field_map';
254
		$this->object_map_table = $this->wpdb->prefix . 'object_sync_sf_object_map';
255
256
		/*
257
		 * These parameters are how we define when syncing should occur on each field map.
258
		 * They get used in the admin settings, as well as the push/pull methods to see if something should happen.
259
		*/
260
		$this->sync_off              = 'off';
261
		$this->sync_wordpress_create = 'wp_create';
262
		$this->sync_wordpress_update = 'wp_update';
263
		$this->sync_wordpress_delete = 'wp_delete';
264
		$this->sync_sf_create        = 'sf_create';
265
		$this->sync_sf_update        = 'sf_update';
266
		$this->sync_sf_delete        = 'sf_delete';
267
268
		// deprecated bit flags from version 1.x. Deprecated since 2.0.0.
269
		$this->sync_off_v1              = 0x0000;
0 ignored issues
show
Bug Best Practice introduced by
The property sync_off_v1 does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
270
		$this->sync_wordpress_create_v1 = 0x0001;
0 ignored issues
show
Bug Best Practice introduced by
The property sync_wordpress_create_v1 does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
271
		$this->sync_wordpress_update_v1 = 0x0002;
0 ignored issues
show
Bug Best Practice introduced by
The property sync_wordpress_update_v1 does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
272
		$this->sync_wordpress_delete_v1 = 0x0004;
0 ignored issues
show
Bug Best Practice introduced by
The property sync_wordpress_delete_v1 does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
273
		$this->sync_sf_create_v1        = 0x0008;
0 ignored issues
show
Bug Best Practice introduced by
The property sync_sf_create_v1 does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
274
		$this->sync_sf_update_v1        = 0x0010;
0 ignored issues
show
Bug Best Practice introduced by
The property sync_sf_update_v1 does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
275
		$this->sync_sf_delete_v1        = 0x0020;
0 ignored issues
show
Bug Best Practice introduced by
The property sync_sf_delete_v1 does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
276
277
		// Define which events are initialized by which system.
278
		$this->wordpress_events  = array( $this->sync_wordpress_create, $this->sync_wordpress_update, $this->sync_wordpress_delete );
0 ignored issues
show
Documentation Bug introduced by
It seems like array($this->sync_wordpr...>sync_wordpress_delete) of type array<integer,string> is incompatible with the declared type string of property $wordpress_events.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
279
		$this->salesforce_events = array( $this->sync_sf_create, $this->sync_sf_update, $this->sync_sf_delete );
0 ignored issues
show
Documentation Bug introduced by
It seems like array($this->sync_sf_cre... $this->sync_sf_delete) of type array<integer,string> is incompatible with the declared type string of property $salesforce_events.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
280
281
		// deprecated bit flags from version 1.x. Deprecated since 2.0.0.
282
		$this->wordpress_events_v1  = array( $this->sync_wordpress_create_v1, $this->sync_wordpress_update_v1, $this->sync_wordpress_delete_v1 );
0 ignored issues
show
Bug Best Practice introduced by
The property wordpress_events_v1 does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
283
		$this->salesforce_events_v1 = array( $this->sync_sf_create_v1, $this->sync_sf_update_v1, $this->sync_sf_delete_v1 );
0 ignored issues
show
Bug Best Practice introduced by
The property salesforce_events_v1 does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
284
285
		// Constants for the directions to map things.
286
		$this->direction_wordpress_sf = 'wp_sf';
287
		$this->direction_sf_wordpress = 'sf_wp';
288
		$this->direction_sync         = 'sync';
289
290
		$this->direction_wordpress  = array( $this->direction_wordpress_sf, $this->direction_sync );
0 ignored issues
show
Documentation Bug introduced by
It seems like array($this->direction_w... $this->direction_sync) of type array<integer,string> is incompatible with the declared type string of property $direction_wordpress.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
291
		$this->direction_salesforce = array( $this->direction_sf_wordpress, $this->direction_sync );
0 ignored issues
show
Documentation Bug introduced by
It seems like array($this->direction_s... $this->direction_sync) of type array<integer,string> is incompatible with the declared type string of property $direction_salesforce.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
292
293
		// This is used when we map a record with default or Master.
294
		$this->salesforce_default_record_type = 'default';
295
296
		// Salesforce has multipicklists and they have a delimiter.
297
		$this->array_delimiter = ';';
298
		// What data types in Salesforce should be an array?
299
		$this->array_types_from_salesforce = array( 'multipicklist' );
0 ignored issues
show
Documentation Bug introduced by
It seems like array('multipicklist') of type array<integer,string> is incompatible with the declared type string of property $array_types_from_salesforce.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
300
		// What data types in Salesforce should be a date field?
301
		$this->date_types_from_salesforce = array( 'date', 'datetime' );
0 ignored issues
show
Documentation Bug introduced by
It seems like array('date', 'datetime') of type array<integer,string> is incompatible with the declared type string of property $date_types_from_salesforce.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
302
		// What data types in Salesforce should be an integer?
303
		$this->int_types_from_salesforce = array( 'integer', 'boolean' );
0 ignored issues
show
Documentation Bug introduced by
It seems like array('integer', 'boolean') of type array<integer,string> is incompatible with the declared type string of property $int_types_from_salesforce.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
304
305
		// Max length for a mapping field.
306
		$this->name_length = 128;
307
308
		// Statuses for object sync.
309
		$this->status_success = 1;
310
		$this->status_error   = 0;
311
312
		$this->debug = get_option( $this->option_prefix . 'debug_mode', false );
0 ignored issues
show
Documentation Bug introduced by
It seems like get_option($this->option... . 'debug_mode', false) can also be of type false. However, the property $debug is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
313
		$this->debug = filter_var( $this->debug, FILTER_VALIDATE_BOOLEAN );
314
315
	}
316
317
	/**
318
	 * Create a fieldmap row between a WordPress and Salesforce object
319
	 *
320
	 * @param array $posted The results of $_POST.
321
	 * @param array $wordpress_fields The fields for the WordPress side of the mapping.
322
	 * @param array $salesforce_fields The fields for the Salesforce side of the mapping.
323
	 * @return int  the last inserted ID.
324
	 */
325
	public function create_fieldmap( $posted = array(), $wordpress_fields = array(), $salesforce_fields = array() ) {
326
		$data = $this->setup_fieldmap_data( $posted, $wordpress_fields, $salesforce_fields );
327
		if ( ! isset( $posted['version'] ) && version_compare( $this->user_installed_version, '1.2.5', '>=' ) ) {
328
			$data['version'] = $this->user_installed_version;
329
		}
330
		$insert = $this->wpdb->insert( $this->fieldmap_table, $data );
331
		if ( 1 === $insert ) {
332
			return $this->wpdb->insert_id;
333
		} else {
334
			return false;
335
		}
336
	}
337
338
	/**
339
	 * Get one or more fieldmap rows between a WordPress and Salesforce object
340
	 *
341
	 * @param int   $id The ID of a desired mapping.
342
	 * @param array $conditions Array of key=>value to match the mapping by.
343
	 * @param bool  $reset Unused parameter.
344
	 * @return array $map a single mapping or $mappings, an array of mappings.
345
	 */
346
	public function get_fieldmaps( $id = null, $conditions = array(), $reset = false ) {
0 ignored issues
show
Unused Code introduced by
The parameter $reset is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

346
	public function get_fieldmaps( $id = null, $conditions = array(), /** @scrutinizer ignore-unused */ $reset = false ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
347
		$table = $this->fieldmap_table;
348
		if ( null !== $id ) { // get one fieldmap.
349
			$map        = $this->wpdb->get_row( 'SELECT * FROM ' . $table . ' WHERE id = ' . $id, ARRAY_A );
350
			$mappings   = array();
351
			$mappings[] = $map;
352
			$mappings   = $this->prepare_fieldmap_data( $mappings );
353
			$map        = $mappings[0];
354
			return $map;
355
		} elseif ( ! empty( $conditions ) ) { // get multiple but with a limitation.
356
			$mappings    = array();
357
			$record_type = '';
358
			// Assemble the SQL.
359
			if ( ! empty( $conditions ) ) {
360
				$where = ' WHERE ';
361
				$i     = 0;
362
				foreach ( $conditions as $key => $value ) {
363
					if ( 'salesforce_record_type' === $key ) {
364
						$record_type = sanitize_text_field( $value );
365
					} else {
366
						$i++;
367
						if ( $i > 1 ) {
368
							$where .= ' AND ';
369
						}
370
						$where .= '`' . $key . '` = "' . $value . '"';
371
					}
372
				}
373
			} else {
374
				$where = '';
375
			}
376
			$mappings = $this->wpdb->get_results( 'SELECT * FROM ' . $table . $where . ' ORDER BY `weight`', ARRAY_A );
377
			if ( ! empty( $mappings ) ) {
378
				$mappings = $this->prepare_fieldmap_data( $mappings, $record_type );
379
			}
380
			return $mappings;
381
		} else { // get all of the mappings. ALL THE MAPPINGS.
382
			if ( version_compare( $this->user_installed_version, '2.1.0', '>=' ) ) {
383
				// if the version is greater than or equal to 2.1.0, the fieldmap table has a wordpress_object_default_status column.
384
				$mappings = $this->wpdb->get_results( "SELECT `id`, `label`, `wordpress_object`, `wordpress_object_default_status`, `salesforce_object`, `salesforce_record_types_allowed`, `salesforce_record_type_default`, `fields`, `pull_trigger_field`, `sync_triggers`, `push_async`, `push_drafts`, `pull_to_drafts`, `weight`, `version` FROM $table", ARRAY_A );
385
			} elseif ( version_compare( $this->user_installed_version, '1.5.0', '>=' ) ) {
386
				// if the version is greater than or equal to 1.5.0, the fieldmap table has a pull_to_drafts column.
387
				$mappings = $this->wpdb->get_results( "SELECT `id`, `label`, `wordpress_object`, `salesforce_object`, `salesforce_record_types_allowed`, `salesforce_record_type_default`, `fields`, `pull_trigger_field`, `sync_triggers`, `push_async`, `push_drafts`, `pull_to_drafts`, `weight`, `version` FROM $table", ARRAY_A );
388
			} elseif ( version_compare( $this->user_installed_version, '1.2.5', '>=' ) ) {
389
				// if the version is greater than or equal to 1.2.5, the fieldmap table has a version column.
390
				$mappings = $this->wpdb->get_results( "SELECT `id`, `label`, `wordpress_object`, `salesforce_object`, `salesforce_record_types_allowed`, `salesforce_record_type_default`, `fields`, `pull_trigger_field`, `sync_triggers`, `push_async`, `push_drafts`, `weight`, `version` FROM $table", ARRAY_A );
391
			} else {
392
				$mappings = $this->wpdb->get_results( "SELECT `id`, `label`, `wordpress_object`, `salesforce_object`, `salesforce_record_types_allowed`, `salesforce_record_type_default`, `fields`, `pull_trigger_field`, `sync_triggers`, `push_async`, `push_drafts`, `weight` FROM $table", ARRAY_A );
393
			}
394
			if ( ! empty( $mappings ) ) {
395
				$mappings = $this->prepare_fieldmap_data( $mappings );
396
			}
397
			return $mappings;
398
		} // End if statement.
399
	}
400
401
	/**
402
	 * For a mapping, get the fieldmaps associated with it.
403
	 *
404
	 * @param array $mapping The mapping for which we are getting the fieldmaps.
405
	 * @param array $directions The direction of the mapping: from WP to SF or vice-versa.
406
	 * @see Object_Sync_Sf_Salesforce_Pull::get_pull_query()
407
	 * @return array of mapped fields
408
	 */
409
	public function get_mapped_fields( $mapping, $directions = array() ) {
410
		$mapped_fields = array();
411
		if ( is_array( $mapping['fields'] ) ) {
412
			foreach ( $mapping['fields'] as $fields ) {
413
				if ( empty( $directions ) || in_array( $fields['direction'], $directions, true ) ) {
414
415
					// in version 1.2.0, we provided an option for API name vs label for Salesforce fields.
416
					if ( version_compare( $this->user_installed_version, '1.2.0', '>=' ) && isset( $fields['salesforce_field']['name'] ) ) {
417
						$array_key = 'name';
418
					} else {
419
						$array_key = 'label';
420
					}
421
422
					// Some field map types (Relation) store a collection of SF objects.
423
					if ( is_array( $fields['salesforce_field'] ) && ! isset( $fields['salesforce_field'][ $array_key ] ) ) {
424
						foreach ( $fields['salesforce_field'] as $sf_field ) {
425
							$mapped_fields[ $sf_field[ $array_key ] ] = $sf_field[ $array_key ];
426
						}
427
					} else { // The rest are just a name/value pair.
428
						$mapped_fields[ $fields['salesforce_field'][ $array_key ] ] = $fields['salesforce_field'][ $array_key ];
429
					}
430
				}
431
			}
432
		}
433
434
		if ( ! empty( $this->get_mapped_record_types ) ) {
435
			$mapped_fields['RecordTypeId'] = 'RecordTypeId';
436
		}
437
438
		return $mapped_fields;
439
	}
440
441
	/**
442
	 * Get the mapped record types for a given mapping.
443
	 *
444
	 * @param array $mapping A mapping from which we wish to estract the record type.
445
	 * @return array of mappings. Empty if the mapping's record type is default, else full of the record types.
446
	 */
447
	public function get_mapped_record_types( $mapping ) {
448
		return $mapping['salesforce_record_type_default'] === $this->salesforce_default_record_type ? array() : array_filter( maybe_unserialize( $mapping['salesforce_record_types_allowed'] ) );
449
	}
450
451
	/**
452
	 * Update a fieldmap row between a WordPress and Salesforce object
453
	 *
454
	 * @param array $posted It's $_POST.
455
	 * @param array $wordpress_fields The fields for the WordPress side of the mapping.
456
	 * @param array $salesforce_fields The fields for the Salesforce side of the mapping.
457
	 * @param int   $id The ID of the mapping.
458
	 * @return boolean whether it was updated
459
	 */
460
	public function update_fieldmap( $posted = array(), $wordpress_fields = array(), $salesforce_fields = array(), $id = '' ) {
461
		$data = $this->setup_fieldmap_data( $posted, $wordpress_fields, $salesforce_fields );
462
		if ( version_compare( $this->user_installed_version, '1.2.5', '>=' ) && ! isset( $data['updated'] ) ) {
463
			$data['version'] = $this->user_installed_version;
464
		}
465
		$update = $this->wpdb->update(
466
			$this->fieldmap_table,
467
			$data,
468
			array(
469
				'id' => $id,
470
			)
471
		);
472
		if ( false === $update ) {
473
			return false;
474
		} else {
475
			return true;
476
		}
477
	}
478
479
	/**
480
	 * Setup fieldmap data
481
	 * Sets up the database entry for mapping the object types between Salesforce and WordPress
482
	 *
483
	 * @param array $posted It's $_POST.
484
	 * @param array $wordpress_fields The fields for the WordPress side of the mapping.
485
	 * @param array $salesforce_fields The fields for the Salesforce side of the mapping.
486
	 * @return array $data the fieldmap's data for the database
487
	 */
488
	private function setup_fieldmap_data( $posted = array(), $wordpress_fields = array(), $salesforce_fields = array() ) {
489
		$data = array(
490
			'label'             => $posted['label'],
491
			'name'              => sanitize_title( $posted['label'] ),
492
			'salesforce_object' => $posted['salesforce_object'],
493
			'wordpress_object'  => $posted['wordpress_object'],
494
		);
495
		// added in version 2.1.0.
496
		$data['wordpress_object_default_status'] = isset( $posted['wordpress_object_default_status'] ) ? sanitize_text_field( $posted['wordpress_object_default_status'] ) : '';
497
		// when importing a fieldmap, there might already be a saved version. if there is, keep it.
498
		$data['version'] = isset( $posted['version'] ) ? $posted['version'] : $this->version;
499
		if ( isset( $posted['wordpress_field'] ) && is_array( $posted['wordpress_field'] ) && isset( $posted['salesforce_field'] ) && is_array( $posted['salesforce_field'] ) ) {
500
			$setup['fields'] = array();
501
			foreach ( $posted['wordpress_field'] as $key => $value ) {
502
				$method_key = array_search( $value, array_column( $wordpress_fields, 'key' ), true );
503
				// in 2.1.0 we added a date format field.
504
				if ( ! isset( $posted['date-format'][ $key ] ) ) {
505
					$posted['date-format'][ $key ] = '';
506
				}
507
				if ( ! isset( $posted['direction'][ $key ] ) ) {
508
					$posted['direction'][ $key ] = 'sync';
509
				}
510
				if ( ! isset( $posted['is_prematch'][ $key ] ) ) {
511
					$posted['is_prematch'][ $key ] = false;
512
				}
513
				if ( ! isset( $posted['is_key'][ $key ] ) ) {
514
					$posted['is_key'][ $key ] = false;
515
				}
516
				if ( ! isset( $posted['is_delete'][ $key ] ) ) {
517
					$posted['is_delete'][ $key ] = false;
518
				}
519
				if ( false === $posted['is_delete'][ $key ] ) {
520
					// I think it's good to over-mention that updateable is really how the Salesforce api spells it.
521
					$updateable_key = array_search( $posted['salesforce_field'][ $key ], array_column( $salesforce_fields, 'name' ), true );
522
523
					$salesforce_field_attributes = array();
524
					foreach ( $salesforce_fields[ $updateable_key ] as $sf_key => $sf_value ) {
525
						if ( isset( $sf_value ) && ! is_array( $sf_value ) ) {
526
							$salesforce_field_attributes[ $sf_key ] = esc_attr( $sf_value );
527
						} elseif ( ! empty( $sf_value ) && is_array( $sf_value ) ) {
528
							$salesforce_field_attributes[ $sf_key ] = maybe_unserialize( $sf_value );
0 ignored issues
show
Bug introduced by
$sf_value of type array is incompatible with the type string expected by parameter $data of maybe_unserialize(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

528
							$salesforce_field_attributes[ $sf_key ] = maybe_unserialize( /** @scrutinizer ignore-type */ $sf_value );
Loading history...
529
						} else {
530
							$salesforce_field_attributes[ $sf_key ] = '';
531
						}
532
					}
533
534
					$setup['fields'][ $key ] = array(
535
						'wordpress_field'  => array(
536
							'label'   => sanitize_text_field( $posted['wordpress_field'][ $key ] ),
537
							'methods' => maybe_unserialize( $wordpress_fields[ $method_key ]['methods'] ),
538
							'type'    => isset( $wordpress_fields[ $method_key ]['type'] ) ? sanitize_text_field( $wordpress_fields[ $method_key ]['type'] ) : 'text',
539
						),
540
						'salesforce_field' => $salesforce_field_attributes,
541
						'date-format'      => esc_attr( $posted['date-format'][ $key ] ),
542
						'is_prematch'      => sanitize_text_field( $posted['is_prematch'][ $key ] ),
543
						'is_key'           => sanitize_text_field( $posted['is_key'][ $key ] ),
544
						'direction'        => sanitize_text_field( $posted['direction'][ $key ] ),
545
						'is_delete'        => sanitize_text_field( $posted['is_delete'][ $key ] ),
546
					);
547
548
					// If the WordPress key or the Salesforce key are blank, remove this incomplete mapping.
549
					// This prevents https://github.com/MinnPost/object-sync-for-salesforce/issues/82 .
550
					if (
551
						empty( $setup['fields'][ $key ]['wordpress_field']['label'] )
552
						||
553
						empty( $setup['fields'][ $key ]['salesforce_field']['name'] )
554
					) {
555
						unset( $setup['fields'][ $key ] );
556
					}
557
				}
558
			} // End foreach() on WordPress fields.
559
			$data['fields'] = maybe_serialize( $setup['fields'] );
560
		} elseif ( isset( $posted['fields'] ) && is_array( $posted['fields'] ) ) {
561
			// if $posted['fields'] is already set, use that.
562
			$data['fields'] = maybe_serialize( $posted['fields'] );
563
		} // End if() WordPress fields are present.
564
565
		if ( isset( $posted['salesforce_record_types_allowed'] ) ) {
566
			$data['salesforce_record_types_allowed'] = maybe_serialize( $posted['salesforce_record_types_allowed'] );
567
		} else {
568
			$data['salesforce_record_types_allowed'] = maybe_serialize(
569
				array(
570
					$this->salesforce_default_record_type => $this->salesforce_default_record_type,
571
				)
572
			);
573
		}
574
		if ( isset( $posted['salesforce_record_type_default'] ) && '' !== $posted['salesforce_record_type_default'] ) {
575
			$data['salesforce_record_type_default'] = $posted['salesforce_record_type_default'];
576
		} else {
577
			$data['salesforce_record_type_default'] = maybe_serialize( $this->salesforce_default_record_type );
578
		}
579
		if ( isset( $posted['pull_trigger_field'] ) ) {
580
			$data['pull_trigger_field'] = $posted['pull_trigger_field'];
581
		}
582
		if ( isset( $posted['sync_triggers'] ) && is_array( $posted['sync_triggers'] ) ) {
583
			$setup['sync_triggers'] = array();
584
			foreach ( $posted['sync_triggers'] as $key => $value ) {
585
				$setup['sync_triggers'][ $key ] = esc_html( $posted['sync_triggers'][ $key ] );
586
			}
587
			// format the sync triggers. if necessary, update the database.
588
			$sync_triggers          = $this->maybe_upgrade_sync_triggers( $setup['sync_triggers'], $data['version'] );
589
			$setup['sync_triggers'] = $sync_triggers;
590
		} else {
591
			$setup['sync_triggers'] = array();
592
		}
593
		$data['sync_triggers'] = maybe_serialize( $setup['sync_triggers'] );
594
		if ( isset( $posted['pull_trigger_field'] ) ) {
595
			$data['pull_trigger_field'] = $posted['pull_trigger_field'];
596
		}
597
598
		// invert value of immediately from user to get value of async.
599
		if ( isset( $posted['push_immediately'] ) && true === filter_var( $posted['push_immediately'], FILTER_VALIDATE_BOOLEAN ) ) {
600
			$data['push_async'] = '';
601
		} else {
602
			$data['push_async'] = '1';
603
		}
604
605
		$data['push_drafts']    = isset( $posted['push_drafts'] ) ? $posted['push_drafts'] : '';
606
		$data['pull_to_drafts'] = isset( $posted['pull_to_drafts'] ) ? $posted['pull_to_drafts'] : '';
607
		$data['weight']         = isset( $posted['weight'] ) ? $posted['weight'] : '';
608
		return $data;
609
	}
610
611
	/**
612
	 * Delete a fieldmap row between a WordPress and Salesforce object
613
	 *
614
	 * @param int $id The ID of a field mapping.
615
	 * @return bool whether it was deleted
616
	 */
617
	public function delete_fieldmap( $id = '' ) {
618
		$data   = array(
619
			'id' => $id,
620
		);
621
		$delete = $this->wpdb->delete( $this->fieldmap_table, $data );
622
		if ( 1 === $delete ) {
623
			return true;
624
		} else {
625
			return false;
626
		}
627
	}
628
629
	/**
630
	 * Create an object map row between a WordPress and Salesforce object
631
	 *
632
	 * @param array $posted It's $_POST.
633
	 * @return false|int of field mapping between WordPress and Salesforce objects
634
	 */
635
	public function create_object_map( $posted = array() ) {
636
		$data            = $this->setup_object_map_data( $posted );
637
		$data['created'] = current_time( 'mysql' );
638
		// Check to see if we don't know the salesforce id and it is not a temporary id, or if this is pending.
639
		// If it is using a temporary id, the map will get updated after it finishes running; it won't call this method unless there's an error, which we should log.
640
		if ( substr( $data['salesforce_id'], 0, 7 ) !== 'tmp_sf_' || ( isset( $data['action'] ) && 'pending' === $data['action'] ) ) {
641
			unset( $data['action'] );
642
			$insert = $this->wpdb->insert( $this->object_map_table, $data );
643
		} else {
644
			$status = 'error';
645
			if ( isset( $this->logging ) ) {
646
				$logging = $this->logging;
647
			} elseif ( class_exists( 'Object_Sync_Sf_Logging' ) ) {
648
				$logging = new Object_Sync_Sf_Logging( $this->wpdb, $this->version );
0 ignored issues
show
Unused Code introduced by
The call to Object_Sync_Sf_Logging::__construct() has too many arguments starting with $this->wpdb. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

648
				$logging = /** @scrutinizer ignore-call */ new Object_Sync_Sf_Logging( $this->wpdb, $this->version );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
649
			}
650
			$logging->setup(
651
				sprintf(
652
					// translators: %1$s is the log status, %2$s is the name of a WordPress object. %3$s is the id of that object.
653
					esc_html__( '%1$s Mapping: error caused by trying to map the WordPress %2$s with ID of %3$s to Salesforce ID starting with "tmp_sf_", which is invalid.', 'object-sync-for-salesforce' ),
654
					ucfirst( esc_attr( $status ) ),
655
					esc_attr( $data['wordpress_object'] ),
656
					absint( $data['wordpress_id'] )
657
				),
658
				'',
659
				0,
660
				0,
661
				$status
662
			);
663
			return false;
664
		}
665
		if ( 1 === $insert ) {
666
			return $this->wpdb->insert_id;
667
		} elseif ( false !== strpos( $this->wpdb->last_error, 'Duplicate entry' ) ) {
668
			// this error should never happen now, I think. But let's watch and see.
669
			$mapping = $this->load_all_by_salesforce( $data['salesforce_id'] )[0];
670
			$id      = $mapping['id'];
671
			$status  = 'error';
672
			if ( isset( $this->logging ) ) {
673
				$logging = $this->logging;
674
			} elseif ( class_exists( 'Object_Sync_Sf_Logging' ) ) {
675
				$logging = new Object_Sync_Sf_Logging( $this->wpdb, $this->version );
676
			}
677
			$logging->setup(
678
				sprintf(
679
					// translators: %1$s is the status word "Error". %2$s is the Id of a Salesforce object. %3$s is the ID of a mapping object.
680
					esc_html__( '%1$s: Mapping: there is already a WordPress object mapped to the Salesforce object %2$s and the mapping object ID is %3$s', 'object-sync-for-salesforce' ),
681
					ucfirst( esc_attr( $status ) ),
682
					esc_attr( $data['salesforce_id'] ),
683
					absint( $id )
684
				),
685
				print_r( $mapping, true ), // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
686
				0,
687
				0,
688
				$status
689
			);
690
			return $id;
691
		} else {
692
			return false;
693
		}
694
	}
695
696
	/**
697
	 * Get all object map rows between WordPress and Salesforce objects.
698
	 *
699
	 * This replaces previous functionality that would return a single object map if there was only one, rather than a multi-dimensional array.
700
	 *
701
	 * @param array $conditions Limitations on the SQL query for object mapping rows.
702
	 * @param bool  $reset Unused parameter.
703
	 * @return $mappings
0 ignored issues
show
Documentation Bug introduced by
The doc comment $mappings at position 0 could not be parsed: Unknown type name '$mappings' at position 0 in $mappings.
Loading history...
704
	 */
705
	public function get_all_object_maps( $conditions = array(), $reset = false ) {
0 ignored issues
show
Unused Code introduced by
The parameter $reset is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

705
	public function get_all_object_maps( $conditions = array(), /** @scrutinizer ignore-unused */ $reset = false ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
706
		$table = $this->object_map_table;
707
		$order = ' ORDER BY object_updated, created';
708
		if ( ! empty( $conditions ) ) { // get multiple but with a limitation.
709
			$mappings = array();
710
711
			if ( ! empty( $conditions ) ) {
712
				$where = ' WHERE ';
713
				$i     = 0;
714
				foreach ( $conditions as $key => $value ) {
715
					$i++;
716
					if ( $i > 1 ) {
717
						$where .= ' AND ';
718
					}
719
					$where .= '`' . $key . '` = "' . $value . '"';
720
				}
721
			} else {
722
				$where = '';
723
			}
724
725
			$mappings = $this->wpdb->get_results( 'SELECT * FROM ' . $table . $where . $order, ARRAY_A );
726
		} else { // get all of the mappings. ALL THE MAPPINGS.
727
			$mappings = $this->wpdb->get_results( 'SELECT * FROM ' . $table . $order, ARRAY_A );
728
		}
729
730
		return $mappings;
731
732
	}
733
734
	/**
735
	 * Get one or more object map rows between WordPress and Salesforce objects
736
	 *
737
	 * @param array $conditions Limitations on the SQL query for object mapping rows.
738
	 * @param bool  $reset Unused parameter.
739
	 * @return array $map or $mappings
740
	 * @deprecated since 1.8.0
741
	 */
742
	public function get_object_maps( $conditions = array(), $reset = false ) {
0 ignored issues
show
Unused Code introduced by
The parameter $reset is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

742
	public function get_object_maps( $conditions = array(), /** @scrutinizer ignore-unused */ $reset = false ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
743
		$table = $this->object_map_table;
744
		$order = ' ORDER BY object_updated, created';
745
		if ( ! empty( $conditions ) ) { // get multiple but with a limitation.
746
			$mappings = array();
747
748
			if ( ! empty( $conditions ) ) {
749
				$where = ' WHERE ';
750
				$i     = 0;
751
				foreach ( $conditions as $key => $value ) {
752
					$i++;
753
					if ( $i > 1 ) {
754
						$where .= ' AND ';
755
					}
756
					$where .= '`' . $key . '` = "' . $value . '"';
757
				}
758
			} else {
759
				$where = '';
760
			}
761
762
			$mappings = $this->wpdb->get_results( 'SELECT * FROM ' . $table . $where . $order, ARRAY_A );
763
			if ( ! empty( $mappings ) && 1 === $this->wpdb->num_rows ) {
764
				$mappings = $mappings[0];
765
			}
766
		} else { // get all of the mappings. ALL THE MAPPINGS.
767
			$mappings = $this->wpdb->get_results( 'SELECT * FROM ' . $table . $order, ARRAY_A );
768
			if ( ! empty( $mappings ) && 1 === $this->wpdb->num_rows ) {
769
				$mappings = $mappings[0];
770
			}
771
		}
772
773
		return $mappings;
774
775
	}
776
777
	/**
778
	 * Update an object map row between a WordPress and Salesforce object
779
	 *
780
	 * @param array $posted It's $_POST.
781
	 * @param array $id The ID of the object map row.
782
	 * @return boolean whether it was updated
783
	 */
784
	public function update_object_map( $posted = array(), $id = '' ) {
785
		$data = $this->setup_object_map_data( $posted );
786
		if ( ! isset( $data['object_updated'] ) ) {
787
			$data['object_updated'] = current_time( 'mysql' );
788
		}
789
		$update = $this->wpdb->update(
790
			$this->object_map_table,
791
			$data,
792
			array(
793
				'id' => $id,
794
			)
795
		);
796
		if ( false === $update ) {
797
			return false;
798
		} else {
799
			return true;
800
		}
801
	}
802
803
	/**
804
	 * Setup the data for the object map
805
	 *
806
	 * @param array $posted It's $_POST.
807
	 * @return array $data Filtered array with only the keys that are in the object map database table. Strips out things from WordPress form if they're present.
808
	 */
809
	private function setup_object_map_data( $posted = array() ) {
810
		$allowed_fields   = $this->wpdb->get_col( "DESC {$this->object_map_table}", 0 );
811
		$allowed_fields[] = 'action'; // we use this in both directions even though it isn't in the database; we remove it from the array later if it is present.
812
813
		$data = array_intersect_key( $posted, array_flip( $allowed_fields ) );
814
		return $data;
815
	}
816
817
	/**
818
	 * Delete an object map row between a WordPress and Salesforce object
819
	 *
820
	 * @param int|array $id The ID or IDs of the object map row(s).
821
	 * @return bool whether it was deleted
822
	 */
823
	public function delete_object_map( $id = '' ) {
824
		if ( is_string( $id ) || is_int( $id ) ) {
825
			$data   = array(
826
				'id' => $id,
827
			);
828
			$delete = $this->wpdb->delete( $this->object_map_table, $data );
829
			if ( 1 === $delete ) {
830
				return true;
831
			} else {
832
				return false;
833
			}
834
		} elseif ( is_array( $id ) ) {
0 ignored issues
show
introduced by
The condition is_array($id) is always true.
Loading history...
835
			$ids    = implode( ',', array_map( 'absint', $id ) );
836
			$delete = $this->wpdb->query( "DELETE FROM $this->object_map_table WHERE ID IN ($ids)" );
837
			if ( false !== $delete ) {
838
				return true;
839
			} else {
840
				return false;
841
			}
842
		}
843
	}
844
845
	/**
846
	 * Generate a temporary ID to store while waiting for a push or pull to complete, before the record has been assigned a new ID
847
	 *
848
	 * @param string $direction Whether this is part of a push or pull action.
849
	 * @return string $id is a temporary string that will be replaced if the modification is successful.
850
	 */
851
	public function generate_temporary_id( $direction ) {
852
		if ( 'push' === $direction ) {
853
			$prefix = 'tmp_sf_';
854
		} elseif ( 'pull' === $direction ) {
855
			$prefix = 'tmp_wp_';
856
		}
857
		$id = uniqid( $prefix, true );
858
		return $id;
859
	}
860
861
	/**
862
	 * Returns Salesforce object mappings for a given WordPress object.
863
	 *
864
	 * @param string $object_type Type of object to load.
865
	 * @param int    $object_id Unique identifier of the target object to load.
866
	 * @param bool   $reset Whether or not the cache should be cleared and fetch from current data.
867
	 *
868
	 * @return array of object maps
869
	 */
870
	public function load_all_by_wordpress( $object_type, $object_id, $reset = false ) {
871
		$conditions = array(
872
			'wordpress_id'     => $object_id,
873
			'wordpress_object' => $object_type,
874
		);
875
		return $this->get_all_object_maps( $conditions, $reset );
876
	}
877
878
	/**
879
	 * Returns Salesforce object mappings for a given Salesforce object.
880
	 *
881
	 * @param string $salesforce_id Type of object to load.
882
	 * @param bool   $reset Whether or not the cache should be cleared and fetch from current data.
883
	 *
884
	 * @return array $maps all the object maps that match the Salesforce Id
885
	 */
886
	public function load_all_by_salesforce( $salesforce_id, $reset = false ) {
887
		$conditions = array(
888
			'salesforce_id' => $salesforce_id,
889
		);
890
891
		$maps = $this->get_all_object_maps( $conditions, $reset );
892
893
		return $maps;
894
	}
895
896
	/**
897
	 * Map values between WordPress and Salesforce objects.
898
	 *
899
	 * @param array  $mapping The fieldmap that maps these types together.
900
	 * @param array  $object WordPress or Salesforce object data.
901
	 * @param array  $trigger The thing that triggered this mapping.
902
	 * @param bool   $use_soap Flag to enforce use of the SOAP API.
903
	 * @param bool   $is_new Indicates whether a mapping object for this entity already exists.
904
	 * @param string $object_id_field optionally pass the object id field name.
905
	 * @return array Associative array of key value pairs.
906
	 */
907
	public function map_params( $mapping, $object, $trigger, $use_soap = false, $is_new = true, $object_id_field = '' ) {
0 ignored issues
show
Unused Code introduced by
The parameter $is_new is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

907
	public function map_params( $mapping, $object, $trigger, $use_soap = false, /** @scrutinizer ignore-unused */ $is_new = true, $object_id_field = '' ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
908
909
		$params = array();
910
911
		// these are the triggers that define whether the action was from WordPress or Salesforce.
912
		$wordpress_haystack  = array_values( $this->wordpress_events );
0 ignored issues
show
Bug introduced by
$this->wordpress_events of type string is incompatible with the type array expected by parameter $array of array_values(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

912
		$wordpress_haystack  = array_values( /** @scrutinizer ignore-type */ $this->wordpress_events );
Loading history...
913
		$salesforce_haystack = array_values( $this->salesforce_events );
914
915
		$has_missing_required_salesforce_field = false;
916
		foreach ( $mapping['fields'] as $fieldmap ) {
917
918
			$fieldmap['wordpress_field']['methods'] = maybe_unserialize( $fieldmap['wordpress_field']['methods'] );
919
920
			$wordpress_field = $fieldmap['wordpress_field']['label'];
921
922
			if ( version_compare( $this->user_installed_version, '1.2.0', '>=' ) && isset( $fieldmap['salesforce_field']['name'] ) ) {
923
				$salesforce_field = $fieldmap['salesforce_field']['name'];
924
				// Load the type of the Salesforce field. We can use this to handle Salesforce field value issues that come up based on what the field sends into WordPress or expects from WordPress.
925
				$salesforce_field_type = $fieldmap['salesforce_field']['type'];
926
			} else {
927
				$salesforce_field = $fieldmap['salesforce_field']['label'];
928
			}
929
930
			// A WordPress event caused this.
931
			if ( in_array( $trigger, array_values( $wordpress_haystack ), true ) ) {
932
933
				// Is the field in WordPress an array, if we unserialize it? Salesforce wants it to be an imploded string.
934
				if ( is_array( maybe_unserialize( $object[ $wordpress_field ] ) ) ) {
935
					// if the WordPress field is a list of capabilities (the source field is wp_capabilities), we need to get the array keys from WordPress to send them to Salesforce.
936
					if ( 'wp_capabilities' === $wordpress_field ) {
937
						$object[ $wordpress_field ] = implode( $this->array_delimiter, array_keys( $object[ $wordpress_field ] ) );
938
					} else {
939
						$object[ $wordpress_field ] = implode( $this->array_delimiter, $object[ $wordpress_field ] );
940
					}
941
				}
942
943
				if ( isset( $salesforce_field_type ) ) {
944
					// Is the Salesforce field a date, and is the WordPress value a valid date?
945
					// According to https://salesforce.stackexchange.com/questions/57032/date-format-with-salesforce-rest-api.
946
					if ( in_array( $salesforce_field_type, $this->date_types_from_salesforce, true ) ) {
0 ignored issues
show
Bug introduced by
$this->date_types_from_salesforce of type string is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

946
					if ( in_array( $salesforce_field_type, /** @scrutinizer ignore-type */ $this->date_types_from_salesforce, true ) ) {
Loading history...
947
						if ( '' === $object[ $wordpress_field ] ) {
948
							$object[ $wordpress_field ] = null;
949
						} else {
950
							if ( false !== strtotime( $object[ $wordpress_field ] ) ) {
951
								$timestamp = strtotime( $object[ $wordpress_field ] );
952
							} else {
953
								$timestamp = $object[ $wordpress_field ];
954
							}
955
							if ( 'datetime' === $salesforce_field_type ) {
956
								$object[ $wordpress_field ] = date_i18n( 'c', $timestamp );
957
							} else {
958
								$object[ $wordpress_field ] = date_i18n( 'Y-m-d', $timestamp );
959
							}
960
						}
961
					}
962
963
					// Boolean SF fields only want real boolean values. NULL is also not allowed.
964
					if ( 'boolean' === $salesforce_field_type ) {
965
						$object[ $wordpress_field ] = (bool) $object[ $wordpress_field ];
966
					}
967
				}
968
969
				$params[ $salesforce_field ] = $object[ $wordpress_field ];
970
971
				// If the field is a key in Salesforce, remove it from $params to avoid upsert errors from Salesforce,
972
				// but still put its name in the params array so we can check for it later.
973
				if ( '1' === $fieldmap['is_key'] ) {
974
					if ( ! $use_soap ) {
975
						unset( $params[ $salesforce_field ] );
976
					}
977
					$params['key'] = array(
978
						'salesforce_field' => $salesforce_field,
979
						'wordpress_field'  => $wordpress_field,
980
						'value'            => $object[ $wordpress_field ],
981
					);
982
				}
983
984
				// If the field is a prematch in Salesforce, put its name in the params array so we can check for it later.
985
				if ( '1' === $fieldmap['is_prematch'] ) {
986
					$params['prematch'] = array(
987
						'salesforce_field' => $salesforce_field,
988
						'wordpress_field'  => $wordpress_field,
989
						'value'            => $object[ $wordpress_field ],
990
					);
991
				}
992
993
				// Skip fields that aren't being pushed to Salesforce.
994
				if ( ! in_array( $fieldmap['direction'], array_values( $this->direction_wordpress ), true ) ) {
995
					// The trigger is a WordPress trigger, but the fieldmap direction is not a WordPress direction.
996
					unset( $params[ $salesforce_field ] );
997
				}
998
999
				// I think it's good to over-mention that updateable is really how the Salesforce api spells it.
1000
				// Skip fields that aren't updateable when mapping params because Salesforce will error otherwise.
1001
				// This happens after dealing with the field types because key and prematch should still be available to the plugin, even if the values are not updateable in Salesforce.
1002
				if ( 1 !== (int) $fieldmap['salesforce_field']['updateable'] ) {
1003
					unset( $params[ $salesforce_field ] );
1004
				}
1005
1006
				// This case means the following:
1007
				// this field is expected by the fieldmap
1008
				// Salesforce's api reports that this field is required
1009
				// we do not have a WordPress value for this field, or it's empty
1010
				// it also means the field has not been unset by prematch, updateable, key, or directional flags prior to this check.
1011
				// When this happens, we should flag that we're missing a required Salesforce field.
1012
				if ( in_array( $salesforce_field, $params, true ) && false === filter_var( $fieldmap['salesforce_field']['nillable'], FILTER_VALIDATE_BOOLEAN ) && ( ! isset( $object[ $wordpress_field ] ) || '' === $object[ $wordpress_field ] ) ) {
1013
					$has_missing_required_salesforce_field = true;
1014
				}
1015
1016
				// we don't need a continue with the unset methods because there's no array being created down here.
1017
			} elseif ( in_array( $trigger, $salesforce_haystack, true ) ) {
1018
1019
				// A Salesforce event caused this.
1020
1021
				if ( isset( $salesforce_field_type ) && isset( $object[ $salesforce_field ] ) && ! is_null( $object[ $salesforce_field ] ) ) {
1022
					// Salesforce provides multipicklist values as a delimited string. If the
1023
					// destination field in WordPress accepts multiple values, explode the string into an array and then serialize it.
1024
					if ( in_array( $salesforce_field_type, $this->array_types_from_salesforce, true ) ) {
1025
						$object[ $salesforce_field ] = explode( $this->array_delimiter, $object[ $salesforce_field ] );
1026
						// if the WordPress field is a list of capabilities (the destination field is wp_capabilities), we need to set the array for WordPress to save it.
1027
						if ( 'wp_capabilities' === $wordpress_field ) {
1028
							$capabilities = array();
1029
							foreach ( $object[ $salesforce_field ] as $capability ) {
1030
								$capabilities[ $capability ] = true;
1031
							}
1032
							$object[ $salesforce_field ] = $capabilities;
1033
						}
1034
					}
1035
1036
					// Handle specific data types from Salesforce.
1037
					switch ( $salesforce_field_type ) {
1038
						case ( in_array( $salesforce_field_type, $this->date_types_from_salesforce, true ) ):
1039
							$format = get_option( 'date_format', 'U' );
1040
							if ( isset( $fieldmap['wordpress_field']['type'] ) && 'datetime' === $fieldmap['wordpress_field']['type'] ) {
1041
								$format = 'Y-m-d H:i:s';
1042
							}
1043
							if ( 'tribe_events' === $mapping['wordpress_object'] && class_exists( 'Tribe__Events__Main' ) ) {
1044
								$format = 'Y-m-d H:i:s';
1045
							}
1046
							if ( 'datetime' === $salesforce_field_type ) {
1047
								// Note: the Salesforce REST API appears to always return datetimes as GMT values. We should retrieve them that way, then format them to deal with them in WordPress appropriately.
1048
								// We should not do any converting unless it's a datetime, because if it's a date, Salesforce stores it as midnight. We don't want to convert that.
1049
								$object[ $salesforce_field ] = get_date_from_gmt( $object[ $salesforce_field ], 'Y-m-d\TH:i:s\Z' ); // convert from GMT to local date/time based on WordPress time zone setting.
1050
							}
1051
							$object[ $salesforce_field ] = date_i18n( $format, strtotime( $object[ $salesforce_field ] ) );
1052
							break;
1053
						case ( in_array( $salesforce_field_type, $this->int_types_from_salesforce, true ) ):
1054
							$object[ $salesforce_field ] = isset( $object[ $salesforce_field ] ) ? (int) $object[ $salesforce_field ] : 0;
1055
							break;
1056
						case 'text':
1057
							$object[ $salesforce_field ] = (string) $object[ $salesforce_field ];
1058
							break;
1059
						case 'url':
1060
							$object[ $salesforce_field ] = esc_url_raw( $object[ $salesforce_field ] );
1061
							break;
1062
					} // end switch
1063
1064
					// set a default WordPress status value based on the fieldmap.
1065
					$params['default_status'] = $mapping['wordpress_object_default_status'];
1066
				} // end if for Salesforce event
1067
1068
				// Make an array because we need to store the methods for each field as well.
1069
				if ( isset( $object[ $salesforce_field ] ) ) {
1070
					$params[ $wordpress_field ]          = array();
1071
					$params[ $wordpress_field ]['value'] = $object[ $salesforce_field ];
1072
				} elseif ( is_null( $object[ $salesforce_field ] ) ) {
1073
					// Salesforce returns blank fields as null fields; set them to blank.
1074
					$params[ $wordpress_field ]          = array();
1075
					$params[ $wordpress_field ]['value'] = '';
1076
				} else {
1077
					// prevent fields that don't exist from being passed.
1078
					continue;
1079
				}
1080
1081
				// If the field is a key in Salesforce, disregard since this is caused by a Salesforce event. We're setting up data to be stored in WordPress here, and WordPress is not concerned with external key designations in Salesforce.
1082
1083
				// If the field is a prematch in Salesforce, put its name in the params array so we can check for it later.
1084
				if ( '1' === $fieldmap['is_prematch'] ) {
1085
					$params['prematch'] = array(
1086
						'salesforce_field' => $salesforce_field,
1087
						'wordpress_field'  => $wordpress_field,
1088
						'value'            => $object[ $salesforce_field ],
1089
						'method_match'     => isset( $fieldmap['wordpress_field']['methods']['match'] ) ? $fieldmap['wordpress_field']['methods']['match'] : $fieldmap['wordpress_field']['methods']['read'],
1090
						'method_read'      => $fieldmap['wordpress_field']['methods']['read'],
1091
						'method_create'    => $fieldmap['wordpress_field']['methods']['create'],
1092
						'method_update'    => $fieldmap['wordpress_field']['methods']['update'],
1093
					);
1094
				}
1095
1096
				// Skip fields that aren't being pulled from Salesforce.
1097
				if ( ! in_array( $fieldmap['direction'], array_values( $this->direction_salesforce ), true ) ) {
1098
					// The trigger is a Salesforce trigger, but the fieldmap direction is not a Salesforce direction.
1099
					unset( $params[ $wordpress_field ] );
1100
					// we also need to continue here, so it doesn't create an empty array below for fields that are WordPress -> Salesforce only.
1101
					continue;
1102
				}
1103
1104
				switch ( $trigger ) {
1105
					case $this->sync_sf_create:
1106
						$params[ $wordpress_field ]['method_modify'] = $fieldmap['wordpress_field']['methods']['create'];
1107
						break;
1108
					case $this->sync_sf_update:
1109
						$params[ $wordpress_field ]['method_modify'] = $fieldmap['wordpress_field']['methods']['update'];
1110
						break;
1111
					case $this->sync_sf_delete:
1112
						$params[ $wordpress_field ]['method_modify'] = $fieldmap['wordpress_field']['methods']['delete'];
1113
						break;
1114
				}
1115
1116
				// always allow for the delete and read methods.
1117
				$params[ $wordpress_field ]['method_delete'] = $fieldmap['wordpress_field']['methods']['delete'];
1118
				$params[ $wordpress_field ]['method_read']   = $fieldmap['wordpress_field']['methods']['read'];
1119
1120
			} // End if() statement.
1121
		} // End foreach() loop.
1122
1123
		if ( true === $has_missing_required_salesforce_field ) {
1124
			update_option( $this->option_prefix . 'missing_required_data_id_' . $object[ $object_id_field ], true, false );
1125
			return array();
1126
		}
1127
1128
		return $params;
1129
1130
	}
1131
1132
	/**
1133
	 * Prepare field map data for use
1134
	 *
1135
	 * @param array  $mappings Array of fieldmaps.
1136
	 * @param string $record_type Optional Salesforce record type to see if it is allowed or not.
1137
	 *
1138
	 * @return array $mappings Associative array of field maps ready to use
1139
	 */
1140
	private function prepare_fieldmap_data( $mappings, $record_type = '' ) {
1141
		foreach ( $mappings as $id => $mapping ) {
1142
			$mappings[ $id ]['salesforce_record_types_allowed'] = isset( $mapping['salesforce_record_types_allowed'] ) ? maybe_unserialize( $mapping['salesforce_record_types_allowed'] ) : array();
1143
			$mappings[ $id ]['fields']                          = isset( $mapping['fields'] ) ? maybe_unserialize( $mapping['fields'] ) : array();
1144
			$mappings[ $id ]['sync_triggers']                   = isset( $mapping['sync_triggers'] ) ? maybe_unserialize( $mapping['sync_triggers'] ) : array();
1145
1146
			// reverse async to get immediately for user friendliness.
1147
			if ( true === filter_var( $mappings[ $id ]['push_async'], FILTER_VALIDATE_BOOLEAN ) ) {
1148
				$mappings[ $id ]['push_immediately'] = '';
1149
			} else {
1150
				$mappings[ $id ]['push_immediately'] = '1';
1151
			}
1152
1153
			if ( '' !== $record_type && ! in_array( $record_type, $mappings[ $id ]['salesforce_record_types_allowed'], true ) ) {
1154
				unset( $mappings[ $id ] );
1155
			}
1156
			// format the sync triggers.
1157
			$sync_triggers                    = $this->maybe_upgrade_sync_triggers( $mappings[ $id ]['sync_triggers'], $mapping['version'], $mapping['id'] );
1158
			$mappings[ $id ]['sync_triggers'] = $sync_triggers;
1159
		}
1160
		return $mappings;
1161
	}
1162
1163
	/**
1164
	 * Format the sync trigger values for storage in the database.
1165
	 *
1166
	 * @param array  $sync_triggers Array of sync triggers.
1167
	 * @param string $mapping_version the database version when the fieldmmap was saved.
1168
	 * @param int    $mapping_id if the fieldmap already exists, this is the ID.
1169
	 *
1170
	 * @return array $sync_triggers possibly updated array of sync triggers.
1171
	 */
1172
	private function maybe_upgrade_sync_triggers( $sync_triggers = array(), $mapping_version, $mapping_id = '' ) {
1173
		// in v2 of this plugin, we replaced the bit flags with strings to make them more legible.
1174
		if ( version_compare( $mapping_version, '2.0.0', '<' ) ) {
1175
			// check if the triggers stored in the database are up to date. if not, update them.
1176
			$intersect = array_intersect( $sync_triggers, array_merge( $this->wordpress_events, $this->salesforce_events ) );
0 ignored issues
show
Bug introduced by
$this->wordpress_events of type string is incompatible with the type array expected by parameter $arrays of array_merge(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1176
			$intersect = array_intersect( $sync_triggers, array_merge( /** @scrutinizer ignore-type */ $this->wordpress_events, $this->salesforce_events ) );
Loading history...
1177
			if ( empty( $intersect ) ) {
1178
				$updated_sync_triggers = array();
1179
				foreach ( $sync_triggers as $key => $value ) {
1180
					if ( $value === (string) $this->sync_off_v1 ) {
1181
						$updated_sync_triggers[] = $this->sync_off;
1182
					}
1183
					if ( $value === (string) $this->sync_wordpress_create_v1 ) {
1184
						$updated_sync_triggers[] = $this->sync_wordpress_create;
1185
					}
1186
					if ( $value === (string) $this->sync_wordpress_update_v1 ) {
1187
						$updated_sync_triggers[] = $this->sync_wordpress_update;
1188
					}
1189
					if ( $value === (string) $this->sync_wordpress_delete_v1 ) {
1190
						$updated_sync_triggers[] = $this->sync_wordpress_delete;
1191
					}
1192
					if ( $value === (string) $this->sync_sf_create_v1 ) {
1193
						$updated_sync_triggers[] = $this->sync_sf_create;
1194
					}
1195
					if ( $value === (string) $this->sync_sf_update_v1 ) {
1196
						$updated_sync_triggers[] = $this->sync_sf_update;
1197
					}
1198
					if ( $value === (string) $this->sync_sf_delete_v1 ) {
1199
						$updated_sync_triggers[] = $this->sync_sf_delete;
1200
					}
1201
				}
1202
1203
				if ( '' !== $mapping_id ) {
1204
					// format the fieldmap update query for the database.
1205
					$data = array();
1206
					if ( ! empty( $updated_sync_triggers ) ) {
1207
						$data['sync_triggers'] = array();
1208
						foreach ( $updated_sync_triggers as $key => $value ) {
1209
							$updated_sync_triggers[ $key ] = esc_html( $updated_sync_triggers[ $key ] );
1210
						}
1211
						$data['sync_triggers'] = maybe_serialize( $updated_sync_triggers );
1212
						$data['version']       = get_option( $this->option_prefix . 'db_version', $this->version );
1213
						// update the sync triggers and version fieldmap in the database.
1214
						$update = $this->wpdb->update(
1215
							$this->fieldmap_table,
1216
							$data,
1217
							array(
1218
								'id' => $mapping_id,
1219
							)
1220
						);
1221
					}
1222
				}
1223
			}
1224
		}
1225
		// whether it was updated or not, this is the array of sync triggers.
1226
		return $sync_triggers;
1227
	}
1228
1229
	/**
1230
	 * Check object map table to see if there have been any failed object map create attempts
1231
	 *
1232
	 * @return array $errors Associative array of rows that failed to finish from either system
1233
	 */
1234
	public function get_failed_object_maps() {
1235
		$table                 = $this->object_map_table;
1236
		$errors                = array();
1237
		$items_per_page        = (int) get_option( $this->option_prefix . 'errors_per_page', 50 );
1238
		$current_error_page    = isset( $_GET['error_page'] ) ? (int) $_GET['error_page'] : 1;
1239
		$offset                = ( $current_error_page * $items_per_page ) - $items_per_page;
1240
		$all_errors            = $this->wpdb->get_results( "SELECT * FROM ${table} WHERE salesforce_id LIKE 'tmp_sf_%' OR wordpress_id LIKE 'tmp_wp_%' LIMIT ${offset}, ${items_per_page}", ARRAY_A );
1241
		$errors_total          = $this->wpdb->get_var( "SELECT COUNT(`id`) FROM ${table} WHERE salesforce_id LIKE 'tmp_sf_%' OR wordpress_id LIKE 'tmp_wp_%'" );
1242
		$errors['total_pages'] = ceil( $errors_total / $items_per_page );
1243
		$errors['pagination']  = paginate_links(
1244
			array(
1245
				'base'      => add_query_arg( 'error_page', '%#%' ),
1246
				'format'    => '',
1247
				'total'     => $errors['total_pages'],
1248
				'prev_text' => __( '&laquo;', 'object-sync-for-salesforce' ),
1249
				'next_text' => __( '&raquo;', 'object-sync-for-salesforce' ),
1250
				'current'   => $current_error_page,
1251
			)
1252
		);
1253
		$errors['error_page']  = $current_error_page;
1254
		$errors['all_errors']  = $all_errors;
1255
		$errors['total']       = $errors_total;
1256
		return $errors;
1257
	}
1258
1259
	/**
1260
	 * Check object map table to see if there have been any failed object map create attempts
1261
	 *
1262
	 * @param int $id The ID of a desired mapping.
1263
	 * @return array $error Associative array of single row that failed to finish based on id
1264
	 */
1265
	public function get_failed_object_map( $id ) {
1266
		$table     = $this->object_map_table;
1267
		$error     = array();
1268
		$error_row = $this->wpdb->get_row( 'SELECT * FROM ' . $table . ' WHERE id = "' . $id . '"', ARRAY_A );
1269
		if ( ! empty( $error_row ) ) {
1270
			$error = $error_row;
1271
		}
1272
		return $error;
1273
	}
1274
1275
}
1276