Completed
Push — master ( 9b4ced...b50b7b )
by Jonathan
28s queued 13s
created

Object_Sync_Sf_Admin::version_options()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 3
nop 0
dl 0
loc 13
rs 9.6111
c 0
b 0
f 0
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
	 * Data for admin notices
138
	 *
139
	 * @var array
140
	 */
141
	public $notices_data;
142
143
	/**
144
	 * Salesforce access token
145
	 *
146
	 * @var string
147
	 */
148
	private $access_token;
149
150
	/**
151
	 * Salesforce instance URL
152
	 *
153
	 * @var string
154
	 */
155
	private $instance_url;
156
157
	/**
158
	 * Salesforce refresh token
159
	 *
160
	 * @var string
161
	 */
162
	private $refresh_token;
163
164
	/**
165
	 * Default path for the Salesforce authorize URL
166
	 *
167
	 * @var string
168
	 */
169
	public $default_authorize_url_path;
170
171
	/**
172
	 * Default path for the Salesforce token URL
173
	 *
174
	 * @var string
175
	 */
176
	public $default_token_url_path;
177
178
	/**
179
	 * What version of the Salesforce API should be the default on the settings screen.
180
	 * Users can edit what version is used, but they won't see a correct list of all their available versions until WordPress has
181
	 * been authenticated with Salesforce.
182
	 *
183
	 * @var string
184
	 * @deprecated as of 2.2.0; will be removed in version 3.0. This property will stay until 3.0.0 because it is a public value and it could be accessed by other code.
185
	 */
186
	public $default_api_version;
187
188
	/**
189
	 * Default max number of pull records. Users can edit this.
190
	 *
191
	 * @var int
192
	 */
193
	public $default_pull_limit;
194
195
	/**
196
	 * Default throttle for how often to pull from Salesforce. Users can edit this.
197
	 *
198
	 * @var int
199
	 */
200
	public $default_pull_throttle;
201
202
	/**
203
	 * Default for whether to limit to triggerable items. Users can edit this.
204
	 *
205
	 * @var bool
206
	 */
207
	public $default_triggerable;
208
209
	/**
210
	 * Default for whether to limit to items that can be updated. Users can edit this.
211
	 *
212
	 * @var bool
213
	 */
214
	public $default_updateable;
215
216
	/**
217
	 * Constructor for admin class
218
	 */
219
	public function __construct() {
220
		$this->version             = object_sync_for_salesforce()->version;
221
		$this->file                = object_sync_for_salesforce()->file;
222
		$this->wpdb                = object_sync_for_salesforce()->wpdb;
223
		$this->slug                = object_sync_for_salesforce()->slug;
224
		$this->option_prefix       = object_sync_for_salesforce()->option_prefix;
225
		$this->action_group_suffix = object_sync_for_salesforce()->action_group_suffix;
226
227
		$this->login_credentials   = object_sync_for_salesforce()->login_credentials;
228
		$this->wordpress           = object_sync_for_salesforce()->wordpress;
229
		$this->salesforce          = object_sync_for_salesforce()->salesforce;
230
		$this->mappings            = object_sync_for_salesforce()->mappings;
231
		$this->push                = object_sync_for_salesforce()->push;
232
		$this->pull                = object_sync_for_salesforce()->pull;
233
		$this->logging             = object_sync_for_salesforce()->logging;
234
		$this->schedulable_classes = object_sync_for_salesforce()->schedulable_classes;
235
		$this->queue               = object_sync_for_salesforce()->queue;
236
237
		// set the Salesforce API version.
238
		// as of version 2.2.0, this is set by the plugin and is not configurable in the interface.
239
		// this class variable will be removed in 3.0.0.
240
		$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. This property will stay until 3.0.0 because it is a public value and it could be accessed by other code. ( Ignorable by Annotation )

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

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

489
			$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...
490
		}
491
492
		$body = sprintf( esc_html__( 'These are the scheduled tasks that were updated: ', 'object-sync-for-salesforce' ) . '<ul>' );
493
		foreach ( $schedules_updated as $schedule_updated ) {
494
			$body .= sprintf(
495
				// translators: placeholders are: 1) the schedule name.
496
				'<li>' . esc_html__( 'Schedule name: %1$s.', 'object-sync-for-salesforce' ) . '</li>',
497
				esc_attr( $schedule_updated )
498
			);
499
		}
500
		$body .= '</ul>';
501
502
		$body .= sprintf( esc_html__( 'These are the scheduled tasks that have the same frequency as they had pre-migration: ', 'object-sync-for-salesforce' ) . '<ul>' );
503
		foreach ( $schedules_restored as $schedule_restored ) {
504
			$body .= sprintf(
505
				// translators: placeholders are: 1) the schedule name.
506
				'<li>' . esc_html__( 'Schedule name: %1$s.', 'object-sync-for-salesforce' ) . '</li>',
507
				esc_attr( $schedule_restored )
508
			);
509
		}
510
		$body .= '</ul>';
511
512
		$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' ) );
513
		$body .= sprintf(
514
			// translators: %1$s is the schedule settings URL.
515
			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

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

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

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

1700
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1701
			$ajax             = true;
1702
		}
1703
1704
		$object_fields = $this->wordpress->get_wordpress_object_fields( $wordpress_object );
1705
1706
		if ( true === $ajax ) {
1707
			$ajax_response = array(
1708
				'fields' => $object_fields,
1709
			);
1710
			wp_send_json_success( $ajax_response );
1711
		} else {
1712
			return $object_fields;
1713
		}
1714
	}
1715
1716
	/**
1717
	 * Manually push the WordPress object to Salesforce
1718
	 * This takes either the $_POST array via ajax, or can be directly called with $wordpress_object and $wordpress_id fields
1719
	 *
1720
	 * @param string $wordpress_object is the name of the WordPress object.
1721
	 * @param int    $wordpress_id is the ID of the WordPress record.
1722
	 * @param bool   $force_return Force the method to return json instead of outputting it.
1723
	 */
1724
	public function push_to_salesforce( $wordpress_object = '', $wordpress_id = '', $force_return = false ) {
1725
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1726
		if ( empty( $wordpress_object ) && empty( $wordpress_id ) ) {
1727
			$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

1727
			$wordpress_object = isset( $post_data['wordpress_object'] ) ? sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $post_data['wordpress_object'] ) ) : '';
Loading history...
1728
			$wordpress_id     = isset( $post_data['wordpress_id'] ) ? absint( $post_data['wordpress_id'] ) : '';
1729
		}
1730
1731
		// clarify what that variable is in this context.
1732
		$object_type = $wordpress_object;
1733
1734
		// When objects are already mapped, there is a Salesforce id as well. Otherwise, it's blank.
1735
		$salesforce_id = isset( $post_data['salesforce_id'] ) ? sanitize_text_field( $post_data['salesforce_id'] ) : '';
1736
		if ( '' === $salesforce_id ) {
1737
			$method = 'POST';
1738
		} else {
1739
			$method = 'PUT';
1740
		}
1741
1742
		$result = $this->push->manual_push( $object_type, $wordpress_id, $method );
1743
1744
		if ( false === $force_return && ! empty( $post_data['wordpress_object'] ) && ! empty( $post_data['wordpress_id'] ) ) {
1745
			wp_send_json_success( $result );
1746
		} else {
1747
			return $result;
1748
		}
1749
1750
	}
1751
1752
	/**
1753
	 * Manually pull the Salesforce object into WordPress
1754
	 * This takes either the $_POST array via ajax, or can be directly called with $salesforce_id fields
1755
	 *
1756
	 * @param string $salesforce_id is the ID of the Salesforce record.
1757
	 * @param string $wordpress_object is the name of the WordPress object.
1758
	 */
1759
	public function pull_from_salesforce( $salesforce_id = '', $wordpress_object = '' ) {
1760
		$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
1761
		if ( empty( $wordpress_object ) && empty( $salesforce_id ) ) {
1762
			$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

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

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

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

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

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

2177
		echo /** @scrutinizer ignore-type */ wp_json_encode( $export );
Loading history...
2178
		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...
2179
	}
2180
2181
	/**
2182
	 * Default display for <input> fields
2183
	 *
2184
	 * @param array $args is the arguments to create the field.
2185
	 */
2186
	public function display_input_field( $args ) {
2187
		$type    = $args['type'];
2188
		$id      = $args['label_for'];
2189
		$name    = $args['name'];
2190
		$desc    = $args['desc'];
2191
		$checked = '';
2192
2193
		$class = 'regular-text';
2194
2195
		if ( 'checkbox' === $type ) {
2196
			$class = 'checkbox';
2197
		}
2198
2199
		if ( isset( $args['class'] ) ) {
2200
			$class = $args['class'];
2201
		}
2202
2203
		if ( ! isset( $args['constant'] ) || ! defined( $args['constant'] ) ) {
2204
			$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

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