Passed
Push — 216-reliably-detect-ssl ( 7e57eb...99ce26 )
by Jonathan
04:19
created

Object_Sync_Sf_Admin::logout()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 7
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 8
rs 10
1
<?php
2
/**
3
 * Class file for the Object_Sync_Sf_Admin class.
4
 *
5
 * @file
6
 */
7
8
if ( ! class_exists( 'Object_Sync_Salesforce' ) ) {
9
	die();
10
}
11
12
/**
13
 * Create default WordPress admin functionality to configure the plugin.
14
 */
15
class Object_Sync_Sf_Admin {
16
17
	protected $wpdb;
18
	protected $version;
19
	protected $login_credentials;
20
	protected $slug;
21
	protected $salesforce;
22
	protected $wordpress;
23
	protected $mappings;
24
	protected $push;
25
	protected $pull;
26
	protected $logging;
27
	protected $schedulable_classes;
28
	protected $queue;
29
	protected $option_prefix;
30
31
	private $sfwp_transients;
32
33
	private $access_token;
34
	private $instance_url;
35
	private $refresh_token;
36
37
	/**
38
	* @var string
39
	* Default path for the Salesforce authorize URL
40
	*/
41
	public $default_authorize_url_path;
42
43
	/**
44
	* @var string
45
	* Default path for the Salesforce token URL
46
	*/
47
	public $default_token_url_path;
48
49
	/**
50
	* @var string
51
	* What version of the Salesforce API should be the default on the settings screen.
52
	* Users can edit this, but they won't see a correct list of all their available versions until WordPress has
53
	* been authenticated with Salesforce.
54
	*/
55
	public $default_api_version;
56
57
	/**
58
	* @var int
59
	* Default max number of pull records
60
	* Users can edit this
61
	*/
62
	public $default_pull_limit;
63
64
	/**
65
	* @var int
66
	* Default pull throttle for how often Salesforce can pull
67
	* Users can edit this
68
	*/
69
	public $default_pull_throttle;
70
71
	/**
72
	* @var bool
73
	* Default for whether to limit to triggerable items
74
	* Users can edit this
75
	*/
76
	public $default_triggerable;
77
78
	/**
79
	* @var bool
80
	* Default for whether to limit to updateable items
81
	* Users can edit this
82
	*/
83
	public $default_updateable;
84
85
	/**
86
	* @var string
87
	* Suffix for action group name
88
	*/
89
	public $action_group_suffix;
90
91
	/**
92
	* Constructor which sets up admin pages
93
	*
94
	* @param object $wpdb
95
	* @param string $version
96
	* @param array $login_credentials
97
	* @param string $slug
98
	* @param object $wordpress
99
	* @param object $salesforce
100
	* @param object $mappings
101
	* @param object $push
102
	* @param object $pull
103
	* @param object $logging
104
	* @param array $schedulable_classes
105
	* @param object $queue
106
	* @throws \Exception
107
	*/
108
	public function __construct( $wpdb, $version, $login_credentials, $slug, $wordpress, $salesforce, $mappings, $push, $pull, $logging, $schedulable_classes, $queue = '', $option_prefix = '' ) {
109
		$this->wpdb                = $wpdb;
110
		$this->version             = $version;
111
		$this->login_credentials   = $login_credentials;
112
		$this->slug                = $slug;
113
		$this->option_prefix       = isset( $option_prefix ) ? $option_prefix : 'object_sync_for_salesforce_';
114
		$this->wordpress           = $wordpress;
115
		$this->salesforce          = $salesforce;
116
		$this->mappings            = $mappings;
117
		$this->push                = $push;
118
		$this->pull                = $pull;
119
		$this->logging             = $logging;
120
		$this->schedulable_classes = $schedulable_classes;
121
		$this->queue               = $queue;
122
123
		$this->sfwp_transients = $this->wordpress->sfwp_transients;
124
125
		// default authorize url path
126
		$this->default_authorize_url_path = '/services/oauth2/authorize';
127
		// default token url path
128
		$this->default_token_url_path = '/services/oauth2/token';
129
		// what Salesforce API version to start the settings with. This is only used in the settings form
130
		$this->default_api_version = '50.0';
131
		// default pull record limit
132
		$this->default_pull_limit = 25;
133
		// default pull throttle for avoiding going over api limits
134
		$this->default_pull_throttle = 5;
135
		// default setting for triggerable items
136
		$this->default_triggerable = true;
137
		// default setting for updateable items
138
		$this->default_updateable = true;
139
		// suffix for action groups
140
		$this->action_group_suffix = '_check_records';
141
142
		$this->add_actions();
143
		$this->add_deprecated_actions();
144
145
	}
146
147
	/**
148
	* Create the action hooks to create the admin page(s)
149
	*
150
	*/
151
	public function add_actions() {
152
		// Settings API forms and notices
153
		add_action( 'admin_init', array( $this, 'salesforce_settings_forms' ) );
154
		add_action( 'admin_init', array( $this, 'notices' ) );
155
		add_action( 'admin_post_post_fieldmap', array( $this, 'prepare_fieldmap_data' ) );
156
		add_action( 'admin_post_delete_fieldmap', array( $this, 'delete_fieldmap' ) );
157
158
		// Ajax for fieldmap forms
159
		add_action( 'wp_ajax_get_salesforce_object_description', array( $this, 'get_salesforce_object_description' ), 10, 1 );
160
		add_action( 'wp_ajax_get_salesforce_object_fields', array( $this, 'get_salesforce_object_fields' ), 10, 1 );
161
		add_action( 'wp_ajax_get_wordpress_object_fields', array( $this, 'get_wordpress_object_fields' ), 10, 1 );
162
163
		// Ajax events that can be manually called
164
		add_action( 'wp_ajax_push_to_salesforce', array( $this, 'push_to_salesforce' ), 10, 3 );
165
		add_action( 'wp_ajax_pull_from_salesforce', array( $this, 'pull_from_salesforce' ), 10, 2 );
166
		add_action( 'wp_ajax_refresh_mapped_data', array( $this, 'refresh_mapped_data' ), 10, 1 );
167
		add_action( 'wp_ajax_clear_sfwp_cache', array( $this, 'clear_sfwp_cache' ) );
168
169
		// we add a Salesforce box on user profiles
170
		add_action( 'edit_user_profile', array( $this, 'show_salesforce_user_fields' ), 10, 1 );
171
		add_action( 'show_user_profile', array( $this, 'show_salesforce_user_fields' ), 10, 1 );
172
173
		// and we can update Salesforce fields on the user profile box
174
		add_action( 'personal_options_update', array( $this, 'save_salesforce_user_fields' ), 10, 1 );
175
		add_action( 'edit_user_profile_update', array( $this, 'save_salesforce_user_fields' ), 10, 1 );
176
177
		// when either field for schedule settings changes
178
		foreach ( $this->schedulable_classes as $key => $value ) {
179
			// if the user doesn't have any action schedule tasks, let's not leave them empty
180
			add_filter( 'pre_update_option_' . $this->option_prefix . $key . '_schedule_number', array( $this, 'initial_action_schedule' ), 10, 3 );
181
			add_filter( 'pre_update_option_' . $this->option_prefix . $key . '_schedule_unit', array( $this, 'initial_action_schedule' ), 10, 3 );
182
183
			// this is if the user is changing their tasks
184
			add_filter( 'update_option_' . $this->option_prefix . $key . '_schedule_number', array( $this, 'change_action_schedule' ), 10, 3 );
185
			add_filter( 'update_option_' . $this->option_prefix . $key . '_schedule_unit', array( $this, 'change_action_schedule' ), 10, 3 );
186
		}
187
188
		// handle post requests for object maps
189
		add_action( 'admin_post_delete_object_map', array( $this, 'delete_object_map' ) );
190
		add_action( 'admin_post_post_object_map', array( $this, 'prepare_object_map_data' ) );
191
192
		// import and export plugin data
193
		add_action( 'admin_post_object_sync_for_salesforce_import', array( $this, 'import_json_file' ) );
194
		add_action( 'admin_post_object_sync_for_salesforce_export', array( $this, 'export_json_file' ) );
195
196
	}
197
198
	/**
199
	* Deprecated action hooks for admin pages
200
	*
201
	*/
202
	private function add_deprecated_actions() {
203
		/**
204
		 * method: get_wordpress_object_description
205
		 * @deprecated since 1.9.0
206
		 */
207
		add_action( 'wp_ajax_get_wordpress_object_description', array( $this, 'get_wordpress_object_fields' ), 10, 1 );
208
		/**
209
		 * method: get_wp_sf_object_fields
210
		 * @deprecated since 1.9.0
211
		 */
212
		add_action( 'wp_ajax_get_wp_sf_object_fields', array( $this, 'get_wp_sf_object_fields' ), 10, 2 );
213
	}
214
215
	/**
216
	* Set up recurring tasks if there are none
217
	*
218
	* @param string $new_schedule
219
	* @param string $old_schedule
220
	* @param string $option_name
221
	* @return string $new_schedule
222
	*
223
	*/
224
	public function initial_action_schedule( $new_schedule, $old_schedule, $option_name ) {
225
226
		// get the current schedule name from the task, based on pattern in the foreach
227
		preg_match( '/' . $this->option_prefix . '(.*)_schedule/', $option_name, $matches );
228
		$schedule_name     = $matches[1];
229
		$action_group_name = $schedule_name . $this->action_group_suffix;
230
231
		// make sure there are no tasks already
232
		$current_tasks = as_get_scheduled_actions(
233
			array(
234
				'hook'  => $this->schedulable_classes[ $schedule_name ]['initializer'],
235
				'group' => $action_group_name,
236
			),
237
			ARRAY_A
238
		);
239
240
		// exit if there are already tasks; they'll be saved if the option data changed
241
		if ( ! empty( $current_tasks ) ) {
242
			return $new_schedule;
243
		}
244
245
		$this->set_action_schedule( $schedule_name, $action_group_name );
246
247
		return $new_schedule;
248
249
	}
250
251
	/**
252
	* Change recurring tasks if options change
253
	*
254
	* @param string $old_schedule
255
	* @param string $new_schedule
256
	* @param string $option_name
257
	*
258
	*/
259
	public function change_action_schedule( $old_schedule, $new_schedule, $option_name ) {
260
261
		// this method does not run if the option's data is unchanged
262
263
		// get the current schedule name from the task, based on pattern in the foreach
264
		preg_match( '/' . $this->option_prefix . '(.*)_schedule/', $option_name, $matches );
265
		$schedule_name     = $matches[1];
266
		$action_group_name = $schedule_name . $this->action_group_suffix;
267
268
		$this->set_action_schedule( $schedule_name, $action_group_name );
269
270
	}
271
272
	/**
273
	* Set up recurring tasks
274
	*
275
	* @param string $schedule_name
276
	* @param string $action_group_name
277
	*
278
	*/
279
	private function set_action_schedule( $schedule_name, $action_group_name ) {
280
		// exit if there is no initializer property on this schedule
281
		if ( ! isset( $this->schedulable_classes[ $schedule_name ]['initializer'] ) ) {
282
			return;
283
		}
284
285
		// cancel previous task
286
		$this->queue->cancel(
287
			$this->schedulable_classes[ $schedule_name ]['initializer'],
288
			array(),
289
			$action_group_name
290
		);
291
292
		// create new recurring task for action-scheduler to check for data to pull from salesforce
293
		$this->queue->schedule_recurring(
294
			time(), // plugin seems to expect UTC
295
			$this->queue->get_frequency( $schedule_name, 'seconds' ),
296
			$this->schedulable_classes[ $schedule_name ]['initializer'],
297
			array(),
298
			$action_group_name
299
		);
300
	}
301
302
	/**
303
	* Create WordPress admin options page
304
	*
305
	*/
306
	public function create_admin_menu() {
307
		$title = __( 'Salesforce', 'object-sync-for-salesforce' );
308
		add_options_page( $title, $title, 'configure_salesforce', 'object-sync-salesforce-admin', array( $this, 'show_admin_page' ) );
309
	}
310
311
	/**
312
	* Render full admin pages in WordPress
313
	* This allows other plugins to add tabs to the Salesforce settings screen
314
	*
315
	* todo: better front end: html, organization of html into templates, css, js
316
	*
317
	*/
318
	public function show_admin_page() {
319
		$get_data = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
320
		echo '<div class="wrap">';
321
		echo '<h1>' . esc_html( get_admin_page_title() ) . '</h1>';
322
		$allowed = $this->check_wordpress_admin_permissions();
323
		if ( false === $allowed ) {
324
			return;
325
		}
326
		$tabs = array(
327
			'settings'      => __( 'Settings', 'object-sync-for-salesforce' ),
328
			'authorize'     => __( 'Authorize', 'object-sync-for-salesforce' ),
329
			'fieldmaps'     => __( 'Fieldmaps', 'object-sync-for-salesforce' ),
330
			'schedule'      => __( 'Scheduling', 'object-sync-for-salesforce' ),
331
			'import-export' => __( 'Import &amp; Export', 'object-sync-for-salesforce' ),
332
		); // this creates the tabs for the admin
333
334
		// optionally make tab(s) for logging and log settings
335
		$logging_enabled      = get_option( $this->option_prefix . 'enable_logging', false );
336
		$tabs['log_settings'] = __( 'Log Settings', 'object-sync-for-salesforce' );
337
338
		$mapping_errors       = $this->mappings->get_failed_object_maps();
339
		$mapping_errors_total = isset( $mapping_errors['total'] ) ? $mapping_errors['total'] : 0;
340
		if ( 0 < $mapping_errors_total ) {
341
			$tabs['mapping_errors'] = __( 'Mapping Errors', 'object-sync-for-salesforce' );
342
		}
343
344
		// filter for extending the tabs available on the page
345
		// currently it will go into the default switch case for $tab
346
		$tabs = apply_filters( $this->option_prefix . 'settings_tabs', $tabs );
347
348
		$tab = isset( $get_data['tab'] ) ? sanitize_key( $get_data['tab'] ) : 'settings';
349
		$this->tabs( $tabs, $tab );
350
351
		$consumer_key    = $this->login_credentials['consumer_key'];
352
		$consumer_secret = $this->login_credentials['consumer_secret'];
353
		$callback_url    = $this->login_credentials['callback_url'];
354
355
		if ( true !== $this->salesforce['is_authorized'] ) {
356
			$url     = esc_url( $callback_url );
357
			$anchor  = esc_html__( 'Authorize tab', 'object-sync-for-salesforce' );
358
			$message = sprintf( 'Salesforce needs to be authorized to connect to this website. Use the <a href="%s">%s</a> to connect.', $url, $anchor );
359
			require( plugin_dir_path( __FILE__ ) . '/../templates/admin/error.php' );
360
		}
361
362
		if ( 0 === count( $this->mappings->get_fieldmaps() ) ) {
363
			$url     = esc_url( get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=fieldmaps' ) );
364
			$anchor  = esc_html__( 'Fieldmaps tab', 'object-sync-for-salesforce' );
365
			$message = sprintf( 'No fieldmaps exist yet. Use the <a href="%s">%s</a> to map WordPress and Salesforce objects to each other.', $url, $anchor );
366
			require( plugin_dir_path( __FILE__ ) . '/../templates/admin/error.php' );
367
		}
368
369
		try {
370
			switch ( $tab ) {
371
				case 'authorize':
372
					if ( isset( $get_data['code'] ) ) {
373
						// this string is an oauth token
374
						$data          = esc_html( wp_unslash( $get_data['code'] ) );
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($get_data['code']) can also be of type array; however, parameter $text of esc_html() does only seem to accept 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

374
						$data          = esc_html( /** @scrutinizer ignore-type */ wp_unslash( $get_data['code'] ) );
Loading history...
375
						$is_authorized = $this->salesforce['sfapi']->request_token( $data );
376
						?>
377
						<script>window.location = '<?php echo esc_url_raw( $callback_url ); ?>'</script>
378
						<?php
379
					} elseif ( true === $this->salesforce['is_authorized'] ) {
380
							require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/authorized.php' );
381
							$this->status( $this->salesforce['sfapi'] );
382
					} elseif ( true === is_object( $this->salesforce['sfapi'] ) && isset( $consumer_key ) && isset( $consumer_secret ) ) {
383
						?>
384
						<p><a class="button button-primary" href="<?php echo esc_url( $this->salesforce['sfapi']->get_authorization_code() ); ?>"><?php echo esc_html__( 'Connect to Salesforce', 'object-sync-for-salesforce' ); ?></a></p>
385
						<?php
386
					} else {
387
						$url    = esc_url( get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=settings' ) );
388
						$anchor = esc_html__( 'Settings', 'object-sync-for-salesforce' );
389
						// translators: placeholders are for the settings tab link: 1) the url, and 2) the anchor text
390
						$message = sprintf( esc_html__( 'Salesforce needs to be authorized to connect to this website but the credentials are missing. Use the <a href="%1$s">%2$s</a> tab to add them.', 'object-sync-salesforce' ), $url, $anchor );
391
						require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/error.php' );
392
					}
393
					break;
394
				case 'fieldmaps':
395
					if ( isset( $get_data['method'] ) ) {
396
397
						$method      = sanitize_key( $get_data['method'] );
398
						$error_url   = get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=fieldmaps&method=' . $method );
399
						$success_url = get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=fieldmaps' );
400
401
						if ( isset( $get_data['transient'] ) ) {
402
							$transient = sanitize_key( $get_data['transient'] );
403
							$posted    = $this->sfwp_transients->get( $transient );
404
						}
405
406
						if ( isset( $posted ) && is_array( $posted ) ) {
407
							$map = $posted;
408
						} elseif ( 'edit' === $method || 'clone' === $method || 'delete' === $method ) {
409
							$map = $this->mappings->get_fieldmaps( isset( $get_data['id'] ) ? sanitize_key( $get_data['id'] ) : '' );
410
						}
411
412
						if ( isset( $map ) && is_array( $map ) ) {
413
							$label                           = $map['label'];
414
							$salesforce_object               = $map['salesforce_object'];
415
							$salesforce_record_types_allowed = maybe_unserialize( $map['salesforce_record_types_allowed'] );
416
							$salesforce_record_type_default  = $map['salesforce_record_type_default'];
417
							$wordpress_object                = $map['wordpress_object'];
418
							$pull_trigger_field              = $map['pull_trigger_field'];
419
							$fieldmap_fields                 = $map['fields'];
420
							$sync_triggers                   = $map['sync_triggers'];
421
							$push_async                      = $map['push_async'];
422
							$push_drafts                     = $map['push_drafts'];
423
							$pull_to_drafts                  = $map['pull_to_drafts'];
424
							$weight                          = $map['weight'];
425
						}
426
427
						if ( 'add' === $method || 'edit' === $method || 'clone' === $method ) {
428
							require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/fieldmaps-add-edit-clone.php' );
429
						} elseif ( 'delete' === $method ) {
430
							require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/fieldmaps-delete.php' );
431
						}
432
					} else {
433
						$fieldmaps = $this->mappings->get_fieldmaps();
434
						require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/fieldmaps-list.php' );
435
					} // End if().
436
					break;
437
				case 'logout':
438
					$this->logout();
439
					break;
440
				case 'clear_cache':
441
					$this->clear_cache();
442
					break;
443
				case 'clear_schedule':
444
					if ( isset( $get_data['schedule_name'] ) ) {
445
						$schedule_name = sanitize_key( $get_data['schedule_name'] );
446
					}
447
					$this->clear_schedule( $schedule_name );
448
					break;
449
				case 'settings':
450
					require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/settings.php' );
451
					break;
452
				case 'mapping_errors':
453
					if ( isset( $get_data['method'] ) ) {
454
455
						$method      = sanitize_key( $get_data['method'] );
456
						$error_url   = get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=mapping_errors&method=' . $method );
457
						$success_url = get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=mapping_errors' );
458
459
						if ( isset( $get_data['map_transient'] ) ) {
460
							$transient = sanitize_key( $get_data['map_transient'] );
461
							$posted    = $this->sfwp_transients->get( $transient );
462
						}
463
464
						if ( isset( $posted ) && is_array( $posted ) ) {
465
							$map_row = $posted;
466
						} elseif ( 'edit' === $method || 'delete' === $method ) {
467
							$map_row = $this->mappings->get_failed_object_map( isset( $get_data['id'] ) ? sanitize_key( $get_data['id'] ) : '' );
468
						}
469
470
						if ( isset( $map_row ) && is_array( $map_row ) ) {
471
							$salesforce_id = $map_row['salesforce_id'];
472
							$wordpress_id  = $map_row['wordpress_id'];
473
						}
474
475
						if ( 'edit' === $method ) {
476
							require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/mapping-errors-edit.php' );
477
						} elseif ( 'delete' === $method ) {
478
							require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/mapping-errors-delete.php' );
479
						}
480
					} else {
481
482
						if ( isset( $get_data['mapping_error_transient'] ) ) {
483
							$transient = sanitize_key( $get_data['mapping_error_transient'] );
484
							$posted    = $this->sfwp_transients->get( $transient );
485
						}
486
487
						$ids_string = '';
488
						$ids        = array();
489
						if ( isset( $posted['delete'] ) ) {
490
							$ids_string = maybe_serialize( $posted['delete'] );
491
							$ids        = $posted['delete'];
492
						}
493
494
						$error_url   = get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=mapping_errors&ids=' . $ids_string );
495
						$success_url = get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=mapping_errors' );
496
						require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/mapping-errors.php' );
497
					}
498
					break;
499
				case 'import-export':
500
					require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/import-export.php' );
501
					break;
502
				default:
503
					$include_settings = apply_filters( $this->option_prefix . 'settings_tab_include_settings', true, $tab );
504
					$content_before   = apply_filters( $this->option_prefix . 'settings_tab_content_before', null, $tab );
505
					$content_after    = apply_filters( $this->option_prefix . 'settings_tab_content_after', null, $tab );
506
					if ( null !== $content_before ) {
507
						echo esc_html( $content_before );
508
					}
509
					if ( true === $include_settings ) {
510
						require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/settings.php' );
511
					}
512
					if ( null !== $content_after ) {
513
						echo esc_html( $content_after );
514
					}
515
					break;
516
			} // End switch().
517
		} catch ( SalesforceApiException $ex ) {
0 ignored issues
show
Bug introduced by
The type SalesforceApiException 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...
518
			echo sprintf(
519
				'<p>Error <strong>%1$s</strong>: %2$s</p>',
520
				absint( $ex->getCode() ),
521
				esc_html( $ex->getMessage() )
522
			);
523
		} catch ( Exception $ex ) {
524
			echo sprintf(
525
				'<p>Error <strong>%1$s</strong>: %2$s</p>',
526
				absint( $ex->getCode() ),
527
				esc_html( $ex->getMessage() )
528
			);
529
		} // End try().
530
		echo '</div>';
531
	}
532
533
	/**
534
	* Create default WordPress admin settings form for salesforce
535
	* This is for the Settings page/tab
536
	*
537
	*/
538
	public function salesforce_settings_forms() {
539
		$get_data = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
540
		$page     = isset( $get_data['tab'] ) ? sanitize_key( $get_data['tab'] ) : 'settings';
541
		$section  = isset( $get_data['tab'] ) ? sanitize_key( $get_data['tab'] ) : 'settings';
542
543
		$input_callback_default   = array( $this, 'display_input_field' );
544
		$input_checkboxes_default = array( $this, 'display_checkboxes' );
545
		$input_select_default     = array( $this, 'display_select' );
546
		$link_default             = array( $this, 'display_link' );
547
548
		$all_field_callbacks = array(
549
			'text'       => $input_callback_default,
550
			'checkboxes' => $input_checkboxes_default,
551
			'select'     => $input_select_default,
552
			'link'       => $link_default,
553
		);
554
555
		$this->fields_settings( 'settings', 'settings', $all_field_callbacks );
556
		$this->fields_fieldmaps( 'fieldmaps', 'objects' );
557
		$this->fields_scheduling( 'schedule', 'schedule', $all_field_callbacks );
558
		$this->fields_log_settings( 'log_settings', 'log_settings', $all_field_callbacks );
559
		$this->fields_errors( 'mapping_errors', 'mapping_errors', $all_field_callbacks );
560
	}
561
562
	/**
563
	* Fields for the Settings tab
564
	* This runs add_settings_section once, as well as add_settings_field and register_setting methods for each option
565
	*
566
	* @param string $page
567
	* @param string $section
568
	* @param string $input_callback
569
	*/
570
	private function fields_settings( $page, $section, $callbacks ) {
571
		add_settings_section( $page, ucwords( $page ), null, $page );
572
		$salesforce_settings = array(
573
			'consumer_key'                   => array(
574
				'title'    => __( 'Consumer Key', 'object-sync-for-salesforce' ),
575
				'callback' => $callbacks['text'],
576
				'page'     => $page,
577
				'section'  => $section,
578
				'args'     => array(
579
					'type'     => 'text',
580
					'validate' => 'sanitize_validate_text',
581
					'desc'     => '',
582
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_CONSUMER_KEY',
583
				),
584
585
			),
586
			'consumer_secret'                => array(
587
				'title'    => __( 'Consumer Secret', 'object-sync-for-salesforce' ),
588
				'callback' => $callbacks['text'],
589
				'page'     => $page,
590
				'section'  => $section,
591
				'args'     => array(
592
					'type'     => 'text',
593
					'validate' => 'sanitize_validate_text',
594
					'desc'     => '',
595
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_CONSUMER_SECRET',
596
				),
597
			),
598
			'callback_url'                   => array(
599
				'title'    => __( 'Callback URL', 'object-sync-for-salesforce' ),
600
				'callback' => $callbacks['text'],
601
				'page'     => $page,
602
				'section'  => $section,
603
				'args'     => array(
604
					'type'     => 'url',
605
					'validate' => 'sanitize_validate_text',
606
					'desc'     => sprintf(
607
						// translators: %1$s is the admin URL for the Authorize tab
608
						__( 'In most cases, you will want to use %1$s for this value.', 'object-sync-for-salesforce' ),
609
						get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=authorize' )
610
					),
611
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_CALLBACK_URL',
612
				),
613
			),
614
			'login_base_url'                 => array(
615
				'title'    => __( 'Login Base URL', 'object-sync-for-salesforce' ),
616
				'callback' => $callbacks['text'],
617
				'page'     => $page,
618
				'section'  => $section,
619
				'args'     => array(
620
					'type'     => 'url',
621
					'validate' => 'sanitize_validate_text',
622
					'desc'     => sprintf(
623
						// translators: 1) production salesforce login, 2) sandbox salesforce login
624
						__( 'For most Salesforce setups, you should use %1$s for production and %2$s for sandbox. If you try to use an instance name as the URL, you may encounter Salesforce errors.', 'object-sync-for-salesforce' ),
625
						esc_url( 'https://login.salesforce.com' ),
626
						esc_url( 'https://test.salesforce.com' )
627
					),
628
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_LOGIN_BASE_URL',
629
				),
630
			),
631
			'authorize_url_path'             => array(
632
				'title'    => __( 'Authorize URL Path', 'object-sync-for-salesforce' ),
633
				'callback' => $callbacks['text'],
634
				'page'     => $page,
635
				'section'  => $section,
636
				'args'     => array(
637
					'type'     => 'text',
638
					'validate' => 'sanitize_validate_text',
639
					'desc'     => __( 'For most Salesforce installs, this should not be changed.', 'object-sync-for-salesforce' ),
640
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_AUTHORIZE_URL_PATH',
641
					'default'  => $this->default_authorize_url_path,
642
				),
643
			),
644
			'token_url_path'                 => array(
645
				'title'    => __( 'Token URL Path', 'object-sync-for-salesforce' ),
646
				'callback' => $callbacks['text'],
647
				'page'     => $page,
648
				'section'  => $section,
649
				'args'     => array(
650
					'type'     => 'text',
651
					'validate' => 'sanitize_validate_text',
652
					'desc'     => __( 'For most Salesforce installs, this should not be changed.', 'object-sync-for-salesforce' ),
653
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_TOKEN_URL_PATH',
654
					'default'  => $this->default_token_url_path,
655
				),
656
			),
657
			'api_version'                    => array(
658
				'title'    => __( 'Salesforce API Version', 'object-sync-for-salesforce' ),
659
				'callback' => $callbacks['text'],
660
				'page'     => $page,
661
				'section'  => $section,
662
				'args'     => array(
663
					'type'     => 'text',
664
					'validate' => 'sanitize_validate_text',
665
					'desc'     => '',
666
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_API_VERSION',
667
					'default'  => $this->default_api_version,
668
				),
669
			),
670
			'object_filters'                 => array(
671
				'title'    => __( 'Limit Salesforce Objects', 'object-sync-for-salesforce' ),
672
				'callback' => $callbacks['checkboxes'],
673
				'page'     => $page,
674
				'section'  => $section,
675
				'args'     => array(
676
					'type'     => 'checkboxes',
677
					'validate' => 'sanitize_validate_text',
678
					'desc'     => __( 'Allows you to limit which Salesforce objects can be mapped', 'object-sync-for-salesforce' ),
679
					'items'    => array(
680
						'triggerable' => array(
681
							'text'    => __( 'Only Triggerable objects', 'object-sync-for-salesforce' ),
682
							'id'      => 'triggerable',
683
							'desc'    => '',
684
							'default' => $this->default_triggerable,
685
						),
686
						'updateable'  => array(
687
							'text'    => __( 'Only Updateable objects', 'object-sync-for-salesforce' ),
688
							'id'      => 'updateable',
689
							'desc'    => '',
690
							'default' => $this->default_updateable,
691
						),
692
					),
693
				),
694
			),
695
			'salesforce_field_display_value' => array(
696
				'title'    => __( 'Salesforce Field Display Value', 'object-sync-for-salesforce' ),
697
				'callback' => $callbacks['select'],
698
				'page'     => $page,
699
				'section'  => $section,
700
				'args'     => array(
701
					'type'     => 'select',
702
					'validate' => 'sanitize_validate_text',
703
					'desc'     => __( 'When choosing Salesforce fields to map, this value determines how the dropdown will identify Salesforce fields.', 'object-sync-for-salesforce' ),
704
					'constant' => '',
705
					'items'    => array(
706
						'field_label' => array(
707
							'text'  => __( 'Field Label', 'object-sync-for-salesforce' ),
708
							'value' => 'field_label',
709
						),
710
						/*'field_name'  => array(
711
							'text'  => __( 'Field Name', 'object-sync-for-salesforce' ),
712
							'value' => 'field_name',
713
						),*/
714
						'api_name'    => array(
715
							'text'  => __( 'API Name', 'object-sync-for-salesforce' ),
716
							'value' => 'api_name',
717
						),
718
					),
719
				),
720
			),
721
			'pull_query_limit'               => array(
722
				'title'    => __( 'Pull query record limit', 'object-sync-for-salesforce' ),
723
				'callback' => $callbacks['text'],
724
				'page'     => $page,
725
				'section'  => $section,
726
				'args'     => array(
727
					'type'     => 'number',
728
					'validate' => 'absint',
729
					'desc'     => __( 'Limit the number of records that can be pulled from Salesforce in a single query.', 'object-sync-for-salesforce' ),
730
					'constant' => '',
731
					'default'  => $this->default_pull_limit,
732
				),
733
			),
734
			'pull_throttle'                  => array(
735
				'title'    => __( 'Pull throttle (seconds)', 'object-sync-for-salesforce' ),
736
				'callback' => $callbacks['text'],
737
				'page'     => $page,
738
				'section'  => $section,
739
				'args'     => array(
740
					'type'     => 'number',
741
					'validate' => 'sanitize_validate_text',
742
					'desc'     => __( 'Number of seconds to wait between repeated salesforce pulls. Prevents the webserver from becoming overloaded in case of too many cron runs, or webhook usage.', 'object-sync-for-salesforce' ),
743
					'constant' => '',
744
					'default'  => $this->default_pull_throttle,
745
				),
746
			),
747
		);
748
749
		// only show soap settings if the soap extension is enabled on the server
750
		if ( true === $this->salesforce['soap_available'] ) {
751
			$salesforce_settings['use_soap']       = array(
752
				'title'    => __( 'Enable the Salesforce SOAP API?', 'object-sync-for-salesforce' ),
753
				'callback' => $callbacks['text'],
754
				'page'     => $page,
755
				'section'  => $section,
756
				'class'    => 'object-sync-for-salesforce-enable-soap',
757
				'args'     => array(
758
					'type'     => 'checkbox',
759
					'validate' => 'sanitize_text_field',
760
					'desc'     => __( 'Check this to enable the SOAP API and use it instead of the REST API when the plugin supports it. https://developer.salesforce.com/blogs/tech-pubs/2011/10/salesforce-apis-what-they-are-when-to-use-them.html to compare the two. Note: if you need to detect Salesforce merges in this plugin, you will need to enable SOAP.', 'object-sync-for-salesforce' ),
761
					'constant' => '',
762
				),
763
			);
764
			$salesforce_settings['soap_wsdl_path'] = array(
765
				'title'    => __( 'Path to SOAP WSDL file', 'object-sync-for-salesforce' ),
766
				'callback' => $callbacks['text'],
767
				'page'     => $page,
768
				'section'  => $section,
769
				'class'    => 'object-sync-for-salesforce-soap-wsdl-path',
770
				'args'     => array(
771
					'type'     => 'text',
772
					'validate' => 'sanitize_text_field',
773
					'desc'     => __( 'Optionally add a path to your own WSDL file. If you do not, the plugin will use the default partner.wsdl.xml from the Force.com toolkit.', 'object-sync-for-salesforce' ),
774
					'constant' => '',
775
				),
776
			);
777
		}
778
779
		$salesforce_settings['debug_mode']               = array(
780
			'title'    => __( 'Debug mode?', 'object-sync-for-salesforce' ),
781
			'callback' => $callbacks['text'],
782
			'page'     => $page,
783
			'section'  => $section,
784
			'args'     => array(
785
				'type'     => 'checkbox',
786
				'validate' => 'sanitize_text_field',
787
				'desc'     => __( 'Debug mode can, combined with the Log Settings, log things like Salesforce API requests. It can create a lot of entries if enabled; it is not recommended to use it in a production environment.', 'object-sync-for-salesforce' ),
788
				'constant' => '',
789
			),
790
		);
791
		$salesforce_settings['delete_data_on_uninstall'] = array(
792
			'title'    => __( 'Delete plugin data on uninstall?', 'object-sync-for-salesforce' ),
793
			'callback' => $callbacks['text'],
794
			'page'     => $page,
795
			'section'  => $section,
796
			'args'     => array(
797
				'type'     => 'checkbox',
798
				'validate' => 'sanitize_text_field',
799
				'desc'     => __( 'If checked, the plugin will delete the tables and other data it creates when you uninstall it. Unchecking this field can be useful if you need to reactivate the plugin for any reason without losing data.', 'object-sync-for-salesforce' ),
800
				'constant' => '',
801
			),
802
		);
803
804
		if ( true === is_object( $this->salesforce['sfapi'] ) && true === $this->salesforce['sfapi']->is_authorized() ) {
805
			$salesforce_settings['api_version'] = array(
806
				'title'    => __( 'Salesforce API Version', 'object-sync-for-salesforce' ),
807
				'callback' => $callbacks['select'],
808
				'page'     => $page,
809
				'section'  => $section,
810
				'args'     => array(
811
					'type'     => 'select',
812
					'validate' => 'sanitize_text_field',
813
					'desc'     => '',
814
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_API_VERSION',
815
					'items'    => $this->version_options(),
816
				),
817
			);
818
		}
819
820
		foreach ( $salesforce_settings as $key => $attributes ) {
821
			$id       = $this->option_prefix . $key;
822
			$name     = $this->option_prefix . $key;
823
			$title    = $attributes['title'];
824
			$callback = $attributes['callback'];
825
			$validate = $attributes['args']['validate'];
826
			$page     = $attributes['page'];
827
			$section  = $attributes['section'];
828
			$class    = isset( $attributes['class'] ) ? $attributes['class'] : '';
829
			$args     = array_merge(
830
				$attributes['args'],
831
				array(
832
					'title'     => $title,
833
					'id'        => $id,
834
					'label_for' => $id,
835
					'name'      => $name,
836
					'class'     => $class,
837
				)
838
			);
839
840
			// if there is a constant and it is defined, don't run a validate function
841
			if ( isset( $attributes['args']['constant'] ) && defined( $attributes['args']['constant'] ) ) {
842
				$validate = '';
843
			}
844
845
			add_settings_field( $id, $title, $callback, $page, $section, $args );
846
			register_setting( $page, $id, array( $this, $validate ) );
847
		}
848
	}
849
850
	/**
851
	* Fields for the Fieldmaps tab
852
	* This runs add_settings_section once, as well as add_settings_field and register_setting methods for each option
853
	*
854
	* @param string $page
855
	* @param string $section
856
	* @param string $input_callback
857
	*/
858
	private function fields_fieldmaps( $page, $section, $input_callback = '' ) {
859
		add_settings_section( $page, ucwords( $page ), null, $page );
860
	}
861
862
	/**
863
	* Fields for the Scheduling tab
864
	* This runs add_settings_section once, as well as add_settings_field and register_setting methods for each option
865
	*
866
	* @param string $page
867
	* @param string $section
868
	* @param string $input_callback
869
	*/
870
	private function fields_scheduling( $page, $section, $callbacks ) {
871
872
		add_settings_section( 'batch', __( 'Batch Settings', 'object-sync-for-salesforce' ), null, $page );
873
		$section           = 'batch';
874
		$schedule_settings = array(
875
			'action_scheduler_batch_size'         => array(
876
				'title'    => __( 'Batch size', 'object-sync-for-salesforce' ),
877
				'callback' => $callbacks['text'],
878
				'page'     => $page,
879
				'section'  => $section,
880
				'args'     => array(
881
					'type'     => 'number',
882
					'validate' => 'absint',
883
					'default'  => 5,
884
					'desc'     => __( 'Set how many actions (checking for data changes, syncing a record, etc. all count as individual actions) can be run in a batch. Start with a low number here, like 5, if you are unsure.', 'object-sync-for-salesforce' ),
885
					'constant' => '',
886
				),
887
888
			),
889
			'action_scheduler_concurrent_batches' => array(
890
				'title'    => __( 'Concurrent batches', 'object-sync-for-salesforce' ),
891
				'callback' => $callbacks['text'],
892
				'page'     => $page,
893
				'section'  => $section,
894
				'args'     => array(
895
					'type'     => 'number',
896
					'validate' => 'absint',
897
					'default'  => 3,
898
					'desc'     => __( 'Set how many batches of actions can be run at once. Start with a low number here, like 3, if you are unsure.', 'object-sync-for-salesforce' ),
899
					'constant' => '',
900
				),
901
			),
902
		);
903
904
		foreach ( $this->schedulable_classes as $key => $value ) {
905
			add_settings_section( $key, $value['label'], null, $page );
906
			if ( isset( $value['initializer'] ) ) {
907
				$schedule_settings[ $key . '_schedule_number' ] = array(
908
					'title'    => __( 'Run schedule every', 'object-sync-for-salesforce' ),
909
					'callback' => $callbacks['text'],
910
					'page'     => $page,
911
					'section'  => $key,
912
					'args'     => array(
913
						'type'     => 'number',
914
						'validate' => 'absint',
915
						'desc'     => '',
916
						'constant' => '',
917
					),
918
				);
919
				$schedule_settings[ $key . '_schedule_unit' ]   = array(
920
					'title'    => __( 'Time unit', 'object-sync-for-salesforce' ),
921
					'callback' => $callbacks['select'],
922
					'page'     => $page,
923
					'section'  => $key,
924
					'args'     => array(
925
						'type'     => 'select',
926
						'validate' => 'sanitize_validate_text',
927
						'desc'     => '',
928
						'items'    => array(
929
							'minutes' => array(
930
								'text'  => __( 'Minutes', 'object-sync-for-salesforce' ),
931
								'value' => 'minutes',
932
							),
933
							'hours'   => array(
934
								'text'  => __( 'Hours', 'object-sync-for-salesforce' ),
935
								'value' => 'hours',
936
							),
937
							'days'    => array(
938
								'text'  => __( 'Days', 'object-sync-for-salesforce' ),
939
								'value' => 'days',
940
							),
941
						),
942
					),
943
				);
944
			}
945
			$schedule_settings[ $key . '_clear_button' ] = array(
946
				// translators: $this->get_schedule_count is an integer showing how many items are in the current queue
947
				'title'    => sprintf( 'This queue has ' . _n( '%s item', '%s items', $this->get_schedule_count( $key ), 'object-sync-for-salesforce' ), $this->get_schedule_count( $key ) ),
948
				'callback' => $callbacks['link'],
949
				'page'     => $page,
950
				'section'  => $key,
951
				'args'     => array(
952
					'label'      => __( 'Clear this queue', 'object-sync-for-salesforce' ),
953
					'desc'       => '',
954
					'url'        => esc_url( '?page=object-sync-salesforce-admin&amp;tab=clear_schedule&amp;schedule_name=' . $key ),
955
					'link_class' => 'button button-secondary',
956
				),
957
			);
958
			foreach ( $schedule_settings as $key => $attributes ) {
959
				$id       = $this->option_prefix . $key;
960
				$name     = $this->option_prefix . $key;
961
				$title    = $attributes['title'];
962
				$callback = $attributes['callback'];
963
				$page     = $attributes['page'];
964
				$section  = $attributes['section'];
965
				$args     = array_merge(
966
					$attributes['args'],
967
					array(
968
						'title'     => $title,
969
						'id'        => $id,
970
						'label_for' => $id,
971
						'name'      => $name,
972
					)
973
				);
974
				add_settings_field( $id, $title, $callback, $page, $section, $args );
975
				register_setting( $page, $id );
976
			}
977
		} // End foreach().
978
	}
979
980
	/**
981
	* Fields for the Log Settings tab
982
	* This runs add_settings_section once, as well as add_settings_field and register_setting methods for each option
983
	*
984
	* @param string $page
985
	* @param string $section
986
	* @param array $callbacks
987
	*/
988
	private function fields_log_settings( $page, $section, $callbacks ) {
989
		add_settings_section( $page, ucwords( str_replace( '_', ' ', $page ) ), null, $page );
990
		$log_settings = array(
991
			'enable_logging'        => array(
992
				'title'    => __( 'Enable Logging?', 'object-sync-for-salesforce' ),
993
				'callback' => $callbacks['text'],
994
				'page'     => $page,
995
				'section'  => $section,
996
				'args'     => array(
997
					'type'     => 'checkbox',
998
					'validate' => 'absint',
999
					'desc'     => '',
1000
					'constant' => '',
1001
				),
1002
			),
1003
			'statuses_to_log'       => array(
1004
				'title'    => __( 'Statuses to log', 'object-sync-for-salesforce' ),
1005
				'callback' => $callbacks['checkboxes'],
1006
				'page'     => $page,
1007
				'section'  => $section,
1008
				'args'     => array(
1009
					'type'     => 'checkboxes',
1010
					'validate' => 'sanitize_validate_text',
1011
					'desc'     => __( 'these are the statuses to log', 'object-sync-for-salesforce' ),
1012
					'items'    => array(
1013
						'error'   => array(
1014
							'text' => __( 'Error', 'object-sync-for-salesforce' ),
1015
							'id'   => 'error',
1016
							'desc' => '',
1017
						),
1018
						'success' => array(
1019
							'text' => __( 'Success', 'object-sync-for-salesforce' ),
1020
							'id'   => 'success',
1021
							'desc' => '',
1022
						),
1023
						'notice'  => array(
1024
							'text' => __( 'Notice', 'object-sync-for-salesforce' ),
1025
							'id'   => 'notice',
1026
							'desc' => '',
1027
						),
1028
						'debug'   => array(
1029
							'text' => __( 'Debug', 'object-sync-for-salesforce' ),
1030
							'id'   => 'debug',
1031
							'desc' => '',
1032
						),
1033
					),
1034
				),
1035
			),
1036
			'prune_logs'            => array(
1037
				'title'    => __( 'Automatically delete old log entries?', 'object-sync-for-salesforce' ),
1038
				'callback' => $callbacks['text'],
1039
				'page'     => $page,
1040
				'section'  => $section,
1041
				'args'     => array(
1042
					'type'     => 'checkbox',
1043
					'validate' => 'absint',
1044
					'desc'     => '',
1045
					'constant' => '',
1046
				),
1047
			),
1048
			'logs_how_old'          => array(
1049
				'title'    => __( 'Age to delete log entries', 'object-sync-for-salesforce' ),
1050
				'callback' => $callbacks['text'],
1051
				'page'     => $page,
1052
				'section'  => $section,
1053
				'args'     => array(
1054
					'type'     => 'text',
1055
					'validate' => 'sanitize_validate_text',
1056
					'desc'     => __( 'If automatic deleting is enabled, it will affect logs this old.', 'object-sync-for-salesforce' ),
1057
					'default'  => '2 weeks',
1058
					'constant' => '',
1059
				),
1060
			),
1061
			'logs_how_often_number' => array(
1062
				'title'    => __( 'Check for old logs every', 'object-sync-for-salesforce' ),
1063
				'callback' => $callbacks['text'],
1064
				'page'     => $page,
1065
				'section'  => $section,
1066
				'args'     => array(
1067
					'type'     => 'number',
1068
					'validate' => 'absint',
1069
					'desc'     => '',
1070
					'default'  => '1',
1071
					'constant' => '',
1072
				),
1073
			),
1074
			'logs_how_often_unit'   => array(
1075
				'title'    => __( 'Time unit', 'object-sync-for-salesforce' ),
1076
				'callback' => $callbacks['select'],
1077
				'page'     => $page,
1078
				'section'  => $section,
1079
				'args'     => array(
1080
					'type'     => 'select',
1081
					'validate' => 'sanitize_validate_text',
1082
					'desc'     => __( 'These two fields are how often the site will check for logs to delete.', 'object-sync-for-salesforce' ),
1083
					'items'    => array(
1084
						'minutes' => array(
1085
							'text'  => __( 'Minutes', 'object-sync-for-salesforce' ),
1086
							'value' => 'minutes',
1087
						),
1088
						'hours'   => array(
1089
							'text'  => __( 'Hours', 'object-sync-for-salesforce' ),
1090
							'value' => 'hours',
1091
						),
1092
						'days'    => array(
1093
							'text'  => __( 'Days', 'object-sync-for-salesforce' ),
1094
							'value' => 'days',
1095
						),
1096
					),
1097
				),
1098
			),
1099
			'logs_how_many_number'  => array(
1100
				'title'    => __( 'Clear this many log entries', 'object-sync-for-salesforce' ),
1101
				'callback' => $callbacks['text'],
1102
				'page'     => $page,
1103
				'section'  => $section,
1104
				'args'     => array(
1105
					'type'     => 'number',
1106
					'validate' => 'absint',
1107
					'desc'     => __( 'This number is how many log entries the plugin will try to clear at a time. If you do not enter a number, the default is 100.', 'object-sync-for-salesforce' ),
1108
					'default'  => '100',
1109
					'constant' => '',
1110
				),
1111
			),
1112
			'triggers_to_log'       => array(
1113
				'title'    => __( 'Triggers to log', 'object-sync-for-salesforce' ),
1114
				'callback' => $callbacks['checkboxes'],
1115
				'page'     => $page,
1116
				'section'  => $section,
1117
				'args'     => array(
1118
					'type'     => 'checkboxes',
1119
					'validate' => 'sanitize_validate_text',
1120
					'desc'     => __( 'these are the triggers to log', 'object-sync-for-salesforce' ),
1121
					'items'    => array(
1122
						$this->mappings->sync_wordpress_create => array(
1123
							'text' => __( 'WordPress create', 'object-sync-for-salesforce' ),
1124
							'id'   => 'wordpress_create',
1125
							'desc' => '',
1126
						),
1127
						$this->mappings->sync_wordpress_update => array(
1128
							'text' => __( 'WordPress update', 'object-sync-for-salesforce' ),
1129
							'id'   => 'wordpress_update',
1130
							'desc' => '',
1131
						),
1132
						$this->mappings->sync_wordpress_delete => array(
1133
							'text' => __( 'WordPress delete', 'object-sync-for-salesforce' ),
1134
							'id'   => 'wordpress_delete',
1135
							'desc' => '',
1136
						),
1137
						$this->mappings->sync_sf_create => array(
1138
							'text' => __( 'Salesforce create', 'object-sync-for-salesforce' ),
1139
							'id'   => 'sf_create',
1140
							'desc' => '',
1141
						),
1142
						$this->mappings->sync_sf_update => array(
1143
							'text' => __( 'Salesforce update', 'object-sync-for-salesforce' ),
1144
							'id'   => 'sf_update',
1145
							'desc' => '',
1146
						),
1147
						$this->mappings->sync_sf_delete => array(
1148
							'text' => __( 'Salesforce delete', 'object-sync-for-salesforce' ),
1149
							'id'   => 'sf_delete',
1150
							'desc' => '',
1151
						),
1152
					),
1153
				),
1154
			),
1155
		);
1156
		foreach ( $log_settings as $key => $attributes ) {
1157
			$id       = $this->option_prefix . $key;
1158
			$name     = $this->option_prefix . $key;
1159
			$title    = $attributes['title'];
1160
			$callback = $attributes['callback'];
1161
			$page     = $attributes['page'];
1162
			$section  = $attributes['section'];
1163
			$args     = array_merge(
1164
				$attributes['args'],
1165
				array(
1166
					'title'     => $title,
1167
					'id'        => $id,
1168
					'label_for' => $id,
1169
					'name'      => $name,
1170
				)
1171
			);
1172
			add_settings_field( $id, $title, $callback, $page, $section, $args );
1173
			register_setting( $page, $id );
1174
		}
1175
	}
1176
1177
	/**
1178
	* Fields for the Mapping errors tab
1179
	* This runs add_settings_section once
1180
	*
1181
	* @param string $page
1182
	* @param string $section
1183
	* @param string $input_callback
1184
	*/
1185
	private function fields_errors( $page, $section, $callbacks ) {
1186
1187
		add_settings_section( $section, __( 'Mapping Error Settings', 'object-sync-for-salesforce' ), null, $page );
1188
		$error_settings = array(
1189
			'errors_per_page' => array(
1190
				'title'    => __( 'Errors per page', 'object-sync-for-salesforce' ),
1191
				'callback' => $callbacks['text'],
1192
				'page'     => $page,
1193
				'section'  => $section,
1194
				'args'     => array(
1195
					'type'     => 'number',
1196
					'validate' => 'absint',
1197
					'default'  => 50,
1198
					'desc'     => __( 'Set how many mapping errors to show on a single page.', 'object-sync-for-salesforce' ),
1199
					'constant' => '',
1200
				),
1201
			),
1202
		);
1203
1204
		foreach ( $error_settings as $key => $attributes ) {
1205
			$id       = $this->option_prefix . $key;
1206
			$name     = $this->option_prefix . $key;
1207
			$title    = $attributes['title'];
1208
			$callback = $attributes['callback'];
1209
			$page     = $attributes['page'];
1210
			$section  = $attributes['section'];
1211
			$args     = array_merge(
1212
				$attributes['args'],
1213
				array(
1214
					'title'     => $title,
1215
					'id'        => $id,
1216
					'label_for' => $id,
1217
					'name'      => $name,
1218
				)
1219
			);
1220
			add_settings_field( $id, $title, $callback, $page, $section, $args );
1221
			register_setting( $page, $id );
1222
		} // End foreach().
1223
	}
1224
1225
	/**
1226
	* Create the notices, settings, and conditions by which admin notices should appear
1227
	*
1228
	*/
1229
	public function notices() {
1230
1231
		// before a notice is displayed, we should make sure we are on a page related to this plugin
1232
		if ( ! isset( $_GET['page'] ) || 'object-sync-salesforce-admin' !== $_GET['page'] ) {
1233
			return;
1234
		}
1235
1236
		$get_data = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
1237
		require_once plugin_dir_path( __FILE__ ) . '../classes/admin-notice.php';
1238
1239
		$notices = array(
1240
			'permission'              => array(
1241
				'condition'   => false === $this->check_wordpress_admin_permissions(),
1242
				'message'     => esc_html__( "Your account does not have permission to edit the Salesforce REST API plugin's settings.", 'object-sync-for-salesforce' ),
1243
				'type'        => 'error',
1244
				'dismissible' => false,
1245
			),
1246
			'not_secure'             => array(
1247
				'condition'   => false === $this->check_wordpress_ssl() && false === $this->check_wordpress_ssl_support(),
1248
				'message'     => esc_html__( 'At least the admin area of your website must use HTTPS to connect with Salesforce. WordPress reports that your site environment does not, and cannot, use HTTPS. You may need to work with your hosting company to make the switch before you can use this plugin.', 'object-sync-for-salesforce' ),
1249
				'type'        => 'error',
1250
				'dismissible' => false,
1251
			),
1252
			'secure_supported'        => array(
1253
				'condition'   => false === $this->check_wordpress_ssl() && true === $this->check_wordpress_ssl_support(),
1254
				'message'     => sprintf(
1255
					// translators: 1) is the site health URL, and 2) is the text for the site health page title.
1256
					__( 'Your website is not currently using HTTPS, but your environment does support it. Visit the <a href="%1$s">%2$s</a> for more information. If you have just migrated to HTTPS, WordPress may take some time to update this detection.', 'object-sync-for-salesforce' ),
1257
					esc_url( admin_url( '/wp-admin/site-health.php' ) ),
1258
					esc_html__( 'Site Health screen', 'object-sync-for-salesforce' )
1259
				),
1260
				'type'        => 'error',
1261
				'dismissible' => false,
1262
			),
1263
			'fieldmap'                => array(
1264
				'condition'   => isset( $get_data['transient'] ),
1265
				'message'     => esc_html__( 'Errors kept this fieldmap from being saved.', 'object-sync-for-salesforce' ),
1266
				'type'        => 'error',
1267
				'dismissible' => true,
1268
			),
1269
			'object_map'              => array(
1270
				'condition'   => isset( $get_data['map_transient'] ),
1271
				'message'     => esc_html__( 'Errors kept this object map from being saved.', 'object-sync-for-salesforce' ),
1272
				'type'        => 'error',
1273
				'dismissible' => true,
1274
			),
1275
			'data_saved'              => array(
1276
				'condition'   => isset( $get_data['data_saved'] ) && 'true' === $get_data['data_saved'],
1277
				'message'     => esc_html__( 'This data was successfully saved.', 'object-sync-for-salesforce' ),
1278
				'type'        => 'success',
1279
				'dismissible' => true,
1280
			),
1281
			'data_save_error'         => array(
1282
				'condition'   => isset( $get_data['data_saved'] ) && 'false' === $get_data['data_saved'],
1283
				'message'     => esc_html__( 'This data was not successfully saved. Try again.', 'object-sync-for-salesforce' ),
1284
				'type'        => 'error',
1285
				'dismissible' => true,
1286
			),
1287
			'mapping_error_transient' => array(
1288
				'condition'   => isset( $get_data['mapping_error_transient'] ),
1289
				'message'     => esc_html__( 'Errors kept these mapping errors from being deleted.', 'object-sync-for-salesforce' ),
1290
				'type'        => 'error',
1291
				'dismissible' => true,
1292
			),
1293
		);
1294
1295
		foreach ( $notices as $key => $value ) {
1296
1297
			$condition = $value['condition'];
1298
			$message   = $value['message'];
1299
1300
			if ( isset( $value['dismissible'] ) ) {
1301
				$dismissible = $value['dismissible'];
1302
			} else {
1303
				$dismissible = false;
1304
			}
1305
1306
			if ( isset( $value['type'] ) ) {
1307
				$type = $value['type'];
1308
			} else {
1309
				$type = '';
1310
			}
1311
1312
			if ( ! isset( $value['template'] ) ) {
1313
				$template = '';
1314
			}
1315
1316
			if ( $condition ) {
1317
				new Object_Sync_Sf_Admin_Notice( $condition, $message, $dismissible, $type, $template );
1318
			}
1319
		}
1320
1321
	}
1322
1323
	/**
1324
	* Get all the Salesforce object settings for fieldmapping
1325
	* This takes either the $_POST array via ajax, or can be directly called with a $data array
1326
	*
1327
	* @param array $data
1328
	* data must contain a salesforce_object
1329
	* can optionally contain a type
1330
	* @return array $object_settings
1331
	*/
1332
	public function get_salesforce_object_description( $data = array() ) {
1333
		$ajax = false;
1334
		if ( empty( $data ) ) {
1335
			$data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1336
			$ajax = true;
1337
		}
1338
1339
		$object_description = array();
1340
1341
		if ( ! empty( $data['salesforce_object'] ) ) {
1342
			$object = $this->salesforce['sfapi']->object_describe( esc_attr( $data['salesforce_object'] ) );
1343
1344
			$object_fields        = array();
1345
			$include_record_types = array();
1346
1347
			// these can come from ajax
1348
			$include = isset( $data['include'] ) ? (array) $data['include'] : array();
1349
			$include = array_map( 'esc_attr', $include );
1350
1351
			if ( in_array( 'fields', $include, true ) || empty( $include ) ) {
1352
				$type = isset( $data['field_type'] ) ? esc_attr( $data['field_type'] ) : ''; // can come from ajax
1353
				foreach ( $object['data']['fields'] as $key => $value ) {
1354
					if ( '' === $type || $type === $value['type'] ) {
1355
						$object_fields[ $key ] = $value;
1356
					}
1357
				}
1358
				$object_description['fields'] = $object_fields;
1359
			}
1360
1361
			if ( in_array( 'recordTypeInfos', $include, true ) ) {
1362
				if ( isset( $object['data']['recordTypeInfos'] ) && count( $object['data']['recordTypeInfos'] ) > 1 ) {
1363
					foreach ( $object['data']['recordTypeInfos'] as $type ) {
1364
						$object_record_types[ $type['recordTypeId'] ] = $type['name'];
1365
					}
1366
					$object_description['recordTypeInfos'] = $object_record_types;
1367
				}
1368
			}
1369
		}
1370
1371
		if ( true === $ajax ) {
1372
			wp_send_json_success( $object_description );
1373
		} else {
1374
			return $object_description;
1375
		}
1376
	}
1377
1378
	/**
1379
	* Get Salesforce object fields for fieldmapping
1380
	* This takes either the $_POST array via ajax, or can be directly called with a $data array
1381
	*
1382
	* @param array $data
1383
	* data must contain a salesforce_object unless it is Ajax
1384
	* data can optionally contain a type for the field
1385
	* @return array $object_fields
1386
	*/
1387
	public function get_salesforce_object_fields( $data = array() ) {
1388
		$ajax      = false;
1389
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1390
		if ( empty( $data ) ) {
1391
			$salesforce_object = isset( $post_data['salesforce_object'] ) ? sanitize_text_field( wp_unslash( $post_data['salesforce_object'] ) ) : '';
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($post_data['salesforce_object']) can also be of type array; however, parameter $str of sanitize_text_field() does only seem to accept 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

1391
			$salesforce_object = isset( $post_data['salesforce_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['salesforce_object'] ) ) : '';
Loading history...
1392
			$ajax              = true;
1393
			// here, we should respect the decision of whether to show the API name or the label
1394
			$display_value = get_option( $this->option_prefix . 'salesforce_field_display_value', 'field_label' );
1395
			if ( 'api_name' === $display_value ) {
1396
				$visible_label_field = 'name';
1397
			} else {
1398
				$visible_label_field = 'label';
1399
			}
1400
			$attributes = array( 'name', $visible_label_field );
1401
		} else {
1402
			$salesforce_object = isset( $data['salesforce_object'] ) ? sanitize_text_field( wp_unslash( $data['salesforce_object'] ) ) : '';
1403
		}
1404
		$object_fields = array();
1405
		if ( ! empty( $salesforce_object ) ) {
1406
			$object               = $this->salesforce['sfapi']->object_describe( esc_attr( $salesforce_object ) );
1407
			$object_fields        = array();
1408
			$type                 = isset( $data['type'] ) ? esc_attr( $data['type'] ) : '';
1409
			$include_record_types = isset( $data['include_record_types'] ) ? esc_attr( $data['include_record_types'] ) : false;
1410
			foreach ( $object['data']['fields'] as $key => $value ) {
1411
				if ( '' === $type || $type === $value['type'] ) {
1412
					$object_fields[ $key ] = $value;
1413
					if ( isset( $attributes ) ) {
1414
						$object_fields[ $key ] = array_intersect_key( $value, array_flip( $attributes ) );
1415
					}
1416
				}
1417
			}
1418
			if ( true === $include_record_types ) {
0 ignored issues
show
introduced by
The condition true === $include_record_types is always false.
Loading history...
1419
				$object_record_types = array();
1420
				if ( isset( $object['data']['recordTypeInfos'] ) && count( $object['data']['recordTypeInfos'] ) > 1 ) {
1421
					foreach ( $object['data']['recordTypeInfos'] as $type ) {
1422
						$object_record_types[ $type['recordTypeId'] ] = $type['name'];
1423
					}
1424
				}
1425
			}
1426
		}
1427
1428
		if ( true === $ajax ) {
1429
			$ajax_response = array(
1430
				'fields' => $object_fields,
1431
			);
1432
			wp_send_json_success( $ajax_response );
1433
		} else {
1434
			return $object_fields;
1435
		}
1436
1437
	}
1438
1439
	/**
1440
	* Get WordPress object fields for fieldmapping
1441
	* This takes either the $_POST array via ajax, or can be directly called with a $wordpress_object field
1442
	*
1443
	* @param string $wordpress_object
1444
	* @return array $object_fields
1445
	*/
1446
	public function get_wordpress_object_fields( $wordpress_object = '' ) {
1447
		$ajax      = false;
1448
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1449
		if ( empty( $wordpress_object ) ) {
1450
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( wp_unslash( $post_data['wordpress_object'] ) ) : '';
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($post_data['wordpress_object']) can also be of type array; however, parameter $str of sanitize_text_field() does only seem to accept 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

1450
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1451
			$ajax             = true;
1452
		}
1453
1454
		$object_fields = $this->wordpress->get_wordpress_object_fields( $wordpress_object );
1455
1456
		if ( true === $ajax ) {
1457
			$ajax_response = array(
1458
				'fields' => $object_fields,
1459
			);
1460
			wp_send_json_success( $ajax_response );
1461
		} else {
1462
			return $object_fields;
1463
		}
1464
	}
1465
1466
	/**
1467
	* Get WordPress and Salesforce object fields together for fieldmapping
1468
	* This takes either the $_POST array via ajax, or can be directly called with $wordpress_object and $salesforce_object fields
1469
	*
1470
	* @deprecated since 1.9.0
1471
	* @param string $wordpress_object
1472
	* @param string $salesforce_object
1473
	* @return array $object_fields
1474
	*/
1475
	public function get_wp_sf_object_fields( $wordpress_object = '', $salesforce = '' ) {
1476
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1477
		if ( empty( $wordpress_object ) ) {
1478
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( wp_unslash( $post_data['wordpress_object'] ) ) : '';
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($post_data['wordpress_object']) can also be of type array; however, parameter $str of sanitize_text_field() does only seem to accept 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

1478
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1479
		}
1480
		if ( empty( $salesforce_object ) ) {
1481
			$salesforce_object = isset( $post_data['salesforce_object'] ) ? sanitize_text_field( wp_unslash( $post_data['salesforce_object'] ) ) : '';
1482
		}
1483
1484
		$object_fields['wordpress']  = $this->get_wordpress_object_fields( $wordpress_object );
1485
		$object_fields['salesforce'] = $this->get_salesforce_object_fields(
1486
			array(
1487
				'salesforce_object' => $salesforce_object,
1488
			)
1489
		);
1490
1491
		if ( ! empty( $post_data ) ) {
1492
			wp_send_json_success( $object_fields );
1493
		} else {
1494
			return $object_fields;
1495
		}
1496
	}
1497
1498
	/**
1499
	* Manually push the WordPress object to Salesforce
1500
	* This takes either the $_POST array via ajax, or can be directly called with $wordpress_object and $wordpress_id fields
1501
	*
1502
	* @param string $wordpress_object
1503
	* @param int $wordpress_id
1504
	* @param bool $force_return Force the method to return json instead of outputting it.
1505
	*/
1506
	public function push_to_salesforce( $wordpress_object = '', $wordpress_id = '', $force_return = false ) {
1507
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1508
		if ( empty( $wordpress_object ) && empty( $wordpress_id ) ) {
1509
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( wp_unslash( $post_data['wordpress_object'] ) ) : '';
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($post_data['wordpress_object']) can also be of type array; however, parameter $str of sanitize_text_field() does only seem to accept 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

1509
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1510
			$wordpress_id     = isset( $post_data['wordpress_id'] ) ? absint( $post_data['wordpress_id'] ) : '';
1511
		}
1512
1513
		// clarify what that variable is in this context.
1514
		$object_type = $wordpress_object;
1515
1516
		// When objects are already mapped, there is a Salesforce id as well. Otherwise, it's blank.
1517
		$salesforce_id = isset( $post_data['salesforce_id'] ) ? sanitize_text_field( $post_data['salesforce_id'] ) : '';
1518
		if ( '' === $salesforce_id ) {
1519
			$method = 'POST';
1520
		} else {
1521
			$method = 'PUT';
1522
		}
1523
1524
		$result = $this->push->manual_push( $object_type, $wordpress_id, $method );
1525
1526
		if ( false === $force_return && ! empty( $post_data['wordpress_object'] ) && ! empty( $post_data['wordpress_id'] ) ) {
1527
			wp_send_json_success( $result );
1528
		} else {
1529
			return $result;
1530
		}
1531
1532
	}
1533
1534
	/**
1535
	* Manually pull the Salesforce object into WordPress
1536
	* This takes either the $_POST array via ajax, or can be directly called with $salesforce_id fields
1537
	*
1538
	* @param string $salesforce_id
1539
	* @param string $wordpress_object
1540
	*/
1541
	public function pull_from_salesforce( $salesforce_id = '', $wordpress_object = '' ) {
1542
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1543
		if ( empty( $wordpress_object ) && empty( $salesforce_id ) ) {
1544
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( wp_unslash( $post_data['wordpress_object'] ) ) : '';
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($post_data['wordpress_object']) can also be of type array; however, parameter $str of sanitize_text_field() does only seem to accept 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

1544
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1545
			$salesforce_id    = isset( $post_data['salesforce_id'] ) ? sanitize_text_field( wp_unslash( $post_data['salesforce_id'] ) ) : '';
1546
		}
1547
		$type   = $this->salesforce['sfapi']->get_sobject_type( $salesforce_id );
1548
		$result = $this->pull->manual_pull( $type, $salesforce_id, $wordpress_object ); // we want the wp object to make sure we get the right fieldmap
1549
		if ( ! empty( $post_data ) ) {
1550
			wp_send_json_success( $result );
1551
		} else {
1552
			return $result;
1553
		}
1554
	}
1555
1556
	/**
1557
	* Manually pull the Salesforce object into WordPress
1558
	* This takes an id for a mapping object row
1559
	*
1560
	* @param int $mapping_id
1561
	*/
1562
	public function refresh_mapped_data( $mapping_id = '' ) {
1563
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1564
		if ( empty( $mapping_id ) ) {
1565
			$mapping_id = isset( $post_data['mapping_id'] ) ? absint( $post_data['mapping_id'] ) : '';
1566
		}
1567
		$result = $this->mappings->get_object_maps(
1568
			array(
1569
				'id' => $mapping_id,
1570
			)
1571
		);
1572
		if ( ! empty( $post_data ) ) {
1573
			wp_send_json_success( $result );
1574
		} else {
1575
			return $result;
1576
		}
1577
	}
1578
1579
	/**
1580
	* Prepare fieldmap data and redirect after processing
1581
	* This runs when the create or update forms are submitted
1582
	* It is public because it depends on an admin hook
1583
	* It then calls the Object_Sync_Sf_Mapping class and sends prepared data over to it, then redirects to the correct page
1584
	* This method does include error handling, by loading the submission in a transient if there is an error, and then deleting it upon success
1585
	*
1586
	*/
1587
	public function prepare_fieldmap_data() {
1588
		$error     = false;
1589
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1590
		$cachekey  = wp_json_encode( $post_data );
1591
		if ( false !== $cachekey ) {
1592
			$cachekey = md5( $cachekey );
1593
		}
1594
1595
		if ( ! isset( $post_data['label'] ) || ! isset( $post_data['salesforce_object'] ) || ! isset( $post_data['wordpress_object'] ) ) {
1596
			$error = true;
1597
		}
1598
		if ( true === $error ) {
1599
			$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1600
			if ( '' !== $cachekey ) {
1601
				$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&transient=' . $cachekey;
0 ignored issues
show
Bug introduced by
Are you sure $cachekey of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

1601
				$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&transient=' . /** @scrutinizer ignore-type */ $cachekey;
Loading history...
1602
			}
1603
		} else { // there are no errors
1604
			// send the row to the fieldmap class
1605
			// if it is add or clone, use the create method
1606
			$method            = esc_attr( $post_data['method'] );
1607
			$salesforce_fields = $this->get_salesforce_object_fields(
1608
				array(
1609
					'salesforce_object' => $post_data['salesforce_object'],
1610
				)
1611
			);
1612
			$wordpress_fields  = $this->get_wordpress_object_fields( $post_data['wordpress_object'] );
1613
			if ( 'add' === $method || 'clone' === $method ) {
1614
				$result = $this->mappings->create_fieldmap( $post_data, $wordpress_fields, $salesforce_fields );
1615
			} elseif ( 'edit' === $method ) { // if it is edit, use the update method
1616
				$id     = esc_attr( $post_data['id'] );
1617
				$result = $this->mappings->update_fieldmap( $post_data, $wordpress_fields, $salesforce_fields, $id );
1618
			}
1619
			if ( false === $result ) { // if the database didn't save, it's still an error
1620
				$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1621
				if ( '' !== $cachekey ) {
1622
					$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&transient=' . $cachekey;
1623
				}
1624
			} else {
1625
				// if the user has saved a fieldmap, clear the currently running query value if there is one
1626
				if ( '' !== get_option( $this->option_prefix . 'currently_pulling_query_' . $post_data['salesforce_object'], '' ) ) {
1627
					$this->pull->clear_current_type_query( $post_data['salesforce_object'] );
1628
				}
1629
				if ( isset( $post_data['transient'] ) ) { // there was previously an error saved. can delete it now.
1630
					$this->sfwp_transients->delete( esc_attr( $post_data['map_transient'] ) );
1631
				}
1632
				// then send the user to the list of fieldmaps
1633
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1634
			}
1635
		}
1636
		wp_safe_redirect( $url );
1637
		exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1638
	}
1639
1640
	/**
1641
	* Delete fieldmap data and redirect after processing
1642
	* This runs when the delete link is clicked, after the user confirms
1643
	* It is public because it depends on an admin hook
1644
	* It then calls the Object_Sync_Sf_Mapping class and the delete method
1645
	*
1646
	*/
1647
	public function delete_fieldmap() {
1648
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1649
		if ( $post_data['id'] ) {
1650
			$result = $this->mappings->delete_fieldmap( $post_data['id'] );
1651
			if ( true === $result ) {
1652
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1653
			} else {
1654
				$url = esc_url_raw( $post_data['redirect_url_error'] . '&id=' . $post_data['id'] );
1655
			}
1656
			wp_safe_redirect( $url );
1657
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1658
		}
1659
	}
1660
1661
	/**
1662
	* Prepare object data and redirect after processing
1663
	* This runs when the update form is submitted
1664
	* It is public because it depends on an admin hook
1665
	* It then calls the Object_Sync_Sf_Mapping class and sends prepared data over to it, then redirects to the correct page
1666
	* This method does include error handling, by loading the submission in a transient if there is an error, and then deleting it upon success
1667
	*
1668
	*/
1669
	public function prepare_object_map_data() {
1670
		$error     = false;
1671
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1672
		$cachekey  = wp_json_encode( $post_data );
1673
		if ( false !== $cachekey ) {
1674
			$cachekey = md5( $cachekey );
1675
		}
1676
1677
		if ( ! isset( $post_data['wordpress_id'] ) || ! isset( $post_data['salesforce_id'] ) ) {
1678
			$error = true;
1679
		}
1680
		if ( true === $error ) {
1681
			$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1682
			if ( '' !== $cachekey ) {
1683
				$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&map_transient=' . $cachekey;
0 ignored issues
show
Bug introduced by
Are you sure $cachekey of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

1683
				$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&map_transient=' . /** @scrutinizer ignore-type */ $cachekey;
Loading history...
1684
			}
1685
		} else { // there are no errors
1686
			// send the row to the object map class
1687
			$method = esc_attr( $post_data['method'] );
1688
			if ( 'edit' === $method ) { // if it is edit, use the update method
1689
				$id     = esc_attr( $post_data['id'] );
1690
				$result = $this->mappings->update_object_map( $post_data, $id );
1691
			}
1692
			if ( false === $result ) { // if the database didn't save, it's still an error
1693
				$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1694
				if ( '' !== $cachekey ) {
1695
					$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&map_transient=' . $cachekey;
1696
				}
1697
			} else {
1698
				if ( isset( $post_data['map_transient'] ) ) { // there was previously an error saved. can delete it now.
1699
					$this->sfwp_transients->delete( esc_attr( $post_data['map_transient'] ) );
1700
				}
1701
				// then send the user to the success redirect url
1702
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1703
			}
1704
		}
1705
		wp_safe_redirect( $url );
1706
		exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1707
	}
1708
1709
	/**
1710
	* Delete object map data and redirect after processing
1711
	* This runs when the delete link is clicked on an error row, after the user confirms
1712
	* It is public because it depends on an admin hook
1713
	* It then calls the Object_Sync_Sf_Mapping class and the delete method
1714
	*
1715
	*/
1716
	public function delete_object_map() {
1717
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1718
		if ( isset( $post_data['id'] ) ) {
1719
			$result = $this->mappings->delete_object_map( $post_data['id'] );
1720
			if ( true === $result ) {
1721
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1722
			} else {
1723
				$url = esc_url_raw( $post_data['redirect_url_error'] . '&id=' . $post_data['id'] );
1724
			}
1725
			wp_safe_redirect( $url );
1726
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1727
		} elseif ( $post_data['delete'] ) {
1728
			$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1729
			$cachekey  = wp_json_encode( $post_data );
1730
			if ( false !== $cachekey ) {
1731
				$cachekey = md5( $cachekey );
1732
			}
1733
			$error = false;
1734
			if ( ! isset( $post_data['delete'] ) ) {
1735
				$error = true;
1736
			}
1737
			if ( true === $error ) {
1738
				$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1739
				if ( '' !== $cachekey ) {
1740
					$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&mapping_error_transient=' . $cachekey;
0 ignored issues
show
Bug introduced by
Are you sure $cachekey of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

1740
					$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&mapping_error_transient=' . /** @scrutinizer ignore-type */ $cachekey;
Loading history...
1741
				}
1742
			} else { // there are no errors
1743
				$result = $this->mappings->delete_object_map( array_keys( $post_data['delete'] ) );
1744
				if ( true === $result ) {
1745
					$url = esc_url_raw( $post_data['redirect_url_success'] );
1746
				}
1747
1748
				if ( false === $result ) { // if the database didn't save, it's still an error
1749
					$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1750
					if ( '' !== $cachekey ) {
1751
						$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&mapping_error_transient=' . $cachekey;
1752
					}
1753
				} else {
1754
					if ( isset( $post_data['mapping_error_transient'] ) ) { // there was previously an error saved. can delete it now.
1755
						$this->sfwp_transients->delete( esc_attr( $post_data['mapping_error_transient'] ) );
1756
					}
1757
					// then send the user to the list of fieldmaps
1758
					$url = esc_url_raw( $post_data['redirect_url_success'] );
1759
				}
1760
			}
1761
			wp_safe_redirect( $url );
1762
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1763
		}
1764
	}
1765
1766
	/**
1767
	* Import a json file and use it for plugin data
1768
	*
1769
	*/
1770
	public function import_json_file() {
1771
1772
		if ( ! wp_verify_nonce( $_POST['object_sync_for_salesforce_nonce_import'], 'object_sync_for_salesforce_nonce_import' ) ) {
1773
			return;
1774
		}
1775
		if ( ! current_user_can( 'manage_options' ) ) {
1776
			return;
1777
		}
1778
		$path      = $_FILES['import_file']['name'];
1779
		$extension = pathinfo( $path, PATHINFO_EXTENSION );
1780
		if ( 'json' !== $extension ) {
1781
			wp_die( __( 'Please upload a valid .json file' ) );
1782
		}
1783
1784
		$import_file = $_FILES['import_file']['tmp_name'];
1785
		if ( empty( $import_file ) ) {
1786
			wp_die( __( 'Please upload a file to import' ) );
1787
		}
1788
1789
		// Retrieve the data from the file and convert the json object to an array.
1790
		$data = (array) json_decode( file_get_contents( $import_file ), true );
1791
1792
		// if there is only one object map, fix the array
1793
		if ( isset( $data['object_maps'] ) ) {
1794
			if ( count( $data['object_maps'] ) === count( $data['object_maps'], COUNT_RECURSIVE ) ) {
1795
				$data['object_maps'] = array( 0 => $data['object_maps'] );
1796
			}
1797
		}
1798
1799
		$overwrite = isset( $_POST['overwrite'] ) ? esc_attr( $_POST['overwrite'] ) : '';
1800
		if ( true === filter_var( $overwrite, FILTER_VALIDATE_BOOLEAN ) ) {
1801
			if ( isset( $data['fieldmaps'] ) ) {
1802
				$fieldmaps = $this->mappings->get_fieldmaps();
1803
				foreach ( $fieldmaps as $fieldmap ) {
1804
					$id     = $fieldmap['id'];
1805
					$delete = $this->mappings->delete_fieldmap( $id );
1806
				}
1807
			}
1808
			if ( isset( $data['object_maps'] ) ) {
1809
				$object_maps = $this->mappings->get_object_maps();
1810
1811
				// if there is only one existing object map, fix the array
1812
				if ( count( $object_maps ) === count( $object_maps, COUNT_RECURSIVE ) ) {
1813
					$object_maps = array( 0 => $object_maps );
1814
				}
1815
1816
				foreach ( $object_maps as $object_map ) {
1817
					$id     = $object_map['id'];
1818
					$delete = $this->mappings->delete_object_map( $id );
1819
				}
1820
			}
1821
			if ( isset( $data['plugin_settings'] ) ) {
1822
				foreach ( $data['plugin_settings'] as $key => $value ) {
1823
					delete_option( $value['option_name'] );
1824
				}
1825
			}
1826
		}
1827
1828
		$success = true;
1829
1830
		if ( isset( $data['fieldmaps'] ) ) {
1831
			foreach ( $data['fieldmaps'] as $fieldmap ) {
1832
				unset( $fieldmap['id'] );
1833
				$create = $this->mappings->create_fieldmap( $fieldmap );
1834
				if ( false === $create ) {
1835
					$success = false;
1836
				}
1837
			}
1838
		}
1839
1840
		if ( isset( $data['object_maps'] ) ) {
1841
			foreach ( $data['object_maps'] as $object_map ) {
1842
				unset( $object_map['id'] );
1843
1844
				if ( $object_map['object_type'] ) {
1845
					$sf_sync_trigger = $this->mappings->sync_sf_create;
1846
					$create          = $this->pull->salesforce_pull_process_records( $object_map['object_type'], $object_map['salesforce_id'], $sf_sync_trigger );
1847
				} else {
1848
					$create = $this->mappings->create_object_map( $object_map );
1849
				}
1850
				if ( false === $create ) {
1851
					$success = false;
1852
				}
1853
			}
1854
		}
1855
1856
		if ( isset( $data['plugin_settings'] ) ) {
1857
			foreach ( $data['plugin_settings'] as $key => $value ) {
1858
				update_option( $value['option_name'], maybe_unserialize( $value['option_value'] ), $value['autoload'] );
1859
			}
1860
		}
1861
1862
		if ( true === $success ) {
1863
			wp_safe_redirect( get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=import-export&data_saved=true' ) );
1864
			exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1865
		} else {
1866
			wp_safe_redirect( get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=import-export&data_saved=false' ) );
1867
			exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1868
		}
1869
1870
	}
1871
1872
	/**
1873
	* Create a json file for exporting
1874
	*
1875
	*/
1876
	public function export_json_file() {
1877
1878
		if ( ! wp_verify_nonce( $_POST['object_sync_for_salesforce_nonce_export'], 'object_sync_for_salesforce_nonce_export' ) ) {
1879
			return;
1880
		}
1881
		if ( ! current_user_can( 'manage_options' ) ) {
1882
			return;
1883
		}
1884
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1885
		$export    = array();
1886
		if ( in_array( 'fieldmaps', $post_data['export'] ) ) {
1887
			$export['fieldmaps'] = $this->mappings->get_fieldmaps();
1888
		}
1889
		if ( in_array( 'object_maps', $post_data['export'] ) ) {
1890
			$export['object_maps'] = $this->mappings->get_object_maps();
1891
		}
1892
		if ( in_array( 'plugin_settings', $post_data['export'] ) ) {
1893
			$export['plugin_settings'] = $this->wpdb->get_results( 'SELECT * FROM ' . $this->wpdb->prefix . 'options' . ' WHERE option_name like "' . $this->option_prefix . '%"', ARRAY_A );
1894
		}
1895
		nocache_headers();
1896
		header( 'Content-Type: application/json; charset=utf-8' );
1897
		header( 'Content-Disposition: attachment; filename=object-sync-for-salesforce-data-export-' . date( 'm-d-Y' ) . '.json' );
1898
		header( 'Expires: 0' );
1899
		echo wp_json_encode( $export );
0 ignored issues
show
Bug introduced by
Are you sure wp_json_encode($export) of type false|string can be used in echo? ( Ignorable by Annotation )

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

1899
		echo /** @scrutinizer ignore-type */ wp_json_encode( $export );
Loading history...
1900
		exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1901
	}
1902
1903
	/**
1904
	* Default display for <input> fields
1905
	*
1906
	* @param array $args
1907
	*/
1908
	public function display_input_field( $args ) {
1909
		$type    = $args['type'];
1910
		$id      = $args['label_for'];
1911
		$name    = $args['name'];
1912
		$desc    = $args['desc'];
1913
		$checked = '';
1914
1915
		$class = 'regular-text';
1916
1917
		if ( 'checkbox' === $type ) {
1918
			$class = 'checkbox';
1919
		}
1920
1921
		if ( isset( $args['class'] ) ) {
1922
			$class = $args['class'];
1923
		}
1924
1925
		if ( ! isset( $args['constant'] ) || ! defined( $args['constant'] ) ) {
1926
			$value = esc_attr( get_option( $id, '' ) );
0 ignored issues
show
Bug introduced by
It seems like get_option($id, '') can also be of type false; however, parameter $text of esc_attr() does only seem to accept 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

1926
			$value = esc_attr( /** @scrutinizer ignore-type */ get_option( $id, '' ) );
Loading history...
1927
			if ( 'checkbox' === $type ) {
1928
				$value = filter_var( get_option( $id, false ), FILTER_VALIDATE_BOOLEAN );
1929
				if ( true === $value ) {
1930
					$checked = 'checked ';
1931
				}
1932
				$value = 1;
1933
			}
1934
			if ( '' === $value && isset( $args['default'] ) && '' !== $args['default'] ) {
1935
				$value = $args['default'];
1936
			}
1937
1938
			echo sprintf(
1939
				'<input type="%1$s" value="%2$s" name="%3$s" id="%4$s" class="%5$s"%6$s>',
1940
				esc_attr( $type ),
1941
				esc_attr( $value ),
1942
				esc_attr( $name ),
1943
				esc_attr( $id ),
1944
				sanitize_html_class( $class . esc_html( ' code' ) ),
1945
				esc_html( $checked )
1946
			);
1947
			if ( '' !== $desc ) {
1948
				echo sprintf(
1949
					'<p class="description">%1$s</p>',
1950
					esc_html( $desc )
1951
				);
1952
			}
1953
		} else {
1954
			echo sprintf(
1955
				'<p><code>%1$s</code></p>',
1956
				esc_html__( 'Defined in wp-config.php', 'object-sync-for-salesforce' )
1957
			);
1958
		}
1959
	}
1960
1961
	/**
1962
	* Display for multiple checkboxes
1963
	* Above method can handle a single checkbox as it is
1964
	*
1965
	* @param array $args
1966
	*/
1967
	public function display_checkboxes( $args ) {
1968
		$type    = 'checkbox';
1969
		$name    = $args['name'];
1970
		$options = get_option( $name, array() );
1971
		foreach ( $args['items'] as $key => $value ) {
1972
			$text    = $value['text'];
1973
			$id      = $value['id'];
1974
			$desc    = $value['desc'];
1975
			$checked = '';
1976
			if ( is_array( $options ) && in_array( (string) $key, $options, true ) ) {
1977
				$checked = 'checked';
1978
			} elseif ( is_array( $options ) && empty( $options ) ) {
1979
				if ( isset( $value['default'] ) && true === $value['default'] ) {
1980
					$checked = 'checked';
1981
				}
1982
			}
1983
			echo sprintf(
1984
				'<div class="checkbox"><label><input type="%1$s" value="%2$s" name="%3$s[]" id="%4$s"%5$s>%6$s</label></div>',
1985
				esc_attr( $type ),
1986
				esc_attr( $key ),
1987
				esc_attr( $name ),
1988
				esc_attr( $id ),
1989
				esc_html( $checked ),
1990
				esc_html( $text )
1991
			);
1992
			if ( '' !== $desc ) {
1993
				echo sprintf(
1994
					'<p class="description">%1$s</p>',
1995
					esc_html( $desc )
1996
				);
1997
			}
1998
		}
1999
	}
2000
2001
	/**
2002
	* Display for a dropdown
2003
	*
2004
	* @param array $args
2005
	*/
2006
	public function display_select( $args ) {
2007
		$type = $args['type'];
2008
		$id   = $args['label_for'];
2009
		$name = $args['name'];
2010
		$desc = $args['desc'];
2011
		if ( ! isset( $args['constant'] ) || ! defined( $args['constant'] ) ) {
2012
			$current_value = get_option( $name );
2013
2014
			echo sprintf(
2015
				'<div class="select"><select id="%1$s" name="%2$s"><option value="">- ' . __( 'Select one', 'object-sync-for-salesforce' ) . ' -</option>',
2016
				esc_attr( $id ),
2017
				esc_attr( $name )
2018
			);
2019
2020
			foreach ( $args['items'] as $key => $value ) {
2021
				$text     = $value['text'];
2022
				$value    = $value['value'];
2023
				$selected = '';
2024
				if ( $key === $current_value || $value === $current_value ) {
2025
					$selected = ' selected';
2026
				}
2027
2028
				echo sprintf(
2029
					'<option value="%1$s"%2$s>%3$s</option>',
2030
					esc_attr( $value ),
2031
					esc_attr( $selected ),
2032
					esc_html( $text )
2033
				);
2034
2035
			}
2036
			echo '</select>';
2037
			if ( '' !== $desc ) {
2038
				echo sprintf(
2039
					'<p class="description">%1$s</p>',
2040
					esc_html( $desc )
2041
				);
2042
			}
2043
			echo '</div>';
2044
		} else {
2045
			echo sprintf(
2046
				'<p><code>%1$s</code></p>',
2047
				esc_html__( 'Defined in wp-config.php', 'object-sync-for-salesforce' )
2048
			);
2049
		}
2050
	}
2051
2052
	/**
2053
	* Dropdown formatted list of Salesforce API versions
2054
	*
2055
	* @return array $args
2056
	*/
2057
	private function version_options() {
2058
		$args = array();
2059
		if ( defined( 'OBJECT_SYNC_SF_SALESFORCE_API_VERSION' ) || ! isset( $_GET['page'] ) || 'object-sync-salesforce-admin' !== $_GET['page'] ) {
2060
			return $args;
2061
		}
2062
		$versions = $this->salesforce['sfapi']->get_api_versions();
2063
		foreach ( $versions['data'] as $key => $value ) {
2064
			$args[] = array(
2065
				'value' => $value['version'],
2066
				'text'  => $value['label'] . ' (' . $value['version'] . ')',
2067
			);
2068
		}
2069
		return $args;
2070
	}
2071
2072
	/**
2073
	* Default display for <a href> links
2074
	*
2075
	* @param array $args
2076
	*/
2077
	public function display_link( $args ) {
2078
		$label = $args['label'];
2079
		$desc  = $args['desc'];
2080
		$url   = $args['url'];
2081
		if ( isset( $args['link_class'] ) ) {
2082
			echo sprintf(
2083
				'<p><a class="%1$s" href="%2$s">%3$s</a></p>',
2084
				esc_attr( $args['link_class'] ),
2085
				esc_url( $url ),
2086
				esc_html( $label )
2087
			);
2088
		} else {
2089
			echo sprintf(
2090
				'<p><a href="%1$s">%2$s</a></p>',
2091
				esc_url( $url ),
2092
				esc_html( $label )
2093
			);
2094
		}
2095
2096
		if ( '' !== $desc ) {
2097
			echo sprintf(
2098
				'<p class="description">%1$s</p>',
2099
				esc_html( $desc )
2100
			);
2101
		}
2102
2103
	}
2104
2105
	/**
2106
	* Allow for a standard sanitize/validate method. We could use more specific ones if need be, but this one provides a baseline.
2107
	*
2108
	* @param string $option
2109
	* @return string $option
2110
	*/
2111
	public function sanitize_validate_text( $option ) {
2112
		if ( is_array( $option ) ) {
0 ignored issues
show
introduced by
The condition is_array($option) is always false.
Loading history...
2113
			$options = array();
2114
			foreach ( $option as $key => $value ) {
2115
				$options[ $key ] = sanitize_text_field( $value );
2116
			}
2117
			return $options;
2118
		}
2119
		$option = sanitize_text_field( $option );
2120
		return $option;
2121
	}
2122
2123
	/**
2124
	* Run a demo of Salesforce API call on the authenticate tab after WordPress has authenticated with it
2125
	*
2126
	* @param object $sfapi
2127
	*/
2128
	private function status( $sfapi ) {
2129
2130
		$versions = $sfapi->get_api_versions();
2131
2132
		// format this array into text so users can see the versions
2133
		if ( true === $versions['cached'] ) {
2134
			$versions_is_cached = esc_html__( 'This list is cached, and', 'object-sync-salesforce' );
2135
		} else {
2136
			$versions_is_cached = esc_html__( 'This list is not cached, but', 'object-sync-salesforce' );
2137
		}
2138
2139
		if ( true === $versions['from_cache'] ) {
2140
			$versions_from_cache = esc_html__( 'items were loaded from the cache', 'object-sync-salesforce' );
2141
		} else {
2142
			$versions_from_cache = esc_html__( 'items were not loaded from the cache', 'object-sync-salesforce' );
2143
		}
2144
2145
		$versions_apicall_summary = sprintf(
2146
			// translators: 1) $versions_is_cached is the "This list is/is not cached, and/but" line, 2) $versions_from_cache is the "items were/were not loaded from the cache" line
2147
			esc_html__( 'Available Salesforce API versions. %1$s %2$s. This is not an authenticated request, so it does not touch the Salesforce token.', 'object-sync-for-salesforce' ),
2148
			$versions_is_cached,
2149
			$versions_from_cache
2150
		);
2151
2152
		$contacts = $sfapi->query( 'SELECT Name, Id from Contact LIMIT 100' );
2153
2154
		// format this array into html so users can see the contacts
2155
		if ( true === $contacts['cached'] ) {
2156
			$contacts_is_cached = esc_html__( 'They are cached, and', 'object-sync-salesforce' );
2157
		} else {
2158
			$contacts_is_cached = esc_html__( 'They are not cached, but', 'object-sync-salesforce' );
2159
		}
2160
2161
		if ( true === $contacts['from_cache'] ) {
2162
			$contacts_from_cache = esc_html__( 'they were loaded from the cache', 'object-sync-salesforce' );
2163
		} else {
2164
			$contacts_from_cache = esc_html__( 'they were not loaded from the cache', 'object-sync-salesforce' );
2165
		}
2166
2167
		if ( true === $contacts['is_redo'] ) {
2168
			$contacts_refreshed_token = esc_html__( 'This request did require refreshing the Salesforce token', 'object-sync-salesforce' );
2169
		} else {
2170
			$contacts_refreshed_token = esc_html__( 'This request did not require refreshing the Salesforce token', 'object-sync-salesforce' );
2171
		}
2172
2173
		// display contact summary if there are any contacts
2174
		if ( 0 < absint( $contacts['data']['totalSize'] ) ) {
2175
			$contacts_apicall_summary = sprintf(
2176
				// translators: 1) $contacts['data']['totalSize'] is the number of items loaded, 2) $contacts['data']['records'][0]['attributes']['type'] is the name of the Salesforce object, 3) $contacts_is_cached is the "They are/are not cached, and/but" line, 4) $contacts_from_cache is the "they were/were not loaded from the cache" line, 5) is the "this request did/did not require refreshing the Salesforce token" line
2177
				esc_html__( 'Salesforce successfully returned %1$s %2$s records. %3$s %4$s. %5$s.', 'object-sync-for-salesforce' ),
2178
				absint( $contacts['data']['totalSize'] ),
2179
				esc_html( $contacts['data']['records'][0]['attributes']['type'] ),
2180
				$contacts_is_cached,
2181
				$contacts_from_cache,
2182
				$contacts_refreshed_token
2183
			);
2184
		} else {
2185
			$contacts_apicall_summary = '';
2186
		}
2187
2188
		require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/status.php' );
2189
2190
	}
2191
2192
	/**
2193
	* Deauthorize WordPress from Salesforce.
2194
	* This deletes the tokens from the database; it does not currently do anything in Salesforce
2195
	* For this plugin at this time, that is the decision we are making: don't do any kind of authorization stuff inside Salesforce
2196
	*/
2197
	private function logout() {
2198
		$this->access_token  = delete_option( $this->option_prefix . 'access_token' );
2199
		$this->instance_url  = delete_option( $this->option_prefix . 'instance_url' );
2200
		$this->refresh_token = delete_option( $this->option_prefix . 'refresh_token' );
2201
		echo sprintf(
2202
			'<p>You have been logged out. You can use the <a href="%1$s">%2$s</a> tab to log in again.</p>',
2203
			esc_url( get_admin_url( null, 'options-general.php?page=object-sync-salesforce-admin&tab=authorize' ) ),
2204
			esc_html__( 'Authorize', 'object-sync-for-salesforce' )
2205
		);
2206
	}
2207
2208
	/**
2209
	* Ajax call to clear the plugin cache.
2210
	*/
2211
	public function clear_sfwp_cache() {
2212
		$result   = $this->clear_cache( true );
2213
		$response = array(
2214
			'message' => $result['message'],
2215
			'success' => $result['success'],
2216
		);
2217
		wp_send_json_success( $response );
2218
	}
2219
2220
	/**
2221
	* Clear the plugin's cache.
2222
	* This uses the flush method contained in the WordPress cache to clear all of this plugin's cached data.
2223
	*/
2224
	private function clear_cache( $ajax = false ) {
2225
		$result = (bool) $this->wordpress->sfwp_transients->flush();
2226
		if ( true === $result ) {
2227
			$message = __( 'The plugin cache has been cleared.', 'object-sync-for-salesforce' );
2228
		} else {
2229
			$message = __( 'There was an error clearing the plugin cache. Try refreshing this page.', 'object-sync-for-salesforce' );
2230
		}
2231
		if ( false === $ajax ) {
2232
			// translators: parameter 1 is the result message
2233
			echo sprintf( '<p>%1$s</p>', $message );
2234
		} else {
2235
			return array(
2236
				'message' => $message,
2237
				'success' => $result,
2238
			);
2239
		}
2240
	}
2241
2242
	/**
2243
	 * Check WordPress Admin permissions.
2244
	 * Check if the current user is allowed to access the Salesforce plugin options.
2245
	 */
2246
	private function check_wordpress_admin_permissions() {
2247
2248
		// one programmatic way to give this capability to additional user roles is the
2249
		// object_sync_for_salesforce_roles_configure_salesforce hook
2250
		// it runs on activation of this plugin, and will assign the below capability to any role
2251
		// coming from the hook
2252
2253
		// alternatively, other roles can get this capability in whatever other way you like
2254
		// point is: to administer this plugin, you need this capability
2255
2256
		if ( ! current_user_can( 'configure_salesforce' ) ) {
2257
			return false;
2258
		} else {
2259
			return true;
2260
		}
2261
2262
	}
2263
2264
	/**
2265
	 * Check WordPress SSL status.
2266
	 * HTTPS is required to connect to Salesforce.
2267
	 *
2268
	 * @return bool $secure_admin
2269
	 */
2270
	private function check_wordpress_ssl() {
2271
2272
		// the wp_is_using_https() function was added in WordPress 5.7.
2273
		if ( ! function_exists( 'wp_is_using_https' ) ) {
2274
			return true;
2275
		}
2276
2277
		// the whole site is already using SSL.
2278
		if ( true === wp_is_using_https() ) {
2279
			return true;
2280
		}
2281
2282
		$secure_admin = false;
2283
2284
		// the admin is already forced to use SSL.
2285
		if ( true === FORCE_SSL_ADMIN ) {
0 ignored issues
show
introduced by
The condition true === FORCE_SSL_ADMIN is always false.
Loading history...
2286
			$secure_admin = true;
2287
		}
2288
2289
		// the server reports that the current URL is using HTTPS.
2290
		if ( isset( $_SERVER['HTTPS'] ) && 'on' === $_SERVER['HTTPS'] ) {
2291
			$secure_admin = true;
2292
		}
2293
2294
		return $secure_admin;
2295
	}
2296
2297
	/**
2298
	 * Check WordPress SSL support.
2299
	 * This allows us to show a different message if SSL is supported, but is not in use.
2300
	 * 
2301
	 * @return bool $https_supported
2302
	 */
2303
	private function check_wordpress_ssl_support() {
2304
2305
		// the wp_is_https_supported() function was added in WordPress 5.7.
2306
		if ( ! function_exists( 'wp_is_https_supported' ) ) {
2307
			return true;
2308
		}
2309
2310
		if ( true === wp_is_https_supported() ) {
2311
			return true;
2312
		}
2313
2314
		$https_supported = false;
2315
		return $https_supported;
2316
2317
	}
2318
2319
	/**
2320
	* Show what we know about this user's relationship to a Salesforce object, if any
2321
	* @param object $user
2322
	*
2323
	*/
2324
	public function show_salesforce_user_fields( $user ) {
2325
		$get_data = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
2326
		if ( true === $this->check_wordpress_admin_permissions() ) {
2327
			$mappings = $this->mappings->load_all_by_wordpress( 'user', $user->ID );
2328
			$fieldmap = $this->mappings->get_fieldmaps(
2329
				null, // id field must be null for multiples
2330
				array(
2331
					'wordpress_object' => 'user',
2332
				)
2333
			);
2334
			if ( count( $mappings ) > 0 ) {
2335
				foreach ( $mappings as $mapping ) {
2336
					if ( isset( $mapping['id'] ) && ! isset( $get_data['edit_salesforce_mapping'] ) && ! isset( $get_data['delete_salesforce_mapping'] ) ) {
2337
						require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/user-profile-salesforce.php' );
2338
					} elseif ( ! empty( $fieldmap ) ) { // is the user mapped to something already?
2339
						if ( isset( $get_data['edit_salesforce_mapping'] ) && true === filter_var( $get_data['edit_salesforce_mapping'], FILTER_VALIDATE_BOOLEAN ) ) {
2340
							require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/user-profile-salesforce-change.php' );
2341
						} elseif ( isset( $get_data['delete_salesforce_mapping'] ) && true === filter_var( $get_data['delete_salesforce_mapping'], FILTER_VALIDATE_BOOLEAN ) ) {
2342
							require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/user-profile-salesforce-delete.php' );
2343
						} else {
2344
							require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/user-profile-salesforce-map.php' );
2345
						}
2346
					}
2347
				}
2348
			} else {
2349
				require_once( plugin_dir_path( __FILE__ ) . '/../templates/admin/user-profile-salesforce-map.php' );
2350
			}
2351
		}
2352
	}
2353
2354
	/**
2355
	* If the user profile has been mapped to Salesforce, do it
2356
	* @param int $user_id
2357
	*
2358
	*/
2359
	public function save_salesforce_user_fields( $user_id ) {
2360
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
2361
		if ( isset( $post_data['salesforce_update_mapped_user'] ) && true === filter_var( $post_data['salesforce_update_mapped_user'], FILTER_VALIDATE_BOOLEAN ) ) {
2362
			$mapping_object                  = $this->mappings->get_object_maps(
2363
				array(
2364
					'wordpress_id'     => $user_id,
2365
					'wordpress_object' => 'user',
2366
				)
2367
			);
2368
			$mapping_object['salesforce_id'] = $post_data['salesforce_id'];
2369
2370
			$result = $this->mappings->update_object_map( $mapping_object, $mapping_object['id'] );
2371
		} elseif ( isset( $post_data['salesforce_create_mapped_user'] ) && true === filter_var( $post_data['salesforce_create_mapped_user'], FILTER_VALIDATE_BOOLEAN ) ) {
2372
			// if a Salesforce ID was entered
2373
			if ( isset( $post_data['salesforce_id'] ) && ! empty( $post_data['salesforce_id'] ) ) {
2374
				$mapping_object = $this->create_object_map( $user_id, 'user', $post_data['salesforce_id'] );
2375
			} elseif ( isset( $post_data['push_new_user_to_salesforce'] ) ) {
2376
				// otherwise, create a new record in Salesforce
2377
				$result = $this->push_to_salesforce( 'user', $user_id );
2378
			}
2379
		} elseif ( isset( $post_data['salesforce_delete_mapped_user'] ) && true === filter_var( $post_data['salesforce_delete_mapped_user'], FILTER_VALIDATE_BOOLEAN ) ) {
2380
			// if a Salesforce ID was entered
2381
			if ( isset( $post_data['mapping_id'] ) && ! empty( $post_data['mapping_id'] ) ) {
2382
				$delete = $this->mappings->delete_object_map( $post_data['mapping_id'] );
2383
			}
2384
		}
2385
	}
2386
2387
	/**
2388
	* Render tabs for settings pages in admin
2389
	* @param array $tabs
2390
	* @param string $tab
2391
	*/
2392
	private function tabs( $tabs, $tab = '' ) {
2393
2394
		$get_data        = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
2395
		$consumer_key    = $this->login_credentials['consumer_key'];
2396
		$consumer_secret = $this->login_credentials['consumer_secret'];
2397
		$callback_url    = $this->login_credentials['callback_url'];
2398
2399
		$current_tab = $tab;
2400
		echo '<h2 class="nav-tab-wrapper">';
2401
		foreach ( $tabs as $tab_key => $tab_caption ) {
2402
			$active = $current_tab === $tab_key ? ' nav-tab-active' : '';
2403
2404
			if ( true === $this->salesforce['is_authorized'] ) {
2405
				echo sprintf(
2406
					'<a class="nav-tab%1$s" href="%2$s">%3$s</a>',
2407
					esc_attr( $active ),
2408
					esc_url( '?page=object-sync-salesforce-admin&tab=' . $tab_key ),
2409
					esc_html( $tab_caption )
2410
				);
2411
			} elseif ( 'settings' === $tab_key || ( 'authorize' === $tab_key && isset( $consumer_key ) && isset( $consumer_secret ) && ! empty( $consumer_key ) && ! empty( $consumer_secret ) ) ) {
2412
				echo sprintf(
2413
					'<a class="nav-tab%1$s" href="%2$s">%3$s</a>',
2414
					esc_attr( $active ),
2415
					esc_url( '?page=object-sync-salesforce-admin&tab=' . $tab_key ),
2416
					esc_html( $tab_caption )
2417
				);
2418
			}
2419
		}
2420
		echo '</h2>';
2421
2422
		if ( isset( $get_data['tab'] ) ) {
2423
			$tab = sanitize_key( $get_data['tab'] );
2424
		} else {
2425
			$tab = '';
2426
		}
2427
	}
2428
2429
	/**
2430
	* Clear schedule
2431
	* This clears the schedule if the user clicks the button
2432
	* @param string $schedule_name
2433
	*/
2434
	private function clear_schedule( $schedule_name = '' ) {
2435
		if ( '' !== $schedule_name ) {
2436
			$this->queue->cancel( $schedule_name );
2437
			// translators: $schedule_name is the name of the current queue. Defaults: salesforce_pull, salesforce_push, salesforce
2438
			echo sprintf( esc_html__( 'You have cleared the %s schedule.', 'object-sync-for-salesforce' ), esc_html( $schedule_name ) );
2439
		} else {
2440
			echo esc_html__( 'You need to specify the name of the schedule you want to clear.', 'object-sync-for-salesforce' );
2441
		}
2442
	}
2443
2444
	/**
2445
	* Get count of schedule items
2446
	* @param string $schedule_name
2447
	* @return int $count
2448
	*/
2449
	private function get_schedule_count( $schedule_name = '' ) {
2450
		if ( '' !== $schedule_name ) {
2451
			$count       = count(
2452
				$this->queue->search(
2453
					array(
2454
						'group'  => $schedule_name,
2455
						'status' => ActionScheduler_Store::STATUS_PENDING,
2456
					),
2457
					'ARRAY_A'
2458
				)
2459
			);
2460
			$group_count = count(
2461
				$this->queue->search(
2462
					array(
2463
						'group'  => $schedule_name . $this->action_group_suffix,
2464
						'status' => ActionScheduler_Store::STATUS_PENDING,
2465
					),
2466
					'ARRAY_A'
2467
				)
2468
			);
2469
			return $count + $group_count;
2470
		} else {
2471
			return 0;
2472
		}
2473
	}
2474
2475
	/**
2476
	* Create an object map between a WordPress object and a Salesforce object
2477
	*
2478
	* @param int $wordpress_id
2479
	*   Unique identifier for the WordPress object
2480
	* @param string $wordpress_object
2481
	*   What kind of object is it?
2482
	* @param string $salesforce_id
2483
	*   Unique identifier for the Salesforce object
2484
	* @param string $action
2485
	*   Did we push or pull?
2486
	*
2487
	* @return int $wpdb->insert_id
2488
	*   This is the database row for the map object
2489
	*
2490
	*/
2491
	private function create_object_map( $wordpress_id, $wordpress_object, $salesforce_id, $action = '' ) {
2492
		// Create object map and save it
2493
		$mapping_object = $this->mappings->create_object_map(
2494
			array(
2495
				'wordpress_id'      => $wordpress_id, // wordpress unique id
2496
				'salesforce_id'     => $salesforce_id, // salesforce unique id. we don't care what kind of object it is at this point
2497
				'wordpress_object'  => $wordpress_object, // keep track of what kind of wp object this is
2498
				'last_sync'         => current_time( 'mysql' ),
2499
				'last_sync_action'  => $action,
2500
				'last_sync_status'  => $this->mappings->status_success,
2501
				'last_sync_message' => __( 'Mapping object updated via function: ', 'object-sync-for-salesforce' ) . __FUNCTION__,
2502
			)
2503
		);
2504
2505
		return $mapping_object;
2506
2507
	}
2508
2509
}
2510