Passed
Pull Request — master (#464)
by Jonathan
06:35 queued 03:05
created

Object_Sync_Sf_Admin::tabs()   B

Complexity

Conditions 11
Paths 14

Size

Total Lines 34
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 25
nc 14
nop 2
dl 0
loc 34
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * 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
	 * @deprecated as of 2.2.0; will be removed in version 3.0.
178
	 */
179
	public $default_api_version;
180
181
	/**
182
	 * Default max number of pull records. Users can edit this.
183
	 *
184
	 * @var int
185
	 */
186
	public $default_pull_limit;
187
188
	/**
189
	 * Default throttle for how often to pull from Salesforce. Users can edit this.
190
	 *
191
	 * @var int
192
	 */
193
	public $default_pull_throttle;
194
195
	/**
196
	 * Default for whether to limit to triggerable items. Users can edit this.
197
	 *
198
	 * @var bool
199
	 */
200
	public $default_triggerable;
201
202
	/**
203
	 * Default for whether to limit to items that can be updated. Users can edit this.
204
	 *
205
	 * @var bool
206
	 */
207
	public $default_updateable;
208
209
	/**
210
	 * Constructor for admin class
211
	 */
212
	public function __construct() {
213
		$this->version             = object_sync_for_salesforce()->version;
214
		$this->file                = object_sync_for_salesforce()->file;
215
		$this->wpdb                = object_sync_for_salesforce()->wpdb;
216
		$this->slug                = object_sync_for_salesforce()->slug;
217
		$this->option_prefix       = object_sync_for_salesforce()->option_prefix;
218
		$this->action_group_suffix = object_sync_for_salesforce()->action_group_suffix;
219
220
		$this->login_credentials   = object_sync_for_salesforce()->login_credentials;
221
		$this->wordpress           = object_sync_for_salesforce()->wordpress;
222
		$this->salesforce          = object_sync_for_salesforce()->salesforce;
223
		$this->mappings            = object_sync_for_salesforce()->mappings;
224
		$this->push                = object_sync_for_salesforce()->push;
225
		$this->pull                = object_sync_for_salesforce()->pull;
226
		$this->logging             = object_sync_for_salesforce()->logging;
227
		$this->schedulable_classes = object_sync_for_salesforce()->schedulable_classes;
228
		$this->queue               = object_sync_for_salesforce()->queue;
229
230
		// set the Salesforce API version.
231
		// as of version 2.2.0, this is set by the plugin and is not configurable in the interface.
232
		// this class variable will be removed in 3.0.0.
233
		$this->default_api_version = $this->login_credentials['rest_api_version'];
0 ignored issues
show
Deprecated Code introduced by
The property Object_Sync_Sf_Admin::$default_api_version has been deprecated: as of 2.2.0; will be removed in version 3.0. ( Ignorable by Annotation )

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

233
		/** @scrutinizer ignore-deprecated */ $this->default_api_version = $this->login_credentials['rest_api_version'];

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
234
235
		$this->sfwp_transients          = object_sync_for_salesforce()->wordpress->sfwp_transients;
236
		$this->admin_settings_url_param = 'object-sync-salesforce-admin';
237
238
		// default authorize url path.
239
		$this->default_authorize_url_path = '/services/oauth2/authorize';
240
		// default token url path.
241
		$this->default_token_url_path = '/services/oauth2/token';
242
		// default pull record limit.
243
		$this->default_pull_limit = 25;
244
		// default pull throttle for avoiding going over api limits.
245
		$this->default_pull_throttle = 5;
246
		// default setting for triggerable items.
247
		$this->default_triggerable = true;
248
		// default setting for updateable items.
249
		$this->default_updateable = true;
250
251
		$this->add_actions();
252
	}
253
254
	/**
255
	 * Create the action hooks to create the admin pages.
256
	 */
257
	public function add_actions() {
258
259
		// settings link.
260
		add_filter( 'plugin_action_links', array( $this, 'plugin_action_links' ), 10, 5 );
261
262
		// CSS and Javascript.
263
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts_and_styles' ) );
264
265
		// Settings API forms and notices.
266
		add_action( 'admin_menu', array( $this, 'create_admin_menu' ) );
267
		add_action( 'admin_init', array( $this, 'salesforce_settings_forms' ) );
268
		add_action( 'admin_init', array( $this, 'notices' ) );
269
		add_action( 'admin_post_post_fieldmap', array( $this, 'prepare_fieldmap_data' ) );
270
		add_action( 'admin_post_delete_fieldmap', array( $this, 'delete_fieldmap' ) );
271
272
		// Ajax for fieldmap forms.
273
		add_action( 'wp_ajax_get_salesforce_object_description', array( $this, 'get_salesforce_object_description' ), 10, 1 );
274
		add_action( 'wp_ajax_get_salesforce_object_fields', array( $this, 'get_salesforce_object_fields' ), 10, 1 );
275
		add_action( 'wp_ajax_get_wordpress_object_fields', array( $this, 'get_wordpress_object_fields' ), 10, 1 );
276
277
		// Ajax events that can be manually called.
278
		add_action( 'wp_ajax_push_to_salesforce', array( $this, 'push_to_salesforce' ), 10, 3 );
279
		add_action( 'wp_ajax_pull_from_salesforce', array( $this, 'pull_from_salesforce' ), 10, 2 );
280
		add_action( 'wp_ajax_refresh_mapped_data', array( $this, 'refresh_mapped_data' ), 10, 1 );
281
		add_action( 'wp_ajax_clear_sfwp_cache', array( $this, 'clear_sfwp_cache' ) );
282
283
		// we add a Salesforce box on user profiles.
284
		add_action( 'edit_user_profile', array( $this, 'show_salesforce_user_fields' ), 10, 1 );
285
		add_action( 'show_user_profile', array( $this, 'show_salesforce_user_fields' ), 10, 1 );
286
287
		// and we can update Salesforce fields on the user profile box.
288
		add_action( 'personal_options_update', array( $this, 'save_salesforce_user_fields' ), 10, 1 );
289
		add_action( 'edit_user_profile_update', array( $this, 'save_salesforce_user_fields' ), 10, 1 );
290
291
		// when either field for schedule settings changes.
292
		foreach ( $this->schedulable_classes as $key => $value ) {
293
			// if the user doesn't have any action schedule tasks, let's not leave them empty.
294
			add_filter( 'pre_update_option_' . $this->option_prefix . $key . '_schedule_number', array( $this, 'initial_action_schedule' ), 10, 3 );
295
			add_filter( 'pre_update_option_' . $this->option_prefix . $key . '_schedule_unit', array( $this, 'initial_action_schedule' ), 10, 3 );
296
297
			// this is if the user is changing their tasks.
298
			add_filter( 'update_option_' . $this->option_prefix . $key . '_schedule_number', array( $this, 'change_action_schedule' ), 10, 3 );
299
			add_filter( 'update_option_' . $this->option_prefix . $key . '_schedule_unit', array( $this, 'change_action_schedule' ), 10, 3 );
300
		}
301
302
		// when ActionScheduler runs its migration, resave the schedule options.
303
		add_action( 'action_scheduler/migration_complete', array( $this, 'resave_action_schedules' ) );
304
305
		// handle post requests for object maps.
306
		add_action( 'admin_post_delete_object_map', array( $this, 'delete_object_map' ) );
307
		add_action( 'admin_post_post_object_map', array( $this, 'prepare_object_map_data' ) );
308
309
		// import and export plugin data.
310
		add_action( 'admin_post_object_sync_for_salesforce_import', array( $this, 'import_json_file' ) );
311
		add_action( 'admin_post_object_sync_for_salesforce_export', array( $this, 'export_json_file' ) );
312
313
	}
314
315
	/**
316
	 * Display a Settings link on the main Plugins page
317
	 *
318
	 * @param array  $links the array of links for the main plugins page.
319
	 * @param string $file the filename.
320
	 * @return array $links the array of links for the main plugins page
321
	 */
322
	public function plugin_action_links( $links, $file ) {
323
		if ( plugin_basename( $this->file ) === $file ) {
324
			$settings = '<a href="' . get_admin_url() . 'options-general.php?page=' . $this->admin_settings_url_param . '">' . __( 'Settings', 'object-sync-for-salesforce' ) . '</a>';
325
			array_unshift( $links, $settings );
326
		}
327
		return $links;
328
	}
329
330
	/**
331
	 * Admin styles. Load the CSS and JavaScript for the plugin's settings
332
	 */
333
	public function admin_scripts_and_styles() {
334
335
		// Developers might not want to bother with select2 or selectwoo, so we allow that to be changeable.
336
		$select_library = apply_filters( $this->option_prefix . 'select_library', 'selectwoo' );
337
338
		/*
339
		 * example to modify the select library
340
		 * add_filter( 'object_sync_for_salesforce_select_library', 'select_library', 10, 1 );
341
		 * function select_library( $select_library ) {
342
		 * 	$select_library = 'select2';
343
		 *  // this could also be empty; in that case we would just use default browser select
344
		 * 	return $select_library;
345
		 * }
346
		*/
347
348
		$javascript_dependencies = array( 'jquery' );
349
		$css_dependencies        = array();
350
		if ( '' !== $select_library ) {
351
			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 );
352
			$javascript_dependencies[] = $select_library . 'js';
353
			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' );
354
			$css_dependencies[] = $select_library . 'css';
355
		}
356
357
		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 );
358
		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' );
359
	}
360
361
	/**
362
	 * Initial recurring tasks for ActionScheduler
363
	 *
364
	 * @param string $new_schedule the new, unserialized option value.
365
	 * @param string $old_schedule the old option value.
366
	 * @param string $option_name option name.
367
	 * @return string $new_schedule
368
	 */
369
	public function initial_action_schedule( $new_schedule, $old_schedule, $option_name ) {
370
371
		// get the current schedule name from the task, based on pattern in the foreach.
372
		preg_match( '/' . $this->option_prefix . '(.*)_schedule/', $option_name, $matches );
373
		$schedule_name     = $matches[1];
374
		$action_group_name = $schedule_name . $this->action_group_suffix;
375
376
		// make sure there are no tasks already.
377
		$current_tasks = as_get_scheduled_actions(
378
			array(
379
				'hook'  => $this->schedulable_classes[ $schedule_name ]['initializer'],
380
				'group' => $action_group_name,
381
			),
382
			ARRAY_A
383
		);
384
385
		// exit if there are already tasks; they'll be saved if the option data changed.
386
		if ( ! empty( $current_tasks ) ) {
387
			return $new_schedule;
388
		}
389
390
		$this->set_action_schedule( $schedule_name, $action_group_name );
391
392
		return $new_schedule;
393
394
	}
395
396
	/**
397
	 * Update recurring tasks for ActionScheduler if options change
398
	 *
399
	 * @param string $old_schedule the old option value.
400
	 * @param string $new_schedule the new, unserialized option value.
401
	 * @param string $option_name option name.
402
	 */
403
	public function change_action_schedule( $old_schedule, $new_schedule, $option_name ) {
404
405
		// this method does not run if the option's data is unchanged.
406
407
		// get the current schedule name from the task, based on pattern in the foreach.
408
		preg_match( '/' . $this->option_prefix . '(.*)_schedule/', $option_name, $matches );
409
		$schedule_name     = $matches[1];
410
		$action_group_name = $schedule_name . $this->action_group_suffix;
411
412
		$this->set_action_schedule( $schedule_name, $action_group_name );
413
414
	}
415
416
	/**
417
	 * Set up recurring tasks for ActionScheduler
418
	 *
419
	 * @param string $schedule_name the name of the schedule.
420
	 * @param string $action_group_name the group's name.
421
	 */
422
	private function set_action_schedule( $schedule_name, $action_group_name ) {
423
		// exit if there is no initializer property on this schedule.
424
		if ( ! isset( $this->schedulable_classes[ $schedule_name ]['initializer'] ) ) {
425
			return;
426
		}
427
428
		// cancel previous task.
429
		$this->queue->cancel(
430
			$this->schedulable_classes[ $schedule_name ]['initializer'],
431
			array(),
432
			$action_group_name
433
		);
434
435
		// create new recurring task for ActionScheduler to check for data to pull from Salesforce.
436
		$this->queue->schedule_recurring(
437
			time(), // plugin seems to expect UTC.
438
			$this->queue->get_frequency( $schedule_name, 'seconds' ),
439
			$this->schedulable_classes[ $schedule_name ]['initializer'],
440
			array(),
441
			$action_group_name
442
		);
443
	}
444
445
	/**
446
	 * When it finishes its migration, resave the scheduled tasks for ActionScheduler.
447
	 */
448
	public function resave_action_schedules() {
449
		// for each schedulable action, go ahead and resave it.
450
		$schedules_updated  = array();
451
		$schedules_restored = array();
452
		foreach ( $this->schedulable_classes as $key => $value ) {
453
			// make sure it has an initializer property; this is used on recurring tasks.
454
			if ( isset( $value['initializer'] ) ) {
455
				// toggle the schedule number setting.
456
				$schedule_option_name  = $this->option_prefix . $key . '_schedule_number';
457
				$previous_option_value = get_option( $schedule_option_name, 0 );
458
				$previous_option_value = filter_var( $previous_option_value, FILTER_SANITIZE_NUMBER_INT );
459
				$new_option_value      = $previous_option_value + 1;
460
				$schedule_updated      = update_option( $schedule_option_name, $new_option_value );
461
				if ( true === $schedule_updated ) {
462
					$schedules_updated[] = $key;
463
					$schedule_restored   = update_option( $schedule_option_name, $previous_option_value );
464
					if ( true === $schedule_restored ) {
465
						$schedules_restored[] = $key;
466
					}
467
				}
468
			}
469
		}
470
471
		// create a log entry from the updated scheduled tasks.
472
		if ( ! empty( $schedules_updated ) || ! empty( $schedules_restored ) ) {
473
			$status = 'success';
474
		} else {
475
			$status = 'error';
476
		}
477
478
		if ( isset( $this->logging ) ) {
479
			$logging = $this->logging;
480
		} elseif ( class_exists( 'Object_Sync_Sf_Logging' ) ) {
481
			$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

481
			$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...
482
		}
483
484
		$body = sprintf( esc_html__( 'These are the scheduled tasks that were updated: ', 'object-sync-for-salesforce' ) . '<ul>' );
485
		foreach ( $schedules_updated as $schedule_updated ) {
486
			$body .= sprintf(
487
				// translators: placeholders are: 1) the schedule name.
488
				'<li>' . esc_html__( 'Schedule name: %1$s.', 'object-sync-for-salesforce' ) . '</li>',
489
				esc_attr( $schedule_updated )
490
			);
491
		}
492
		$body .= '</ul>';
493
494
		$body .= sprintf( esc_html__( 'These are the scheduled tasks that have the same frequency as they had pre-migration: ', 'object-sync-for-salesforce' ) . '<ul>' );
495
		foreach ( $schedules_restored as $schedule_restored ) {
496
			$body .= sprintf(
497
				// translators: placeholders are: 1) the schedule name.
498
				'<li>' . esc_html__( 'Schedule name: %1$s.', 'object-sync-for-salesforce' ) . '</li>',
499
				esc_attr( $schedule_restored )
500
			);
501
		}
502
		$body .= '</ul>';
503
504
		$body .= sprintf( esc_html__( 'If any tasks were not updated, or were not able to keep the same frequency they had before, go to the Scheduling tab to update them.', 'object-sync-for-salesforce' ) );
505
		$body .= sprintf(
506
			// translators: %1$s is the schedule settings URL.
507
			wp_kses_post( 'If any tasks were not updated, or were not able to keep the same frequency they had before, go to the <a href="' . admin_url( 'options-general.php?page=object-sync-salesforce-admin&tab=schedule' ) . '">%1$s</a> tab to update them.', 'object-sync-for-salesforce' ),
0 ignored issues
show
Unused Code introduced by
The call to wp_kses_post() has too many arguments starting with 'object-sync-for-salesforce'. ( Ignorable by Annotation )

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

507
			/** @scrutinizer ignore-call */ 
508
   wp_kses_post( 'If any tasks were not updated, or were not able to keep the same frequency they had before, go to the <a href="' . admin_url( 'options-general.php?page=object-sync-salesforce-admin&tab=schedule' ) . '">%1$s</a> tab to update them.', 'object-sync-for-salesforce' ),

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...
508
			esc_html__( 'Scheduling', 'object-sync-for-salesforce' )
509
		);
510
511
		$logging->setup(
512
			sprintf(
513
				// translators: %1$s is the log status, %2$s is the name of a WordPress object. %3$s is the id of that object.
514
				esc_html__( '%1$s ActionScheduler: the ActionScheduler library has completed its migration. See the log entry content for status on each recurring task.', 'object-sync-for-salesforce' ),
515
				ucfirst( esc_attr( $status ) )
516
			),
517
			$body,
518
			0,
519
			0,
520
			$status
521
		);
522
	}
523
524
	/**
525
	 * Create the WordPress admin options page
526
	 */
527
	public function create_admin_menu() {
528
		$title = __( 'Salesforce', 'object-sync-for-salesforce' );
529
		add_options_page( $title, $title, 'configure_salesforce', $this->admin_settings_url_param, array( $this, 'show_admin_page' ) );
530
	}
531
532
	/**
533
	 * Render the admin pages in WordPress. This also allows other plugins to add tabs to this plugin's settings screen
534
	 */
535
	public function show_admin_page() {
536
		$get_data = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
537
		echo '<div class="wrap">';
538
		echo '<h1>' . esc_html( get_admin_page_title() ) . '</h1>';
539
		$allowed = $this->check_wordpress_admin_permissions();
540
		if ( false === $allowed ) {
541
			return;
542
		}
543
		$tabs = array(
544
			'settings'      => __( 'Settings', 'object-sync-for-salesforce' ),
545
			'authorize'     => __( 'Authorize', 'object-sync-for-salesforce' ),
546
			'fieldmaps'     => __( 'Fieldmaps', 'object-sync-for-salesforce' ),
547
			'schedule'      => __( 'Scheduling', 'object-sync-for-salesforce' ),
548
			'import-export' => __( 'Import &amp; Export', 'object-sync-for-salesforce' ),
549
		); // this creates the tabs for the admin.
550
551
		// optionally make tab(s) for logging and log settings.
552
		$logging_enabled      = get_option( $this->option_prefix . 'enable_logging', false );
553
		$tabs['log_settings'] = __( 'Log Settings', 'object-sync-for-salesforce' );
554
555
		$mapping_errors       = $this->mappings->get_failed_object_maps();
556
		$mapping_errors_total = isset( $mapping_errors['total'] ) ? $mapping_errors['total'] : 0;
557
		if ( 0 < $mapping_errors_total ) {
558
			$tabs['mapping_errors'] = __( 'Mapping Errors', 'object-sync-for-salesforce' );
559
		}
560
561
		// filter for extending the tabs available on the page
562
		// currently it will go into the default switch case for $tab.
563
		$tabs = apply_filters( $this->option_prefix . 'settings_tabs', $tabs );
564
565
		$tab = isset( $get_data['tab'] ) ? sanitize_key( $get_data['tab'] ) : 'settings';
566
		$this->tabs( $tabs, $tab );
567
568
		$consumer_key    = $this->login_credentials['consumer_key'];
569
		$consumer_secret = $this->login_credentials['consumer_secret'];
570
		$callback_url    = $this->login_credentials['callback_url'];
571
572
		if ( true !== $this->salesforce['is_authorized'] ) {
573
			$url     = esc_url( $callback_url );
574
			$anchor  = esc_html__( 'Authorize tab', 'object-sync-for-salesforce' );
575
			$message = sprintf( 'Salesforce needs to be authorized to connect to this website. Use the <a href="%s">%s</a> to connect.', $url, $anchor );
576
			require plugin_dir_path( $this->file ) . '/templates/admin/error.php';
577
		}
578
579
		if ( 0 === count( $this->mappings->get_fieldmaps() ) ) {
580
			$url     = esc_url( get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=fieldmaps' ) );
581
			$anchor  = esc_html__( 'Fieldmaps tab', 'object-sync-for-salesforce' );
582
			$message = sprintf( 'No fieldmaps exist yet. Use the <a href="%s">%s</a> to map WordPress and Salesforce objects to each other.', $url, $anchor );
583
			require plugin_dir_path( $this->file ) . '/templates/admin/error.php';
584
		}
585
586
		try {
587
			switch ( $tab ) {
588
				case 'authorize':
589
					if ( isset( $get_data['code'] ) ) {
590
						// this string is an oauth token.
591
						$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

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

1623
			$salesforce_object = isset( $post_data['salesforce_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['salesforce_object'] ) ) : '';
Loading history...
1624
			$ajax              = true;
1625
			// here, we should respect the decision of whether to show the API name or the label.
1626
			$display_value = get_option( $this->option_prefix . 'salesforce_field_display_value', 'field_label' );
1627
			if ( 'api_name' === $display_value ) {
1628
				$visible_label_field = 'name';
1629
			} else {
1630
				$visible_label_field = 'label';
1631
			}
1632
			$attributes = array( 'name', $visible_label_field );
1633
		} else {
1634
			$salesforce_object = isset( $data['salesforce_object'] ) ? sanitize_text_field( wp_unslash( $data['salesforce_object'] ) ) : '';
1635
		}
1636
		$object_fields = array();
1637
		if ( ! empty( $salesforce_object ) ) {
1638
			$object               = $this->salesforce['sfapi']->object_describe( esc_attr( $salesforce_object ) );
1639
			$object_fields        = array();
1640
			$type                 = isset( $data['type'] ) ? esc_attr( $data['type'] ) : '';
1641
			$include_record_types = isset( $data['include_record_types'] ) ? esc_attr( $data['include_record_types'] ) : false;
1642
			foreach ( $object['data']['fields'] as $key => $value ) {
1643
				if ( '' === $type || $type === $value['type'] ) {
1644
					$object_fields[ $key ] = $value;
1645
					if ( isset( $attributes ) ) {
1646
						$object_fields[ $key ] = array_intersect_key( $value, array_flip( $attributes ) );
1647
					}
1648
				}
1649
			}
1650
			if ( true === $include_record_types ) {
0 ignored issues
show
introduced by
The condition true === $include_record_types is always false.
Loading history...
1651
				$object_record_types = array();
1652
				if ( isset( $object['data']['recordTypeInfos'] ) && count( $object['data']['recordTypeInfos'] ) > 1 ) {
1653
					foreach ( $object['data']['recordTypeInfos'] as $type ) {
1654
						$object_record_types[ $type['recordTypeId'] ] = $type['name'];
1655
					}
1656
				}
1657
			}
1658
		}
1659
1660
		if ( true === $ajax ) {
1661
			$ajax_response = array(
1662
				'fields' => $object_fields,
1663
			);
1664
			wp_send_json_success( $ajax_response );
1665
		} else {
1666
			return $object_fields;
1667
		}
1668
1669
	}
1670
1671
	/**
1672
	 * Get WordPress object fields for fieldmapping
1673
	 * This takes either the $_POST array via ajax, or can be directly called with a $wordpress_object field
1674
	 *
1675
	 * @param string $wordpress_object is the name of the WordPress object.
1676
	 * @return array $object_fields
1677
	 */
1678
	public function get_wordpress_object_fields( $wordpress_object = '' ) {
1679
		$ajax      = false;
1680
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1681
		if ( empty( $wordpress_object ) ) {
1682
			$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

1682
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1683
			$ajax             = true;
1684
		}
1685
1686
		$object_fields = $this->wordpress->get_wordpress_object_fields( $wordpress_object );
1687
1688
		if ( true === $ajax ) {
1689
			$ajax_response = array(
1690
				'fields' => $object_fields,
1691
			);
1692
			wp_send_json_success( $ajax_response );
1693
		} else {
1694
			return $object_fields;
1695
		}
1696
	}
1697
1698
	/**
1699
	 * Manually push the WordPress object to Salesforce
1700
	 * This takes either the $_POST array via ajax, or can be directly called with $wordpress_object and $wordpress_id fields
1701
	 *
1702
	 * @param string $wordpress_object is the name of the WordPress object.
1703
	 * @param int    $wordpress_id is the ID of the WordPress record.
1704
	 * @param bool   $force_return Force the method to return json instead of outputting it.
1705
	 */
1706
	public function push_to_salesforce( $wordpress_object = '', $wordpress_id = '', $force_return = false ) {
1707
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1708
		if ( empty( $wordpress_object ) && empty( $wordpress_id ) ) {
1709
			$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

1709
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1710
			$wordpress_id     = isset( $post_data['wordpress_id'] ) ? absint( $post_data['wordpress_id'] ) : '';
1711
		}
1712
1713
		// clarify what that variable is in this context.
1714
		$object_type = $wordpress_object;
1715
1716
		// When objects are already mapped, there is a Salesforce id as well. Otherwise, it's blank.
1717
		$salesforce_id = isset( $post_data['salesforce_id'] ) ? sanitize_text_field( $post_data['salesforce_id'] ) : '';
1718
		if ( '' === $salesforce_id ) {
1719
			$method = 'POST';
1720
		} else {
1721
			$method = 'PUT';
1722
		}
1723
1724
		$result = $this->push->manual_push( $object_type, $wordpress_id, $method );
1725
1726
		if ( false === $force_return && ! empty( $post_data['wordpress_object'] ) && ! empty( $post_data['wordpress_id'] ) ) {
1727
			wp_send_json_success( $result );
1728
		} else {
1729
			return $result;
1730
		}
1731
1732
	}
1733
1734
	/**
1735
	 * Manually pull the Salesforce object into WordPress
1736
	 * This takes either the $_POST array via ajax, or can be directly called with $salesforce_id fields
1737
	 *
1738
	 * @param string $salesforce_id is the ID of the Salesforce record.
1739
	 * @param string $wordpress_object is the name of the WordPress object.
1740
	 */
1741
	public function pull_from_salesforce( $salesforce_id = '', $wordpress_object = '' ) {
1742
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1743
		if ( empty( $wordpress_object ) && empty( $salesforce_id ) ) {
1744
			$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

1744
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1745
			$salesforce_id    = isset( $post_data['salesforce_id'] ) ? sanitize_text_field( wp_unslash( $post_data['salesforce_id'] ) ) : '';
1746
		}
1747
		$type   = $this->salesforce['sfapi']->get_sobject_type( $salesforce_id );
1748
		$result = $this->pull->manual_pull( $type, $salesforce_id, $wordpress_object ); // we want the wp object to make sure we get the right fieldmap.
1749
		if ( ! empty( $post_data ) ) {
1750
			wp_send_json_success( $result );
1751
		} else {
1752
			return $result;
1753
		}
1754
	}
1755
1756
	/**
1757
	 * Manually pull the Salesforce object into WordPress
1758
	 * This takes an id for a mapping object row
1759
	 *
1760
	 * @param int $mapping_id is the ID of the mapping object record.
1761
	 */
1762
	public function refresh_mapped_data( $mapping_id = '' ) {
1763
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1764
		if ( empty( $mapping_id ) ) {
1765
			$mapping_id = isset( $post_data['mapping_id'] ) ? absint( $post_data['mapping_id'] ) : '';
1766
		}
1767
		$result = $this->mappings->get_all_object_maps(
1768
			array(
1769
				'id' => $mapping_id,
1770
			)
1771
		);
1772
1773
		$object_map = array();
1774
1775
		// result is an array of arrays, not just one array.
1776
		if ( 1 === count( $result ) ) {
1777
			$object_map = $result[0];
1778
		}
1779
1780
		if ( ! empty( $post_data ) ) {
1781
			wp_send_json_success( $object_map );
1782
		} else {
1783
			return $object_map;
1784
		}
1785
	}
1786
1787
	/**
1788
	 * Prepare fieldmap data and redirect after processing
1789
	 * This runs when the create or update forms are submitted
1790
	 * It is public because it depends on an admin hook
1791
	 * It then calls the Object_Sync_Sf_Mapping class and sends prepared data over to it, then redirects to the correct page
1792
	 * This method does include error handling, by loading the submission in a transient if there is an error, and then deleting it upon success
1793
	 */
1794
	public function prepare_fieldmap_data() {
1795
		$error     = false;
1796
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1797
		$cachekey  = wp_json_encode( $post_data );
1798
		if ( false !== $cachekey ) {
1799
			$cachekey = md5( $cachekey );
1800
		}
1801
1802
		if ( ! isset( $post_data['label'] ) || ! isset( $post_data['salesforce_object'] ) || ! isset( $post_data['wordpress_object'] ) ) {
1803
			$error = true;
1804
		}
1805
		if ( true === $error ) {
1806
			$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1807
			if ( '' !== $cachekey ) {
1808
				$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

1808
				$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&transient=' . /** @scrutinizer ignore-type */ $cachekey;
Loading history...
1809
			}
1810
		} else { // there are no errors
1811
			// send the row to the fieldmap class
1812
			// if it is add or clone, use the create method.
1813
			$method            = esc_attr( $post_data['method'] );
1814
			$salesforce_fields = $this->get_salesforce_object_fields(
1815
				array(
1816
					'salesforce_object' => $post_data['salesforce_object'],
1817
				)
1818
			);
1819
			$wordpress_fields  = $this->get_wordpress_object_fields( $post_data['wordpress_object'] );
1820
			if ( 'add' === $method || 'clone' === $method ) {
1821
				$result = $this->mappings->create_fieldmap( $post_data, $wordpress_fields, $salesforce_fields );
1822
			} elseif ( 'edit' === $method ) { // if it is edit, use the update method.
1823
				$id     = esc_attr( $post_data['id'] );
1824
				$result = $this->mappings->update_fieldmap( $post_data, $wordpress_fields, $salesforce_fields, $id );
1825
			}
1826
			if ( false === $result ) { // if the database didn't save, it's still an error.
1827
				$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1828
				if ( '' !== $cachekey ) {
1829
					$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&transient=' . $cachekey;
1830
				}
1831
			} else {
1832
				// if the user has saved a fieldmap, clear the currently running query value if there is one.
1833
				if ( '' !== get_option( $this->option_prefix . 'currently_pulling_query_' . $post_data['salesforce_object'], '' ) ) {
1834
					$this->pull->clear_current_type_query( $post_data['salesforce_object'] );
1835
				}
1836
				if ( isset( $post_data['transient'] ) ) { // there was previously an error saved. can delete it now.
1837
					$this->sfwp_transients->delete( esc_attr( $post_data['map_transient'] ) );
1838
				}
1839
				// then send the user to the list of fieldmaps.
1840
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1841
			}
1842
		}
1843
		wp_safe_redirect( $url );
1844
		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...
1845
	}
1846
1847
	/**
1848
	 * Delete fieldmap data and redirect after processing
1849
	 * This runs when the delete link is clicked, after the user confirms
1850
	 * It is public because it depends on an admin hook
1851
	 * It then calls the Object_Sync_Sf_Mapping class and the delete method
1852
	 */
1853
	public function delete_fieldmap() {
1854
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1855
		if ( $post_data['id'] ) {
1856
			$result = $this->mappings->delete_fieldmap( $post_data['id'] );
1857
			if ( true === $result ) {
1858
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1859
			} else {
1860
				$url = esc_url_raw( $post_data['redirect_url_error'] . '&id=' . $post_data['id'] );
1861
			}
1862
			wp_safe_redirect( $url );
1863
			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...
1864
		}
1865
	}
1866
1867
	/**
1868
	 * Prepare object data and redirect after processing
1869
	 * This runs when the update form is submitted
1870
	 * It is public because it depends on an admin hook
1871
	 * It then calls the Object_Sync_Sf_Mapping class and sends prepared data over to it, then redirects to the correct page
1872
	 * This method does include error handling, by loading the submission in a transient if there is an error, and then deleting it upon success
1873
	 */
1874
	public function prepare_object_map_data() {
1875
		$error     = false;
1876
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1877
		$cachekey  = wp_json_encode( $post_data );
1878
		if ( false !== $cachekey ) {
1879
			$cachekey = md5( $cachekey );
1880
		}
1881
1882
		if ( ! isset( $post_data['wordpress_id'] ) || ! isset( $post_data['salesforce_id'] ) ) {
1883
			$error = true;
1884
		}
1885
		if ( true === $error ) {
1886
			$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1887
			if ( '' !== $cachekey ) {
1888
				$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

1888
				$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&map_transient=' . /** @scrutinizer ignore-type */ $cachekey;
Loading history...
1889
			}
1890
		} else { // there are no errors
1891
			// send the row to the object map class.
1892
			$method = esc_attr( $post_data['method'] );
1893
			if ( 'edit' === $method ) { // if it is edit, use the update method.
1894
				$id     = esc_attr( $post_data['id'] );
1895
				$result = $this->mappings->update_object_map( $post_data, $id );
1896
			}
1897
			if ( false === $result ) { // if the database didn't save, it's still an error.
1898
				$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1899
				if ( '' !== $cachekey ) {
1900
					$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&map_transient=' . $cachekey;
1901
				}
1902
			} else {
1903
				if ( isset( $post_data['map_transient'] ) ) { // there was previously an error saved. can delete it now.
1904
					$this->sfwp_transients->delete( esc_attr( $post_data['map_transient'] ) );
1905
				}
1906
				// then send the user to the success redirect url.
1907
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1908
			}
1909
		}
1910
		wp_safe_redirect( $url );
1911
		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...
1912
	}
1913
1914
	/**
1915
	 * Delete object map data and redirect after processing
1916
	 * This runs when the delete link is clicked on an error row, after the user confirms
1917
	 * It is public because it depends on an admin hook
1918
	 * It then calls the Object_Sync_Sf_Mapping class and the delete method
1919
	 */
1920
	public function delete_object_map() {
1921
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1922
		if ( isset( $post_data['id'] ) ) {
1923
			$result = $this->mappings->delete_object_map( $post_data['id'] );
1924
			if ( true === $result ) {
1925
				$url = esc_url_raw( $post_data['redirect_url_success'] );
1926
			} else {
1927
				$url = esc_url_raw( $post_data['redirect_url_error'] . '&id=' . $post_data['id'] );
1928
			}
1929
			wp_safe_redirect( $url );
1930
			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...
1931
		} elseif ( $post_data['delete'] ) {
1932
			$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1933
			$cachekey  = wp_json_encode( $post_data );
1934
			if ( false !== $cachekey ) {
1935
				$cachekey = md5( $cachekey );
1936
			}
1937
			$error = false;
1938
			if ( ! isset( $post_data['delete'] ) ) {
1939
				$error = true;
1940
			}
1941
			if ( true === $error ) {
1942
				$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1943
				if ( '' !== $cachekey ) {
1944
					$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

1944
					$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&mapping_error_transient=' . /** @scrutinizer ignore-type */ $cachekey;
Loading history...
1945
				}
1946
			} else { // there are no errors.
1947
				$result = $this->mappings->delete_object_map( array_keys( $post_data['delete'] ) );
1948
				if ( true === $result ) {
1949
					$url = esc_url_raw( $post_data['redirect_url_success'] );
1950
				}
1951
1952
				if ( false === $result ) { // if the database didn't save, it's still an error.
1953
					$this->sfwp_transients->set( $cachekey, $post_data, $this->wordpress->options['cache_expiration'] );
1954
					if ( '' !== $cachekey ) {
1955
						$url = esc_url_raw( $post_data['redirect_url_error'] ) . '&mapping_error_transient=' . $cachekey;
1956
					}
1957
				} else {
1958
					if ( isset( $post_data['mapping_error_transient'] ) ) { // there was previously an error saved. can delete it now.
1959
						$this->sfwp_transients->delete( esc_attr( $post_data['mapping_error_transient'] ) );
1960
					}
1961
					// then send the user to the list of fieldmaps.
1962
					$url = esc_url_raw( $post_data['redirect_url_success'] );
1963
				}
1964
			}
1965
			wp_safe_redirect( $url );
1966
			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...
1967
		}
1968
	}
1969
1970
	/**
1971
	 * Import a json file and use it for plugin data
1972
	 */
1973
	public function import_json_file() {
1974
1975
		if ( ! wp_verify_nonce( $_POST['object_sync_for_salesforce_nonce_import'], 'object_sync_for_salesforce_nonce_import' ) ) {
1976
			return;
1977
		}
1978
		if ( ! current_user_can( 'manage_options' ) ) {
1979
			return;
1980
		}
1981
		$path      = $_FILES['import_file']['name'];
1982
		$extension = pathinfo( $path, PATHINFO_EXTENSION );
1983
		if ( 'json' !== $extension ) {
1984
			wp_die( esc_html__( 'Please upload a valid .json file', 'object-sync-for-salesforce' ) );
1985
		}
1986
1987
		$import_file = $_FILES['import_file']['tmp_name'];
1988
		if ( empty( $import_file ) ) {
1989
			wp_die( esc_html__( 'Please upload a file to import', 'object-sync-for-salesforce' ) );
1990
		}
1991
1992
		// Retrieve the data from the file and convert the json object to an array.
1993
		$data = (array) json_decode( file_get_contents( $import_file ), true ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
1994
1995
		$overwrite = isset( $_POST['overwrite'] ) ? esc_attr( $_POST['overwrite'] ) : '';
1996
		if ( true === filter_var( $overwrite, FILTER_VALIDATE_BOOLEAN ) ) {
1997
			if ( isset( $data['fieldmaps'] ) ) {
1998
				$fieldmaps = $this->mappings->get_fieldmaps();
1999
				foreach ( $fieldmaps as $fieldmap ) {
2000
					$id     = $fieldmap['id'];
2001
					$delete = $this->mappings->delete_fieldmap( $id );
2002
				}
2003
			}
2004
			if ( isset( $data['object_maps'] ) ) {
2005
				$object_maps = $this->mappings->get_all_object_maps();
2006
				foreach ( $object_maps as $object_map ) {
2007
					$id     = $object_map['id'];
2008
					$delete = $this->mappings->delete_object_map( $id );
2009
				}
2010
			}
2011
			if ( isset( $data['plugin_settings'] ) ) {
2012
				foreach ( $data['plugin_settings'] as $key => $value ) {
2013
					delete_option( $value['option_name'] );
2014
				}
2015
			}
2016
		}
2017
2018
		// if the option says to, set all the imported fieldmaps to inactive.
2019
		$import_fieldmaps_inactive = isset( $_POST['import_fieldmaps_inactive'] ) ? esc_attr( $_POST['import_fieldmaps_inactive'] ) : '';
2020
		if ( true === filter_var( $import_fieldmaps_inactive, FILTER_VALIDATE_BOOLEAN ) ) {
2021
			if ( isset( $data['fieldmaps'] ) ) {
2022
				foreach ( $data['fieldmaps'] as $key => $fieldmap ) {
2023
					$data['fieldmaps'][ $key ]['fieldmap_status'] = 'inactive';
2024
				}
2025
			}
2026
		}
2027
2028
		$success = true;
2029
2030
		if ( isset( $data['fieldmaps'] ) ) {
2031
			$successful_fieldmaps = array();
2032
			$error_fieldmaps      = array();
2033
			foreach ( $data['fieldmaps'] as $fieldmap ) {
2034
				unset( $fieldmap['id'] );
2035
				$create = $this->mappings->create_fieldmap( $fieldmap );
2036
				if ( false === $create ) {
2037
					$success = false;
2038
				}
2039
				if ( false === $create ) {
2040
					$error_fieldmaps[] = $fieldmap;
2041
				} else {
2042
					$successful_fieldmaps[] = $create;
2043
				}
2044
			}
2045
		}
2046
2047
		if ( isset( $data['object_maps'] ) ) {
2048
			$successful_object_maps = array();
2049
			$error_object_maps      = array();
2050
			foreach ( $data['object_maps'] as $object_map ) {
2051
				unset( $object_map['id'] );
2052
				if ( isset( $object_map['object_type'] ) ) {
2053
					$sf_sync_trigger = $this->mappings->sync_sf_create;
2054
					$create          = $this->pull->salesforce_pull_process_records( $object_map['object_type'], $object_map['salesforce_id'], $sf_sync_trigger );
2055
				} else {
2056
					$create = $this->mappings->create_object_map( $object_map );
2057
				}
2058
				if ( false === $create ) {
2059
					$error_object_maps[] = $object_map;
2060
				} else {
2061
					$successful_object_maps[] = $create;
2062
				}
2063
			}
2064
		}
2065
2066
		if ( isset( $data['plugin_settings'] ) ) {
2067
			foreach ( $data['plugin_settings'] as $key => $value ) {
2068
				update_option( $value['option_name'], maybe_unserialize( $value['option_value'] ), $value['autoload'] );
2069
			}
2070
		}
2071
2072
		if ( ! empty( $error_fieldmaps ) && ! empty( $error_object_maps ) ) {
2073
			$status = 'error';
2074
			if ( isset( $this->logging ) ) {
2075
				$logging = $this->logging;
2076
			} elseif ( class_exists( 'Object_Sync_Sf_Logging' ) ) {
2077
				$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

2077
				$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...
2078
			}
2079
2080
			$body = sprintf( esc_html__( 'These are the import items that were not able to save: ', 'object-sync-for-salesforce' ) . '<ul>' );
2081
			foreach ( $error_fieldmaps as $fieldmap ) {
2082
				$body .= sprintf(
2083
					// translators: placeholders are: 1) the fieldmap row ID, 2) the Salesforce object type, 3) the WordPress object type.
2084
					'<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>',
2085
					isset( $fieldmap['id'] ) ? absint( $fieldmap['id'] ) : '',
2086
					esc_attr( $fieldmap['salesforce_object'] ),
2087
					esc_attr( $fieldmap['wordpress_object'] )
2088
				);
2089
			}
2090
			foreach ( $error_object_maps as $mapping_object ) {
2091
				$body .= sprintf(
2092
					// translators: placeholders are: 1) the mapping object row ID, 2) the ID of the Salesforce object, 3) the WordPress object type.
2093
					'<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>',
2094
					isset( $mapping_object['id'] ) ? absint( $mapping_object['id'] ) : '',
2095
					esc_attr( $mapping_object['salesforce_id'] ),
2096
					esc_attr( $mapping_object['wordpress_object'] )
2097
				);
2098
			}
2099
			$body .= sprintf( '</ul>' );
2100
2101
			$logging->setup(
2102
				sprintf(
2103
					// translators: %1$s is the log status.
2104
					esc_html__( '%1$s on import: some of the rows were unable to save. Read this post for details.', 'object-sync-for-salesforce' ),
2105
					ucfirst( esc_attr( $status ) )
2106
				),
2107
				$body,
2108
				0,
2109
				0,
2110
				$status
2111
			);
2112
		}
2113
2114
		if ( empty( $error_fieldmaps ) && empty( $error_object_maps ) && ( ! empty( $successful_fieldmaps ) || ! empty( $successful_object_maps ) ) ) {
2115
			$this->clear_cache( false );
2116
			wp_safe_redirect( get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=import-export&data_saved=true' ) );
2117
			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...
2118
		} elseif ( ! empty( $error_fieldmaps ) && ! empty( $successful_fieldmaps ) ) {
2119
			$this->clear_cache( false );
2120
			wp_safe_redirect( get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=import-export&data_saved=partial' ) );
2121
		} elseif ( ! empty( $error_object_maps ) && ! empty( $successful_object_maps ) ) {
2122
			$this->clear_cache( false );
2123
			wp_safe_redirect( get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=import-export&data_saved=partial' ) );
2124
		} else {
2125
			wp_safe_redirect( get_admin_url( null, 'options-general.php?page=' . $this->admin_settings_url_param . '&tab=import-export&data_saved=false' ) );
2126
			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...
2127
		}
2128
2129
	}
2130
2131
	/**
2132
	 * Create a json file for exporting
2133
	 */
2134
	public function export_json_file() {
2135
2136
		if ( ! wp_verify_nonce( $_POST['object_sync_for_salesforce_nonce_export'], 'object_sync_for_salesforce_nonce_export' ) ) {
2137
			return;
2138
		}
2139
		if ( ! current_user_can( 'manage_options' ) ) {
2140
			return;
2141
		}
2142
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
2143
		$export    = array();
2144
		if ( in_array( 'fieldmaps', $post_data['export'], true ) ) {
2145
			$export['fieldmaps'] = $this->mappings->get_fieldmaps();
2146
		}
2147
		if ( in_array( 'object_maps', $post_data['export'], true ) ) {
2148
			$export['object_maps'] = $this->mappings->get_all_object_maps();
2149
		}
2150
		if ( in_array( 'plugin_settings', $post_data['export'], true ) ) {
2151
			$wpdb                      = $this->wpdb;
2152
			$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 );
2153
			$export['plugin_settings'] = $export_results;
2154
		}
2155
		nocache_headers();
2156
		header( 'Content-Type: application/json; charset=utf-8' );
2157
		header( 'Content-Disposition: attachment; filename=object-sync-for-salesforce-data-export-' . gmdate( 'm-d-Y' ) . '.json' );
2158
		header( 'Expires: 0' );
2159
		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

2159
		echo /** @scrutinizer ignore-type */ wp_json_encode( $export );
Loading history...
2160
		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...
2161
	}
2162
2163
	/**
2164
	 * Default display for <input> fields
2165
	 *
2166
	 * @param array $args is the arguments to create the field.
2167
	 */
2168
	public function display_input_field( $args ) {
2169
		$type    = $args['type'];
2170
		$id      = $args['label_for'];
2171
		$name    = $args['name'];
2172
		$desc    = $args['desc'];
2173
		$checked = '';
2174
2175
		$class = 'regular-text';
2176
2177
		if ( 'checkbox' === $type ) {
2178
			$class = 'checkbox';
2179
		}
2180
2181
		if ( isset( $args['class'] ) ) {
2182
			$class = $args['class'];
2183
		}
2184
2185
		if ( ! isset( $args['constant'] ) || ! defined( $args['constant'] ) ) {
2186
			$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

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