Passed
Push — 216-reliably-detect-ssl ( 1dda3d...3ad069 )
by Jonathan
10:31 queued 07:31
created

Object_Sync_Sf_Admin::fields_settings()   C

Complexity

Conditions 8
Paths 20

Size

Total Lines 286
Code Lines 225

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 225
nc 20
nop 3
dl 0
loc 286
rs 6.7555
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Create default WordPress admin functionality to configure the plugin.
4
 *
5
 * @class   Object_Sync_Sf_Admin
6
 * @package Object_Sync_Salesforce
7
 */
8
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * Object_Sync_Sf_Admin class.
13
 */
14
class Object_Sync_Sf_Admin {
15
16
	/**
17
	 * Current version of the plugin
18
	 *
19
	 * @var string
20
	 */
21
	public $version;
22
23
	/**
24
	 * The main plugin file
25
	 *
26
	 * @var string
27
	 */
28
	public $file;
29
30
	/**
31
	 * Global object of `$wpdb`, the WordPress database
32
	 *
33
	 * @var object
34
	 */
35
	public $wpdb;
36
37
	/**
38
	 * The plugin's slug so we can include it when necessary
39
	 *
40
	 * @var string
41
	 */
42
	public $slug;
43
44
	/**
45
	 * The plugin's prefix when saving options to the database
46
	 *
47
	 * @var string
48
	 */
49
	public $option_prefix;
50
51
	/**
52
	 * Suffix for group name in ActionScheduler
53
	 *
54
	 * @var string
55
	 */
56
	public $action_group_suffix;
57
58
	/**
59
	 * Login credentials for the Salesforce API; comes from wp-config or from the plugin settings
60
	 *
61
	 * @var array
62
	 */
63
	public $login_credentials;
64
65
	/**
66
	 * Array of what classes in the plugin can be scheduled to occur with `wp_cron` events
67
	 *
68
	 * @var array
69
	 */
70
	public $schedulable_classes;
71
72
	/**
73
	 * Object_Sync_Sf_Queue class
74
	 *
75
	 * @var object
76
	 */
77
	public $queue;
78
79
	/**
80
	 * Object_Sync_Sf_Logging class
81
	 *
82
	 * @var object
83
	 */
84
	public $logging;
85
86
	/**
87
	 * Object_Sync_Sf_Mapping class
88
	 *
89
	 * @var object
90
	 */
91
	public $mappings;
92
93
	/**
94
	 * Object_Sync_Sf_WordPress class
95
	 *
96
	 * @var object
97
	 */
98
	public $wordpress;
99
100
	/**
101
	 * Object_Sync_Sf_Salesforce class
102
	 * This contains Salesforce API methods
103
	 *
104
	 * @var array
105
	 */
106
	public $salesforce;
107
108
	/**
109
	 * Object_Sync_Sf_Salesforce_Push class
110
	 *
111
	 * @var object
112
	 */
113
	public $push;
114
115
	/**
116
	 * Object_Sync_Sf_Salesforce_Pull class
117
	 *
118
	 * @var object
119
	 */
120
	public $pull;
121
122
	/**
123
	 * Object_Sync_Sf_WordPress_Transient class
124
	 *
125
	 * @var object
126
	 */
127
	private $sfwp_transients;
128
129
	/**
130
	 * URL fragment for the plugin's settings page
131
	 *
132
	 * @var string
133
	 */
134
	private $admin_settings_url_param;
135
136
	/**
137
	 * Salesforce access token
138
	 *
139
	 * @var string
140
	 */
141
	private $access_token;
142
143
	/**
144
	 * Salesforce instance URL
145
	 *
146
	 * @var string
147
	 */
148
	private $instance_url;
149
150
	/**
151
	 * Salesforce refresh token
152
	 *
153
	 * @var string
154
	 */
155
	private $refresh_token;
156
157
	/**
158
	 * Default path for the Salesforce authorize URL
159
	 *
160
	 * @var string
161
	 */
162
	public $default_authorize_url_path;
163
164
	/**
165
	 * Default path for the Salesforce token URL
166
	 *
167
	 * @var string
168
	 */
169
	public $default_token_url_path;
170
171
	/**
172
	 * What version of the Salesforce API should be the default on the settings screen.
173
	 * Users can edit what version is used, but they won't see a correct list of all their available versions until WordPress has
174
	 * been authenticated with Salesforce.
175
	 *
176
	 * @var string
177
	 */
178
	public $default_api_version;
179
180
	/**
181
	 * Default max number of pull records. Users can edit this.
182
	 *
183
	 * @var int
184
	 */
185
	public $default_pull_limit;
186
187
	/**
188
	 * Default throttle for how often to pull from Salesforce. Users can edit this.
189
	 *
190
	 * @var int
191
	 */
192
	public $default_pull_throttle;
193
194
	/**
195
	 * Default for whether to limit to triggerable items. Users can edit this.
196
	 *
197
	 * @var bool
198
	 */
199
	public $default_triggerable;
200
201
	/**
202
	 * Default for whether to limit to items that can be updated. Users can edit this.
203
	 *
204
	 * @var bool
205
	 */
206
	public $default_updateable;
207
208
	/**
209
	 * Constructor for admin class
210
	 */
211
	public function __construct() {
212
		$this->version             = object_sync_for_salesforce()->version;
213
		$this->file                = object_sync_for_salesforce()->file;
214
		$this->wpdb                = object_sync_for_salesforce()->wpdb;
215
		$this->slug                = object_sync_for_salesforce()->slug;
216
		$this->option_prefix       = object_sync_for_salesforce()->option_prefix;
217
		$this->action_group_suffix = object_sync_for_salesforce()->action_group_suffix;
218
219
		$this->login_credentials   = object_sync_for_salesforce()->login_credentials;
220
		$this->wordpress           = object_sync_for_salesforce()->wordpress;
221
		$this->salesforce          = object_sync_for_salesforce()->salesforce;
222
		$this->mappings            = object_sync_for_salesforce()->mappings;
223
		$this->push                = object_sync_for_salesforce()->push;
224
		$this->pull                = object_sync_for_salesforce()->pull;
225
		$this->logging             = object_sync_for_salesforce()->logging;
226
		$this->schedulable_classes = object_sync_for_salesforce()->schedulable_classes;
227
		$this->queue               = object_sync_for_salesforce()->queue;
228
229
		$this->sfwp_transients          = object_sync_for_salesforce()->wordpress->sfwp_transients;
230
		$this->admin_settings_url_param = 'object-sync-salesforce-admin';
231
232
		// default authorize url path.
233
		$this->default_authorize_url_path = '/services/oauth2/authorize';
234
		// default token url path.
235
		$this->default_token_url_path = '/services/oauth2/token';
236
		// what Salesforce API version to start the settings with. This is only used in the settings form.
237
		$this->default_api_version = defined( 'OBJECT_SYNC_SF_DEFAULT_API_VERSION' ) ? OBJECT_SYNC_SF_DEFAULT_API_VERSION : '52.0';
238
		// default pull record limit.
239
		$this->default_pull_limit = 25;
240
		// default pull throttle for avoiding going over api limits.
241
		$this->default_pull_throttle = 5;
242
		// default setting for triggerable items.
243
		$this->default_triggerable = true;
244
		// default setting for updateable items.
245
		$this->default_updateable = true;
246
247
		$this->add_actions();
248
	}
249
250
	/**
251
	 * Create the action hooks to create the admin pages.
252
	 */
253
	public function add_actions() {
254
255
		// settings link.
256
		add_filter( 'plugin_action_links', array( $this, 'plugin_action_links' ), 10, 5 );
257
258
		// CSS and Javascript.
259
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts_and_styles' ) );
260
261
		// Settings API forms and notices.
262
		add_action( 'admin_menu', array( $this, 'create_admin_menu' ) );
263
		add_action( 'admin_init', array( $this, 'salesforce_settings_forms' ) );
264
		add_action( 'admin_init', array( $this, 'notices' ) );
265
		add_action( 'admin_post_post_fieldmap', array( $this, 'prepare_fieldmap_data' ) );
266
		add_action( 'admin_post_delete_fieldmap', array( $this, 'delete_fieldmap' ) );
267
268
		// Ajax for fieldmap forms.
269
		add_action( 'wp_ajax_get_salesforce_object_description', array( $this, 'get_salesforce_object_description' ), 10, 1 );
270
		add_action( 'wp_ajax_get_salesforce_object_fields', array( $this, 'get_salesforce_object_fields' ), 10, 1 );
271
		add_action( 'wp_ajax_get_wordpress_object_fields', array( $this, 'get_wordpress_object_fields' ), 10, 1 );
272
273
		// Ajax events that can be manually called.
274
		add_action( 'wp_ajax_push_to_salesforce', array( $this, 'push_to_salesforce' ), 10, 3 );
275
		add_action( 'wp_ajax_pull_from_salesforce', array( $this, 'pull_from_salesforce' ), 10, 2 );
276
		add_action( 'wp_ajax_refresh_mapped_data', array( $this, 'refresh_mapped_data' ), 10, 1 );
277
		add_action( 'wp_ajax_clear_sfwp_cache', array( $this, 'clear_sfwp_cache' ) );
278
279
		// we add a Salesforce box on user profiles.
280
		add_action( 'edit_user_profile', array( $this, 'show_salesforce_user_fields' ), 10, 1 );
281
		add_action( 'show_user_profile', array( $this, 'show_salesforce_user_fields' ), 10, 1 );
282
283
		// and we can update Salesforce fields on the user profile box.
284
		add_action( 'personal_options_update', array( $this, 'save_salesforce_user_fields' ), 10, 1 );
285
		add_action( 'edit_user_profile_update', array( $this, 'save_salesforce_user_fields' ), 10, 1 );
286
287
		// when either field for schedule settings changes.
288
		foreach ( $this->schedulable_classes as $key => $value ) {
289
			// if the user doesn't have any action schedule tasks, let's not leave them empty.
290
			add_filter( 'pre_update_option_' . $this->option_prefix . $key . '_schedule_number', array( $this, 'initial_action_schedule' ), 10, 3 );
291
			add_filter( 'pre_update_option_' . $this->option_prefix . $key . '_schedule_unit', array( $this, 'initial_action_schedule' ), 10, 3 );
292
293
			// this is if the user is changing their tasks.
294
			add_filter( 'update_option_' . $this->option_prefix . $key . '_schedule_number', array( $this, 'change_action_schedule' ), 10, 3 );
295
			add_filter( 'update_option_' . $this->option_prefix . $key . '_schedule_unit', array( $this, 'change_action_schedule' ), 10, 3 );
296
		}
297
298
		// handle post requests for object maps.
299
		add_action( 'admin_post_delete_object_map', array( $this, 'delete_object_map' ) );
300
		add_action( 'admin_post_post_object_map', array( $this, 'prepare_object_map_data' ) );
301
302
		// import and export plugin data.
303
		add_action( 'admin_post_object_sync_for_salesforce_import', array( $this, 'import_json_file' ) );
304
		add_action( 'admin_post_object_sync_for_salesforce_export', array( $this, 'export_json_file' ) );
305
306
	}
307
308
	/**
309
	 * Display a Settings link on the main Plugins page
310
	 *
311
	 * @param array  $links the array of links for the main plugins page.
312
	 * @param string $file the filename.
313
	 * @return array $links the array of links for the main plugins page
314
	 */
315
	public function plugin_action_links( $links, $file ) {
316
		if ( plugin_basename( $this->file ) === $file ) {
317
			$settings = '<a href="' . get_admin_url() . 'options-general.php?page=' . $this->admin_settings_url_param . '">' . __( 'Settings', 'object-sync-for-salesforce' ) . '</a>';
318
			array_unshift( $links, $settings );
319
		}
320
		return $links;
321
	}
322
323
	/**
324
	 * Admin styles. Load the CSS and JavaScript for the plugin's settings
325
	 */
326
	public function admin_scripts_and_styles() {
327
328
		// Developers might not want to bother with select2 or selectwoo, so we allow that to be changeable.
329
		$select_library = apply_filters( $this->option_prefix . 'select_library', 'selectwoo' );
330
331
		/*
332
		 * example to modify the select library
333
		 * add_filter( 'object_sync_for_salesforce_select_library', 'select_library', 10, 1 );
334
		 * function select_library( $select_library ) {
335
		 * 	$select_library = 'select2';
336
		 *  // this could also be empty; in that case we would just use default browser select
337
		 * 	return $select_library;
338
		 * }
339
		*/
340
341
		$javascript_dependencies = array( 'jquery' );
342
		$css_dependencies        = array();
343
		if ( '' !== $select_library ) {
344
			wp_enqueue_script( $select_library . 'js', plugins_url( 'assets/js/vendor/' . $select_library . '.min.js', $this->file ), array( 'jquery' ), filemtime( plugin_dir_path( $this->file ) . 'assets/js/vendor/' . $select_library . '.min.js' ), true );
345
			$javascript_dependencies[] = $select_library . 'js';
346
			wp_enqueue_style( $select_library . 'css', plugins_url( 'assets/css/vendor/' . $select_library . '.min.css', $this->file ), array(), filemtime( plugin_dir_path( $this->file ) . 'assets/css/vendor/' . $select_library . '.min.css' ), 'all' );
347
			$css_dependencies[] = $select_library . 'css';
348
		}
349
350
		wp_enqueue_script( $this->slug . '-admin', plugins_url( 'assets/js/object-sync-for-salesforce-admin.min.js', $this->file ), $javascript_dependencies, filemtime( plugin_dir_path( $this->file ) . 'assets/js/object-sync-for-salesforce-admin.min.js' ), true );
351
		wp_enqueue_style( $this->slug . '-admin', plugins_url( 'assets/css/object-sync-for-salesforce-admin.css', $this->file ), $css_dependencies, filemtime( plugin_dir_path( $this->file ) . 'assets/css/object-sync-for-salesforce-admin.css' ), 'all' );
352
	}
353
354
	/**
355
	 * Initial recurring tasks for ActionScheduler
356
	 *
357
	 * @param string $new_schedule the new, unserialized option value.
358
	 * @param string $old_schedule the old option value.
359
	 * @param string $option_name option name.
360
	 * @return string $new_schedule
361
	 */
362
	public function initial_action_schedule( $new_schedule, $old_schedule, $option_name ) {
363
364
		// get the current schedule name from the task, based on pattern in the foreach.
365
		preg_match( '/' . $this->option_prefix . '(.*)_schedule/', $option_name, $matches );
366
		$schedule_name     = $matches[1];
367
		$action_group_name = $schedule_name . $this->action_group_suffix;
368
369
		// make sure there are no tasks already.
370
		$current_tasks = as_get_scheduled_actions(
371
			array(
372
				'hook'  => $this->schedulable_classes[ $schedule_name ]['initializer'],
373
				'group' => $action_group_name,
374
			),
375
			ARRAY_A
376
		);
377
378
		// exit if there are already tasks; they'll be saved if the option data changed.
379
		if ( ! empty( $current_tasks ) ) {
380
			return $new_schedule;
381
		}
382
383
		$this->set_action_schedule( $schedule_name, $action_group_name );
384
385
		return $new_schedule;
386
387
	}
388
389
	/**
390
	 * Update recurring tasks for ActionScheduler if options change
391
	 *
392
	 * @param string $old_schedule the old option value.
393
	 * @param string $new_schedule the new, unserialized option value.
394
	 * @param string $option_name option name.
395
	 */
396
	public function change_action_schedule( $old_schedule, $new_schedule, $option_name ) {
397
398
		// this method does not run if the option's data is unchanged.
399
400
		// get the current schedule name from the task, based on pattern in the foreach.
401
		preg_match( '/' . $this->option_prefix . '(.*)_schedule/', $option_name, $matches );
402
		$schedule_name     = $matches[1];
403
		$action_group_name = $schedule_name . $this->action_group_suffix;
404
405
		$this->set_action_schedule( $schedule_name, $action_group_name );
406
407
	}
408
409
	/**
410
	 * Set up recurring tasks for ActionScheduler
411
	 *
412
	 * @param string $schedule_name the name of the schedule.
413
	 * @param string $action_group_name the group's name.
414
	 */
415
	private function set_action_schedule( $schedule_name, $action_group_name ) {
416
		// exit if there is no initializer property on this schedule.
417
		if ( ! isset( $this->schedulable_classes[ $schedule_name ]['initializer'] ) ) {
418
			return;
419
		}
420
421
		// cancel previous task.
422
		$this->queue->cancel(
423
			$this->schedulable_classes[ $schedule_name ]['initializer'],
424
			array(),
425
			$action_group_name
426
		);
427
428
		// create new recurring task for ActionScheduler to check for data to pull from Salesforce.
429
		$this->queue->schedule_recurring(
430
			time(), // plugin seems to expect UTC.
431
			$this->queue->get_frequency( $schedule_name, 'seconds' ),
432
			$this->schedulable_classes[ $schedule_name ]['initializer'],
433
			array(),
434
			$action_group_name
435
		);
436
	}
437
438
	/**
439
	 * Create the WordPress admin options page
440
	 */
441
	public function create_admin_menu() {
442
		$title = __( 'Salesforce', 'object-sync-for-salesforce' );
443
		add_options_page( $title, $title, 'configure_salesforce', $this->admin_settings_url_param, array( $this, 'show_admin_page' ) );
444
	}
445
446
	/**
447
	 * Render the admin pages in WordPress. This also allows other plugins to add tabs to this plugin's settings screen
448
	 */
449
	public function show_admin_page() {
450
		$get_data = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
451
		echo '<div class="wrap">';
452
		echo '<h1>' . esc_html( get_admin_page_title() ) . '</h1>';
453
		$allowed = $this->check_wordpress_admin_permissions();
454
		if ( false === $allowed ) {
455
			return;
456
		}
457
		$tabs = array(
458
			'settings'      => __( 'Settings', 'object-sync-for-salesforce' ),
459
			'authorize'     => __( 'Authorize', 'object-sync-for-salesforce' ),
460
			'fieldmaps'     => __( 'Fieldmaps', 'object-sync-for-salesforce' ),
461
			'schedule'      => __( 'Scheduling', 'object-sync-for-salesforce' ),
462
			'import-export' => __( 'Import &amp; Export', 'object-sync-for-salesforce' ),
463
		); // this creates the tabs for the admin.
464
465
		// optionally make tab(s) for logging and log settings.
466
		$logging_enabled      = get_option( $this->option_prefix . 'enable_logging', false );
467
		$tabs['log_settings'] = __( 'Log Settings', 'object-sync-for-salesforce' );
468
469
		$mapping_errors       = $this->mappings->get_failed_object_maps();
470
		$mapping_errors_total = isset( $mapping_errors['total'] ) ? $mapping_errors['total'] : 0;
471
		if ( 0 < $mapping_errors_total ) {
472
			$tabs['mapping_errors'] = __( 'Mapping Errors', 'object-sync-for-salesforce' );
473
		}
474
475
		// filter for extending the tabs available on the page
476
		// currently it will go into the default switch case for $tab.
477
		$tabs = apply_filters( $this->option_prefix . 'settings_tabs', $tabs );
478
479
		$tab = isset( $get_data['tab'] ) ? sanitize_key( $get_data['tab'] ) : 'settings';
480
		$this->tabs( $tabs, $tab );
481
482
		$consumer_key    = $this->login_credentials['consumer_key'];
483
		$consumer_secret = $this->login_credentials['consumer_secret'];
484
		$callback_url    = $this->login_credentials['callback_url'];
485
486
		if ( true !== $this->salesforce['is_authorized'] ) {
487
			$url     = esc_url( $callback_url );
488
			$anchor  = esc_html__( 'Authorize tab', 'object-sync-for-salesforce' );
489
			$message = sprintf( 'Salesforce needs to be authorized to connect to this website. Use the <a href="%s">%s</a> to connect.', $url, $anchor );
490
			require plugin_dir_path( $this->file ) . '/templates/admin/error.php';
491
		}
492
493
		if ( 0 === count( $this->mappings->get_fieldmaps() ) ) {
494
			$url     = esc_url( get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=fieldmaps' ) );
495
			$anchor  = esc_html__( 'Fieldmaps tab', 'object-sync-for-salesforce' );
496
			$message = sprintf( 'No fieldmaps exist yet. Use the <a href="%s">%s</a> to map WordPress and Salesforce objects to each other.', $url, $anchor );
497
			require plugin_dir_path( $this->file ) . '/templates/admin/error.php';
498
		}
499
500
		try {
501
			switch ( $tab ) {
502
				case 'authorize':
503
					if ( isset( $get_data['code'] ) ) {
504
						// this string is an oauth token.
505
						$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

505
						$data          = esc_html( /** @scrutinizer ignore-type */ wp_unslash( $get_data['code'] ) );
Loading history...
506
						$is_authorized = $this->salesforce['sfapi']->request_token( $data );
507
						?>
508
						<script>window.location = '<?php echo esc_url_raw( $callback_url ); ?>'</script>
509
						<?php
510
					} elseif ( true === $this->salesforce['is_authorized'] ) {
511
							require_once plugin_dir_path( $this->file ) . '/templates/admin/authorized.php';
512
							$this->status( $this->salesforce['sfapi'] );
513
					} elseif ( true === is_object( $this->salesforce['sfapi'] ) && isset( $consumer_key ) && isset( $consumer_secret ) ) {
514
						?>
515
						<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>
516
						<?php
517
					} else {
518
						$url    = esc_url( get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=settings' ) );
519
						$anchor = esc_html__( 'Settings', 'object-sync-for-salesforce' );
520
						// translators: placeholders are for the settings tab link: 1) the url, and 2) the anchor text.
521
						$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-for-salesforce' ), $url, $anchor );
522
						require_once plugin_dir_path( $this->file ) . '/templates/admin/error.php';
523
					}
524
					break;
525
				case 'fieldmaps':
526
					if ( isset( $get_data['method'] ) ) {
527
528
						$method      = sanitize_key( $get_data['method'] );
529
						$error_url   = get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=fieldmaps&method=' . $method );
530
						$success_url = get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=fieldmaps' );
531
532
						$disable_mapped_fields = get_option( $this->option_prefix . 'disable_mapped_fields', false );
533
						$disable_mapped_fields = filter_var( $disable_mapped_fields, FILTER_VALIDATE_BOOLEAN );
534
						$fieldmap_class        = 'fieldmap';
535
						if ( true === $disable_mapped_fields ) {
536
							$fieldmap_class .= ' fieldmap-disable-mapped-fields';
537
						}
538
539
						if ( isset( $get_data['transient'] ) ) {
540
							$transient = sanitize_key( $get_data['transient'] );
541
							$posted    = $this->sfwp_transients->get( $transient );
542
						}
543
544
						if ( isset( $posted ) && is_array( $posted ) ) {
545
							$map = $posted;
546
						} elseif ( 'edit' === $method || 'clone' === $method || 'delete' === $method ) {
547
							$map = $this->mappings->get_fieldmaps( isset( $get_data['id'] ) ? sanitize_key( $get_data['id'] ) : '' );
548
						}
549
550
						if ( 'add' === $method || ( isset( $map ) && is_array( $map ) && isset( $map['id'] ) ) ) {
551
							if ( isset( $map ) && is_array( $map ) && isset( $map['id'] ) ) {
552
								$label                           = $map['label'];
553
								$salesforce_object               = $map['salesforce_object'];
554
								$salesforce_record_types_allowed = maybe_unserialize( $map['salesforce_record_types_allowed'] );
555
								$salesforce_record_type_default  = $map['salesforce_record_type_default'];
556
								$wordpress_object                = $map['wordpress_object'];
557
								$pull_trigger_field              = $map['pull_trigger_field'];
558
								$fieldmap_fields                 = $map['fields'];
559
								$sync_triggers                   = $map['sync_triggers'];
560
								$push_async                      = $map['push_async'];
561
								$push_drafts                     = $map['push_drafts'];
562
								$pull_to_drafts                  = $map['pull_to_drafts'];
563
								$weight                          = $map['weight'];
564
							}
565
							if ( 'add' === $method || 'edit' === $method || 'clone' === $method ) {
566
								require_once plugin_dir_path( $this->file ) . '/templates/admin/fieldmaps-add-edit-clone.php';
567
							} elseif ( 'delete' === $method ) {
568
								require_once plugin_dir_path( $this->file ) . '/templates/admin/fieldmaps-delete.php';
569
							}
570
						} else {
571
							$no_fieldmap_url = get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=fieldmaps&missing_fieldmap=true' );
572
							wp_safe_redirect( $no_fieldmap_url );
573
							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...
574
						}
575
					} else {
576
						$fieldmaps = $this->mappings->get_fieldmaps();
577
						require_once plugin_dir_path( $this->file ) . '/templates/admin/fieldmaps-list.php';
578
					} // End if statement.
579
					break;
580
				case 'logout':
581
					$this->logout();
582
					break;
583
				case 'clear_cache':
584
					$this->clear_cache();
585
					break;
586
				case 'clear_schedule':
587
					if ( isset( $get_data['schedule_name'] ) ) {
588
						$schedule_name = sanitize_key( $get_data['schedule_name'] );
589
					}
590
					$this->clear_schedule( $schedule_name );
591
					break;
592
				case 'settings':
593
					require_once plugin_dir_path( $this->file ) . '/templates/admin/settings.php';
594
					break;
595
				case 'mapping_errors':
596
					if ( isset( $get_data['method'] ) ) {
597
598
						$method      = sanitize_key( $get_data['method'] );
599
						$error_url   = get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=mapping_errors&method=' . $method );
600
						$success_url = get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=mapping_errors' );
601
602
						if ( isset( $get_data['map_transient'] ) ) {
603
							$transient = sanitize_key( $get_data['map_transient'] );
604
							$posted    = $this->sfwp_transients->get( $transient );
605
						}
606
607
						if ( isset( $posted ) && is_array( $posted ) ) {
608
							$map_row = $posted;
609
						} elseif ( 'edit' === $method || 'delete' === $method ) {
610
							$map_row = $this->mappings->get_failed_object_map( isset( $get_data['id'] ) ? sanitize_key( $get_data['id'] ) : '' );
611
						}
612
613
						if ( isset( $map_row ) && is_array( $map_row ) ) {
614
							$salesforce_id = $map_row['salesforce_id'];
615
							$wordpress_id  = $map_row['wordpress_id'];
616
						}
617
618
						if ( 'edit' === $method ) {
619
							require_once plugin_dir_path( $this->file ) . '/templates/admin/mapping-errors-edit.php';
620
						} elseif ( 'delete' === $method ) {
621
							require_once plugin_dir_path( $this->file ) . '/templates/admin/mapping-errors-delete.php';
622
						}
623
					} else {
624
625
						if ( isset( $get_data['mapping_error_transient'] ) ) {
626
							$transient = sanitize_key( $get_data['mapping_error_transient'] );
627
							$posted    = $this->sfwp_transients->get( $transient );
628
						}
629
630
						$ids_string = '';
631
						$ids        = array();
632
						if ( isset( $posted['delete'] ) ) {
633
							$ids_string = maybe_serialize( $posted['delete'] );
634
							$ids        = $posted['delete'];
635
						}
636
637
						$error_url   = get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=mapping_errors&ids=' . $ids_string );
638
						$success_url = get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=mapping_errors' );
639
						require_once plugin_dir_path( $this->file ) . '/templates/admin/mapping-errors.php';
640
					}
641
					break;
642
				case 'import-export':
643
					require_once plugin_dir_path( $this->file ) . '/templates/admin/import-export.php';
644
					break;
645
				default:
646
					$include_settings = apply_filters( $this->option_prefix . 'settings_tab_include_settings', true, $tab );
647
					$content_before   = apply_filters( $this->option_prefix . 'settings_tab_content_before', null, $tab );
648
					$content_after    = apply_filters( $this->option_prefix . 'settings_tab_content_after', null, $tab );
649
					if ( null !== $content_before ) {
650
						echo esc_html( $content_before );
651
					}
652
					if ( true === $include_settings ) {
653
						require_once plugin_dir_path( $this->file ) . '/templates/admin/settings.php';
654
					}
655
					if ( null !== $content_after ) {
656
						echo esc_html( $content_after );
657
					}
658
					break;
659
			} // End switch statement.
660
		} catch ( Object_Sync_Sf_Exception $ex ) {
661
			echo sprintf(
662
				'<p>Error <strong>%1$s</strong>: %2$s</p>',
663
				absint( $ex->getCode() ),
664
				esc_html( $ex->getMessage() )
665
			);
666
		} // End try for menu/page setup.
667
		echo '</div>';
668
	}
669
670
	/**
671
	 * Create default WordPress admin settings form. This runs the Settings page.
672
	 */
673
	public function salesforce_settings_forms() {
674
		$get_data = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
675
		$page     = isset( $get_data['tab'] ) ? sanitize_key( $get_data['tab'] ) : 'settings';
676
		$section  = isset( $get_data['tab'] ) ? sanitize_key( $get_data['tab'] ) : 'settings';
677
678
		$input_callback_default   = array( $this, 'display_input_field' );
679
		$input_checkboxes_default = array( $this, 'display_checkboxes' );
680
		$input_select_default     = array( $this, 'display_select' );
681
		$link_default             = array( $this, 'display_link' );
682
683
		$all_field_callbacks = array(
684
			'text'       => $input_callback_default,
685
			'checkboxes' => $input_checkboxes_default,
686
			'select'     => $input_select_default,
687
			'link'       => $link_default,
688
		);
689
690
		$this->fields_settings( 'settings', 'settings', $all_field_callbacks );
691
		$this->fields_fieldmaps( 'fieldmaps', 'objects' );
692
		$this->fields_scheduling( 'schedule', 'schedule', $all_field_callbacks );
693
		$this->fields_log_settings( 'log_settings', 'log_settings', $all_field_callbacks );
694
		$this->fields_errors( 'mapping_errors', 'mapping_errors', $all_field_callbacks );
695
	}
696
697
	/**
698
	 * Fields for the Settings tab
699
	 * This runs add_settings_section once, as well as add_settings_field and register_setting methods for each option
700
	 *
701
	 * @param string $page what page we're on.
702
	 * @param string $section what section of the page.
703
	 * @param array  $callbacks method to call.
704
	 */
705
	private function fields_settings( $page, $section, $callbacks ) {
706
		add_settings_section( $page, ucwords( $page ), null, $page );
707
		$salesforce_settings = array(
708
			'consumer_key'                   => array(
709
				'title'    => __( 'Consumer Key', 'object-sync-for-salesforce' ),
710
				'callback' => $callbacks['text'],
711
				'page'     => $page,
712
				'section'  => $section,
713
				'args'     => array(
714
					'type'     => 'text',
715
					'validate' => 'sanitize_validate_text',
716
					'desc'     => '',
717
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_CONSUMER_KEY',
718
				),
719
720
			),
721
			'consumer_secret'                => array(
722
				'title'    => __( 'Consumer Secret', 'object-sync-for-salesforce' ),
723
				'callback' => $callbacks['text'],
724
				'page'     => $page,
725
				'section'  => $section,
726
				'args'     => array(
727
					'type'     => 'text',
728
					'validate' => 'sanitize_validate_text',
729
					'desc'     => '',
730
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_CONSUMER_SECRET',
731
				),
732
			),
733
			'callback_url'                   => array(
734
				'title'    => __( 'Callback URL', 'object-sync-for-salesforce' ),
735
				'callback' => $callbacks['text'],
736
				'page'     => $page,
737
				'section'  => $section,
738
				'args'     => array(
739
					'type'     => 'url',
740
					'validate' => 'sanitize_validate_text',
741
					'desc'     => sprintf(
742
						// translators: %1$s is the admin URL for the Authorize tab.
743
						__( 'In most cases, you will want to use %1$s for this value.', 'object-sync-for-salesforce' ),
744
						get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=authorize' )
745
					),
746
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_CALLBACK_URL',
747
				),
748
			),
749
			'login_base_url'                 => array(
750
				'title'    => __( 'Login Base URL', 'object-sync-for-salesforce' ),
751
				'callback' => $callbacks['text'],
752
				'page'     => $page,
753
				'section'  => $section,
754
				'args'     => array(
755
					'type'     => 'url',
756
					'validate' => 'sanitize_validate_text',
757
					'desc'     => sprintf(
758
						// translators: 1) production salesforce login, 2) sandbox salesforce login.
759
						__( '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' ),
760
						esc_url( 'https://login.salesforce.com' ),
761
						esc_url( 'https://test.salesforce.com' )
762
					),
763
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_LOGIN_BASE_URL',
764
				),
765
			),
766
			'authorize_url_path'             => array(
767
				'title'    => __( 'Authorize URL Path', 'object-sync-for-salesforce' ),
768
				'callback' => $callbacks['text'],
769
				'page'     => $page,
770
				'section'  => $section,
771
				'args'     => array(
772
					'type'     => 'text',
773
					'validate' => 'sanitize_validate_text',
774
					'desc'     => __( 'For most Salesforce installs, this should not be changed.', 'object-sync-for-salesforce' ),
775
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_AUTHORIZE_URL_PATH',
776
					'default'  => $this->default_authorize_url_path,
777
				),
778
			),
779
			'token_url_path'                 => array(
780
				'title'    => __( 'Token URL Path', 'object-sync-for-salesforce' ),
781
				'callback' => $callbacks['text'],
782
				'page'     => $page,
783
				'section'  => $section,
784
				'args'     => array(
785
					'type'     => 'text',
786
					'validate' => 'sanitize_validate_text',
787
					'desc'     => __( 'For most Salesforce installs, this should not be changed.', 'object-sync-for-salesforce' ),
788
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_TOKEN_URL_PATH',
789
					'default'  => $this->default_token_url_path,
790
				),
791
			),
792
			'api_version'                    => array(
793
				'title'    => __( 'Salesforce API Version', 'object-sync-for-salesforce' ),
794
				'callback' => $callbacks['text'],
795
				'page'     => $page,
796
				'section'  => $section,
797
				'args'     => array(
798
					'type'     => 'text',
799
					'validate' => 'sanitize_validate_text',
800
					'desc'     => '',
801
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_API_VERSION',
802
					'default'  => $this->default_api_version,
803
				),
804
			),
805
			'object_filters'                 => array(
806
				'title'    => __( 'Limit Salesforce Objects', 'object-sync-for-salesforce' ),
807
				'callback' => $callbacks['checkboxes'],
808
				'page'     => $page,
809
				'section'  => $section,
810
				'args'     => array(
811
					'type'     => 'checkboxes',
812
					'validate' => 'sanitize_validate_text',
813
					'desc'     => __( 'Allows you to limit which Salesforce objects can be mapped', 'object-sync-for-salesforce' ),
814
					'items'    => array(
815
						'triggerable' => array(
816
							'text'    => __( 'Only Triggerable objects', 'object-sync-for-salesforce' ),
817
							'id'      => 'triggerable',
818
							'desc'    => '',
819
							'default' => $this->default_triggerable,
820
						),
821
						'updateable'  => array(
822
							'text'    => __( 'Only Updateable objects', 'object-sync-for-salesforce' ),
823
							'id'      => 'updateable',
824
							'desc'    => '',
825
							'default' => $this->default_updateable,
826
						),
827
					),
828
				),
829
			),
830
			'salesforce_field_display_value' => array(
831
				'title'    => __( 'Salesforce Field Display Value', 'object-sync-for-salesforce' ),
832
				'callback' => $callbacks['select'],
833
				'page'     => $page,
834
				'section'  => $section,
835
				'args'     => array(
836
					'type'     => 'select',
837
					'validate' => 'sanitize_validate_text',
838
					'desc'     => __( 'When choosing Salesforce fields to map, this value determines how the dropdown will identify Salesforce fields.', 'object-sync-for-salesforce' ),
839
					'constant' => '',
840
					'items'    => array(
841
						'field_label' => array(
842
							'text'  => __( 'Field Label', 'object-sync-for-salesforce' ),
843
							'value' => 'field_label',
844
						),
845
						'api_name'    => array(
846
							'text'  => __( 'API Name', 'object-sync-for-salesforce' ),
847
							'value' => 'api_name',
848
						),
849
					),
850
				),
851
			),
852
			'disable_mapped_fields'          => array(
853
				'title'    => __( 'Prevent Duplicate Field Mapping?', 'object-sync-for-salesforce' ),
854
				'callback' => $callbacks['text'],
855
				'page'     => $page,
856
				'section'  => $section,
857
				'args'     => array(
858
					'type'     => 'checkbox',
859
					'validate' => 'sanitize_text_field',
860
					'desc'     => __( 'If checked, any WordPress or Salesforce field that has already been mapped, or that is selected while creating or editing a fieldmap, cannot be mapped again.', 'object-sync-for-salesforce' ),
861
					'constant' => '',
862
				),
863
			),
864
			'pull_query_limit'               => array(
865
				'title'    => __( 'Pull Query Record Limit', 'object-sync-for-salesforce' ),
866
				'callback' => $callbacks['text'],
867
				'page'     => $page,
868
				'section'  => $section,
869
				'args'     => array(
870
					'type'     => 'number',
871
					'validate' => 'absint',
872
					'desc'     => __( 'Limit the number of records that can be pulled from Salesforce in a single query.', 'object-sync-for-salesforce' ),
873
					'constant' => '',
874
					'default'  => $this->default_pull_limit,
875
				),
876
			),
877
			'pull_throttle'                  => array(
878
				'title'    => __( 'Pull Throttle (In Seconds)', 'object-sync-for-salesforce' ),
879
				'callback' => $callbacks['text'],
880
				'page'     => $page,
881
				'section'  => $section,
882
				'args'     => array(
883
					'type'     => 'number',
884
					'validate' => 'sanitize_validate_text',
885
					'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' ),
886
					'constant' => '',
887
					'default'  => $this->default_pull_throttle,
888
				),
889
			),
890
		);
891
892
		// only show soap settings if the soap extension is enabled on the server.
893
		if ( true === $this->salesforce['soap_available'] ) {
894
			$salesforce_settings['use_soap']       = array(
895
				'title'    => __( 'Enable the Salesforce SOAP API?', 'object-sync-for-salesforce' ),
896
				'callback' => $callbacks['text'],
897
				'page'     => $page,
898
				'section'  => $section,
899
				'class'    => 'object-sync-for-salesforce-enable-soap',
900
				'args'     => array(
901
					'type'     => 'checkbox',
902
					'validate' => 'sanitize_text_field',
903
					'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' ),
904
					'constant' => '',
905
				),
906
			);
907
			$salesforce_settings['soap_wsdl_path'] = array(
908
				'title'    => __( 'Path to SOAP WSDL File', 'object-sync-for-salesforce' ),
909
				'callback' => $callbacks['text'],
910
				'page'     => $page,
911
				'section'  => $section,
912
				'class'    => 'object-sync-for-salesforce-soap-wsdl-path',
913
				'args'     => array(
914
					'type'     => 'text',
915
					'validate' => 'sanitize_text_field',
916
					'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' ),
917
					'constant' => '',
918
				),
919
			);
920
		}
921
922
		$salesforce_settings['debug_mode']               = array(
923
			'title'    => __( 'Debug Mode?', 'object-sync-for-salesforce' ),
924
			'callback' => $callbacks['text'],
925
			'page'     => $page,
926
			'section'  => $section,
927
			'args'     => array(
928
				'type'     => 'checkbox',
929
				'validate' => 'sanitize_text_field',
930
				'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' ),
931
				'constant' => '',
932
			),
933
		);
934
		$salesforce_settings['delete_data_on_uninstall'] = array(
935
			'title'    => __( 'Delete Plugin Data on Uninstall?', 'object-sync-for-salesforce' ),
936
			'callback' => $callbacks['text'],
937
			'page'     => $page,
938
			'section'  => $section,
939
			'args'     => array(
940
				'type'     => 'checkbox',
941
				'validate' => 'sanitize_text_field',
942
				'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' ),
943
				'constant' => '',
944
			),
945
		);
946
947
		// if the user has authenticated with Salesforce, override the text field with a dropdown of available Salesforce API verisons.
948
		if ( true === is_object( $this->salesforce['sfapi'] ) && true === $this->salesforce['sfapi']->is_authorized() ) {
949
			$salesforce_settings['api_version'] = array(
950
				'title'    => __( 'Salesforce API Version', 'object-sync-for-salesforce' ),
951
				'callback' => $callbacks['select'],
952
				'page'     => $page,
953
				'section'  => $section,
954
				'args'     => array(
955
					'type'     => 'select',
956
					'validate' => 'sanitize_text_field',
957
					'desc'     => '',
958
					'constant' => 'OBJECT_SYNC_SF_SALESFORCE_API_VERSION',
959
					'items'    => $this->version_options(),
960
				),
961
			);
962
		}
963
964
		foreach ( $salesforce_settings as $key => $attributes ) {
965
			$id       = $this->option_prefix . $key;
966
			$name     = $this->option_prefix . $key;
967
			$title    = $attributes['title'];
968
			$callback = $attributes['callback'];
969
			$validate = $attributes['args']['validate'];
970
			$page     = $attributes['page'];
971
			$section  = $attributes['section'];
972
			$class    = isset( $attributes['class'] ) ? $attributes['class'] : '';
973
			$args     = array_merge(
974
				$attributes['args'],
975
				array(
976
					'title'     => $title,
977
					'id'        => $id,
978
					'label_for' => $id,
979
					'name'      => $name,
980
					'class'     => $class,
981
				)
982
			);
983
984
			// if there is a constant and it is defined, don't run a validate function.
985
			if ( isset( $attributes['args']['constant'] ) && defined( $attributes['args']['constant'] ) ) {
986
				$validate = '';
987
			}
988
989
			add_settings_field( $id, $title, $callback, $page, $section, $args );
990
			register_setting( $page, $id, array( $this, $validate ) );
991
		}
992
	}
993
994
	/**
995
	 * Fields for the Fieldmaps tab
996
	 * This runs add_settings_section once, as well as add_settings_field and register_setting methods for each option
997
	 *
998
	 * @param string $page what page we're on.
999
	 * @param string $section what section of the page.
1000
	 * @param string $input_callback method to call.
1001
	 */
1002
	private function fields_fieldmaps( $page, $section, $input_callback = '' ) {
1003
		add_settings_section( $page, ucwords( $page ), null, $page );
1004
	}
1005
1006
	/**
1007
	 * Fields for the Scheduling tab
1008
	 * This runs add_settings_section once, as well as add_settings_field and register_setting methods for each option
1009
	 *
1010
	 * @param string $page what page we're on.
1011
	 * @param string $section what section of the page.
1012
	 * @param array  $callbacks method to call.
1013
	 */
1014
	private function fields_scheduling( $page, $section, $callbacks ) {
1015
1016
		add_settings_section( 'batch', __( 'Batch Settings', 'object-sync-for-salesforce' ), null, $page );
1017
		$section           = 'batch';
1018
		$schedule_settings = array(
1019
			'action_scheduler_batch_size'         => array(
1020
				'title'    => __( 'Batch Size', 'object-sync-for-salesforce' ),
1021
				'callback' => $callbacks['text'],
1022
				'page'     => $page,
1023
				'section'  => $section,
1024
				'args'     => array(
1025
					'type'     => 'number',
1026
					'validate' => 'absint',
1027
					'default'  => 5,
1028
					'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' ),
1029
					'constant' => '',
1030
				),
1031
1032
			),
1033
			'action_scheduler_concurrent_batches' => array(
1034
				'title'    => __( 'Concurrent Batches', 'object-sync-for-salesforce' ),
1035
				'callback' => $callbacks['text'],
1036
				'page'     => $page,
1037
				'section'  => $section,
1038
				'args'     => array(
1039
					'type'     => 'number',
1040
					'validate' => 'absint',
1041
					'default'  => 3,
1042
					'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' ),
1043
					'constant' => '',
1044
				),
1045
			),
1046
		);
1047
1048
		foreach ( $this->schedulable_classes as $key => $value ) {
1049
			add_settings_section( $key, $value['label'], null, $page );
1050
			if ( isset( $value['initializer'] ) ) {
1051
				$schedule_settings[ $key . '_schedule_number' ] = array(
1052
					'title'    => __( 'Run Schedule Every', 'object-sync-for-salesforce' ),
1053
					'callback' => $callbacks['text'],
1054
					'page'     => $page,
1055
					'section'  => $key,
1056
					'args'     => array(
1057
						'type'     => 'number',
1058
						'validate' => 'absint',
1059
						'desc'     => '',
1060
						'constant' => '',
1061
					),
1062
				);
1063
				$schedule_settings[ $key . '_schedule_unit' ]   = array(
1064
					'title'    => __( 'Time Unit', 'object-sync-for-salesforce' ),
1065
					'callback' => $callbacks['select'],
1066
					'page'     => $page,
1067
					'section'  => $key,
1068
					'args'     => array(
1069
						'type'     => 'select',
1070
						'validate' => 'sanitize_validate_text',
1071
						'desc'     => '',
1072
						'items'    => array(
1073
							'minutes' => array(
1074
								'text'  => __( 'Minutes', 'object-sync-for-salesforce' ),
1075
								'value' => 'minutes',
1076
							),
1077
							'hours'   => array(
1078
								'text'  => __( 'Hours', 'object-sync-for-salesforce' ),
1079
								'value' => 'hours',
1080
							),
1081
							'days'    => array(
1082
								'text'  => __( 'Days', 'object-sync-for-salesforce' ),
1083
								'value' => 'days',
1084
							),
1085
						),
1086
					),
1087
				);
1088
			}
1089
			$schedule_settings[ $key . '_clear_button' ] = array(
1090
				// translators: $this->get_schedule_count is an integer showing how many items are in the current queue.
1091
				'title'    => sprintf( 'This Queue Has ' . _n( '%s Item', '%s Items', $this->get_schedule_count( $key ), 'object-sync-for-salesforce' ), $this->get_schedule_count( $key ) ),
1092
				'callback' => $callbacks['link'],
1093
				'page'     => $page,
1094
				'section'  => $key,
1095
				'args'     => array(
1096
					'label'      => __( 'Clear this queue', 'object-sync-for-salesforce' ),
1097
					'desc'       => '',
1098
					'url'        => esc_url( '?page=' . $this->admin_settings_url_param . '&amp;tab=clear_schedule&amp;schedule_name=' . $key ),
1099
					'link_class' => 'button button-secondary',
1100
				),
1101
			);
1102
			foreach ( $schedule_settings as $key => $attributes ) {
1103
				$id       = $this->option_prefix . $key;
1104
				$name     = $this->option_prefix . $key;
1105
				$title    = $attributes['title'];
1106
				$callback = $attributes['callback'];
1107
				$page     = $attributes['page'];
1108
				$section  = $attributes['section'];
1109
				$args     = array_merge(
1110
					$attributes['args'],
1111
					array(
1112
						'title'     => $title,
1113
						'id'        => $id,
1114
						'label_for' => $id,
1115
						'name'      => $name,
1116
					)
1117
				);
1118
				add_settings_field( $id, $title, $callback, $page, $section, $args );
1119
				register_setting( $page, $id );
1120
			}
1121
		} // End foreach statement.
1122
	}
1123
1124
	/**
1125
	 * Fields for the Log Settings tab
1126
	 * This runs add_settings_section once, as well as add_settings_field and register_setting methods for each option
1127
	 *
1128
	 * @param string $page what page we're on.
1129
	 * @param string $section what section of the page.
1130
	 * @param array  $callbacks method to call.
1131
	 */
1132
	private function fields_log_settings( $page, $section, $callbacks ) {
1133
		add_settings_section( $page, ucwords( str_replace( '_', ' ', $page ) ), null, $page );
1134
		$log_settings = array(
1135
			'enable_logging'        => array(
1136
				'title'    => __( 'Enable Logging?', 'object-sync-for-salesforce' ),
1137
				'callback' => $callbacks['text'],
1138
				'page'     => $page,
1139
				'section'  => $section,
1140
				'args'     => array(
1141
					'type'     => 'checkbox',
1142
					'validate' => 'absint',
1143
					'desc'     => '',
1144
					'constant' => '',
1145
				),
1146
			),
1147
			'statuses_to_log'       => array(
1148
				'title'    => __( 'Statuses to Log', 'object-sync-for-salesforce' ),
1149
				'callback' => $callbacks['checkboxes'],
1150
				'page'     => $page,
1151
				'section'  => $section,
1152
				'args'     => array(
1153
					'type'     => 'checkboxes',
1154
					'validate' => 'sanitize_validate_text',
1155
					'desc'     => __( 'These are the statuses to log', 'object-sync-for-salesforce' ),
1156
					'items'    => array(
1157
						'error'   => array(
1158
							'text' => __( 'Error', 'object-sync-for-salesforce' ),
1159
							'id'   => 'error',
1160
							'desc' => '',
1161
						),
1162
						'success' => array(
1163
							'text' => __( 'Success', 'object-sync-for-salesforce' ),
1164
							'id'   => 'success',
1165
							'desc' => '',
1166
						),
1167
						'notice'  => array(
1168
							'text' => __( 'Notice', 'object-sync-for-salesforce' ),
1169
							'id'   => 'notice',
1170
							'desc' => '',
1171
						),
1172
						'debug'   => array(
1173
							'text' => __( 'Debug', 'object-sync-for-salesforce' ),
1174
							'id'   => 'debug',
1175
							'desc' => '',
1176
						),
1177
					),
1178
				),
1179
			),
1180
			'prune_logs'            => array(
1181
				'title'    => __( 'Automatically Delete Old Log Entries?', 'object-sync-for-salesforce' ),
1182
				'callback' => $callbacks['text'],
1183
				'page'     => $page,
1184
				'section'  => $section,
1185
				'args'     => array(
1186
					'type'     => 'checkbox',
1187
					'validate' => 'absint',
1188
					'desc'     => '',
1189
					'constant' => '',
1190
				),
1191
			),
1192
			'logs_how_old'          => array(
1193
				'title'    => __( 'Age to Delete Log Entries', 'object-sync-for-salesforce' ),
1194
				'callback' => $callbacks['text'],
1195
				'page'     => $page,
1196
				'section'  => $section,
1197
				'args'     => array(
1198
					'type'     => 'text',
1199
					'validate' => 'sanitize_validate_text',
1200
					'desc'     => __( 'If automatic deleting is enabled, it will affect logs this old.', 'object-sync-for-salesforce' ),
1201
					'default'  => '2 weeks',
1202
					'constant' => '',
1203
				),
1204
			),
1205
			'logs_how_often_number' => array(
1206
				'title'    => __( 'Check For Old Logs Every', 'object-sync-for-salesforce' ),
1207
				'callback' => $callbacks['text'],
1208
				'page'     => $page,
1209
				'section'  => $section,
1210
				'args'     => array(
1211
					'type'     => 'number',
1212
					'validate' => 'absint',
1213
					'desc'     => '',
1214
					'default'  => '1',
1215
					'constant' => '',
1216
				),
1217
			),
1218
			'logs_how_often_unit'   => array(
1219
				'title'    => __( 'Time Unit', 'object-sync-for-salesforce' ),
1220
				'callback' => $callbacks['select'],
1221
				'page'     => $page,
1222
				'section'  => $section,
1223
				'args'     => array(
1224
					'type'     => 'select',
1225
					'validate' => 'sanitize_validate_text',
1226
					'desc'     => __( 'These two fields are how often the site will check for logs to delete.', 'object-sync-for-salesforce' ),
1227
					'items'    => array(
1228
						'minutes' => array(
1229
							'text'  => __( 'Minutes', 'object-sync-for-salesforce' ),
1230
							'value' => 'minutes',
1231
						),
1232
						'hours'   => array(
1233
							'text'  => __( 'Hours', 'object-sync-for-salesforce' ),
1234
							'value' => 'hours',
1235
						),
1236
						'days'    => array(
1237
							'text'  => __( 'Days', 'object-sync-for-salesforce' ),
1238
							'value' => 'days',
1239
						),
1240
					),
1241
				),
1242
			),
1243
			'logs_how_many_number'  => array(
1244
				'title'    => __( 'Clear This Many Log Entries', 'object-sync-for-salesforce' ),
1245
				'callback' => $callbacks['text'],
1246
				'page'     => $page,
1247
				'section'  => $section,
1248
				'args'     => array(
1249
					'type'     => 'number',
1250
					'validate' => 'absint',
1251
					'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' ),
1252
					'default'  => '100',
1253
					'constant' => '',
1254
				),
1255
			),
1256
			'triggers_to_log'       => array(
1257
				'title'    => __( 'Triggers to Log', 'object-sync-for-salesforce' ),
1258
				'callback' => $callbacks['checkboxes'],
1259
				'page'     => $page,
1260
				'section'  => $section,
1261
				'args'     => array(
1262
					'type'     => 'checkboxes',
1263
					'validate' => 'sanitize_validate_text',
1264
					'desc'     => __( 'These are the triggers to log', 'object-sync-for-salesforce' ),
1265
					'items'    => array(
1266
						$this->mappings->sync_wordpress_create => array(
1267
							'text' => __( 'WordPress Create', 'object-sync-for-salesforce' ),
1268
							'id'   => 'wordpress_create',
1269
							'desc' => '',
1270
						),
1271
						$this->mappings->sync_wordpress_update => array(
1272
							'text' => __( 'WordPress Update', 'object-sync-for-salesforce' ),
1273
							'id'   => 'wordpress_update',
1274
							'desc' => '',
1275
						),
1276
						$this->mappings->sync_wordpress_delete => array(
1277
							'text' => __( 'WordPress Delete', 'object-sync-for-salesforce' ),
1278
							'id'   => 'wordpress_delete',
1279
							'desc' => '',
1280
						),
1281
						$this->mappings->sync_sf_create => array(
1282
							'text' => __( 'Salesforce Create', 'object-sync-for-salesforce' ),
1283
							'id'   => 'sf_create',
1284
							'desc' => '',
1285
						),
1286
						$this->mappings->sync_sf_update => array(
1287
							'text' => __( 'Salesforce Update', 'object-sync-for-salesforce' ),
1288
							'id'   => 'sf_update',
1289
							'desc' => '',
1290
						),
1291
						$this->mappings->sync_sf_delete => array(
1292
							'text' => __( 'Salesforce Delete', 'object-sync-for-salesforce' ),
1293
							'id'   => 'sf_delete',
1294
							'desc' => '',
1295
						),
1296
					),
1297
				),
1298
			),
1299
		);
1300
		foreach ( $log_settings as $key => $attributes ) {
1301
			$id       = $this->option_prefix . $key;
1302
			$name     = $this->option_prefix . $key;
1303
			$title    = $attributes['title'];
1304
			$callback = $attributes['callback'];
1305
			$page     = $attributes['page'];
1306
			$section  = $attributes['section'];
1307
			$args     = array_merge(
1308
				$attributes['args'],
1309
				array(
1310
					'title'     => $title,
1311
					'id'        => $id,
1312
					'label_for' => $id,
1313
					'name'      => $name,
1314
				)
1315
			);
1316
			add_settings_field( $id, $title, $callback, $page, $section, $args );
1317
			register_setting( $page, $id );
1318
		}
1319
	}
1320
1321
	/**
1322
	 * Fields for the Mapping Errors tab
1323
	 * This runs add_settings_section once, as well as add_settings_field and register_setting methods for each option
1324
	 *
1325
	 * @param string $page what page we're on.
1326
	 * @param string $section what section of the page.
1327
	 * @param array  $callbacks method to call.
1328
	 */
1329
	private function fields_errors( $page, $section, $callbacks ) {
1330
1331
		add_settings_section( $section, __( 'Mapping Error Settings', 'object-sync-for-salesforce' ), null, $page );
1332
		$error_settings = array(
1333
			'errors_per_page' => array(
1334
				'title'    => __( 'Errors per page', 'object-sync-for-salesforce' ),
1335
				'callback' => $callbacks['text'],
1336
				'page'     => $page,
1337
				'section'  => $section,
1338
				'args'     => array(
1339
					'type'     => 'number',
1340
					'validate' => 'absint',
1341
					'default'  => 50,
1342
					'desc'     => __( 'Set how many mapping errors to show on a single page.', 'object-sync-for-salesforce' ),
1343
					'constant' => '',
1344
				),
1345
			),
1346
		);
1347
1348
		foreach ( $error_settings as $key => $attributes ) {
1349
			$id       = $this->option_prefix . $key;
1350
			$name     = $this->option_prefix . $key;
1351
			$title    = $attributes['title'];
1352
			$callback = $attributes['callback'];
1353
			$page     = $attributes['page'];
1354
			$section  = $attributes['section'];
1355
			$args     = array_merge(
1356
				$attributes['args'],
1357
				array(
1358
					'title'     => $title,
1359
					'id'        => $id,
1360
					'label_for' => $id,
1361
					'name'      => $name,
1362
				)
1363
			);
1364
			add_settings_field( $id, $title, $callback, $page, $section, $args );
1365
			register_setting( $page, $id );
1366
		} // End foreach() method.
1367
	}
1368
1369
	/**
1370
	 * Create the notices, settings, and conditions by which admin notices should appear
1371
	 */
1372
	public function notices() {
1373
1374
		// before a notice is displayed, we should make sure we are on a page related to this plugin.
1375
		if ( ! isset( $_GET['page'] ) || $this->admin_settings_url_param !== $_GET['page'] ) {
1376
			return;
1377
		}
1378
1379
		$get_data = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
1380
1381
		$notices = array(
1382
			'permission'              => array(
1383
				'condition'   => ( false === $this->check_wordpress_admin_permissions() ),
1384
				'message'     => __( "Your account does not have permission to edit the Object Sync for Salesforce plugin's settings.", 'object-sync-for-salesforce' ),
1385
				'type'        => 'error',
1386
				'dismissible' => false,
1387
			),
1388
			'not_secure'              => array(
1389
				'condition'   => ( false === $this->check_wordpress_ssl() && false === $this->check_wordpress_ssl_support() ),
1390
				'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' ),
1391
				'type'        => 'error',
1392
				'dismissible' => false,
1393
			),
1394
			'secure_supported'        => array(
1395
				'condition'   => ( false === $this->check_wordpress_ssl() && true === $this->check_wordpress_ssl_support() ),
1396
				'message'     => sprintf(
1397
					// translators: 1) is the site health URL, and 2) is the text for the site health page title.
1398
					__( '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' ),
1399
					esc_url( admin_url( '/wp-admin/site-health.php' ) ),
1400
					esc_html__( 'Site Health screen', 'object-sync-for-salesforce' )
1401
				),
1402
				'type'        => 'error',
1403
				'dismissible' => false,
1404
			),
1405
			'fieldmap'                => array(
1406
				'condition'   => isset( $get_data['transient'] ),
1407
				'message'     => esc_html__( 'Errors kept this fieldmap from being saved.', 'object-sync-for-salesforce' ),
1408
				'type'        => 'error',
1409
				'dismissible' => true,
1410
			),
1411
			'fieldmap_missing'        => array(
1412
				'condition'   => isset( $get_data['missing_fieldmap'] ),
1413
				'message'     => __( 'There is no fieldmap with the supplied ID. Instead, the list of all available fieldmaps is displayed.', 'object-sync-for-salesforce' ),
1414
				'type'        => 'error',
1415
				'dismissible' => true,
1416
			),
1417
			'object_map'              => array(
1418
				'condition'   => isset( $get_data['map_transient'] ),
1419
				'message'     => esc_html__( 'Errors kept this object map from being saved.', 'object-sync-for-salesforce' ),
1420
				'type'        => 'error',
1421
				'dismissible' => true,
1422
			),
1423
			'data_saved'              => array(
1424
				'condition'   => isset( $get_data['data_saved'] ) && 'true' === $get_data['data_saved'],
1425
				'message'     => esc_html__( 'This data was successfully saved.', 'object-sync-for-salesforce' ),
1426
				'type'        => 'success',
1427
				'dismissible' => true,
1428
			),
1429
			'data_save_partial'       => array(
1430
				'condition'   => isset( $get_data['data_saved'] ) && 'partial' === $get_data['data_saved'],
1431
				'message'     => __( 'This data was partially successfully saved. This means some of the data was unable to save. If you have enabled logging in the plugin settings, there should be a log entry with further details.', 'object-sync-for-salesforce' ),
1432
				'type'        => 'error',
1433
				'dismissible' => true,
1434
			),
1435
			'data_save_error'         => array(
1436
				'condition'   => isset( $get_data['data_saved'] ) && 'false' === $get_data['data_saved'],
1437
				'message'     => esc_html__( 'This data was not successfully saved. Try again.', 'object-sync-for-salesforce' ),
1438
				'type'        => 'error',
1439
				'dismissible' => true,
1440
			),
1441
			'mapping_error_transient' => array(
1442
				'condition'   => isset( $get_data['mapping_error_transient'] ),
1443
				'message'     => esc_html__( 'Errors kept these mapping errors from being deleted.', 'object-sync-for-salesforce' ),
1444
				'type'        => 'error',
1445
				'dismissible' => true,
1446
			),
1447
		);
1448
1449
		foreach ( $notices as $key => $value ) {
1450
1451
			$condition = (bool) $value['condition'];
1452
			$message   = $value['message'];
1453
1454
			if ( isset( $value['dismissible'] ) ) {
1455
				$dismissible = $value['dismissible'];
1456
			} else {
1457
				$dismissible = false;
1458
			}
1459
1460
			if ( isset( $value['type'] ) ) {
1461
				$type = $value['type'];
1462
			} else {
1463
				$type = '';
1464
			}
1465
1466
			if ( ! isset( $value['template'] ) ) {
1467
				$template = '';
1468
			}
1469
1470
			if ( $condition ) {
1471
				new Object_Sync_Sf_Admin_Notice( $condition, $message, $dismissible, $type, $template );
1472
			}
1473
		}
1474
1475
	}
1476
1477
	/**
1478
	 * Get all the Salesforce object settings for fieldmapping
1479
	 * This takes either the $_POST array via ajax, or can be directly called with a $data array
1480
	 *
1481
	 * @param array $data must contain a Salesforce object, can optionally contain a type.
1482
	 * @return array $object_settings
1483
	 */
1484
	public function get_salesforce_object_description( $data = array() ) {
1485
		$ajax = false;
1486
		if ( empty( $data ) ) {
1487
			$data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1488
			$ajax = true;
1489
		}
1490
1491
		$object_description = array();
1492
1493
		if ( ! empty( $data['salesforce_object'] ) ) {
1494
			$object = $this->salesforce['sfapi']->object_describe( esc_attr( $data['salesforce_object'] ) );
1495
1496
			$object_fields        = array();
1497
			$include_record_types = array();
1498
1499
			// these can come from ajax.
1500
			$include = isset( $data['include'] ) ? (array) $data['include'] : array();
1501
			$include = array_map( 'esc_attr', $include );
1502
1503
			if ( in_array( 'fields', $include, true ) || empty( $include ) ) {
1504
				$type = isset( $data['field_type'] ) ? esc_attr( $data['field_type'] ) : ''; // can come from ajax.
1505
1506
				// here, we should respect the decision of whether to show the API name or the label.
1507
				$display_value = get_option( $this->option_prefix . 'salesforce_field_display_value', 'field_label' );
1508
				if ( 'api_name' === $display_value ) {
1509
					$visible_label_field = 'name';
1510
				} else {
1511
					$visible_label_field = 'label';
1512
				}
1513
				$attributes = array( 'name', $visible_label_field );
1514
1515
				foreach ( $object['data']['fields'] as $key => $value ) {
1516
					if ( '' === $type || $type === $value['type'] ) {
1517
						$object_fields[ $key ] = $value;
1518
						if ( isset( $attributes ) ) {
1519
							$object_fields[ $key ] = array_intersect_key( $value, array_flip( $attributes ) );
1520
						}
1521
					}
1522
				}
1523
				$object_description['fields'] = $object_fields;
1524
			}
1525
1526
			if ( in_array( 'recordTypeInfos', $include, true ) ) {
1527
				if ( isset( $object['data']['recordTypeInfos'] ) && count( $object['data']['recordTypeInfos'] ) > 1 ) {
1528
					foreach ( $object['data']['recordTypeInfos'] as $type ) {
1529
						$object_record_types[ $type['recordTypeId'] ] = $type['name'];
1530
					}
1531
					$object_description['recordTypeInfos'] = $object_record_types;
1532
				}
1533
			}
1534
		}
1535
1536
		if ( true === $ajax ) {
1537
			wp_send_json_success( $object_description );
1538
		} else {
1539
			return $object_description;
1540
		}
1541
	}
1542
1543
	/**
1544
	 * Get all the Salesforce fields settings for fieldmapping
1545
	 * This takes either the $_POST array via ajax, or can be directly called with a $data array
1546
	 *
1547
	 * @param array $data must contain a Salesforce object unless it is Ajax, can optionally contain a type.
1548
	 * @return array $object_fields
1549
	 */
1550
	public function get_salesforce_object_fields( $data = array() ) {
1551
		$ajax      = false;
1552
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1553
		if ( empty( $data ) ) {
1554
			$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

1554
			$salesforce_object = isset( $post_data['salesforce_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['salesforce_object'] ) ) : '';
Loading history...
1555
			$ajax              = true;
1556
			// here, we should respect the decision of whether to show the API name or the label.
1557
			$display_value = get_option( $this->option_prefix . 'salesforce_field_display_value', 'field_label' );
1558
			if ( 'api_name' === $display_value ) {
1559
				$visible_label_field = 'name';
1560
			} else {
1561
				$visible_label_field = 'label';
1562
			}
1563
			$attributes = array( 'name', $visible_label_field );
1564
		} else {
1565
			$salesforce_object = isset( $data['salesforce_object'] ) ? sanitize_text_field( wp_unslash( $data['salesforce_object'] ) ) : '';
1566
		}
1567
		$object_fields = array();
1568
		if ( ! empty( $salesforce_object ) ) {
1569
			$object               = $this->salesforce['sfapi']->object_describe( esc_attr( $salesforce_object ) );
1570
			$object_fields        = array();
1571
			$type                 = isset( $data['type'] ) ? esc_attr( $data['type'] ) : '';
1572
			$include_record_types = isset( $data['include_record_types'] ) ? esc_attr( $data['include_record_types'] ) : false;
1573
			foreach ( $object['data']['fields'] as $key => $value ) {
1574
				if ( '' === $type || $type === $value['type'] ) {
1575
					$object_fields[ $key ] = $value;
1576
					if ( isset( $attributes ) ) {
1577
						$object_fields[ $key ] = array_intersect_key( $value, array_flip( $attributes ) );
1578
					}
1579
				}
1580
			}
1581
			if ( true === $include_record_types ) {
0 ignored issues
show
introduced by
The condition true === $include_record_types is always false.
Loading history...
1582
				$object_record_types = array();
1583
				if ( isset( $object['data']['recordTypeInfos'] ) && count( $object['data']['recordTypeInfos'] ) > 1 ) {
1584
					foreach ( $object['data']['recordTypeInfos'] as $type ) {
1585
						$object_record_types[ $type['recordTypeId'] ] = $type['name'];
1586
					}
1587
				}
1588
			}
1589
		}
1590
1591
		if ( true === $ajax ) {
1592
			$ajax_response = array(
1593
				'fields' => $object_fields,
1594
			);
1595
			wp_send_json_success( $ajax_response );
1596
		} else {
1597
			return $object_fields;
1598
		}
1599
1600
	}
1601
1602
	/**
1603
	 * Get WordPress object fields for fieldmapping
1604
	 * This takes either the $_POST array via ajax, or can be directly called with a $wordpress_object field
1605
	 *
1606
	 * @param string $wordpress_object is the name of the WordPress object.
1607
	 * @return array $object_fields
1608
	 */
1609
	public function get_wordpress_object_fields( $wordpress_object = '' ) {
1610
		$ajax      = false;
1611
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1612
		if ( empty( $wordpress_object ) ) {
1613
			$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

1613
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1614
			$ajax             = true;
1615
		}
1616
1617
		$object_fields = $this->wordpress->get_wordpress_object_fields( $wordpress_object );
1618
1619
		if ( true === $ajax ) {
1620
			$ajax_response = array(
1621
				'fields' => $object_fields,
1622
			);
1623
			wp_send_json_success( $ajax_response );
1624
		} else {
1625
			return $object_fields;
1626
		}
1627
	}
1628
1629
	/**
1630
	 * Manually push the WordPress object to Salesforce
1631
	 * This takes either the $_POST array via ajax, or can be directly called with $wordpress_object and $wordpress_id fields
1632
	 *
1633
	 * @param string $wordpress_object is the name of the WordPress object.
1634
	 * @param int    $wordpress_id is the ID of the WordPress record.
1635
	 * @param bool   $force_return Force the method to return json instead of outputting it.
1636
	 */
1637
	public function push_to_salesforce( $wordpress_object = '', $wordpress_id = '', $force_return = false ) {
1638
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1639
		if ( empty( $wordpress_object ) && empty( $wordpress_id ) ) {
1640
			$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

1640
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1641
			$wordpress_id     = isset( $post_data['wordpress_id'] ) ? absint( $post_data['wordpress_id'] ) : '';
1642
		}
1643
1644
		// clarify what that variable is in this context.
1645
		$object_type = $wordpress_object;
1646
1647
		// When objects are already mapped, there is a Salesforce id as well. Otherwise, it's blank.
1648
		$salesforce_id = isset( $post_data['salesforce_id'] ) ? sanitize_text_field( $post_data['salesforce_id'] ) : '';
1649
		if ( '' === $salesforce_id ) {
1650
			$method = 'POST';
1651
		} else {
1652
			$method = 'PUT';
1653
		}
1654
1655
		$result = $this->push->manual_push( $object_type, $wordpress_id, $method );
1656
1657
		if ( false === $force_return && ! empty( $post_data['wordpress_object'] ) && ! empty( $post_data['wordpress_id'] ) ) {
1658
			wp_send_json_success( $result );
1659
		} else {
1660
			return $result;
1661
		}
1662
1663
	}
1664
1665
	/**
1666
	 * Manually pull the Salesforce object into WordPress
1667
	 * This takes either the $_POST array via ajax, or can be directly called with $salesforce_id fields
1668
	 *
1669
	 * @param string $salesforce_id is the ID of the Salesforce record.
1670
	 * @param string $wordpress_object is the name of the WordPress object.
1671
	 */
1672
	public function pull_from_salesforce( $salesforce_id = '', $wordpress_object = '' ) {
1673
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1674
		if ( empty( $wordpress_object ) && empty( $salesforce_id ) ) {
1675
			$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

1675
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1676
			$salesforce_id    = isset( $post_data['salesforce_id'] ) ? sanitize_text_field( wp_unslash( $post_data['salesforce_id'] ) ) : '';
1677
		}
1678
		$type   = $this->salesforce['sfapi']->get_sobject_type( $salesforce_id );
1679
		$result = $this->pull->manual_pull( $type, $salesforce_id, $wordpress_object ); // we want the wp object to make sure we get the right fieldmap.
1680
		if ( ! empty( $post_data ) ) {
1681
			wp_send_json_success( $result );
1682
		} else {
1683
			return $result;
1684
		}
1685
	}
1686
1687
	/**
1688
	 * Manually pull the Salesforce object into WordPress
1689
	 * This takes an id for a mapping object row
1690
	 *
1691
	 * @param int $mapping_id is the ID of the mapping object record.
1692
	 */
1693
	public function refresh_mapped_data( $mapping_id = '' ) {
1694
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1695
		if ( empty( $mapping_id ) ) {
1696
			$mapping_id = isset( $post_data['mapping_id'] ) ? absint( $post_data['mapping_id'] ) : '';
1697
		}
1698
		$result = $this->mappings->get_all_object_maps(
1699
			array(
1700
				'id' => $mapping_id,
1701
			)
1702
		);
1703
1704
		$object_map = array();
1705
1706
		// result is an array of arrays, not just one array.
1707
		if ( 1 === count( $result ) ) {
1708
			$object_map = $result[0];
1709
		}
1710
1711
		if ( ! empty( $post_data ) ) {
1712
			wp_send_json_success( $object_map );
1713
		} else {
1714
			return $object_map;
1715
		}
1716
	}
1717
1718
	/**
1719
	 * Prepare fieldmap data and redirect after processing
1720
	 * This runs when the create or update forms are submitted
1721
	 * It is public because it depends on an admin hook
1722
	 * It then calls the Object_Sync_Sf_Mapping class and sends prepared data over to it, then redirects to the correct page
1723
	 * This method does include error handling, by loading the submission in a transient if there is an error, and then deleting it upon success
1724
	 */
1725
	public function prepare_fieldmap_data() {
1726
		$error     = false;
1727
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1728
		$cachekey  = wp_json_encode( $post_data );
1729
		if ( false !== $cachekey ) {
1730
			$cachekey = md5( $cachekey );
1731
		}
1732
1733
		if ( ! isset( $post_data['label'] ) || ! isset( $post_data['salesforce_object'] ) || ! isset( $post_data['wordpress_object'] ) ) {
1734
			$error = true;
1735
		}
1736
		if ( true === $error ) {
1737
			$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1738
			if ( '' !== $cachekey ) {
1739
				$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

1739
				$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&transient=' . /** @scrutinizer ignore-type */ $cachekey;
Loading history...
1740
			}
1741
		} else { // there are no errors
1742
			// send the row to the fieldmap class
1743
			// if it is add or clone, use the create method.
1744
			$method            = esc_attr( $post_data['method'] );
1745
			$salesforce_fields = $this->get_salesforce_object_fields(
1746
				array(
1747
					'salesforce_object' => $post_data['salesforce_object'],
1748
				)
1749
			);
1750
			$wordpress_fields  = $this->get_wordpress_object_fields( $post_data['wordpress_object'] );
1751
			if ( 'add' === $method || 'clone' === $method ) {
1752
				$result = $this->mappings->create_fieldmap( $post_data, $wordpress_fields, $salesforce_fields );
1753
			} elseif ( 'edit' === $method ) { // if it is edit, use the update method.
1754
				$id     = esc_attr( $post_data['id'] );
1755
				$result = $this->mappings->update_fieldmap( $post_data, $wordpress_fields, $salesforce_fields, $id );
1756
			}
1757
			if ( false === $result ) { // if the database didn't save, it's still an error.
1758
				$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1759
				if ( '' !== $cachekey ) {
1760
					$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&transient=' . $cachekey;
1761
				}
1762
			} else {
1763
				// if the user has saved a fieldmap, clear the currently running query value if there is one.
1764
				if ( '' !== get_option( $this->option_prefix . 'currently_pulling_query_' . $post_data['salesforce_object'], '' ) ) {
1765
					$this->pull->clear_current_type_query( $post_data['salesforce_object'] );
1766
				}
1767
				if ( isset( $post_data['transient'] ) ) { // there was previously an error saved. can delete it now.
1768
					$this->sfwp_transients->delete( esc_attr( $post_data['map_transient'] ) );
1769
				}
1770
				// then send the user to the list of fieldmaps.
1771
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1772
			}
1773
		}
1774
		wp_safe_redirect( $url );
1775
		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...
1776
	}
1777
1778
	/**
1779
	 * Delete fieldmap data and redirect after processing
1780
	 * This runs when the delete link is clicked, after the user confirms
1781
	 * It is public because it depends on an admin hook
1782
	 * It then calls the Object_Sync_Sf_Mapping class and the delete method
1783
	 */
1784
	public function delete_fieldmap() {
1785
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1786
		if ( $post_data['id'] ) {
1787
			$result = $this->mappings->delete_fieldmap( $post_data['id'] );
1788
			if ( true === $result ) {
1789
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1790
			} else {
1791
				$url = esc_url_raw( $post_data['redirect_url_error'] . '&id=' . $post_data['id'] );
1792
			}
1793
			wp_safe_redirect( $url );
1794
			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...
1795
		}
1796
	}
1797
1798
	/**
1799
	 * Prepare object data and redirect after processing
1800
	 * This runs when the update form is submitted
1801
	 * It is public because it depends on an admin hook
1802
	 * It then calls the Object_Sync_Sf_Mapping class and sends prepared data over to it, then redirects to the correct page
1803
	 * This method does include error handling, by loading the submission in a transient if there is an error, and then deleting it upon success
1804
	 */
1805
	public function prepare_object_map_data() {
1806
		$error     = false;
1807
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1808
		$cachekey  = wp_json_encode( $post_data );
1809
		if ( false !== $cachekey ) {
1810
			$cachekey = md5( $cachekey );
1811
		}
1812
1813
		if ( ! isset( $post_data['wordpress_id'] ) || ! isset( $post_data['salesforce_id'] ) ) {
1814
			$error = true;
1815
		}
1816
		if ( true === $error ) {
1817
			$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1818
			if ( '' !== $cachekey ) {
1819
				$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

1819
				$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&map_transient=' . /** @scrutinizer ignore-type */ $cachekey;
Loading history...
1820
			}
1821
		} else { // there are no errors
1822
			// send the row to the object map class.
1823
			$method = esc_attr( $post_data['method'] );
1824
			if ( 'edit' === $method ) { // if it is edit, use the update method.
1825
				$id     = esc_attr( $post_data['id'] );
1826
				$result = $this->mappings->update_object_map( $post_data, $id );
1827
			}
1828
			if ( false === $result ) { // if the database didn't save, it's still an error.
1829
				$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1830
				if ( '' !== $cachekey ) {
1831
					$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&map_transient=' . $cachekey;
1832
				}
1833
			} else {
1834
				if ( isset( $post_data['map_transient'] ) ) { // there was previously an error saved. can delete it now.
1835
					$this->sfwp_transients->delete( esc_attr( $post_data['map_transient'] ) );
1836
				}
1837
				// then send the user to the success redirect url.
1838
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1839
			}
1840
		}
1841
		wp_safe_redirect( $url );
1842
		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...
1843
	}
1844
1845
	/**
1846
	 * Delete object map data and redirect after processing
1847
	 * This runs when the delete link is clicked on an error row, after the user confirms
1848
	 * It is public because it depends on an admin hook
1849
	 * It then calls the Object_Sync_Sf_Mapping class and the delete method
1850
	 */
1851
	public function delete_object_map() {
1852
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1853
		if ( isset( $post_data['id'] ) ) {
1854
			$result = $this->mappings->delete_object_map( $post_data['id'] );
1855
			if ( true === $result ) {
1856
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1857
			} else {
1858
				$url = esc_url_raw( $post_data['redirect_url_error'] . '&id=' . $post_data['id'] );
1859
			}
1860
			wp_safe_redirect( $url );
1861
			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...
1862
		} elseif ( $post_data['delete'] ) {
1863
			$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1864
			$cachekey  = wp_json_encode( $post_data );
1865
			if ( false !== $cachekey ) {
1866
				$cachekey = md5( $cachekey );
1867
			}
1868
			$error = false;
1869
			if ( ! isset( $post_data['delete'] ) ) {
1870
				$error = true;
1871
			}
1872
			if ( true === $error ) {
1873
				$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1874
				if ( '' !== $cachekey ) {
1875
					$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

1875
					$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&mapping_error_transient=' . /** @scrutinizer ignore-type */ $cachekey;
Loading history...
1876
				}
1877
			} else { // there are no errors.
1878
				$result = $this->mappings->delete_object_map( array_keys( $post_data['delete'] ) );
1879
				if ( true === $result ) {
1880
					$url = esc_url_raw( $post_data['redirect_url_success'] );
1881
				}
1882
1883
				if ( false === $result ) { // if the database didn't save, it's still an error.
1884
					$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1885
					if ( '' !== $cachekey ) {
1886
						$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&mapping_error_transient=' . $cachekey;
1887
					}
1888
				} else {
1889
					if ( isset( $post_data['mapping_error_transient'] ) ) { // there was previously an error saved. can delete it now.
1890
						$this->sfwp_transients->delete( esc_attr( $post_data['mapping_error_transient'] ) );
1891
					}
1892
					// then send the user to the list of fieldmaps.
1893
					$url = esc_url_raw( $post_data['redirect_url_success'] );
1894
				}
1895
			}
1896
			wp_safe_redirect( $url );
1897
			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...
1898
		}
1899
	}
1900
1901
	/**
1902
	 * Import a json file and use it for plugin data
1903
	 */
1904
	public function import_json_file() {
1905
1906
		if ( ! wp_verify_nonce( $_POST['object_sync_for_salesforce_nonce_import'], 'object_sync_for_salesforce_nonce_import' ) ) {
1907
			return;
1908
		}
1909
		if ( ! current_user_can( 'manage_options' ) ) {
1910
			return;
1911
		}
1912
		$path      = $_FILES['import_file']['name'];
1913
		$extension = pathinfo( $path, PATHINFO_EXTENSION );
1914
		if ( 'json' !== $extension ) {
1915
			wp_die( esc_html__( 'Please upload a valid .json file', 'object-sync-for-salesforce' ) );
1916
		}
1917
1918
		$import_file = $_FILES['import_file']['tmp_name'];
1919
		if ( empty( $import_file ) ) {
1920
			wp_die( esc_html__( 'Please upload a file to import', 'object-sync-for-salesforce' ) );
1921
		}
1922
1923
		// Retrieve the data from the file and convert the json object to an array.
1924
		$data = (array) json_decode( file_get_contents( $import_file ), true ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
1925
1926
		$overwrite = isset( $_POST['overwrite'] ) ? esc_attr( $_POST['overwrite'] ) : '';
1927
		if ( true === filter_var( $overwrite, FILTER_VALIDATE_BOOLEAN ) ) {
1928
			if ( isset( $data['fieldmaps'] ) ) {
1929
				$fieldmaps = $this->mappings->get_fieldmaps();
1930
				foreach ( $fieldmaps as $fieldmap ) {
1931
					$id     = $fieldmap['id'];
1932
					$delete = $this->mappings->delete_fieldmap( $id );
1933
				}
1934
			}
1935
			if ( isset( $data['object_maps'] ) ) {
1936
				$object_maps = $this->mappings->get_all_object_maps();
1937
				foreach ( $object_maps as $object_map ) {
1938
					$id     = $object_map['id'];
1939
					$delete = $this->mappings->delete_object_map( $id );
1940
				}
1941
			}
1942
			if ( isset( $data['plugin_settings'] ) ) {
1943
				foreach ( $data['plugin_settings'] as $key => $value ) {
1944
					delete_option( $value['option_name'] );
1945
				}
1946
			}
1947
		}
1948
1949
		$success = true;
1950
1951
		if ( isset( $data['fieldmaps'] ) ) {
1952
			$successful_fieldmaps = array();
1953
			$error_fieldmaps      = array();
1954
			foreach ( $data['fieldmaps'] as $fieldmap ) {
1955
				unset( $fieldmap['id'] );
1956
				$create = $this->mappings->create_fieldmap( $fieldmap );
1957
				if ( false === $create ) {
1958
					$success = false;
1959
				}
1960
				if ( false === $create ) {
1961
					$error_fieldmaps[] = $object_map;
1962
				} else {
1963
					$successful_fieldmaps[] = $create;
1964
				}
1965
			}
1966
		}
1967
1968
		if ( isset( $data['object_maps'] ) ) {
1969
			$successful_object_maps = array();
1970
			$error_object_maps      = array();
1971
			foreach ( $data['object_maps'] as $object_map ) {
1972
				unset( $object_map['id'] );
1973
				if ( isset( $object_map['object_type'] ) ) {
1974
					$sf_sync_trigger = $this->mappings->sync_sf_create;
1975
					$create          = $this->pull->salesforce_pull_process_records( $object_map['object_type'], $object_map['salesforce_id'], $sf_sync_trigger );
1976
				} else {
1977
					$create = $this->mappings->create_object_map( $object_map );
1978
				}
1979
				if ( false === $create ) {
1980
					$error_object_maps[] = $object_map;
1981
				} else {
1982
					$successful_object_maps[] = $create;
1983
				}
1984
			}
1985
		}
1986
1987
		if ( isset( $data['plugin_settings'] ) ) {
1988
			foreach ( $data['plugin_settings'] as $key => $value ) {
1989
				update_option( $value['option_name'], maybe_unserialize( $value['option_value'] ), $value['autoload'] );
1990
			}
1991
		}
1992
1993
		if ( ! empty( $error_fieldmaps ) && ! empty( $error_object_maps ) ) {
1994
			$status = 'error';
1995
			if ( isset( $this->logging ) ) {
1996
				$logging = $this->logging;
1997
			} elseif ( class_exists( 'Object_Sync_Sf_Logging' ) ) {
1998
				$logging = new Object_Sync_Sf_Logging( $this->wpdb, $this->version );
0 ignored issues
show
Unused Code introduced by
The call to Object_Sync_Sf_Logging::__construct() has too many arguments starting with $this->wpdb. ( Ignorable by Annotation )

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

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

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

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

Loading history...
1999
			}
2000
2001
			$body = sprintf( esc_html__( 'These are the import items that were not able to save: ', 'object-sync-for-salesforce' ) . '<ul>' );
2002
			foreach ( $error_fieldmaps as $fieldmap ) {
2003
				$body .= sprintf(
2004
					// translators: placeholders are: 1) the fieldmap row ID, 2) the Salesforce object type, 3) the WordPress object type.
2005
					'<li>' . esc_html__( 'Fieldmap id (if it exists): %1$s. Salesforce object type: %2$s. WordPress object type: %3$s', 'object-sync-for-salesforce' ) . '</li>',
2006
					isset( $fieldmap['id'] ) ? absint( $fieldmap['id'] ) : '',
2007
					esc_attr( $fieldmap['salesforce_object'] ),
2008
					esc_attr( $fieldmap['wordpress_object'] )
2009
				);
2010
			}
2011
			foreach ( $error_object_maps as $mapping_object ) {
2012
				$body .= sprintf(
2013
					// translators: placeholders are: 1) the mapping object row ID, 2) the ID of the Salesforce object, 3) the WordPress object type.
2014
					'<li>' . esc_html__( 'Mapping object id (if it exists): %1$s. Salesforce Id: %2$s. WordPress object type: %3$s', 'object-sync-for-salesforce' ) . '</li>',
2015
					isset( $mapping_object['id'] ) ? absint( $mapping_object['id'] ) : '',
2016
					esc_attr( $mapping_object['salesforce_id'] ),
2017
					esc_attr( $mapping_object['wordpress_object'] )
2018
				);
2019
			}
2020
			$body .= sprintf( '</ul>' );
2021
2022
			$logging->setup(
2023
				sprintf(
2024
					// translators: %1$s is the log status.
2025
					esc_html__( '%1$s on import: some of the rows were unable to save. Read this post for details.', 'object-sync-for-salesforce' ),
2026
					ucfirst( esc_attr( $status ) )
2027
				),
2028
				$body,
2029
				0,
2030
				0,
2031
				$status
2032
			);
2033
		}
2034
2035
		if ( empty( $error_fieldmaps ) && empty( $error_object_maps ) && ( ! empty( $successful_fieldmaps ) || ! empty( $successful_object_maps ) ) ) {
2036
			$this->clear_cache( false );
2037
			wp_safe_redirect( get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=import-export&data_saved=true' ) );
2038
			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...
2039
		} elseif ( ! empty( $error_fieldmaps ) && ! empty( $successful_fieldmaps ) ) {
2040
			$this->clear_cache( false );
2041
			wp_safe_redirect( get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=import-export&data_saved=partial' ) );
2042
		} elseif ( ! empty( $error_object_maps ) && ! empty( $successful_object_maps ) ) {
2043
			$this->clear_cache( false );
2044
			wp_safe_redirect( get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=import-export&data_saved=partial' ) );
2045
		} else {
2046
			wp_safe_redirect( get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=import-export&data_saved=false' ) );
2047
			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...
2048
		}
2049
2050
	}
2051
2052
	/**
2053
	 * Create a json file for exporting
2054
	 */
2055
	public function export_json_file() {
2056
2057
		if ( ! wp_verify_nonce( $_POST['object_sync_for_salesforce_nonce_export'], 'object_sync_for_salesforce_nonce_export' ) ) {
2058
			return;
2059
		}
2060
		if ( ! current_user_can( 'manage_options' ) ) {
2061
			return;
2062
		}
2063
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
2064
		$export    = array();
2065
		if ( in_array( 'fieldmaps', $post_data['export'], true ) ) {
2066
			$export['fieldmaps'] = $this->mappings->get_fieldmaps();
2067
		}
2068
		if ( in_array( 'object_maps', $post_data['export'], true ) ) {
2069
			$export['object_maps'] = $this->mappings->get_all_object_maps();
2070
		}
2071
		if ( in_array( 'plugin_settings', $post_data['export'], true ) ) {
2072
			$wpdb                      = $this->wpdb;
2073
			$export_results            = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$wpdb->base_prefix}options` WHERE option_name LIKE %s;", $wpdb->esc_like( $this->option_prefix ) . '%' ), ARRAY_A );
2074
			$export['plugin_settings'] = $export_results;
2075
		}
2076
		nocache_headers();
2077
		header( 'Content-Type: application/json; charset=utf-8' );
2078
		header( 'Content-Disposition: attachment; filename=object-sync-for-salesforce-data-export-' . gmdate( 'm-d-Y' ) . '.json' );
2079
		header( 'Expires: 0' );
2080
		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

2080
		echo /** @scrutinizer ignore-type */ wp_json_encode( $export );
Loading history...
2081
		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...
2082
	}
2083
2084
	/**
2085
	 * Default display for <input> fields
2086
	 *
2087
	 * @param array $args is the arguments to create the field.
2088
	 */
2089
	public function display_input_field( $args ) {
2090
		$type    = $args['type'];
2091
		$id      = $args['label_for'];
2092
		$name    = $args['name'];
2093
		$desc    = $args['desc'];
2094
		$checked = '';
2095
2096
		$class = 'regular-text';
2097
2098
		if ( 'checkbox' === $type ) {
2099
			$class = 'checkbox';
2100
		}
2101
2102
		if ( isset( $args['class'] ) ) {
2103
			$class = $args['class'];
2104
		}
2105
2106
		if ( ! isset( $args['constant'] ) || ! defined( $args['constant'] ) ) {
2107
			$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

2107
			$value = esc_attr( /** @scrutinizer ignore-type */ get_option( $id, '' ) );
Loading history...
2108
			if ( 'checkbox' === $type ) {
2109
				$value = filter_var( get_option( $id, false ), FILTER_VALIDATE_BOOLEAN );
2110
				if ( true === $value ) {
2111
					$checked = 'checked ';
2112
				}
2113
				$value = 1;
2114
			}
2115
			if ( '' === $value && isset( $args['default'] ) && '' !== $args['default'] ) {
2116
				$value = $args['default'];
2117
			}
2118
2119
			echo sprintf(
2120
				'<input type="%1$s" value="%2$s" name="%3$s" id="%4$s" class="%5$s"%6$s>',
2121
				esc_attr( $type ),
2122
				esc_attr( $value ),
2123
				esc_attr( $name ),
2124
				esc_attr( $id ),
2125
				sanitize_html_class( $class . esc_html( ' code' ) ),
2126
				esc_html( $checked )
2127
			);
2128
			if ( '' !== $desc ) {
2129
				echo sprintf(
2130
					'<p class="description">%1$s</p>',
2131
					esc_html( $desc )
2132
				);
2133
			}
2134
		} else {
2135
			echo sprintf(
2136
				'<p><code>%1$s</code></p>',
2137
				esc_html__( 'Defined in wp-config.php', 'object-sync-for-salesforce' )
2138
			);
2139
		}
2140
	}
2141
2142
	/**
2143
	 * Display for multiple checkboxes
2144
	 * Above method can handle a single checkbox as it is
2145
	 *
2146
	 * @param array $args is the arguments to create the checkboxes.
2147
	 */
2148
	public function display_checkboxes( $args ) {
2149
		$type    = 'checkbox';
2150
		$name    = $args['name'];
2151
		$options = get_option( $name, array() );
2152
		foreach ( $args['items'] as $key => $value ) {
2153
			$text    = $value['text'];
2154
			$id      = $value['id'];
2155
			$desc    = $value['desc'];
2156
			$checked = '';
2157
			if ( is_array( $options ) && in_array( (string) $key, $options, true ) ) {
2158
				$checked = 'checked';
2159
			} elseif ( is_array( $options ) && empty( $options ) ) {
2160
				if ( isset( $value['default'] ) && true === $value['default'] ) {
2161
					$checked = 'checked';
2162
				}
2163
			}
2164
			echo sprintf(
2165
				'<div class="checkbox"><label><input type="%1$s" value="%2$s" name="%3$s[]" id="%4$s"%5$s>%6$s</label></div>',
2166
				esc_attr( $type ),
2167
				esc_attr( $key ),
2168
				esc_attr( $name ),
2169
				esc_attr( $id ),
2170
				esc_html( $checked ),
2171
				esc_html( $text )
2172
			);
2173
			if ( '' !== $desc ) {
2174
				echo sprintf(
2175
					'<p class="description">%1$s</p>',
2176
					esc_html( $desc )
2177
				);
2178
			}
2179
		}
2180
	}
2181
2182
	/**
2183
	 * Display for a dropdown
2184
	 *
2185
	 * @param array $args is the arguments needed to create the dropdown.
2186
	 */
2187
	public function display_select( $args ) {
2188
		$type = $args['type'];
2189
		$id   = $args['label_for'];
2190
		$name = $args['name'];
2191
		$desc = $args['desc'];
2192
		if ( ! isset( $args['constant'] ) || ! defined( $args['constant'] ) ) {
2193
			$current_value = get_option( $name );
2194
2195
			echo sprintf(
2196
				'<div class="select"><select id="%1$s" name="%2$s"><option value="">- ' . esc_html__( 'Select one', 'object-sync-for-salesforce' ) . ' -</option>',
2197
				esc_attr( $id ),
2198
				esc_attr( $name )
2199
			);
2200
2201
			foreach ( $args['items'] as $key => $value ) {
2202
				$text     = $value['text'];
2203
				$value    = $value['value'];
2204
				$selected = '';
2205
				if ( $key === $current_value || $value === $current_value ) {
2206
					$selected = ' selected';
2207
				}
2208
2209
				echo sprintf(
2210
					'<option value="%1$s"%2$s>%3$s</option>',
2211
					esc_attr( $value ),
2212
					esc_attr( $selected ),
2213
					esc_html( $text )
2214
				);
2215
2216
			}
2217
			echo '</select>';
2218
			if ( '' !== $desc ) {
2219
				echo sprintf(
2220
					'<p class="description">%1$s</p>',
2221
					esc_html( $desc )
2222
				);
2223
			}
2224
			echo '</div>';
2225
		} else {
2226
			echo sprintf(
2227
				'<p><code>%1$s</code></p>',
2228
				esc_html__( 'Defined in wp-config.php', 'object-sync-for-salesforce' )
2229
			);
2230
		}
2231
	}
2232
2233
	/**
2234
	 * Dropdown formatted list of Salesforce API versions
2235
	 *
2236
	 * @return array $args is the array of API versions for the dropdown.
2237
	 */
2238
	private function version_options() {
2239
		$args = array();
2240
		if ( defined( 'OBJECT_SYNC_SF_SALESFORCE_API_VERSION' ) || ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== $this->admin_settings_url_param ) {
2241
			return $args;
2242
		}
2243
		$versions = $this->salesforce['sfapi']->get_api_versions();
2244
		foreach ( $versions['data'] as $key => $value ) {
2245
			$args[] = array(
2246
				'value' => $value['version'],
2247
				'text'  => $value['label'] . ' (' . $value['version'] . ')',
2248
			);
2249
		}
2250
		return $args;
2251
	}
2252
2253
	/**
2254
	 * Default display for <a href> links
2255
	 *
2256
	 * @param array $args is the arguments to make the link.
2257
	 */
2258
	public function display_link( $args ) {
2259
		$label = $args['label'];
2260
		$desc  = $args['desc'];
2261
		$url   = $args['url'];
2262
		if ( isset( $args['link_class'] ) ) {
2263
			echo sprintf(
2264
				'<p><a class="%1$s" href="%2$s">%3$s</a></p>',
2265
				esc_attr( $args['link_class'] ),
2266
				esc_url( $url ),
2267
				esc_html( $label )
2268
			);
2269
		} else {
2270
			echo sprintf(
2271
				'<p><a href="%1$s">%2$s</a></p>',
2272
				esc_url( $url ),
2273
				esc_html( $label )
2274
			);
2275
		}
2276
2277
		if ( '' !== $desc ) {
2278
			echo sprintf(
2279
				'<p class="description">%1$s</p>',
2280
				esc_html( $desc )
2281
			);
2282
		}
2283
2284
	}
2285
2286
	/**
2287
	 * Allow for a standard sanitize/validate method. We could use more specific ones if need be, but this one provides a baseline.
2288
	 *
2289
	 * @param string $option is the option value.
2290
	 * @return string $option is the sanitized option value.
2291
	 */
2292
	public function sanitize_validate_text( $option ) {
2293
		if ( is_array( $option ) ) {
0 ignored issues
show
introduced by
The condition is_array($option) is always false.
Loading history...
2294
			$options = array();
2295
			foreach ( $option as $key => $value ) {
2296
				$options[ $key ] = sanitize_text_field( $value );
2297
			}
2298
			return $options;
2299
		}
2300
		$option = sanitize_text_field( $option );
2301
		return $option;
2302
	}
2303
2304
	/**
2305
	 * Run a demo of Salesforce API call on the authenticate tab after WordPress has authenticated with it
2306
	 *
2307
	 * @param object $sfapi this is the Salesforce API object.
2308
	 */
2309
	private function status( $sfapi ) {
2310
2311
		$versions = $sfapi->get_api_versions();
2312
2313
		// format this array into text so users can see the versions.
2314
		if ( true === $versions['cached'] ) {
2315
			$versions_is_cached = esc_html__( 'This list is cached, and', 'object-sync-for-salesforce' );
2316
		} else {
2317
			$versions_is_cached = esc_html__( 'This list is not cached, but', 'object-sync-for-salesforce' );
2318
		}
2319
2320
		if ( true === $versions['from_cache'] ) {
2321
			$versions_from_cache = esc_html__( 'items were loaded from the cache', 'object-sync-for-salesforce' );
2322
		} else {
2323
			$versions_from_cache = esc_html__( 'items were not loaded from the cache', 'object-sync-for-salesforce' );
2324
		}
2325
2326
		$versions_apicall_summary = sprintf(
2327
			// 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.
2328
			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' ),
2329
			$versions_is_cached,
2330
			$versions_from_cache
2331
		);
2332
2333
		$contacts = $sfapi->query( 'SELECT Name, Id from Contact LIMIT 100' );
2334
2335
		// format this array into html so users can see the contacts.
2336
		if ( true === $contacts['cached'] ) {
2337
			$contacts_is_cached = esc_html__( 'They are cached, and', 'object-sync-for-salesforce' );
2338
		} else {
2339
			$contacts_is_cached = esc_html__( 'They are not cached, but', 'object-sync-for-salesforce' );
2340
		}
2341
2342
		if ( true === $contacts['from_cache'] ) {
2343
			$contacts_from_cache = esc_html__( 'they were loaded from the cache', 'object-sync-for-salesforce' );
2344
		} else {
2345
			$contacts_from_cache = esc_html__( 'they were not loaded from the cache', 'object-sync-for-salesforce' );
2346
		}
2347
2348
		if ( true === $contacts['is_redo'] ) {
2349
			$contacts_refreshed_token = esc_html__( 'This request did require refreshing the Salesforce token', 'object-sync-for-salesforce' );
2350
		} else {
2351
			$contacts_refreshed_token = esc_html__( 'This request did not require refreshing the Salesforce token', 'object-sync-for-salesforce' );
2352
		}
2353
2354
		// display contact summary if there are any contacts.
2355
		if ( 0 < absint( $contacts['data']['totalSize'] ) ) {
2356
			$contacts_apicall_summary = sprintf(
2357
				// 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.
2358
				esc_html__( 'Salesforce successfully returned %1$s %2$s records. %3$s %4$s. %5$s.', 'object-sync-for-salesforce' ),
2359
				absint( $contacts['data']['totalSize'] ),
2360
				esc_html( $contacts['data']['records'][0]['attributes']['type'] ),
2361
				$contacts_is_cached,
2362
				$contacts_from_cache,
2363
				$contacts_refreshed_token
2364
			);
2365
		} else {
2366
			$contacts_apicall_summary = '';
2367
		}
2368
2369
		require_once plugin_dir_path( $this->file ) . '/templates/admin/status.php';
2370
2371
	}
2372
2373
	/**
2374
	 * Deauthorize WordPress from Salesforce.
2375
	 * This deletes the tokens from the database; it does not currently do anything in Salesforce
2376
	 * For this plugin at this time, that is the decision we are making: don't do any kind of authorization stuff inside Salesforce
2377
	 */
2378
	private function logout() {
2379
		$delete_access_token = delete_option( $this->option_prefix . 'access_token' );
2380
		if ( true === $delete_access_token ) {
2381
			$this->access_token = '';
2382
		}
2383
		$delete_instance_url = delete_option( $this->option_prefix . 'instance_url' );
2384
		if ( true === $delete_instance_url ) {
2385
			$this->instance_url = '';
2386
		}
2387
		$delete_refresh_token = delete_option( $this->option_prefix . 'refresh_token' );
2388
		if ( true === $delete_refresh_token ) {
2389
			$this->refresh_token = '';
2390
		}
2391
		echo sprintf(
2392
			'<p>You have been logged out. You can use the <a href="%1$s">%2$s</a> tab to log in again.</p>',
2393
			esc_url( get_admin_url( null, 'options-general.php?page' . $this->admin_settings_url_param . '&tab=authorize' ) ),
2394
			esc_html__( 'Authorize', 'object-sync-for-salesforce' )
2395
		);
2396
	}
2397
2398
	/**
2399
	 * Ajax call to clear the plugin cache.
2400
	 */
2401
	public function clear_sfwp_cache() {
2402
		$result   = $this->clear_cache( true );
2403
		$response = array(
2404
			'message' => $result['message'],
2405
			'success' => $result['success'],
2406
		);
2407
		wp_send_json_success( $response );
2408
	}
2409
2410
	/**
2411
	 * Clear the plugin's cache.
2412
	 * This uses the flush method contained in the WordPress cache to clear all of this plugin's cached data.
2413
	 *
2414
	 * @param bool $ajax Whether this is an Ajax request or not.
2415
	 * @return array
2416
	 */
2417
	private function clear_cache( $ajax = false ) {
2418
		$result  = $this->wordpress->sfwp_transients->flush();
2419
		$success = $result['success'];
2420
		if ( 0 < $result['count'] ) {
2421
			if ( true === $success ) {
2422
				$message = __( 'The plugin cache has been cleared.', 'object-sync-for-salesforce' );
2423
			} else {
2424
				$message = __( 'There was an error clearing the plugin cache. Try refreshing this page.', 'object-sync-for-salesforce' );
2425
			}
2426
		} else {
2427
			$success = true;
2428
			$message = __( 'The cache was not cleared because it is empty. You can try again later.', 'object-sync-for-salesforce' );
2429
		}
2430
		if ( false === $ajax ) {
2431
			echo '<p>' . esc_html( $message ) . '</p>';
2432
		} else {
2433
			return array(
2434
				'message' => esc_html( $message ),
2435
				'success' => $success,
2436
			);
2437
		}
2438
	}
2439
2440
	/**
2441
	 * Check WordPress Admin permissions
2442
	 * Check if the current user is allowed to access the Salesforce plugin options
2443
	 */
2444
	private function check_wordpress_admin_permissions() {
2445
2446
		// one programmatic way to give this capability to additional user roles is the
2447
		// object_sync_for_salesforce_roles_configure_salesforce hook
2448
		// it runs on activation of this plugin, and will assign the below capability to any role
2449
		// coming from the hook.
2450
2451
		// alternatively, other roles can get this capability in whatever other way you like
2452
		// point is: to administer this plugin, you need this capability.
2453
2454
		if ( ! current_user_can( 'configure_salesforce' ) ) {
2455
			return false;
2456
		} else {
2457
			return true;
2458
		}
2459
2460
	}
2461
2462
	/**
2463
	 * Check WordPress SSL status.
2464
	 * HTTPS is required to connect to Salesforce.
2465
	 *
2466
	 * @return bool $secure_admin
2467
	 */
2468
	private function check_wordpress_ssl() {
2469
2470
		// the wp_is_using_https() function was added in WordPress 5.7.
2471
		if ( ! function_exists( 'wp_is_using_https' ) ) {
2472
			return true;
2473
		}
2474
2475
		// the whole site is already using SSL.
2476
		if ( true === wp_is_using_https() ) {
2477
			return true;
2478
		}
2479
2480
		$secure_admin = false;
2481
2482
		// the admin is already forced to use SSL.
2483
		if ( true === FORCE_SSL_ADMIN ) {
0 ignored issues
show
introduced by
The condition true === FORCE_SSL_ADMIN is always false.
Loading history...
2484
			$secure_admin = true;
2485
		}
2486
2487
		// the server reports that the current URL is using HTTPS.
2488
		if ( isset( $_SERVER['HTTPS'] ) && 'on' === $_SERVER['HTTPS'] ) {
2489
			$secure_admin = true;
2490
		}
2491
2492
		return $secure_admin;
2493
	}
2494
2495
	/**
2496
	 * Check WordPress SSL support.
2497
	 * This allows us to show a different message if SSL is supported, but is not in use.
2498
	 *
2499
	 * @return bool $https_supported
2500
	 */
2501
	private function check_wordpress_ssl_support() {
2502
2503
		// the wp_is_https_supported() function was added in WordPress 5.7.
2504
		if ( ! function_exists( 'wp_is_https_supported' ) ) {
2505
			return true;
2506
		}
2507
2508
		if ( true === wp_is_https_supported() ) {
2509
			return true;
2510
		}
2511
2512
		$https_supported = false;
2513
		return $https_supported;
2514
2515
	}
2516
2517
	/**
2518
	 * Show what we know about this user's relationship to a Salesforce object, if any
2519
	 *
2520
	 * @param object $user this is the user object from WordPress.
2521
	 */
2522
	public function show_salesforce_user_fields( $user ) {
2523
		$get_data = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
2524
		if ( true === $this->check_wordpress_admin_permissions() ) {
2525
			$mappings = $this->mappings->load_all_by_wordpress( 'user', $user->ID );
2526
			$fieldmap = $this->mappings->get_fieldmaps(
2527
				null, // id field must be null for multiples.
2528
				array(
2529
					'wordpress_object' => 'user',
2530
				)
2531
			);
2532
			if ( count( $mappings ) > 0 ) {
2533
				foreach ( $mappings as $mapping ) {
2534
					if ( isset( $mapping['id'] ) && ! isset( $get_data['edit_salesforce_mapping'] ) && ! isset( $get_data['delete_salesforce_mapping'] ) ) {
2535
						require_once plugin_dir_path( $this->file ) . '/templates/admin/user-profile-salesforce.php';
2536
					} elseif ( ! empty( $fieldmap ) ) { // is the user mapped to something already?
2537
						if ( isset( $get_data['edit_salesforce_mapping'] ) && true === filter_var( $get_data['edit_salesforce_mapping'], FILTER_VALIDATE_BOOLEAN ) ) {
2538
							require_once plugin_dir_path( $this->file ) . '/templates/admin/user-profile-salesforce-change.php';
2539
						} elseif ( isset( $get_data['delete_salesforce_mapping'] ) && true === filter_var( $get_data['delete_salesforce_mapping'], FILTER_VALIDATE_BOOLEAN ) ) {
2540
							require_once plugin_dir_path( $this->file ) . '/templates/admin/user-profile-salesforce-delete.php';
2541
						} else {
2542
							require_once plugin_dir_path( $this->file ) . '/templates/admin/user-profile-salesforce-map.php';
2543
						}
2544
					}
2545
				}
2546
			} else {
2547
				require_once plugin_dir_path( $this->file ) . '/templates/admin/user-profile-salesforce-map.php';
2548
			}
2549
		}
2550
	}
2551
2552
	/**
2553
	 * If the user profile has been mapped to Salesforce, do it
2554
	 *
2555
	 * @param int $user_id the ID of the WordPress user.
2556
	 */
2557
	public function save_salesforce_user_fields( $user_id ) {
2558
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
2559
		if ( isset( $post_data['salesforce_update_mapped_user'] ) && true === filter_var( $post_data['salesforce_update_mapped_user'], FILTER_VALIDATE_BOOLEAN ) ) {
2560
			$mapping_objects = $this->mappings->get_all_object_maps(
2561
				array(
2562
					'wordpress_id'     => $user_id,
2563
					'wordpress_object' => 'user',
2564
				)
2565
			);
2566
			foreach ( $mapping_objects as $mapping_object ) {
2567
				$mapping_object['salesforce_id'] = $post_data['salesforce_id'];
2568
				$result                          = $this->mappings->update_object_map( $mapping_object, $mapping_object['id'] );
2569
			}
2570
		} elseif ( isset( $post_data['salesforce_create_mapped_user'] ) && true === filter_var( $post_data['salesforce_create_mapped_user'], FILTER_VALIDATE_BOOLEAN ) ) {
2571
			// if a Salesforce ID was entered.
2572
			if ( isset( $post_data['salesforce_id'] ) && ! empty( $post_data['salesforce_id'] ) ) {
2573
				$mapping_object = $this->create_object_map( $user_id, 'user', $post_data['salesforce_id'] );
2574
			} elseif ( isset( $post_data['push_new_user_to_salesforce'] ) ) {
2575
				// otherwise, create a new record in Salesforce.
2576
				$result = $this->push_to_salesforce( 'user', $user_id );
2577
			}
2578
		} elseif ( isset( $post_data['salesforce_delete_mapped_user'] ) && true === filter_var( $post_data['salesforce_delete_mapped_user'], FILTER_VALIDATE_BOOLEAN ) ) {
2579
			// if a Salesforce ID was entered.
2580
			if ( isset( $post_data['mapping_id'] ) && ! empty( $post_data['mapping_id'] ) ) {
2581
				$delete = $this->mappings->delete_object_map( $post_data['mapping_id'] );
2582
			}
2583
		}
2584
	}
2585
2586
	/**
2587
	 * Render tabs for settings pages in admin
2588
	 *
2589
	 * @param array  $tabs is the tabs for the settings menu.
2590
	 * @param string $tab is a single tab.
2591
	 */
2592
	private function tabs( $tabs, $tab = '' ) {
2593
2594
		$get_data        = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
2595
		$consumer_key    = $this->login_credentials['consumer_key'];
2596
		$consumer_secret = $this->login_credentials['consumer_secret'];
2597
		$callback_url    = $this->login_credentials['callback_url'];
2598
2599
		$current_tab = $tab;
2600
		echo '<h2 class="nav-tab-wrapper">';
2601
		foreach ( $tabs as $tab_key => $tab_caption ) {
2602
			$active = $current_tab === $tab_key ? ' nav-tab-active' : '';
2603
2604
			if ( true === $this->salesforce['is_authorized'] ) {
2605
				echo sprintf(
2606
					'<a class="nav-tab%1$s" href="%2$s">%3$s</a>',
2607
					esc_attr( $active ),
2608
					esc_url( '?page=' . $this->admin_settings_url_param . '&tab=' . $tab_key ),
2609
					esc_html( $tab_caption )
2610
				);
2611
			} elseif ( 'settings' === $tab_key || ( 'authorize' === $tab_key && isset( $consumer_key ) && isset( $consumer_secret ) && ! empty( $consumer_key ) && ! empty( $consumer_secret ) ) ) {
2612
				echo sprintf(
2613
					'<a class="nav-tab%1$s" href="%2$s">%3$s</a>',
2614
					esc_attr( $active ),
2615
					esc_url( '?page=' . $this->admin_settings_url_param . '&tab=' . $tab_key ),
2616
					esc_html( $tab_caption )
2617
				);
2618
			}
2619
		}
2620
		echo '</h2>';
2621
2622
		if ( isset( $get_data['tab'] ) ) {
2623
			$tab = sanitize_key( $get_data['tab'] );
2624
		} else {
2625
			$tab = '';
2626
		}
2627
	}
2628
2629
	/**
2630
	 * Clear schedule
2631
	 * This clears the schedule if the user clicks the button
2632
	 *
2633
	 * @param string $schedule_name is the name of the schedule being cleared.
2634
	 */
2635
	private function clear_schedule( $schedule_name = '' ) {
2636
		if ( '' !== $schedule_name ) {
2637
			$this->queue->cancel( $schedule_name );
2638
			// translators: $schedule_name is the name of the current queue. Defaults: salesforce_pull, salesforce_push, salesforce.
2639
			echo sprintf( esc_html__( 'You have cleared the %s schedule.', 'object-sync-for-salesforce' ), esc_html( $schedule_name ) );
2640
		} else {
2641
			echo esc_html__( 'You need to specify the name of the schedule you want to clear.', 'object-sync-for-salesforce' );
2642
		}
2643
	}
2644
2645
	/**
2646
	 * Get count of schedule items
2647
	 *
2648
	 * @param string $schedule_name is the name of the schedule.
2649
	 * @return int $count
2650
	 */
2651
	private function get_schedule_count( $schedule_name = '' ) {
2652
		if ( '' !== $schedule_name ) {
2653
			$count       = count(
2654
				$this->queue->search(
2655
					array(
2656
						'group'  => $schedule_name,
2657
						'status' => ActionScheduler_Store::STATUS_PENDING,
2658
					),
2659
					'ARRAY_A'
2660
				)
2661
			);
2662
			$group_count = count(
2663
				$this->queue->search(
2664
					array(
2665
						'group'  => $schedule_name . $this->action_group_suffix,
2666
						'status' => ActionScheduler_Store::STATUS_PENDING,
2667
					),
2668
					'ARRAY_A'
2669
				)
2670
			);
2671
			return $count + $group_count;
2672
		} else {
2673
			return 0;
2674
		}
2675
	}
2676
2677
	/**
2678
	 * Create an object map between a WordPress object and a Salesforce object
2679
	 *
2680
	 * @param int    $wordpress_id Unique identifier for the WordPress object.
2681
	 * @param string $wordpress_object What kind of object is it.
2682
	 * @param string $salesforce_id Unique identifier for the Salesforce object.
2683
	 * @param string $action Did we push or pull.
2684
	 * @return int   $wpdb->insert_id This is the database row for the map object
2685
	 */
2686
	private function create_object_map( $wordpress_id, $wordpress_object, $salesforce_id, $action = '' ) {
2687
		// Create object map and save it.
2688
		$mapping_object = $this->mappings->create_object_map(
2689
			array(
2690
				'wordpress_id'      => $wordpress_id, // wordpress unique id.
2691
				'salesforce_id'     => $salesforce_id, // salesforce unique id. we don't care what kind of object it is at this point.
2692
				'wordpress_object'  => $wordpress_object, // keep track of what kind of wp object this is.
2693
				'last_sync'         => current_time( 'mysql' ),
2694
				'last_sync_action'  => $action,
2695
				'last_sync_status'  => $this->mappings->status_success,
2696
				'last_sync_message' => __( 'Mapping object updated via function: ', 'object-sync-for-salesforce' ) . __FUNCTION__,
2697
			)
2698
		);
2699
2700
		return $mapping_object;
2701
2702
	}
2703
2704
}
2705