Object_Sync_Sf_Salesforce_Push   F
last analyzed

Complexity

Total Complexity 162

Size/Duplication

Total Lines 1576
Duplicated Lines 0 %

Importance

Changes 17
Bugs 7 Features 1
Metric Value
eloc 759
c 17
b 7
f 1
dl 0
loc 1576
rs 1.841
wmc 162

26 Methods

Rating   Name   Duplication   Size   Complexity  
C add_actions() 0 44 13
A add_user() 0 3 1
A acf_save() 0 7 3
A edit_term() 0 3 1
A delete_user() 0 4 1
A delete_attachment() 0 4 1
A delete_term() 0 4 1
A object_update() 0 2 1
A add_term() 0 3 1
A edit_attachment() 0 3 1
A object_delete() 0 2 1
A delete_comment() 0 4 1
A object_insert() 0 2 1
A edit_comment() 0 3 1
A edit_user() 0 3 1
A um_add_user() 0 9 2
A add_comment() 0 3 1
D post_actions() 0 53 18
B manual_push() 0 36 11
A add_attachment() 0 3 1
A __construct() 0 24 1
F salesforce_push_object_crud() 0 242 35
A parse_error_message() 0 9 2
A create_object_map() 0 27 3
F salesforce_push_sync_rest() 0 707 54
A is_push_allowed() 0 41 5

How to fix   Complexity   

Complex Class

Complex classes like Object_Sync_Sf_Salesforce_Push often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Object_Sync_Sf_Salesforce_Push, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Push data from WordPress into Salesforce
4
 *
5
 * @class   Object_Sync_Sf_Salesforce_Push
6
 * @package Object_Sync_Salesforce
7
 */
8
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * Object_Sync_Sf_Salesforce_Push class.
13
 */
14
class Object_Sync_Sf_Salesforce_Push {
15
16
	/**
17
	 * Current version of the plugin
18
	 *
19
	 * @var string
20
	 */
21
	public $version;
22
23
	/**
24
	 * The main plugin file
25
	 *
26
	 * @var string
27
	 */
28
	public $file;
29
30
	/**
31
	 * Global object of `$wpdb`, the WordPress database
32
	 *
33
	 * @var object
34
	 */
35
	public $wpdb;
36
37
	/**
38
	 * The plugin's slug so we can include it when necessary
39
	 *
40
	 * @var string
41
	 */
42
	public $slug;
43
44
	/**
45
	 * The plugin's prefix when saving options to the database
46
	 *
47
	 * @var string
48
	 */
49
	public $option_prefix;
50
51
	/**
52
	 * Login credentials for the Salesforce API; comes from wp-config or from the plugin settings
53
	 *
54
	 * @var array
55
	 */
56
	public $login_credentials;
57
58
	/**
59
	 * Array of what classes in the plugin can be scheduled to occur with `wp_cron` events
60
	 *
61
	 * @var array
62
	 */
63
	public $schedulable_classes;
64
65
	/**
66
	 * Object_Sync_Sf_Queue class
67
	 *
68
	 * @var object
69
	 */
70
	public $queue;
71
72
	/**
73
	 * Object_Sync_Sf_Logging class
74
	 *
75
	 * @var object
76
	 */
77
	public $logging;
78
79
	/**
80
	 * Object_Sync_Sf_Mapping class
81
	 *
82
	 * @var object
83
	 */
84
	public $mappings;
85
86
	/**
87
	 * Object_Sync_Sf_WordPress class
88
	 *
89
	 * @var object
90
	 */
91
	public $wordpress;
92
93
	/**
94
	 * Object_Sync_Sf_Salesforce class
95
	 * This contains Salesforce API methods
96
	 *
97
	 * @var array
98
	 */
99
	public $salesforce;
100
101
	/**
102
	 * Object_Sync_Sf_Sync_Transients class
103
	 *
104
	 * @var object
105
	 */
106
	public $sync_transients;
107
108
	/**
109
	 * Whether the plugin is in debug mode
110
	 *
111
	 * @var bool
112
	 */
113
	public $debug;
114
115
	/**
116
	 * The name of the ActionScheduler queue
117
	 *
118
	 * @var string
119
	 */
120
	public $schedule_name;
121
122
	/**
123
	 * Constructor for push class
124
	 */
125
	public function __construct() {
126
		$this->version             = object_sync_for_salesforce()->version;
127
		$this->file                = object_sync_for_salesforce()->file;
128
		$this->wpdb                = object_sync_for_salesforce()->wpdb;
129
		$this->slug                = object_sync_for_salesforce()->slug;
130
		$this->option_prefix       = object_sync_for_salesforce()->option_prefix;
131
		$this->login_credentials   = object_sync_for_salesforce()->login_credentials;
132
		$this->schedulable_classes = object_sync_for_salesforce()->schedulable_classes;
133
134
		$this->queue      = object_sync_for_salesforce()->queue;
135
		$this->logging    = object_sync_for_salesforce()->logging;
136
		$this->mappings   = object_sync_for_salesforce()->mappings;
137
		$this->wordpress  = object_sync_for_salesforce()->wordpress;
138
		$this->salesforce = object_sync_for_salesforce()->salesforce;
139
140
		$this->sync_transients = new Object_Sync_Sf_Sync_Transients();
141
142
		$this->schedule_name = 'salesforce_push';
143
144
		// Create action hooks for WordPress objects. We run this after plugins are loaded in case something depends on another plugin.
145
		add_action( 'plugins_loaded', array( $this, 'add_actions' ) );
0 ignored issues
show
Bug introduced by
The function add_action was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

145
		/** @scrutinizer ignore-call */ 
146
  add_action( 'plugins_loaded', array( $this, 'add_actions' ) );
Loading history...
146
147
		// use the option value for whether we're in debug mode.
148
		$this->debug = filter_var( get_option( $this->option_prefix . 'debug_mode', false ), FILTER_VALIDATE_BOOLEAN );
0 ignored issues
show
Bug introduced by
The function get_option was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

148
		$this->debug = filter_var( /** @scrutinizer ignore-call */ get_option( $this->option_prefix . 'debug_mode', false ), FILTER_VALIDATE_BOOLEAN );
Loading history...
149
150
	}
151
152
	/**
153
	 * Create the action hooks based on what object maps exist from the admin settings.
154
	 * We do not have any actions for blogroll at this time.
155
	 */
156
	public function add_actions() {
157
		$db_version = get_option( $this->option_prefix . 'db_version', false );
0 ignored issues
show
Bug introduced by
The function get_option was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

157
		$db_version = /** @scrutinizer ignore-call */ get_option( $this->option_prefix . 'db_version', false );
Loading history...
158
		if ( $db_version === $this->version ) {
159
			foreach ( $this->mappings->get_fieldmaps( null, $this->mappings->active_fieldmap_conditions ) as $mapping ) {
160
				$object_type = $mapping['wordpress_object'];
161
				if ( 'user' === $object_type ) {
162
					if ( defined( 'ultimatemember_plugin_name' ) ) {
163
						add_action( 'um_user_register', array( $this, 'um_add_user' ), 11, 2 );
0 ignored issues
show
Bug introduced by
The function add_action was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

163
						/** @scrutinizer ignore-call */ 
164
      add_action( 'um_user_register', array( $this, 'um_add_user' ), 11, 2 );
Loading history...
164
					} else {
165
						add_action( 'user_register', array( $this, 'add_user' ), 11, 1 );
166
					}
167
					add_action( 'profile_update', array( $this, 'edit_user' ), 11, 2 );
168
					add_action( 'delete_user', array( $this, 'delete_user' ) );
169
				} elseif ( 'post' === $object_type ) {
170
					add_action( 'save_post', array( $this, 'post_actions' ), 11, 2 );
171
					if ( class_exists( 'ACF' ) ) {
172
						// front end forms on ACF use this hook.
173
						add_action( 'acf/save_post', array( $this, 'acf_save' ), 10 );
174
					}
175
				} elseif ( 'attachment' === $object_type ) {
176
					add_action( 'add_attachment', array( $this, 'add_attachment' ) );
177
					add_action( 'edit_attachment', array( $this, 'edit_attachment' ) );
178
					add_action( 'delete_attachment', array( $this, 'delete_attachment' ) );
179
				} elseif ( 'category' === $object_type || 'tag' === $object_type || 'post_tag' === $object_type ) {
180
					add_action( 'create_term', array( $this, 'add_term' ), 11, 3 );
181
					add_action( 'edit_terms', array( $this, 'edit_term' ), 11, 2 );
182
					add_action( 'delete_term', array( $this, 'delete_term' ), 10, 4 );
183
				} elseif ( 'comment' === $object_type ) {
184
					add_action( 'comment_post', array( $this, 'add_comment' ), 11, 3 );
185
					add_action( 'edit_comment', array( $this, 'edit_comment' ) );
186
					add_action( 'delete_comment', array( $this, 'delete_comment' ) ); // to be clear: this only runs when the comment gets deleted from the trash, either manually or automatically.
187
				} else { // this is for custom post types
188
					// we still have to use save_post because save_post_type fails to pull in the metadata.
189
					add_action( 'save_post', array( $this, 'post_actions' ), 11, 2 );
190
					if ( class_exists( 'ACF' ) ) {
191
						// front end forms on ACF use this hook.
192
						add_action( 'acf/save_post', array( $this, 'acf_save' ), 10 );
193
					}
194
				}
195
			}
196
		}
197
198
		// hook that action-scheduler can call.
199
		add_action( $this->option_prefix . 'push_record', array( $this, 'salesforce_push_sync_rest' ), 10, 4 );
200
201
	}
202
203
	/**
204
	 * Method for ajax hooks to call for pushing manually
205
	 *
206
	 * @param string $object_type the WordPress object type.
207
	 * @param int    $wordpress_id the WordPress record ID.
208
	 * @param string $http_method the HTTP method that was called.
209
	 */
210
	public function manual_push( $object_type, $wordpress_id, $http_method ) {
211
		$object = $this->wordpress->get_wordpress_object_data( $object_type, $wordpress_id );
212
		// run the WordPress trigger that corresponds to the HTTP method.
213
		switch ( $http_method ) {
214
			case 'POST':
215
				$trigger = $this->mappings->sync_wordpress_create;
216
				break;
217
			case 'PUT':
218
				$trigger = $this->mappings->sync_wordpress_update;
219
				break;
220
			case 'DELETE':
221
				$trigger = $this->mappings->sync_wordpress_delete;
222
				break;
223
		}
224
		if ( isset( $trigger ) ) {
225
			$results = $this->salesforce_push_object_crud( $object_type, $object, $trigger, true );
226
			foreach ( $results as $result ) {
227
				if ( isset( $result['status'] ) && 'success' === $result['status'] ) {
228
					if ( 'POST' === $http_method || 'PUT' === $http_method ) {
229
						$code = '201';
230
					} elseif ( 'DELETE' === $http_method ) {
231
						$code = '204';
232
					}
233
				} else {
234
					$code = '405';
235
				}
236
			}
237
		} else {
238
			$code   = '405';
239
			$result = '';
240
		}
241
		$result = array(
242
			'code'   => $code,
243
			'result' => $results,
244
		);
245
		return $result;
246
	}
247
248
	/**
249
	 * Callback method for adding a user
250
	 *
251
	 * @param string $user_id the WordPress user ID.
252
	 */
253
	public function add_user( $user_id ) {
254
		$user = $this->wordpress->get_wordpress_object_data( 'user', $user_id );
255
		$this->object_insert( $user, 'user' );
256
	}
257
258
	/**
259
	 * Callback method for adding a user via the Ultimate Member plugin
260
	 *
261
	 * @param string $user_id the WordPress user ID.
262
	 * @param array  $form_data the data that was sent to create the user.
263
	 */
264
	public function um_add_user( $user_id, $form_data = array() ) {
265
		// in at least some cases, the form data array does not have the ID field, and this can cause errors.
266
		// if we don't have the ID field value in the form data, add it so it acts like the others.
267
		$structure               = $this->wordpress->get_wordpress_table_structure( 'user' );
268
		$wordpress_id_field_name = $structure['id_field'];
269
		if ( ! isset( $form_data[ $wordpress_id_field_name ] ) ) {
270
			$form_data[ $wordpress_id_field_name ] = $user_id;
271
		}
272
		$this->object_insert( $form_data, 'user' );
273
	}
274
275
	/**
276
	 * Callback method for editing a user
277
	 *
278
	 * @param string $user_id the WordPress user ID.
279
	 * @param object $old_user_data the previously used user data.
280
	 */
281
	public function edit_user( $user_id, $old_user_data ) {
0 ignored issues
show
Unused Code introduced by
The parameter $old_user_data 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

281
	public function edit_user( $user_id, /** @scrutinizer ignore-unused */ $old_user_data ) {

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...
282
		$user = $this->wordpress->get_wordpress_object_data( 'user', $user_id );
283
		$this->object_update( $user, 'user' );
284
	}
285
286
	/**
287
	 * Callback method for deleting a user
288
	 *
289
	 * @param string $user_id the WordPress user ID.
290
	 */
291
	public function delete_user( $user_id ) {
292
		// flag that this item has been deleted.
293
		$user = $this->wordpress->get_wordpress_object_data( 'user', $user_id, true );
294
		$this->object_delete( $user, 'user' );
295
	}
296
297
	/**
298
	 * Callback method for saving a post with ACF. If it's a front end save, send the data to post_actions.
299
	 *
300
	 * @param int $post_id the ID of the post.
301
	 */
302
	public function acf_save( $post_id ) {
303
		if ( is_admin() ) {
0 ignored issues
show
Bug introduced by
The function is_admin was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

303
		if ( /** @scrutinizer ignore-call */ is_admin() ) {
Loading history...
304
			return;
305
		}
306
		$post = get_post( $post_id );
0 ignored issues
show
Bug introduced by
The function get_post was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

306
		$post = /** @scrutinizer ignore-call */ get_post( $post_id );
Loading history...
307
		if ( null !== $post ) {
308
			$this->post_actions( $post_id, $post );
309
		}
310
	}
311
312
	/**
313
	 * Callback method for posts of any type
314
	 * This can handle create, update, and delete actions
315
	 *
316
	 * @param string        $post_id the WordPress post ID.
317
	 * @param WP_Post|array $post the WordPress post object. For our purposes, it's never an array.
0 ignored issues
show
Bug introduced by
The type WP_Post was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
318
	 */
319
	public function post_actions( $post_id, $post ) {
320
321
		// if this runs on a non-object due to another operation, don't continue.
322
		if ( ! is_object( $post ) ) {
323
			return;
324
		}
325
326
		$post_type = $post->post_type;
327
328
		if ( isset( $post->post_status ) && 'auto-draft' === $post->post_status ) {
329
			return;
330
		}
331
		// this plugin does not sync log, revision, or scheduled-action posts with Salesforce since they're all included in this plugin for other purposes.
332
		if ( isset( $post->post_type ) && in_array( $post->post_type, array( 'wp_log', 'revision', 'scheduled-action' ), true ) ) {
333
			return;
334
		}
335
		if ( $post->post_modified_gmt === $post->post_date_gmt && 'trash' !== $post->post_status ) {
336
			$update = 0;
337
			$delete = 0;
338
		} elseif ( 'trash' !== $post->post_status ) {
339
			$update = 1;
340
			$delete = 0;
341
		} elseif ( 'trash' === $post->post_status ) {
342
			$update = 0;
343
			$delete = 1;
344
		}
345
346
		// add support for Woocommerce if it is installed.
347
		if ( defined( 'WC_VERSION' ) ) {
348
			// statuses to ignore.
349
			if ( isset( $post->post_status ) && in_array( $post->post_status, array( 'wc-pending' ), true ) ) {
350
				return;
351
			}
352
			// statuses to count as new. note that the api will also check to see if it already has been mapped before saving.
353
			if ( isset( $post->post_status ) && in_array( $post->post_status, array( 'wc-on-hold', 'wc-processing' ), true ) ) {
354
				$update = 0;
355
				$delete = 0;
356
			}
357
		}
358
359
		// if it is NOT a deletion, don't flag it as such.
360
		if ( 1 !== $delete ) {
361
			$post = $this->wordpress->get_wordpress_object_data( $post->post_type, $post_id );
362
		} else {
363
			// otherwise, flag that this item has been deleted.
364
			$post = $this->wordpress->get_wordpress_object_data( $post->post_type, $post_id, true );
365
		}
366
		if ( 1 === $update ) {
367
			$this->object_update( $post, $post_type );
368
		} elseif ( 1 === $delete ) {
369
			$this->object_delete( $post, $post_type );
370
		} else {
371
			$this->object_insert( $post, $post_type );
372
		}
373
	}
374
375
	/**
376
	 * Callback method for adding an attachment
377
	 *
378
	 * @param string $post_id the WordPress attachment ID.
379
	 */
380
	public function add_attachment( $post_id ) {
381
		$attachment = $this->wordpress->get_wordpress_object_data( 'attachment', $post_id );
382
		$this->object_insert( $attachment, 'attachment' );
383
	}
384
385
	/**
386
	 * Callback method for editing an attachment
387
	 *
388
	 * @param string $post_id the WordPress attachment ID.
389
	 */
390
	public function edit_attachment( $post_id ) {
391
		$attachment = $this->wordpress->get_wordpress_object_data( 'attachment', $post_id );
392
		$this->object_update( $attachment, 'attachment' );
393
	}
394
395
	/**
396
	 * Callback method for deleting an attachment
397
	 *
398
	 * @param string $post_id the WordPress attachment ID.
399
	 */
400
	public function delete_attachment( $post_id ) {
401
		// flag that this item has been deleted.
402
		$attachment = $this->wordpress->get_wordpress_object_data( 'attachment', $post_id, true );
403
		$this->object_delete( $attachment, 'attachment' );
404
	}
405
406
	/**
407
	 * Callback method for adding a term
408
	 *
409
	 * @param string $term_id the term ID.
410
	 * @param string $tt_id the taxonomy ID.
411
	 * @param string $taxonomy the taxonomy name.
412
	 */
413
	public function add_term( $term_id, $tt_id, $taxonomy ) {
414
		$term = $this->wordpress->get_wordpress_object_data( $taxonomy, $term_id );
415
		$this->object_insert( $term, $taxonomy );
416
	}
417
418
	/**
419
	 * Callback method for editing a term
420
	 *
421
	 * @param string $term_id the term ID.
422
	 * @param string $taxonomy the taxonomy name.
423
	 */
424
	public function edit_term( $term_id, $taxonomy ) {
425
		$term = $this->wordpress->get_wordpress_object_data( $taxonomy, $term_id );
426
		$this->object_update( $term, $taxonomy );
427
	}
428
429
	/**
430
	 * Callback method for deleting a term
431
	 *
432
	 * @param int    $term (id).
433
	 * @param int    $tt_id the term taxonomy ID.
434
	 * @param string $taxonomy (slug).
435
	 * @param object $deleted_term the deleted term object.
436
	 */
437
	public function delete_term( $term, $tt_id, $taxonomy, $deleted_term ) {
438
		$deleted_term = (array) $deleted_term;
439
		$type         = $deleted_term['taxonomy'];
440
		$this->object_delete( $deleted_term, $type );
441
	}
442
443
	/**
444
	 * Callback method for adding a comment
445
	 *
446
	 * @param string $comment_id the WordPress comment ID.
447
	 * @param string $comment_approved if the comment was approved.
448
	 * @param array  $commentdata the data for the comment.
449
	 */
450
	public function add_comment( $comment_id, $comment_approved, $commentdata = array() ) {
0 ignored issues
show
Unused Code introduced by
The parameter $commentdata 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

450
	public function add_comment( $comment_id, $comment_approved, /** @scrutinizer ignore-unused */ $commentdata = array() ) {

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...
Unused Code introduced by
The parameter $comment_approved 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

450
	public function add_comment( $comment_id, /** @scrutinizer ignore-unused */ $comment_approved, $commentdata = array() ) {

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...
451
		$comment = $this->wordpress->get_wordpress_object_data( 'comment', $comment_id );
452
		$this->object_insert( $comment, 'comment' );
453
	}
454
455
	/**
456
	 * Callback method for editing a comment
457
	 *
458
	 * @param string $comment_id the WordPress comment ID.
459
	 */
460
	public function edit_comment( $comment_id ) {
461
		$comment = $this->wordpress->get_wordpress_object_data( 'comment', $comment_id );
462
		$this->object_update( $comment, 'comment' );
463
	}
464
465
	/**
466
	 * Callback method for deleting a comment
467
	 *
468
	 * @param string $comment_id the WordPress comment ID.
469
	 */
470
	public function delete_comment( $comment_id ) {
471
		// flag that this item has been deleted.
472
		$comment = $this->wordpress->get_wordpress_object_data( 'comment', $comment_id, true );
473
		$this->object_delete( $comment, 'comment' );
474
	}
475
476
	/**
477
	 * Insert a new object
478
	 * This calls the overall push crud method, which controls queuing and sending data to the Salesforce class.
479
	 *
480
	 * @param array  $object the object data to send to WordPress.
481
	 * @param string $type the WordPress object type.
482
	 */
483
	private function object_insert( $object, $type ) {
484
		$this->salesforce_push_object_crud( $type, $object, $this->mappings->sync_wordpress_create );
485
	}
486
487
	/**
488
	 * Update an existing object
489
	 * This calls the overall push crud method, which controls queuing and sending data to the Salesforce class.
490
	 *
491
	 * @param array  $object the object data to send to WordPress.
492
	 * @param string $type the WordPress object type.
493
	 */
494
	private function object_update( $object, $type ) {
495
		$this->salesforce_push_object_crud( $type, $object, $this->mappings->sync_wordpress_update );
496
	}
497
498
	/**
499
	 * Delete an existing object
500
	 * This calls the overall push crud method, which controls queuing and sending data to the Salesforce class.
501
	 *
502
	 * @param array  $object the object data to send to WordPress.
503
	 * @param string $type the WordPress object type.
504
	 */
505
	private function object_delete( $object, $type ) {
506
		$this->salesforce_push_object_crud( $type, $object, $this->mappings->sync_wordpress_delete );
507
	}
508
509
	/**
510
	 * Push objects to Salesforce.
511
	 * This method decides whether to do the processing immediately or queue it to the schedule class (or skip it based on another plugin's activity)
512
	 *
513
	 * @param string $object_type Type of WordPress object.
514
	 * @param array  $object The WordPress data that needs to be sent to Salesforce.
515
	 * @param int    $sf_sync_trigger The trigger being responded to.
516
	 * @param bool   $manual check if we are calling this manually.
517
	 */
518
	private function salesforce_push_object_crud( $object_type, $object, $sf_sync_trigger, $manual = false ) {
519
520
		$structure               = $this->wordpress->get_wordpress_table_structure( $object_type );
521
		$wordpress_id_field_name = $structure['id_field'];
522
523
		$transients_to_delete = array();
524
525
		// load mappings that match this criteria
526
		// in this case, it's all mappings that correspond to the posted WordPress object.
527
		$sf_mappings = $this->mappings->get_fieldmaps(
528
			null, // id field must be null for multiples.
529
			array_merge(
530
				$this->mappings->active_fieldmap_conditions,
531
				array(
532
					'wordpress_object' => $object_type,
533
				)
534
			),
535
		);
536
537
		$results = array();
538
539
		foreach ( $sf_mappings as $fieldmap_key => $mapping ) { // for each mapping of this object.
540
541
			$transients_to_delete[ $fieldmap_key ] = array(
542
				'fieldmap'   => $mapping,
543
				'transients' => array(),
544
			);
545
546
			// there is a WordPress object to push.
547
			if ( isset( $object[ $wordpress_id_field_name ] ) ) {
548
				// todo: we might want to loop through these?
549
				$mapping_object = $this->mappings->load_all_by_wordpress( $object_type, $object[ $wordpress_id_field_name ] );
550
				if ( ! empty( $mapping_object ) ) {
551
					$mapping_object = $mapping_object[0];
552
				}
553
554
				// there is already a mapping object for this WordPress object.
555
				if ( isset( $mapping_object['id'] ) ) {
556
					// if there's already a transient for pulling this Salesforce record, its ID will be stored in the transient.
557
					$mapping_object_id_transient = $mapping_object['salesforce_id'];
558
					$transient_is_pulling        = (int) $this->sync_transients->get( 'salesforce_pulling_' . $mapping_object_id_transient, '', $mapping['id'] );
559
				} else {
560
					// there is not a mapping object for this WordPress object id yet
561
					// check for an existing transient for pulling this Salesforce record. If it exists, the Salesforce ID will be stored in the transient.
562
					$mapping_object_id_transient = $this->sync_transients->get( 'salesforce_pulling_object_id', '', $mapping['id'] );
563
					$transient_is_pulling        = (int) $this->sync_transients->get( 'salesforce_pulling_' . $mapping_object_id_transient, '', $mapping['id'] );
564
				}
565
566
				// if there is a valid transient value, we're currently pulling this record and not pushing it.
567
				if ( 1 === $transient_is_pulling ) {
568
					$salesforce_pulling = 1;
569
				} else {
570
					$salesforce_pulling = 0;
571
				}
572
573
				if ( 1 === $salesforce_pulling ) {
574
					// if it is pulling, delete the transient and continue on through the loop.
575
					// we need to either do this for every individual mapping object, or only do it when all the mapping objects are done.
576
							$transients_to_delete[ $fieldmap_key ]['transients'][] = $mapping_object_id_transient;
577
					if ( true === $this->debug ) {
578
						// create log entry for failed pull.
579
						$status = 'debug';
580
						$title  = sprintf(
581
							// translators: placeholders are: 1) the log status, 2) the mapping object ID transient.
582
							esc_html__( '%1$s: mapping object transient ID %2$s is currently pulling, so we do not push it.', 'object-sync-for-salesforce' ),
0 ignored issues
show
Bug introduced by
The function esc_html__ was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

582
							/** @scrutinizer ignore-call */ 
583
       esc_html__( '%1$s: mapping object transient ID %2$s is currently pulling, so we do not push it.', 'object-sync-for-salesforce' ),
Loading history...
583
							ucfirst( esc_attr( $status ) ),
0 ignored issues
show
Bug introduced by
The function esc_attr was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

583
							ucfirst( /** @scrutinizer ignore-call */ esc_attr( $status ) ),
Loading history...
584
							$mapping_object_id_transient
585
						);
586
						$debug = array(
587
							'title'   => $title,
588
							'message' => '',
589
							'trigger' => $sf_sync_trigger,
590
							'parent'  => '',
591
							'status'  => $status,
592
						);
593
						$this->logging->setup( $debug );
594
					}
595
596
					continue;
597
				}
598
			} else {
599
				// if we don't have a WordPress object id, we've got no business doing stuff in Salesforce.
600
				$status = 'error';
601
				$title  = sprintf(
602
					// translators: placeholders are: 1) the log status, 2) the name of the WordPress id field.
603
					esc_html__( '%1$s: Salesforce Push: unable to process queue item because it has no WordPress %2$s.', 'object-sync-for-salesforce' ),
604
					ucfirst( esc_attr( $status ) ),
605
					esc_attr( $wordpress_id_field_name )
606
				);
607
				$result = array(
608
					'title'   => $title,
609
					'message' => print_r( $object, true ), // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
610
					'trigger' => $sf_sync_trigger,
611
					'parent'  => 0, // parent id goes here but we don't have one, so make it 0.
612
					'status'  => $status,
613
				);
614
				$this->logging->setup( $result );
615
				return $result;
616
			} // End if() statement.
617
618
			$map_sync_triggers = $mapping['sync_triggers'];
619
620
			$push_allowed = $this->is_push_allowed( $object_type, $object, $sf_sync_trigger, $mapping, $map_sync_triggers );
621
622
			if ( false === $push_allowed ) {
623
624
				// we need to get the WordPress id here so we can check to see if the object already has a map.
625
				$structure               = $this->wordpress->get_wordpress_table_structure( $object_type );
626
				$wordpress_id_field_name = $structure['id_field'];
627
628
				// this returns the WordPress rows that map to the individual Salesfoce row
629
				// we don't need to loop through these because we're just generating an error log for push not allowed.
630
				$mapping_objects = $this->mappings->load_all_by_wordpress( $object_type, $object[ $wordpress_id_field_name ] );
631
				if ( ! empty( $mapping_objects ) ) {
632
					$mapping_object = $mapping_objects[0];
633
				}
634
635
				// hook to allow other plugins to define or alter the mapping object.
636
				$mapping_object = apply_filters( $this->option_prefix . 'push_mapping_object', $mapping_object, $object, $mapping );
0 ignored issues
show
Bug introduced by
The function apply_filters was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

636
				$mapping_object = /** @scrutinizer ignore-call */ apply_filters( $this->option_prefix . 'push_mapping_object', $mapping_object, $object, $mapping );
Loading history...
637
638
				// are these objects already connected in WordPress?
639
				if ( isset( $mapping_object['id'] ) ) {
640
					$is_new = false;
641
				} else {
642
					$is_new = true;
643
				}
644
645
				$status = 'error';
646
				// create log entry for not allowed push.
647
				$op = '';
648
				switch ( $sf_sync_trigger ) {
649
					case $this->mappings->sync_wordpress_create:
650
						if ( true === $is_new ) {
651
							$op = 'Create';
652
						}
653
						break;
654
					case $this->mappings->sync_wordpress_update:
655
						if ( false === $is_new ) {
656
							$op = 'Update';
657
						}
658
						break;
659
					case $this->mappings->sync_wordpress_delete:
660
						if ( false === $is_new ) {
661
							$op = 'Delete';
662
						}
663
						break;
664
				}
665
				$log_status = 'notice';
666
				$title      = sprintf(
667
					// translators: placeholders are: 1) the log status, capitalized, 2) the name of the current operation, 3) the name of the WordPress object type, 4) the name of the WordPress ID field, 5) the value of the object's ID in WordPress, 6) the name of the Salesforce object.
668
					esc_html__( '%1$s: %2$s Salesforce %3$s with WordPress %4$s with %5$s of %6$s was not allowed by this fieldmap.', 'object-sync-for-salesforce' ),
669
					ucfirst( esc_attr( $log_status ) ),
670
					esc_attr( $op ),
671
					esc_attr( $mapping['wordpress_object'] ),
672
					esc_attr( $wordpress_id_field_name ),
673
					esc_attr( $object[ $wordpress_id_field_name ] ),
674
					esc_attr( $mapping['salesforce_object'] )
675
				);
676
				$result = array(
677
					'title'   => $title,
678
					'message' => '',
679
					'trigger' => $sf_sync_trigger,
680
					'parent'  => esc_attr( $object[ $wordpress_id_field_name ] ),
681
					'status'  => $log_status,
682
				);
683
				if ( '' !== $op ) {
684
					$this->logging->setup( $result );
685
				}
686
				$results[] = $result;
687
688
				if ( isset( $mapping['always_delete_object_maps_on_delete'] ) && ( '1' === $mapping['always_delete_object_maps_on_delete'] ) ) {
689
					if ( $sf_sync_trigger === $this->mappings->sync_wordpress_delete ) {
690
						foreach ( $mapping_objects as $mapping_object ) {
691
							if ( isset( $mapping_object['id'] ) ) {
692
								$this->mappings->delete_object_map( $mapping_object['id'] );
693
							}
694
						}
695
					}
696
				}
697
				continue;
698
			}
699
700
			// push drafts if the setting says so
701
			// post status is draft, or post status is inherit and post type is not attachment.
702
			if ( ( ! isset( $mapping['push_drafts'] ) || '1' !== $mapping['push_drafts'] ) && isset( $object['post_status'] ) && ( 'draft' === $object['post_status'] || ( 'inherit' === $object['post_status'] && 'attachment' !== $object['post_type'] ) ) ) {
703
				// skip this object if it is a draft and the fieldmap settings told us to ignore it.
704
				continue;
705
			}
706
707
			if ( isset( $mapping['push_async'] ) && ( '1' === $mapping['push_async'] ) && false === $manual ) {
708
				// because this item is async, add it to the queue so it can be pushed to Salesforce.
709
				$this->queue->add(
710
					$this->schedulable_classes[ $this->schedule_name ]['callback'],
711
					array(
712
						'object_type'     => $object_type,
713
						'object'          => filter_var( $object[ $wordpress_id_field_name ], FILTER_VALIDATE_INT ),
714
						'mapping'         => filter_var( $mapping['id'], FILTER_VALIDATE_INT ),
715
						'sf_sync_trigger' => $sf_sync_trigger,
716
					),
717
					$this->schedule_name
718
				);
719
720
				$log_status = 'success';
721
				$title      = sprintf(
722
					// translators: placeholders are: 1) the log status, 2) the name of the WordPress object type, 3) the name of the WordPress ID field, 4) the value of the object's ID in WordPress, 5) the name of the Salesforce object.
723
					esc_html__( '%1$s: Add to queue: Push WordPress %2$s with %3$s of %4$s to Salesforce %5$s.', 'object-sync-for-salesforce' ),
724
					ucfirst( esc_attr( $log_status ) ),
725
					esc_attr( $mapping['wordpress_object'] ),
726
					esc_attr( $wordpress_id_field_name ),
727
					esc_attr( $object[ $wordpress_id_field_name ] ),
728
					esc_attr( $mapping['salesforce_object'] )
729
				);
730
731
				$result    = array(
732
					'title'   => $title,
733
					'message' => '',
734
					'trigger' => $sf_sync_trigger,
735
					'parent'  => esc_attr( $object[ $wordpress_id_field_name ] ),
736
					'status'  => $log_status,
737
				);
738
				$results[] = $result;
739
			} else {
740
				// this one is not async. do it immediately.
741
				$push      = $this->salesforce_push_sync_rest( $object_type, $object, $mapping, $sf_sync_trigger );
742
				$results[] = $push;
743
			} // End if() statement.
744
		} // End foreach() on fieldmaps.
745
746
		// delete transients that we've already processed for this WordPress object.
747
		foreach ( $transients_to_delete as $key => $value ) {
748
			$fieldmap_id = $value['fieldmap']['id'];
749
			$transients  = (array) $value['transients'];
750
			foreach ( $transients as $transient_end ) {
751
				$this->sync_transients->delete( 'salesforce_pulling_' . $transient_end, '', $fieldmap_id );
752
			}
753
			$pulling_id = $this->sync_transients->get( 'salesforce_pulling_object_id', '', $fieldmap_id );
754
			if ( in_array( $pulling_id, $transients, true ) ) {
755
				$this->sync_transients->delete( 'salesforce_pulling_object_id', '', $fieldmap_id );
756
			}
757
		}
758
759
		return $results;
760
	}
761
762
	/**
763
	 * Sync WordPress objects and Salesforce objects using the REST API.
764
	 *
765
	 * @param string    $object_type Type of WordPress object.
766
	 * @param array|int $object The WordPress object data or its ID value.
767
	 * @param array     $mapping Salesforce field mapping data array or ID.
768
	 * @param int       $sf_sync_trigger Trigger for this sync.
769
	 * @return true or exit the method
770
	 */
771
	public function salesforce_push_sync_rest( $object_type, $object, $mapping, $sf_sync_trigger ) {
772
773
		// when using async, this task receives the WordPress object id value as an integer. otherwise, it receives the WordPress object data as an array.
774
		if ( is_int( $object ) ) {
775
			$wordpress_id = $object;
776
			// if this is NOT a deletion, try to get all of the object's data.
777
			if ( $sf_sync_trigger !== $this->mappings->sync_wordpress_delete ) {
778
				$object = $this->wordpress->get_wordpress_object_data( $object_type, $wordpress_id );
779
			} else {
780
				// otherwise, flag it as a delete and limit what we try to get.
781
				$object = $this->wordpress->get_wordpress_object_data( $object_type, $wordpress_id, true );
782
			}
783
		}
784
785
		if ( is_int( $mapping ) ) {
0 ignored issues
show
introduced by
The condition is_int($mapping) is always false.
Loading history...
786
			$mapping_id = $mapping;
787
			$mapping    = $this->mappings->get_fieldmaps( $mapping_id );
788
		}
789
790
		// If Salesforce is not authorized, don't do anything.
791
		// it's unclear to me if we need to do something else here or if this is sufficient. This is all Drupal does.
792
		if ( true !== $this->salesforce['is_authorized'] ) {
793
			return;
794
		}
795
796
		$sfapi = $this->salesforce['sfapi'];
797
798
		// we need to get the WordPress id here so we can check to see if the object already has a map.
799
		$structure               = $this->wordpress->get_wordpress_table_structure( $object_type );
800
		$wordpress_id_field_name = $structure['id_field'];
801
802
		// check to make sure we have a value for the WordPress ID.
803
		if ( ! isset( $object[ $wordpress_id_field_name ] ) ) {
804
			// create log entry for missing WordPress ID.
805
			$status = 'error';
806
			$title  = sprintf(
807
				// translators: placeholders are: 1) the log status, 2) the name of the Salesforce object, 3) the name of the WordPress object type, 4) the WordPress id field name.
808
				esc_html__( '%1$s: Pushing to Salesforce %2$s. The WordPress %3$s %4$s value is missing from the request. It may have been deleted.', 'object-sync-for-salesforce' ),
0 ignored issues
show
Bug introduced by
The function esc_html__ was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

808
				/** @scrutinizer ignore-call */ 
809
    esc_html__( '%1$s: Pushing to Salesforce %2$s. The WordPress %3$s %4$s value is missing from the request. It may have been deleted.', 'object-sync-for-salesforce' ),
Loading history...
809
				ucfirst( esc_attr( $status ) ),
0 ignored issues
show
Bug introduced by
The function esc_attr was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

809
				ucfirst( /** @scrutinizer ignore-call */ esc_attr( $status ) ),
Loading history...
810
				esc_attr( $mapping['salesforce_object'] ),
811
				esc_attr( $mapping['wordpress_object'] ),
812
				esc_attr( $wordpress_id_field_name )
813
			);
814
			$body = sprintf(
815
				// translators: placeholders are 1) the object's data that was attempted.
816
				'<p>' . esc_html__( 'The WordPress object data that was attempted: %1$s', 'object-sync-for-salesforce' ) . '</p>',
817
				print_r( $object, true ) // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
0 ignored issues
show
Bug introduced by
It seems like print_r($object, true) can also be of type true; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

817
				/** @scrutinizer ignore-type */ print_r( $object, true ) // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
Loading history...
818
			);
819
			$result = array(
820
				'title'   => $title,
821
				'message' => $body,
822
				'trigger' => $sf_sync_trigger,
823
				'parent'  => 0,
824
				'status'  => $status,
825
			);
826
			$this->logging->setup( $result );
827
		}
828
829
		// this returns the row that maps the individual WordPress row to the individual Salesfoce row
830
		// todo: we might need to loop through these?
831
		$mapping_object = $this->mappings->load_all_by_wordpress( $object_type, $object[ $wordpress_id_field_name ] );
832
		if ( ! empty( $mapping_object ) ) {
833
			$mapping_object = $mapping_object[0];
834
		}
835
836
		// hook to allow other plugins to define or alter the mapping object.
837
		$mapping_object = apply_filters( $this->option_prefix . 'push_mapping_object', $mapping_object, $object, $mapping );
0 ignored issues
show
Bug introduced by
The function apply_filters was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

837
		$mapping_object = /** @scrutinizer ignore-call */ apply_filters( $this->option_prefix . 'push_mapping_object', $mapping_object, $object, $mapping );
Loading history...
838
839
		// we already have the data from WordPress at this point; we just need to work with it in Salesforce.
840
		$synced_object = array(
841
			'wordpress_object' => $object,
842
			'mapping_object'   => $mapping_object,
843
			'queue_item'       => false,
844
			'mapping'          => $mapping,
845
		);
846
847
		$op     = '';
848
		$result = '';
849
850
		// deleting mapped objects.
851
		if ( $sf_sync_trigger === $this->mappings->sync_wordpress_delete ) {
852
			if ( isset( $mapping_object['id'] ) ) {
853
				$op = 'Delete';
854
855
				$mapping_objects = $this->mappings->load_object_maps_by_salesforce_id( $mapping_object['salesforce_id'], $mapping );
856
857
				// only delete if there are no additional mapping objects for this record.
858
				if ( 1 === count( $mapping_objects ) ) {
859
860
					$frequencies = $this->queue->get_frequencies();
861
					$seconds     = reset( $frequencies )['frequency'] + 60;
862
863
					// right here we should set the pushing transient.
864
					$this->sync_transients->set( 'salesforce_pushing_' . $mapping_object['salesforce_id'], '', $mapping['id'], 1, $seconds );
865
					$this->sync_transients->set( 'salesforce_pushing_object_id', '', $mapping['id'], $mapping_object['salesforce_id'] );
866
867
					try {
868
						$api_result = $sfapi->object_delete( $mapping['salesforce_object'], $mapping_object['salesforce_id'] );
869
					} catch ( Object_Sync_Sf_Exception $e ) {
870
						$status = 'error';
871
						// create log entry for failed delete.
872
						$title = sprintf(
873
							// translators: placeholders are: 1) the log status, 2) what operation is happening, 3) the name of the Salesforce object, 4) the Salesforce Id value, 5) the name of the WordPress object type, 6) the WordPress id field name, 7) the WordPress object id value.
874
							esc_html__( '%1$s: %2$s Salesforce %3$s %4$s (WordPress %5$s with %6$s of %7$s)', 'object-sync-for-salesforce' ),
875
							ucfirst( esc_attr( $status ) ),
876
							esc_attr( $op ),
877
							esc_attr( $mapping['salesforce_object'] ),
878
							esc_attr( $mapping_object['salesforce_id'] ),
879
							esc_attr( $mapping['wordpress_object'] ),
880
							esc_attr( $wordpress_id_field_name ),
881
							esc_attr( $object[ "$wordpress_id_field_name" ] )
882
						);
883
884
						// set up error message.
885
						$default_message = esc_html__( 'An error occurred pushing this data to Salesforce. See the plugin logs.', 'object-sync-for-salesforce' );
886
						$message         = $this->parse_error_message( $e, $default_message );
0 ignored issues
show
Bug introduced by
$e of type Object_Sync_Sf_Exception is incompatible with the type array expected by parameter $e of Object_Sync_Sf_Salesforc...::parse_error_message(). ( Ignorable by Annotation )

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

886
						$message         = $this->parse_error_message( /** @scrutinizer ignore-type */ $e, $default_message );
Loading history...
887
888
						$result = array(
889
							'title'   => $title,
890
							'message' => $message,
891
							'trigger' => $sf_sync_trigger,
892
							'parent'  => $object[ "$wordpress_id_field_name" ],
893
							'status'  => $status,
894
						);
895
						$this->logging->setup( $result );
896
897
						// hook for push fail.
898
						do_action( $this->option_prefix . 'push_fail', $op, $sfapi->response, $synced_object, $wordpress_id_field_name );
0 ignored issues
show
Bug introduced by
The function do_action was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

898
						/** @scrutinizer ignore-call */ 
899
      do_action( $this->option_prefix . 'push_fail', $op, $sfapi->response, $synced_object, $wordpress_id_field_name );
Loading history...
899
900
					}
901
902
					if ( ! isset( $e ) ) {
903
						// create log entry for successful delete if the result had no errors.
904
						$status = 'success';
905
						$title  = sprintf(
906
							// translators: placeholders are: 1) the log status, 2) what operation is happening, 3) the name of the Salesforce object, 4) the Salesforce Id value, 5) the name of the WordPress object type, 6) the WordPress id field name, 7) the WordPress object id value.
907
							esc_html__( '%1$s: %2$s Salesforce %3$s %4$s (WordPress %5$s with %6$s of %7$s)', 'object-sync-for-salesforce' ),
908
							ucfirst( esc_attr( $status ) ),
909
							esc_attr( $op ),
910
							esc_attr( $mapping['salesforce_object'] ),
911
							esc_attr( $mapping_object['salesforce_id'] ),
912
							esc_attr( $mapping['wordpress_object'] ),
913
							esc_attr( $wordpress_id_field_name ),
914
							esc_attr( $object[ "$wordpress_id_field_name" ] )
915
						);
916
						$result = array(
917
							'title'   => $title,
918
							'message' => '',
919
							'trigger' => $sf_sync_trigger,
920
							'parent'  => $object[ "$wordpress_id_field_name" ],
921
							'status'  => $status,
922
						);
923
						$this->logging->setup( $result );
924
925
						// hook for push success.
926
						do_action( $this->option_prefix . 'push_success', $op, $sfapi->response, $synced_object, $mapping_object['salesforce_id'], $wordpress_id_field_name );
927
					}
928
				} else {
929
					$more_ids = '<p>' . esc_html__( 'The Salesforce record was not deleted because there are multiple WordPress IDs that match this Salesforce ID. They are:', 'object-sync-for-salesforce' ) . '</p>';
930
931
					$more_ids .= '<ul>';
932
					foreach ( $mapping_objects as $match ) {
933
						$more_ids .= '<li>' . $match['wordpress_id'] . '</li>';
934
					}
935
					$more_ids .= '</ul>';
936
937
					$more_ids .= '<p>' . esc_html__( 'The map row between this WordPress object and the Salesforce object, as stored in the WordPress database, will be deleted, and this WordPress object has been deleted, but Salesforce will remain untouched.', 'object-sync-for-salesforce' ) . '</p>';
938
939
					$status = 'notice';
940
					$title  = sprintf(
941
						// translators: placeholders are: 1) the log status, 2) what operation is happening, 3) the name of the Salesforce object, 4) the Salesforce Id value, 5) the name of the WordPress object type, 6) the WordPress id field name, 7) the WordPress object id value.
942
						esc_html__( '%1$s: %2$s on Salesforce %3$s with Id of %4$s was stopped because there are other Salesforce records mapped to WordPress %5$s with %6$s of %7$s', 'object-sync-for-salesforce' ),
943
						ucfirst( esc_attr( $status ) ),
944
						esc_attr( $op ),
945
						esc_attr( $mapping['salesforce_object'] ),
946
						esc_attr( $mapping_object['salesforce_id'] ),
947
						esc_attr( $mapping['wordpress_object'] ),
948
						esc_attr( $wordpress_id_field_name ),
949
						esc_attr( $object[ "$wordpress_id_field_name" ] )
950
					);
951
					$result = array(
952
						'title'   => $title,
953
						'message' => $more_ids,
954
						'trigger' => $sf_sync_trigger,
955
						'parent'  => $object[ "$wordpress_id_field_name" ],
956
						'status'  => $status,
957
					);
958
					$this->logging->setup( $result );
959
960
				} // End if() statement.
961
962
				// right here we should change the pushing_object_id transient to the Salesforce Id value.
963
				if ( isset( $api_result['code'] ) && (int) 204 === $api_result['code'] ) {
964
					$this->sync_transients->set( 'salesforce_pushing_' . $mapping_object['salesforce_id'], '', $mapping['id'], 1 );
965
					$this->sync_transients->set( 'salesforce_pushing_object_id', '', $mapping['id'], $mapping_object['salesforce_id'] );
966
				}
967
968
				// delete the map row from WordPress after the Salesforce row has been deleted
969
				// we delete the map row even if the Salesforce delete failed, because the WordPress object is gone.
970
				$this->mappings->delete_object_map( $mapping_object['id'] );
971
972
			} // End if(). there is no map row
973
974
			return $result;
975
		} // End if() statement.
976
977
		// are these objects already connected in WordPress?
978
		if ( isset( $mapping_object['id'] ) ) {
979
			$is_new = false;
980
		} else {
981
			$is_new = true;
982
		}
983
984
		// map the WordPress values to Salesforce fields.
985
		$params = $this->mappings->map_params( $mapping, $object, $sf_sync_trigger, false, $is_new, $wordpress_id_field_name );
986
987
		// hook to allow other plugins to modify the $params array
988
		// use hook to map fields between the WordPress and Salesforce objects
989
		// returns $params.
990
		$params = apply_filters( $this->option_prefix . 'push_params_modify', $params, $mapping, $object, $sf_sync_trigger, false, $is_new );
991
992
		// if we don't get any params, there are no fields that should be sent to Salesforce.
993
		if ( empty( $params ) ) {
994
995
			// if the parameters array is empty at this point, we should create a log entry to that effect.
996
			// I think it should be a debug message, unless we learn from users that it should be raised to an error.
997
			if ( true === $this->debug ) {
998
				$status = 'debug';
999
				$title  = sprintf(
1000
					// translators: %1$s is the log status.
1001
					esc_html__( '%1$s Mapping: according to the current plugin settings, there are no parameters in the current dataset that can be pushed to Salesforce.', 'object-sync-for-salesforce' ),
1002
					ucfirst( esc_attr( $status ) )
1003
				);
1004
				$body = sprintf(
1005
					// translators: placeholders are: 1) the fieldmap row ID, 2) the name of the WordPress object, 3) the name of the Salesforce object.
1006
					'<p>' . esc_html__( 'There is a fieldmap with ID of %1$s and it maps the WordPress %2$s object to the Salesforce %3$s object.', 'object-sync-for-salesforce' ) . '</p>',
1007
					absint( $mapping['id'] ),
0 ignored issues
show
Bug introduced by
The function absint was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1007
					/** @scrutinizer ignore-call */ 
1008
     absint( $mapping['id'] ),
Loading history...
1008
					esc_attr( $mapping['wordpress_object'] ),
1009
					esc_attr( $mapping['salesforce_object'] )
1010
				);
1011
				// whether it's a new mapping object or not.
1012
				if ( false === $is_new ) {
1013
					// this one is not new.
1014
					$body .= sprintf(
1015
						// translators: placeholders are: 1) the mapping object row ID, 2) the name of the WordPress object, 3) the ID of the WordPress object, 4) the ID of the Salesforce object it was trying to map.
1016
						'<p>' . esc_html__( 'There is an existing object map with ID of %1$s and it is mapped to the WordPress %2$s with ID of %3$s and the Salesforce object with ID of %4$s.', 'object-sync-for-salesforce' ) . '</p>',
1017
						absint( $mapping_object['id'] ),
1018
						esc_attr( $mapping_object['wordpress_object'] ),
1019
						esc_attr( $mapping_object['wordpress_id'] ),
1020
						esc_attr( $mapping_object['salesforce_id'] )
1021
					);
1022
				} else {
1023
					// this one is new.
1024
					$body .= sprintf(
1025
						// translators: placeholders are: 1) the name of the WordPress object, 2) the ID of the WordPress object, 3) the Salesforce object type.
1026
						'<p>' . esc_html__( 'The plugin was trying to push the WordPress %1$s with ID of %2$s to the Salesforce %3$s object type.', 'object-sync-for-salesforce' ) . '</p>',
1027
						esc_attr( $mapping['wordpress_object'] ),
1028
						esc_attr( $object[ $wordpress_id_field_name ] ),
1029
						esc_attr( $mapping['salesforce_object'] )
1030
					);
1031
				}
1032
				$body .= sprintf(
1033
					// translators: placeholders are 1) the object's data that was attempted.
1034
					'<p>' . esc_html__( 'The WordPress object data that was attempted: %1$s', 'object-sync-for-salesforce' ) . '</p>',
1035
					print_r( $object, true ) // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
1036
				);
1037
				$this->logging->setup(
1038
					$title,
1039
					$body,
1040
					$sf_sync_trigger,
1041
					0,
1042
					$status
1043
				);
1044
			} // end debug mode check.
1045
1046
			return;
1047
		} // end if params are empty.
1048
1049
		// if there is a prematch WordPress field - ie email - on the fieldmap object.
1050
		if ( isset( $params['prematch'] ) && is_array( $params['prematch'] ) ) {
1051
			$prematch_field_wordpress  = $params['prematch']['wordpress_field'];
1052
			$prematch_field_salesforce = $params['prematch']['salesforce_field'];
1053
			$prematch_value            = $params['prematch']['value'];
1054
			unset( $params['prematch'] );
1055
		}
1056
1057
		// if there is an external key field in Salesforce - ie mailchimp user id - on the fieldmap object.
1058
		if ( isset( $params['key'] ) && is_array( $params['key'] ) ) {
1059
			$key_field_wordpress  = $params['key']['wordpress_field'];
1060
			$key_field_salesforce = $params['key']['salesforce_field'];
1061
			$key_value            = $params['key']['value'];
1062
			unset( $params['key'] );
1063
		}
1064
1065
		$frequencies  = $this->queue->get_frequencies();
1066
		$seconds      = reset( $frequencies )['frequency'] + 60;
1067
		$saved_params = filter_var( get_option( $this->option_prefix . 'missing_required_data_id_' . $object[ $wordpress_id_field_name ], false ), FILTER_VALIDATE_BOOLEAN );
0 ignored issues
show
Bug introduced by
The function get_option was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1067
		$saved_params = filter_var( /** @scrutinizer ignore-call */ get_option( $this->option_prefix . 'missing_required_data_id_' . $object[ $wordpress_id_field_name ], false ), FILTER_VALIDATE_BOOLEAN );
Loading history...
1068
1069
		// start the is_new stuff.
1070
		if ( true === $is_new || true === $saved_params ) {
1071
			if ( true === $saved_params ) {
1072
				delete_option( $this->option_prefix . 'missing_required_data_id_' . $object[ $wordpress_id_field_name ] );
0 ignored issues
show
Bug introduced by
The function delete_option was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1072
				/** @scrutinizer ignore-call */ 
1073
    delete_option( $this->option_prefix . 'missing_required_data_id_' . $object[ $wordpress_id_field_name ] );
Loading history...
1073
			}
1074
			// right here we should set the pushing transient
1075
			// this means we have to create the mapping object here as well, and update it with the correct IDs after successful response
1076
			// create the mapping object between the rows.
1077
			$temporary_map_id  = $this->mappings->generate_temporary_id( 'push' );
1078
			$mapping_object_id = $this->create_object_map( $object, $wordpress_id_field_name, $temporary_map_id, $mapping, true );
1079
			$this->sync_transients->set( 'salesforce_pushing_' . $temporary_map_id, '', $mapping['id'], 1, $seconds );
1080
			$this->sync_transients->set( 'salesforce_pushing_object_id', '', $mapping['id'], $temporary_map_id );
1081
			$mapping_object  = array();
1082
			$mapping_objects = $this->mappings->get_all_object_maps(
1083
				array(
1084
					'id' => $mapping_object_id,
1085
				)
1086
			);
1087
			if ( isset( $mapping_objects[0] ) && is_array( $mapping_objects[0] ) ) {
1088
				$mapping_object = $mapping_objects[0];
1089
			}
1090
1091
			// setup SF record type. CampaignMember objects get their Campaign's type
1092
			// i am still a bit confused about this.
1093
			if ( $mapping['salesforce_record_type_default'] !== $this->mappings->salesforce_default_record_type && empty( $params['RecordTypeId'] ) && ( 'CampaignMember' !== $mapping['salesforce_object'] ) ) {
1094
				$params['RecordTypeId'] = $mapping['salesforce_record_type_default'];
1095
			}
1096
1097
			// hook to allow other plugins to modify the $salesforce_id string here
1098
			// use hook to change the object that is being matched to developer's own criteria
1099
			// ex: match a Salesforce Contact based on a connected email address object
1100
			// returns a $salesforce_id.
1101
			// it should keep NULL if there is no match
1102
			// the function that calls this hook needs to check the mapping to make sure the WordPress object is the right type.
1103
			$salesforce_id = apply_filters( $this->option_prefix . 'find_sf_object_match', null, $object, $mapping, 'push' );
1104
1105
			// hook to allow other plugins to do something right before Salesforce data is saved
1106
			// ex: run WordPress methods on an object if it exists, or do something in preparation for it if it doesn't.
1107
			do_action( $this->option_prefix . 'pre_push', $salesforce_id, $mapping, $object, $wordpress_id_field_name, $params );
1108
1109
			// hook to allow other plugins to change params on update actions only
1110
			// use hook to map fields between the WordPress and Salesforce objects
1111
			// returns $params.
1112
			$params = apply_filters( $this->option_prefix . 'push_update_params_modify', $params, $salesforce_id, $mapping, $object, $mapping['wordpress_object'] );
1113
1114
			if ( isset( $prematch_field_wordpress ) || isset( $key_field_wordpress ) || null !== $salesforce_id ) {
1115
1116
				// if either prematch criteria exists, make the values queryable.
1117
1118
				if ( isset( $prematch_field_wordpress ) ) {
1119
					// a prematch has been specified, attempt an upsert().
1120
					// prematch values with punctuation need to be escaped.
1121
					$encoded_prematch_value = rawurlencode( $prematch_value );
1122
					// for at least 'email' fields, periods also need to be escaped:
1123
					// see https://developer.salesforce.com/forums?id=906F000000099xPIAQ.
1124
					$encoded_prematch_value = str_replace( '.', '%2E', $encoded_prematch_value );
1125
				}
1126
1127
				if ( isset( $key_field_wordpress ) ) {
1128
					// an external key has been specified, attempt an upsert().
1129
					// external key values with punctuation need to be escaped.
1130
					$encoded_key_value = rawurlencode( $key_value );
1131
					// for at least 'email' fields, periods also need to be escaped:
1132
					// see https://developer.salesforce.com/forums?id=906F000000099xPIAQ.
1133
					$encoded_key_value = str_replace( '.', '%2E', $encoded_key_value );
1134
				}
1135
1136
				if ( isset( $prematch_field_wordpress ) ) {
1137
					$upsert_key   = $prematch_field_salesforce;
1138
					$upsert_value = $encoded_prematch_value;
1139
				} elseif ( isset( $key_field_wordpress ) ) {
1140
					$upsert_key   = $key_field_salesforce;
1141
					$upsert_value = $encoded_key_value;
1142
				}
1143
1144
				if ( null !== $salesforce_id ) {
1145
					$upsert_key   = 'Id';
1146
					$upsert_value = $salesforce_id;
1147
				}
1148
1149
				$op = 'Upsert';
1150
			} else {
1151
				$op = 'Create';
1152
			}
1153
1154
			try {
1155
1156
				if ( 'Upsert' === $op ) {
1157
					$api_result = $sfapi->object_upsert( $mapping['salesforce_object'], $upsert_key, $upsert_value, $params );
1158
					// Handle upsert responses.
1159
					switch ( $sfapi->response['code'] ) {
1160
						// On Upsert:update retrieved object.
1161
						case '204':
1162
							$sf_object       = $sfapi->object_readby_external_id(
1163
								$mapping['salesforce_object'],
1164
								$upsert_key,
1165
								$upsert_value,
1166
								array(
1167
									'cache' => false,
1168
								)
1169
							);
1170
							$salesforce_data = $sf_object['data'];
1171
							break;
1172
						// Handle duplicate records.
1173
						case '300':
1174
							$api_result['data']['errorCode'] = $sfapi->response['error'] . ' (' . $upsert_key . ':' . $upsert_value . ')';
1175
							break;
1176
					}
1177
				} else {
1178
					// No key or prematch field exists on this field map object, create a new object in Salesforce.
1179
					$api_result = $sfapi->object_create( $mapping['salesforce_object'], $params );
1180
				} // End if() statement.
1181
			} catch ( Object_Sync_Sf_Exception $e ) {
1182
				// create log entry for failed create or upsert.
1183
				$status = 'error';
1184
				$title  = sprintf(
1185
					// translators: placeholders are: 1) the log status, 2) what operation is happening, 3) the name of the Salesforce object, 4) the Salesforce Id value if there is one, 5) the name of the WordPress object type, 6) the WordPress id field name, 7) the WordPress object id value.
1186
					esc_html__( '%1$s: %2$s Salesforce %3$s %4$s (WordPress %5$s with %6$s of %7$s)', 'object-sync-for-salesforce' ),
1187
					ucfirst( esc_attr( $status ) ),
1188
					esc_attr( $op ),
1189
					esc_attr( $mapping['salesforce_object'] ),
1190
					isset( $salesforce_id ) ? ' ' . esc_attr( $salesforce_id ) : '',
1191
					esc_attr( $mapping['wordpress_object'] ),
1192
					esc_attr( $wordpress_id_field_name ),
1193
					esc_attr( $object[ "$wordpress_id_field_name" ] )
1194
				);
1195
1196
				// set up error message.
1197
				$default_message = esc_html__( 'An error occurred pushing this data to Salesforce. See the plugin logs.', 'object-sync-for-salesforce' );
1198
				$message         = $this->parse_error_message( $e, $default_message );
1199
1200
				$result = array(
1201
					'title'   => $title,
1202
					'message' => $message,
1203
					'trigger' => $sf_sync_trigger,
1204
					'parent'  => $object[ "$wordpress_id_field_name" ],
1205
					'status'  => $status,
1206
				);
1207
				$this->logging->setup( $result );
1208
1209
				// update the mapping object to reflect the error status.
1210
				$mapping_object['last_sync_message'] = $message;
1211
				$mapping_object['last_sync']         = current_time( 'mysql' );
0 ignored issues
show
Bug introduced by
The function current_time was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1211
				$mapping_object['last_sync']         = /** @scrutinizer ignore-call */ current_time( 'mysql' );
Loading history...
1212
				$mapping_object_updated              = $this->mappings->update_object_map( $mapping_object, $mapping_object['id'] );
1213
1214
				// save the mapping object to the synced object.
1215
				$synced_object['mapping_object'] = $mapping_object;
1216
1217
				// hook for push fail.
1218
				do_action( $this->option_prefix . 'push_fail', $op, $sfapi->response, $synced_object );
1219
1220
				return;
1221
			} // End try() method.
1222
1223
			// Salesforce api call was successful
1224
			// this means the object has already been created/updated in Salesforce
1225
			// this is not redundant because this is where it creates the object mapping rows in WordPress if the object does not already have one (we are still inside $is_new === TRUE here).
1226
1227
			if ( empty( $api_result['data']['errorCode'] ) ) {
1228
1229
				if ( ! isset( $salesforce_data ) ) {
1230
					// if we didn't set $salesforce_data already, set it now.
1231
					$sf_object       = $sfapi->object_read(
1232
						$mapping['salesforce_object'],
1233
						$api_result['data']['id'],
1234
						array(
1235
							'cache' => false,
1236
						)
1237
					);
1238
					$salesforce_data = $sf_object['data'];
1239
				}
1240
1241
				$salesforce_id = $salesforce_data['Id'];
1242
				$status        = 'success';
1243
				$title         = sprintf(
1244
					// translators: placeholders are: 1) the log status ,2) what operation is happening, 3) the name of the Salesforce object, 4) the Salesforce Id value, 5) the name of the WordPress object type, 6) the WordPress id field name, 7) the WordPress object id value.
1245
					esc_html__( '%1$s: %2$s Salesforce %3$s %4$s (WordPress %5$s with %6$s of %7$s)', 'object-sync-for-salesforce' ),
1246
					ucfirst( esc_attr( $status ) ),
1247
					esc_attr( $op ),
1248
					esc_attr( $mapping['salesforce_object'] ),
1249
					esc_attr( $salesforce_id ),
1250
					esc_attr( $mapping['wordpress_object'] ),
1251
					esc_attr( $wordpress_id_field_name ),
1252
					esc_attr( $object[ "$wordpress_id_field_name" ] )
1253
				);
1254
				$result = array(
1255
					'title'   => $title,
1256
					'message' => '',
1257
					'trigger' => $sf_sync_trigger,
1258
					'parent'  => $object[ "$wordpress_id_field_name" ],
1259
					'status'  => $status,
1260
				);
1261
				$this->logging->setup( $result );
1262
1263
				// right here we should change the pushing transient to the LastModifiedDate for the Salesforce object.
1264
				if ( isset( $salesforce_data['LastModifiedDate'] ) ) {
1265
					$this->sync_transients->set( 'salesforce_pushing_' . $salesforce_id, '', $mapping['id'], strtotime( $salesforce_data['LastModifiedDate'] ) );
1266
					$this->sync_transients->set( 'salesforce_pushing_object_id', '', $mapping['id'], $salesforce_id );
1267
				}
1268
1269
				// update that mapping object.
1270
				$mapping_object['salesforce_id']     = $salesforce_id;
1271
				$mapping_object['last_sync']         = current_time( 'mysql' );
1272
				$mapping_object['last_sync_message'] = esc_html__( 'Mapping object updated via function: ', 'object-sync-for-salesforce' ) . __FUNCTION__;
1273
				$mapping_object_updated              = $this->mappings->update_object_map( $mapping_object, $mapping_object['id'] );
1274
1275
				// save the mapping object to the synced object.
1276
				$synced_object['mapping_object'] = $mapping_object;
1277
1278
				// hook for push success.
1279
				do_action( $this->option_prefix . 'push_success', $op, $sfapi->response, $synced_object, $salesforce_id, $wordpress_id_field_name );
1280
			} else {
1281
1282
				// create log entry for failed create or upsert
1283
				// this is part of the drupal module but I am failing to understand when it would ever fire, since the catch should catch the errors
1284
				// if we see this in the log entries, we can understand what it does, but probably not until then.
1285
				$status = 'error';
1286
				$title  = sprintf(
1287
					// translators: placeholders are: 1) error code the Salesforce API returned, 2) what operation is happening, 3) the name of the WordPress object type, 4) the WordPress id field name, 5) the WordPress object id value.
1288
					esc_html__( '%1$s error syncing: %2$s to Salesforce (WordPress %3$s with %4$s of %5$s)', 'object-sync-for-salesforce' ),
1289
					esc_attr( $api_result['data']['errorCode'] ),
1290
					esc_attr( $op ),
1291
					esc_attr( $mapping['wordpress_object'] ),
1292
					esc_attr( $wordpress_id_field_name ),
1293
					esc_attr( $object[ "$wordpress_id_field_name" ] )
1294
				);
1295
				$body = sprintf(
1296
					// translators: placeholders are 1) the name of the Salesforce object type, 2) the error message returned from the Salesforce APIs, 3) the parameters that were attempted.
1297
					'<p>' . esc_html__( 'Object: %1$s', 'object-sync-for-salesforce' ) . '</p><p>' . esc_html__( 'Message: %2$s', 'object-sync-for-salesforce' ) . '</p><p>' . esc_html__( 'Params: %3$s', 'object-sync-for-salesforce' ) . '</p>',
1298
					esc_attr( $mapping['salesforce_object'] ),
1299
					esc_html( $api_result['data']['message'] ),
0 ignored issues
show
Bug introduced by
The function esc_html was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1299
					/** @scrutinizer ignore-call */ 
1300
     esc_html( $api_result['data']['message'] ),
Loading history...
1300
					print_r( $params, true ) // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
1301
				);
1302
				if ( isset( $upsert_key ) && isset( $upsert_value ) ) {
1303
					$body .= sprintf(
1304
						// translators: placeholders are 1) the upsert key attempted, 2) the upsert value attempted.
1305
						'<p>' . esc_html__( 'Upsert key: %1$s', 'object-sync-for-salesforce' ) . '</p><p>' . esc_html__( 'Upsert value: %2$s', 'object-sync-for-salesforce' ) . '</p>',
1306
						esc_attr( $upsert_key ),
1307
						esc_attr( $upsert_value )
1308
					);
1309
				}
1310
				$result = array(
1311
					'title'   => $title,
1312
					'message' => $body,
1313
					'trigger' => $sf_sync_trigger,
1314
					'parent'  => $object[ "$wordpress_id_field_name" ],
1315
					'status'  => $status,
1316
				);
1317
				$this->logging->setup( $result );
1318
1319
				// update the mapping object to reflect the error status.
1320
				$mapping_object['last_sync_message'] = isset( $api_result['data']['message'] ) ? esc_html( $api_result['data']['message'] ) : esc_html__( 'An error occurred pushing this data to Salesforce. See the plugin logs.', 'object-sync-for-salesforce' );
1321
				$mapping_object['last_sync']         = current_time( 'mysql' );
1322
				$mapping_object_updated              = $this->mappings->update_object_map( $mapping_object, $mapping_object['id'] );
1323
1324
				// save the mapping object to the synced object.
1325
				$synced_object['mapping_object'] = $mapping_object;
1326
1327
				// hook for push fail.
1328
				do_action( $this->option_prefix . 'push_fail', $op, $sfapi->response, $synced_object );
1329
1330
				return $result;
1331
			} // End if() statement.
1332
		} else {
1333
			// $is_new is false here; we are updating an already mapped object
1334
1335
			// right here we should set the pushing transient.
1336
			$this->sync_transients->set( 'salesforce_pushing_' . $mapping_object['salesforce_id'], '', $mapping['id'], 1, $seconds );
1337
			$this->sync_transients->set( 'salesforce_pushing_object_id', '', $mapping['id'], $mapping_object['salesforce_id'] );
1338
1339
			// there is an existing object link
1340
			// if the last sync is greater than the last time this object was updated, skip it
1341
			// this keeps us from doing redundant syncs.
1342
			$mapping_object['object_updated'] = current_time( 'mysql' );
1343
			if ( $mapping_object['last_sync'] > $mapping_object['object_updated'] ) {
1344
				$status = 'notice';
1345
				$title  = sprintf(
1346
					// translators: placeholders are: 1) the log status, 2) what operation is happening, 3) the name of the WordPress object type, 4) the WordPress id field name, 5) the WordPress object id value, 6) the Salesforce Id value.
1347
					esc_html__( '%1$s: %2$s: Did not sync WordPress %3$s with %4$s of %5$s with Salesforce Id %6$s because the last sync timestamp was greater than the object updated timestamp.', 'object-sync-for-salesforce' ),
1348
					ucfirst( esc_attr( $status ) ),
1349
					esc_attr( $op ),
1350
					esc_attr( $mapping['wordpress_object'] ),
1351
					esc_attr( $wordpress_id_field_name ),
1352
					esc_attr( $object[ "$wordpress_id_field_name" ] ),
1353
					esc_attr( $mapping_object['salesforce_id'] )
1354
				);
1355
				$body = sprintf(
1356
					// translators: placeholders are 1) when a sync on this mapping last occured, 2) when the object was last updated.
1357
					'<p>' . esc_html__( 'Last sync time: %1$s', 'object-sync-for-salesforce' ) . '</p><p>' . esc_html__( 'Object updated time: %2$s', 'object-sync-for-salesforce' ) . '</p>',
1358
					esc_attr( $mapping_object['last_sync'] ),
1359
					esc_html( $mapping_object['object_updated'] )
1360
				);
1361
				$result = array(
1362
					'title'   => $title,
1363
					'message' => $body,
1364
					'trigger' => $sf_sync_trigger,
1365
					'parent'  => 0, // parent id goes here but we don't have one, so make it 0.
1366
					'status'  => $status,
1367
				);
1368
				$this->logging->setup( $result );
1369
				return $result;
1370
			}
1371
1372
			// try to make a Salesforce update call.
1373
			try {
1374
1375
				// hook to allow other plugins to do something right before Salesforce data is saved
1376
				// ex: run WordPress methods on an object if it exists, or do something in preparation for it if it doesn't.
1377
				do_action( $this->option_prefix . 'pre_push', $mapping_object['salesforce_id'], $mapping, $object, $wordpress_id_field_name, $params );
1378
1379
				// hook to allow other plugins to change params on update actions only
1380
				// use hook to map fields between the WordPress and Salesforce objects
1381
				// returns $params.
1382
				$params = apply_filters( $this->option_prefix . 'push_update_params_modify', $params, $mapping_object['salesforce_id'], $mapping, $object, $mapping['wordpress_object'] );
1383
1384
				$op         = 'Update';
1385
				$api_result = $sfapi->object_update( $mapping['salesforce_object'], $mapping_object['salesforce_id'], $params );
1386
1387
				$mapping_object['last_sync_status']  = $this->mappings->status_success;
1388
				$mapping_object['last_sync_message'] = esc_html__( 'Mapping object updated via function: ', 'object-sync-for-salesforce' ) . __FUNCTION__;
1389
1390
				$status = 'success';
1391
				$title  = sprintf(
1392
					// translators: placeholders are: 1) the log status, 2) what operation is happening, 3) the name of the Salesforce object, 4) the Salesforce Id value, 5) the name of the WordPress object type, 6) the WordPress id field name, 7) the WordPress object id value.
1393
					esc_html__( '%1$s: %2$s Salesforce %3$s %4$s (WordPress %5$s with %6$s of %7$s)', 'object-sync-for-salesforce' ),
1394
					ucfirst( esc_attr( $status ) ),
1395
					esc_attr( $op ),
1396
					esc_attr( $mapping['salesforce_object'] ),
1397
					esc_attr( $mapping_object['salesforce_id'] ),
1398
					esc_attr( $mapping['wordpress_object'] ),
1399
					esc_attr( $wordpress_id_field_name ),
1400
					esc_attr( $object[ "$wordpress_id_field_name" ] )
1401
				);
1402
				$result = array(
1403
					'title'   => $title,
1404
					'message' => '',
1405
					'trigger' => $sf_sync_trigger,
1406
					'parent'  => 0, // parent id goes here but we don't have one, so make it 0.
1407
					'status'  => $status,
1408
				);
1409
				$this->logging->setup( $result );
1410
1411
				// hook for push success.
1412
				do_action( $this->option_prefix . 'push_success', $op, $sfapi->response, $synced_object, $mapping_object['salesforce_id'], $wordpress_id_field_name );
1413
1414
			} catch ( Object_Sync_Sf_Exception $e ) {
1415
				// create log entry for failed update.
1416
				$status = 'error';
1417
				$title  = sprintf(
1418
					// translators: placeholders are: 1) the log status, 2) what operation is happening, 3) the name of the Salesforce object, 4) the Salesforce Id value, 5) the name of the WordPress object type, 6) the WordPress id field name, 7) the WordPress object id value.
1419
					esc_html__( '%1$s: %2$s Salesforce %3$s %4$s (WordPress %5$s with %6$s of %7$s)', 'object-sync-for-salesforce' ),
1420
					ucfirst( esc_attr( $status ) ),
1421
					esc_attr( $op ),
1422
					esc_attr( $mapping['salesforce_object'] ),
1423
					esc_attr( $mapping_object['salesforce_id'] ),
1424
					esc_attr( $mapping['wordpress_object'] ),
1425
					esc_attr( $wordpress_id_field_name ),
1426
					esc_attr( $object[ "$wordpress_id_field_name" ] )
1427
				);
1428
1429
				// set up error message.
1430
				$default_message = esc_html__( 'An error occurred pushing this data to Salesforce. See the plugin logs.', 'object-sync-for-salesforce' );
1431
				$message         = $this->parse_error_message( $e, $default_message );
1432
1433
				$result = array(
1434
					'title'   => $title,
1435
					'message' => $message,
1436
					'trigger' => $sf_sync_trigger,
1437
					'parent'  => $object[ "$wordpress_id_field_name" ],
1438
					'status'  => $status,
1439
				);
1440
				$this->logging->setup( $result );
1441
1442
				$mapping_object['last_sync_status']  = $this->mappings->status_error;
1443
				$mapping_object['last_sync_message'] = $message;
1444
1445
				// hook for push fail.
1446
				do_action( $this->option_prefix . 'push_fail', $op, $sfapi->response, $synced_object );
1447
1448
			} // End try() method.
1449
1450
			if ( ! isset( $salesforce_data ) ) {
1451
				// if we didn't set $salesforce_data already, set it now.
1452
				$sf_object       = $sfapi->object_read(
1453
					$mapping['salesforce_object'],
1454
					$mapping_object['salesforce_id'],
1455
					array(
1456
						'cache' => false,
1457
					)
1458
				);
1459
				$salesforce_data = $sf_object['data'];
1460
			}
1461
1462
			// right here we should change the pushing transient to the LastModifiedDate for the Salesforce object.
1463
			if ( isset( $salesforce_data['LastModifiedDate'] ) ) {
1464
				$this->sync_transients->set( 'salesforce_pushing_' . $mapping_object['salesforce_id'], '', $mapping['id'], strtotime( $salesforce_data['LastModifiedDate'] ) );
1465
				$this->sync_transients->set( 'salesforce_pushing_object_id', '', $mapping['id'], $mapping_object['salesforce_id'] );
1466
			}
1467
1468
			// tell the mapping object - whether it is new or already existed - how we just used it.
1469
			$mapping_object['last_sync_action'] = 'push';
1470
			$mapping_object['last_sync']        = current_time( 'mysql' );
1471
1472
			// update that mapping object.
1473
			$map_result = $this->mappings->update_object_map( $mapping_object, $mapping_object['id'] );
1474
1475
		} // End if(). this is the end of the if is_new stuff
1476
1477
		return $result;
1478
1479
	}
1480
1481
	/**
1482
	 * Format the error message
1483
	 *
1484
	 * @param array  $e the exception from the Salesforce class.
1485
	 * @param string $default_message if there is one.
1486
	 * @return string $message what is getting stored.
1487
	 */
1488
	private function parse_error_message( $e, $default_message = '' ) {
1489
		$message = $default_message;
1490
		$errors  = $e->getMessage();
1491
		// try to retrieve a usable error message to save.
1492
		if ( is_string( $errors ) ) {
1493
			$message = $errors;
1494
		}
1495
		$message = wp_kses_post( $message );
0 ignored issues
show
Bug introduced by
The function wp_kses_post was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1495
		$message = /** @scrutinizer ignore-call */ wp_kses_post( $message );
Loading history...
1496
		return $message;
1497
	}
1498
1499
	/**
1500
	 * Create an object map between a WordPress object and a Salesforce object
1501
	 *
1502
	 * @param array  $wordpress_object Array of the WordPress object's data.
1503
	 * @param string $id_field_name How this object names its primary field. ie Id or comment_id or whatever.
1504
	 * @param string $salesforce_id Unique identifier for the Salesforce object.
1505
	 * @param array  $field_mapping The row that maps the object types together, including which fields match which other fields.
1506
	 * @param bool   $pending check if it is a pending action or the full object map has already been created.
1507
	 * @return int   $wpdb->insert_id This is the database row for the map object.
1508
	 */
1509
	private function create_object_map( $wordpress_object, $id_field_name, $salesforce_id, $field_mapping, $pending = false ) {
1510
1511
		if ( true === $pending ) {
1512
			$action = 'pending';
1513
		} else {
1514
			$action = 'created';
1515
		}
1516
1517
		// Create object map and save it. Only do this if we have a valid WordPress ID.
1518
		if ( isset( $wordpress_object[ $id_field_name ] ) ) {
1519
			$mapping_object = $this->mappings->create_object_map(
1520
				array(
1521
					'wordpress_id'      => $wordpress_object[ $id_field_name ], // WordPress unique id.
1522
					'salesforce_id'     => $salesforce_id, // Salesforce unique id. we don't care what kind of object it is at this point.
1523
					'wordpress_object'  => $field_mapping['wordpress_object'], // keep track of what kind of wp object this is.
1524
					'last_sync'         => current_time( 'mysql' ),
0 ignored issues
show
Bug introduced by
The function current_time was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1524
					'last_sync'         => /** @scrutinizer ignore-call */ current_time( 'mysql' ),
Loading history...
1525
					'last_sync_action'  => 'push',
1526
					'last_sync_status'  => $this->mappings->status_success,
1527
					'last_sync_message' => sprintf(
1528
						// translators: placeholder is for the action that occurred on the mapping object (pending or created).
1529
						esc_html__( 'Mapping object %1$s via function: ', 'object-sync-for-salesforce' ) . __FUNCTION__,
0 ignored issues
show
Bug introduced by
The function esc_html__ was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1529
						/** @scrutinizer ignore-call */ 
1530
      esc_html__( 'Mapping object %1$s via function: ', 'object-sync-for-salesforce' ) . __FUNCTION__,
Loading history...
1530
						esc_attr( $action )
0 ignored issues
show
Bug introduced by
The function esc_attr was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1530
						/** @scrutinizer ignore-call */ 
1531
      esc_attr( $action )
Loading history...
1531
					),
1532
					'action'            => $action,
1533
				)
1534
			);
1535
			return $mapping_object;
1536
		}
1537
	}
1538
1539
	/**
1540
	 * Find out if push is allowed for this record
1541
	 *
1542
	 * @param string $object_type WordPress object type.
1543
	 * @param array  $object Array of the WordPress object's data.
1544
	 * @param string $sf_sync_trigger The current operation's trigger.
1545
	 * @param array  $mapping the fieldmap that maps the two object types.
1546
	 * @param array  $map_sync_triggers the enabld map triggers.
1547
	 * @return bool $push_allowed Whether all this stuff allows the $api_result to be pushed to Salesforce
1548
	 */
1549
	private function is_push_allowed( $object_type, $object, $sf_sync_trigger, $mapping, $map_sync_triggers ) {
1550
1551
		// default is push is allowed.
1552
		$push_allowed = true;
1553
1554
		// if the current fieldmap does not allow the wp create trigger, we need to check if there is an object map for the WordPress object ID. if not, set push_allowed to false.
1555
		if ( ! in_array( $this->mappings->sync_wordpress_create, (array) $map_sync_triggers, true ) ) {
1556
			$structure               = $this->wordpress->get_wordpress_table_structure( $object_type );
1557
			$wordpress_id_field_name = $structure['id_field'];
1558
			$object_map              = array();
1559
			// we only need to check against the first mapping object, if it exists. we don't need to loop through them.
1560
			$object_maps = $this->mappings->load_all_by_wordpress( $object_type, $object[ $wordpress_id_field_name ] );
1561
			if ( ! empty( $object_maps ) ) {
1562
				$object_map = $object_maps[0];
1563
			}
1564
			if ( empty( $object_map ) ) {
1565
				$push_allowed = false;
1566
			}
1567
		}
1568
1569
		// check if this is a Salesforce sync trigger.
1570
		if ( ! in_array( $sf_sync_trigger, (array) $map_sync_triggers, true ) ) {
1571
			$push_allowed = false;
1572
		}
1573
1574
		// hook to allow other plugins to prevent a push per-mapping.
1575
		$push_allowed = apply_filters( $this->option_prefix . 'push_object_allowed', $push_allowed, $object_type, $object, $sf_sync_trigger, $mapping );
0 ignored issues
show
Bug introduced by
The function apply_filters was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1575
		$push_allowed = /** @scrutinizer ignore-call */ apply_filters( $this->option_prefix . 'push_object_allowed', $push_allowed, $object_type, $object, $sf_sync_trigger, $mapping );
Loading history...
1576
1577
		// example to keep from pushing the user with ID of 1.
1578
		/* // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
1579
		add_filter( 'object_sync_for_salesforce_push_object_allowed', 'check_user', 10, 5 );
1580
		// can always reduce this number if all the arguments are not necessary
1581
		function check_user( $push_allowed, $object_type, $object, $sf_sync_trigger, $mapping ) {
1582
			if ( 'user' === $object_type && 1 === $object['ID'] ) { // do not add user 1 to salesforce
1583
				$push_allowed = false;
1584
			}
1585
			return $push_allowed;
1586
		}
1587
		*/
1588
1589
		return $push_allowed;
1590
	}
1591
1592
}
1593