Completed
Push — 161-improved-credential-encryp... ( fc3bb4 )
by Armando
01:57
created

WP2D_Options   F

Complexity

Total Complexity 104

Size/Duplication

Total Lines 1095
Duplicated Lines 2.28 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 104
lcom 1
cbo 2
dl 25
loc 1095
rs 1.22
c 0
b 0
f 0

37 Methods

Rating   Name   Duplication   Size   Complexity  
A __clone() 0 2 1
A __construct() 0 2 1
A instance() 8 8 2
A _setup() 0 11 1
A _current_tab() 0 10 3
A _options_page_tabs() 0 28 5
C admin_options_page() 0 83 12
A is_pod_set_up() 0 3 3
A setup_wpadmin_pages() 0 11 1
A register_settings() 0 16 3
A setup_section() 0 12 1
B pod_render() 0 266 2
A username_render() 0 5 1
A password_render() 0 11 4
A defaults_section() 0 27 1
A post_types_render() 0 20 3
A post_to_diaspora_render() 0 6 2
A fullentrylink_render() 0 11 2
A display_render() 0 7 1
A tags_to_post_render() 0 21 3
A global_tags_render() 7 7 1
A custom_tags_render() 10 10 1
B aspects_services_render() 0 54 7
A get_option() 0 19 6
A get_options() 0 3 1
A set_option() 0 11 4
A save() 0 3 1
A get_valid_values() 0 5 2
A is_valid_value() 0 7 2
A attempt_password_upgrade() 0 9 3
B validate_settings() 0 69 8
A validate_checkboxes() 0 7 2
A validate_single_selects() 0 9 4
A validate_multi_selects() 0 16 5
A validate_tags() 0 11 1
A validate_tag() 0 5 1
A validate_aspects_services() 0 9 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WP2D_Options often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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

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

1
<?php
2
/**
3
 * Plugin Options.
4
 *
5
 * @package WP_To_Diaspora\Options
6
 * @since   1.3.0
7
 */
8
9
// Exit if accessed directly.
10
defined( 'ABSPATH' ) || exit;
11
12
/**
13
 * Class to manage the settings using the Settings API.
14
 */
15
class WP2D_Options {
16
17
	/**
18
	 * Only instance of this class.
19
	 *
20
	 * @var WP2D_Options
21
	 */
22
	private static $_instance;
23
24
	/**
25
	 * All default plugin options.
26
	 *
27
	 * @var array
28
	 */
29
	private static $_default_options = [
30
		'aspects_list'       => [],
31
		'services_list'      => [],
32
		'post_to_diaspora'   => true,
33
		'enabled_post_types' => [ 'post' ],
34
		'fullentrylink'      => true,
35
		'display'            => 'full',
36
		'tags_to_post'       => [ 'global', 'custom', 'post' ],
37
		'global_tags'        => '',
38
		'aspects'            => [ 'public' ],
39
		'services'           => [],
40
		'auth_key_hash'      => '',
41
		'version'            => WP2D_VERSION,
42
	];
43
44
	/**
45
	 * Valid values for select fields.
46
	 *
47
	 * @var array
48
	 */
49
	private static $_valid_values = [
50
		'display'      => [ 'full', 'excerpt', 'none' ],
51
		'tags_to_post' => [ 'global', 'custom', 'post' ],
52
	];
53
54
	/**
55
	 * All plugin options.
56
	 *
57
	 * @var array
58
	 */
59
	private static $_options;
60
61
	/** Singleton, keep private. */
62
	final private function __clone() {
63
	}
64
65
	/** Singleton, keep private. */
66
	final private function __construct() {
67
	}
68
69
	/**
70
	 * Create / Get the instance of this class.
71
	 *
72
	 * @return WP2D_Options Instance of this class.
73
	 */
74 View Code Duplication
	public static function instance() {
75
		if ( null === self::$_instance ) {
76
			self::$_instance = new self();
77
			self::$_instance->_setup();
78
		}
79
80
		return self::$_instance;
81
	}
82
83
	/**
84
	 * Set up the options menu.
85
	 */
86
	private function _setup() {
87
88
		// Populate options array.
89
		$this->get_option();
90
91
		// Setup Options page and Contextual Help.
92
		add_action( 'admin_menu', [ $this, 'setup_wpadmin_pages' ] );
93
94
		// Register all settings.
95
		add_action( 'admin_init', [ $this, 'register_settings' ] );
96
	}
97
98
99
	/**
100
	 * Get the currently selected tab.
101
	 *
102
	 * @todo Multi-level if statement to make it look prettier.
103
	 *
104
	 * @param string $default Tab to select if the current selection is invalid.
105
	 *
106
	 * @return string Return the currently selected tab.
107
	 */
108
	private function _current_tab( $default = 'defaults' ) {
109
		$tab = ( isset ( $_GET['tab'] ) ? $_GET['tab'] : $default );
110
111
		// If the pod settings aren't configured yet, open the 'Setup' tab.
112
		if ( ! $this->is_pod_set_up() ) {
113
			$tab = 'setup';
114
		}
115
116
		return $tab;
117
	}
118
119
	/**
120
	 * Output all options tabs and return an array of them all, if requested by $return.
121
	 *
122
	 * @param bool $return Define if the options tabs should be returned.
123
	 *
124
	 * @return array (If requested) An array of the outputted options tabs.
125
	 */
126
	private function _options_page_tabs( $return = false ) {
127
		// The array defining all options sections to be shown as tabs.
128
		$tabs = [];
129
		if ( $this->is_pod_set_up() ) {
130
			$tabs['defaults'] = __( 'Defaults', 'wp-to-diaspora' );
131
		}
132
133
		// Add the 'Setup' tab to the end of the list.
134
		$tabs['setup'] = __( 'Setup', 'wp-to-diaspora' ) . '<span id="pod-connection-status" class="dashicons-before hidden"></span><span class="spinner"></span>';
135
136
		// Container for all options tabs.
137
		$out = '<h2 id="options-tabs" class="nav-tab-wrapper">';
138
		foreach ( $tabs as $tab => $name ) {
139
			// The tab link.
140
			$out .= '<a class="nav-tab' . ( $this->_current_tab() === $tab ? ' nav-tab-active' : '' ) . '" href="?page=wp_to_diaspora&tab=' . $tab . '">' . $name . '</a>';
141
		}
142
		$out .= '</h2>';
143
144
		// Output the container with all tabs.
145
		echo $out;
146
147
		// Check if the tabs should be returned.
148
		if ( $return ) {
149
			return $tabs;
150
		}
151
152
		return [];
153
	}
154
155
156
	/**
157
	 * Set up admin options page.
158
	 */
159
	public function admin_options_page() {
160
		?>
161
		<div class="wrap">
162
			<h2>WP to diaspora*</h2>
163
164
			<div id="wp2d-message" class="notice hidden" <?php echo defined( 'WP2D_DEBUGGING' ) ? ' data-debugging' : ''; ?>></div>
165
166
			<?php
167
			// Check the connection status to diaspora.
168
			if ( ! $this->is_pod_set_up() ) {
169
				add_settings_error(
170
					'wp_to_diaspora_settings',
171
					'wp_to_diaspora_connected',
172
					__( 'First of all, set up the connection to your pod below.', 'wp-to-diaspora' ),
173
					'updated'
174
				);
175
			} else {
176
				// Get initial aspects list and connected services.
177
				// DON'T check for empty services list here!!
178
				// It could always be empty, resulting in this code being run every time the page is loaded.
179
				// The aspects will at least have a "Public" entry after the initial fetch.
180
				$aspects_list = $this->get_option( 'aspects_list' );
181
				if ( ( $force = get_transient( 'wp2d_no_js_force_refetch' ) ) || empty( $aspects_list ) ) {
182
183
					// Set up the connection to diaspora*.
184
					$api = WP2D_Helpers::api_quick_connect();
185
					if ( ! $api->has_last_error() ) {
186
						// Get the loaded aspects.
187
						if ( is_array( $aspects = $api->get_aspects() ) ) {
188
							// Save the new list of aspects.
189
							$this->set_option( 'aspects_list', $aspects );
190
						}
191
192
						// Get the loaded services.
193
						if ( is_array( $services = $api->get_services() ) ) {
194
							// Save the new list of services.
195
							$this->set_option( 'services_list', $services );
196
						}
197
198
						$this->save();
199
					}
200
201
					if ( $force ) {
202
						delete_transient( 'wp2d_no_js_force_refetch' );
203
						$message = ( ! $api->has_last_error() ) ? __( 'Connection successful.', 'wp-to-diaspora' ) : $api->get_last_error();
204
						add_settings_error(
205
							'wp_to_diaspora_settings',
206
							'wp_to_diaspora_connected',
207
							$message,
208
							$api->has_last_error() ? 'error' : 'updated'
209
						);
210
					}
211
				}
212
			}
213
214
			// Output success or error message.
215
			settings_errors( 'wp_to_diaspora_settings' );
216
			?>
217
218
			<?php $page_tabs = array_keys( $this->_options_page_tabs( true ) ); ?>
219
220
			<form action="<?php echo admin_url( 'options.php' ); ?>" method="post">
221
				<input id="wp2d_no_js" type="hidden" name="wp_to_diaspora_settings[no_js]" value="1">
222
				<?php
223
				// Load the settings fields.
224
				settings_fields( 'wp_to_diaspora_settings' );
225
				do_settings_sections( 'wp_to_diaspora_settings' );
226
227
				// Get the name of the current tab, if set, else take the first one from the list.
228
				$tab = $this->_current_tab( $page_tabs[0] );
229
230
				// Add Save and Reset buttons.
231
				echo '<input id="submit-' . esc_attr( $tab ) . '" name="wp_to_diaspora_settings[submit_' . esc_attr( $tab ) . ']" type="submit" class="button-primary" value="' . esc_attr__( 'Save Changes' ) . '" />&nbsp;';
232
				if ( 'setup' !== $tab ) {
233
					echo '<input id="reset-' . esc_attr( $tab ) . '" name="wp_to_diaspora_settings[reset_' . esc_attr( $tab ) . ']" type="submit" class="button-secondary" value="' . esc_attr__( 'Reset Defaults', 'wp-to-diaspora' ) . '" />';
234
				}
235
				?>
236
237
			</form>
238
		</div>
239
240
		<?php
241
	}
242
243
	/**
244
	 * Return if the settings for the pod setup have been entered.
245
	 *
246
	 * @return bool If the setup for the pod has been done.
247
	 */
248
	public function is_pod_set_up() {
249
		return ( $this->get_option( 'pod' ) && $this->get_option( 'username' ) && $this->get_option( 'password' ) );
250
	}
251
252
	/**
253
	 * Setup Contextual Help and Options pages.
254
	 */
255
	public function setup_wpadmin_pages() {
256
		// Add options page.
257
		$hook = add_options_page( 'WP to diaspora*', 'WP to diaspora*', 'manage_options', 'wp_to_diaspora', [ $this, 'admin_options_page' ] );
258
259
		// Setup the contextual help menu after the options page has been loaded.
260
		add_action( 'load-' . $hook, [ 'WP2D_Contextual_Help', 'instance' ] );
261
262
		// Setup the contextual help menu tab for post types. Checks are made there!
263
		add_action( 'load-post.php', [ 'WP2D_Contextual_Help', 'instance' ] );
264
		add_action( 'load-post-new.php', [ 'WP2D_Contextual_Help', 'instance' ] );
265
	}
266
267
	/**
268
	 * Initialise the settings sections and fields of the currently selected tab.
269
	 */
270
	public function register_settings() {
271
		// Register the settings with validation callback.
272
		register_setting( 'wp_to_diaspora_settings', 'wp_to_diaspora_settings', [ $this, 'validate_settings' ] );
273
274
		// Load only the sections of the selected tab.
275
		switch ( $this->_current_tab() ) {
276
			case 'defaults' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
277
				// Add a "Defaults" section that contains all posting settings to be used by default.
278
				add_settings_section( 'wp_to_diaspora_defaults_section', __( 'Posting Defaults', 'wp-to-diaspora' ), [ $this, 'defaults_section' ], 'wp_to_diaspora_settings' );
279
				break;
280
			case 'setup' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
281
				// Add a "Setup" section that contains the Pod domain, Username and Password.
282
				add_settings_section( 'wp_to_diaspora_setup_section', __( 'diaspora* Setup', 'wp-to-diaspora' ), [ $this, 'setup_section' ], 'wp_to_diaspora_settings' );
283
				break;
284
		}
285
	}
286
287
288
	/**
289
	 * Callback for the "Setup" section.
290
	 */
291
	public function setup_section() {
292
		esc_html_e( 'Set up the connection to your diaspora* account.', 'wp-to-diaspora' );
293
294
		// Pod entry field.
295
		add_settings_field( 'pod', __( 'Diaspora* Pod', 'wp-to-diaspora' ), [ $this, 'pod_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_setup_section' );
296
297
		// Username entry field.
298
		add_settings_field( 'username', __( 'Username' ), [ $this, 'username_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_setup_section' );
299
300
		// Password entry field.
301
		add_settings_field( 'password', __( 'Password' ), [ $this, 'password_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_setup_section' );
302
	}
303
304
	/**
305
	 * Render the "Pod" field.
306
	 */
307
	public function pod_render() {
308
		// Update entries: curl -G 'https://the-federation.info/graphql?raw' --data-urlencode 'query={nodes(platform:"diaspora"){host}}' | jq '.data.nodes[].host'
309
310
		$pod_list = [
311
			'a.grumpy.world',
312
			'amiensdiaspora.fr',
313
			'azazel.ultragreen.net',
314
			'berlinspora.de',
315
			'blue.skye.cx',
316
			'bobspora.com',
317
			'brighton.social',
318
			'catpod.cat.scot',
319
			'community.sandtler.club',
320
			'd.aheapofseconds.com',
321
			'd.consumium.org',
322
			'd.kretschmann.social',
323
			'dame.life',
324
			'datataffel.dk',
325
			'deko.cloud',
326
			'despora.de',
327
			'dia.ax9.eu',
328
			'dia.gelse.eu',
329
			'dia.goexchange.de',
330
			'dia.gordons.gen.nz',
331
			'dia.sh',
332
			'dia.z-gears.com',
333
			'diapod.net',
334
			'diapod.org',
335
			'diasp.ca',
336
			'diasp.cz',
337
			'diasp.de',
338
			'diasp.eu',
339
			'diasp.eu.com',
340
			'diasp.in',
341
			'diasp.nl',
342
			'diasp.org',
343
			'diasp.pl',
344
			'diasp.sk',
345
			'diaspod.de',
346
			'diaspora-fr.org',
347
			'diaspora.1312.media',
348
			'diaspora.abendstille.at',
349
			'diaspora.animalnet.de',
350
			'diaspora.asrun.eu',
351
			'diaspora.baucum.me',
352
			'diaspora.boris.syncloud.it',
353
			'diaspora.by5.de',
354
			'diaspora.com.ar',
355
			'diaspora.conxtor.com',
356
			'diaspora.counterspin.org.nz',
357
			'diaspora.crossfamilyweb.com',
358
			'diaspora.cyber-tribal.com',
359
			'diaspora.digi-merc.org',
360
			'diaspora.ennder.fr',
361
			'diaspora.flidais.net',
362
			'diaspora.flyar.net',
363
			'diaspora.gegeweb.eu',
364
			'diaspora.gianforte.org',
365
			'diaspora.goethe12.de',
366
			'diaspora.goethe20.de',
367
			'diaspora.hofud.com',
368
			'diaspora.immae.eu',
369
			'diaspora.in.ua',
370
			'diaspora.kajalinifi.de',
371
			'diaspora.kapper.net',
372
			'diaspora.koehn.com',
373
			'diaspora.laka.lv',
374
			'diaspora.lebarjack.com',
375
			'diaspora.levity.nl',
376
			'diaspora.libertais.org',
377
			'diaspora.mathematicon.com',
378
			'diaspora.microdata.co.uk',
379
			'diaspora.mifritscher.de',
380
			'diaspora.monovs.com',
381
			'diaspora.normandie-libre.fr',
382
			'diaspora.otherreality.net',
383
			'diaspora.pades-asso.fr',
384
			'diaspora.permutationsofchaos.com',
385
			'diaspora.pingupod.de',
386
			'diaspora.psyco.fr',
387
			'diaspora.raven-ip.com',
388
			'diaspora.ruhrmail.de',
389
			'diaspora.sceal.ie',
390
			'diaspora.schoenf.de',
391
			'diaspora.skewed.de',
392
			'diaspora.snakenode.eu',
393
			'diaspora.softwarelivre.org',
394
			'diaspora.solusar.de',
395
			'diaspora.somethinghub.com',
396
			'diaspora.subsignal.org',
397
			'diaspora.the-penguin.de',
398
			'diaspora.thus.ch',
399
			'diaspora.totg.fr',
400
			'diaspora.town',
401
			'diaspora.tribblefamily.org',
402
			'diaspora.u4u.org',
403
			'diaspora.vrije-mens.org',
404
			'diaspora.walkingmountains.fr',
405
			'diaspora.weidestraat.nl',
406
			'diaspora.wfn.reisen',
407
			'diaspora.williamsonday.org',
408
			'diaspora.wollner.za.net',
409
			'diasporabr.com.br',
410
			'diasporabrazil.org',
411
			'diasporanederland.nl',
412
			'diasporapod.no',
413
			'diasporasocial.net',
414
			'diasporing.ch',
415
			'diaspote.org',
416
			'diaspsocial.com',
417
			'dorf-post.de',
418
			'dspr.geekshell.fr',
419
			'dspr.io',
420
			'ege.land',
421
			'espora.com.es',
422
			'expod.de',
423
			'failure.net',
424
			'fb.irc.org.my',
425
			'flokk.no',
426
			'framasphere.org',
427
			'freehuman.fr',
428
			'friendsmeet.win',
429
			'gesichtsbu.ch',
430
			'gnc.thepew.life',
431
			'hanksdiasporadevtestpod.com',
432
			'huyakhuyak.online',
433
			'iitians.xyz',
434
			'iliketoast.net',
435
			'ingtech.net',
436
			'jardin.umaneti.net',
437
			'jochem.name',
438
			'joindiaspora.com',
439
			'jons.gr',
440
			'kapok.se',
441
			'kentshikama.com',
442
			'kitsune.click',
443
			'lanx.fr',
444
			'libdi.net',
445
			'librenet.gr',
446
			'livepods.net',
447
			'lstoll-diaspora.herokuapp.com',
448
			'mindfeed.herokuapp.com',
449
			'mondiaspora.net',
450
			'my.wallmeier.info',
451
			'nerdpol.ch',
452
			'nota.404.mn',
453
			'pekospora.pekoyama.com',
454
			'phreedom.tk',
455
			'plusfriends.net',
456
			'pluspora.com',
457
			'pod.aevl.us',
458
			'pod.afox.me',
459
			'pod.alhague.net',
460
			'pod.asap-soft.com',
461
			'pod.athrandironline.com',
462
			'pod.beautifulmathuncensored.de',
463
			'pod.braitbart.net',
464
			'pod.brief.guru',
465
			'pod.buergi.lugs.ch',
466
			'pod.buffalopugs.org',
467
			'pod.chabotsi.fr',
468
			'pod.cyberdungeon.de',
469
			'pod.dapor.net',
470
			'pod.ddna.co',
471
			'pod.diaspora.la',
472
			'pod.diaspora.software',
473
			'pod.digitac.cc',
474
			'pod.dirkomatik.de',
475
			'pod.disroot.org',
476
			'pod.dukun.de',
477
			'pod.fab-l3.org',
478
			'pod.ferner-online.de',
479
			'pod.fulll.name',
480
			'pod.g3l.org',
481
			'pod.geraspora.de',
482
			'pod.gothic.net.au',
483
			'pod.haxxors.com',
484
			'pod.hoizi.net',
485
			'pod.interlin.nl',
486
			'pod.itabs.nl',
487
			'pod.jamidisi-edu.de',
488
			'pod.jeweet.net',
489
			'pod.jns.im',
490
			'pod.jpope.org',
491
			'pod.karlsborg.de',
492
			'pod.kneedrag.org',
493
			'pod.libreplanetbr.org',
494
			'pod.mesdonnees.ch',
495
			'pod.mttv.it',
496
			'pod.nomorestars.com',
497
			'pod.omgsrsly.net',
498
			'pod.orkz.net',
499
			'pod.pc-tiede.de',
500
			'pod.polichinel.nl',
501
			'pod.psynet.su',
502
			'pod.qarte.de',
503
			'pod.reckord.de',
504
			'pod.redfish.ca',
505
			'pod.sfunk1x.com',
506
			'pod.storel.li',
507
			'pod.tchncs.de',
508
			'pod.thing.org',
509
			'pod.thomasdalichow.de',
510
			'pod.togart.de',
511
			'pod.tsumi.it',
512
			'pod2.hanksdiasporadevtestpod.com',
513
			'podbay.net',
514
			'podricing.pw',
515
			'podverdorie.mrbim.nl',
516
			'protagio.social',
517
			'pubpod.alqualonde.org',
518
			'revreso.de',
519
			'rhizome-dev.ezpz.io',
520
			'rhizome.hfbk.net',
521
			'ruhrspora.de',
522
			'russiandiaspora.org',
523
			'sechat.org',
524
			'share.naturalnews.com',
525
			'shoosh.de',
526
			'shrekislove.us',
527
			'social.antisthenes.org',
528
			'social.cloudair.org',
529
			'social.cooleysekula.net',
530
			'social.daxbau.net',
531
			'social.elinux.org',
532
			'social.gibberfish.org',
533
			'social.hauspie.fr',
534
			'social.hiernaux.eu',
535
			'social.hugin-hosting.de',
536
			'social.mbuto.me',
537
			'social.mrzyx.de',
538
			'social.sumptuouscapital.com',
539
			'social.tethered-node.de',
540
			'social.tftsr.com',
541
			'sphere.libratoi.fr',
542
			'spyurk.am',
543
			'subvillage.de',
544
			'swecha.social',
545
			'sysad.org',
546
			'the-dank.com',
547
			'tippentappen.de',
548
			'titis.xyz',
549
			'treehouse.pub',
550
			'tyrell.marway.org',
551
			'vecinoscde.org',
552
			'verge.info.tm',
553
			'weare.boldlygoingnowhere.org',
554
			'webthinking.de',
555
			'wertier.de',
556
			'wgserv.eu',
557
			'whatsnewz.com',
558
			'wk3.org',
559
			'www.diasporaix.de',
560
			'www.geekbrigade.co.uk',
561
			'xn--b1aafeapdba0ada1es.xn--p1ai',
562
			'zxc.st',
563
		];
564
		?>
565
		https://<input type="text" name="wp_to_diaspora_settings[pod]" value="<?php echo esc_attr( $this->get_option( 'pod' ) ); ?>" placeholder="e.g. joindiaspora.com" autocomplete="on" list="pod-list" required>
566
		<datalist id="pod-list">
567
			<?php foreach ( $pod_list as $pod ) : ?>
568
				<option value="<?php echo esc_attr( $pod ); ?>"></option>
569
			<?php endforeach; ?>
570
		</datalist>
571
		<?php
572
	}
573
574
	/**
575
	 * Render the "Username" field.
576
	 */
577
	public function username_render() {
578
		?>
579
		<input type="text" name="wp_to_diaspora_settings[username]" value="<?php echo esc_attr( $this->get_option( 'username' ) ); ?>" placeholder="<?php esc_attr_e( 'Username' ); ?>" required>
580
		<?php
581
	}
582
583
	/**
584
	 * Render the "Password" field.
585
	 */
586
	public function password_render() {
587
		// Special case if we already have a password.
588
		$has_password = ( '' !== $this->get_option( 'password', '' ) );
589
		$placeholder  = $has_password ? __( 'Password already set.', 'wp-to-diaspora' ) : __( 'Password' );
590
		$required     = $has_password ? '' : ' required';
591
		?>
592
		<input type="password" name="wp_to_diaspora_settings[password]" value="" placeholder="<?php echo esc_attr( $placeholder ); ?>"<?php echo esc_attr( $required ); ?>>
593
		<?php if ( $has_password ) : ?>
594
			<p class="description"><?php esc_html_e( 'If you would like to change the password type a new one. Otherwise leave this blank.', 'wp-to-diaspora' ); ?></p>
595
		<?php endif;
596
	}
597
598
599
	/**
600
	 * Callback for the "Defaults" section.
601
	 */
602
	public function defaults_section() {
603
		esc_html_e( 'Define the default posting behaviour for all posts here. These settings can be modified for each post individually, by changing the values in the "WP to diaspora*" meta box, which gets displayed in your post edit screen.', 'wp-to-diaspora' );
604
605
		// Post types field.
606
		add_settings_field( 'enabled_post_types', __( 'Post types', 'wp-to-diaspora' ), [ $this, 'post_types_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section' );
607
608
		// Post to diaspora* checkbox.
609
		add_settings_field( 'post_to_diaspora', __( 'Post to diaspora*', 'wp-to-diaspora' ), [ $this, 'post_to_diaspora_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section', $this->get_option( 'post_to_diaspora' ) );
610
611
		// Full entry link checkbox.
612
		add_settings_field( 'fullentrylink', __( 'Show "Posted at" link?', 'wp-to-diaspora' ), [ $this, 'fullentrylink_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section', $this->get_option( 'fullentrylink' ) );
613
614
		// Full text, excerpt or none radio buttons.
615
		add_settings_field( 'display', __( 'Display', 'wp-to-diaspora' ), [ $this, 'display_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section', $this->get_option( 'display' ) );
616
617
		// Tags to post dropdown.
618
		add_settings_field( 'tags_to_post', __( 'Tags to post', 'wp-to-diaspora' ), [ $this, 'tags_to_post_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section', $this->get_option( 'tags_to_post', 'gc' ) );
619
620
		// Global tags field.
621
		add_settings_field( 'global_tags', __( 'Global tags', 'wp-to-diaspora' ), [ $this, 'global_tags_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section', $this->get_option( 'global_tags' ) );
622
623
		// Aspects checkboxes.
624
		add_settings_field( 'aspects', __( 'Aspects', 'wp-to-diaspora' ), [ $this, 'aspects_services_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section', [ 'aspects', $this->get_option( 'aspects' ) ] );
625
626
		// Services checkboxes.
627
		add_settings_field( 'services', __( 'Services', 'wp-to-diaspora' ), [ $this, 'aspects_services_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section', [ 'services', $this->get_option( 'services' ) ] );
628
	}
629
630
	/**
631
	 * Render the "Post types" checkboxes.
632
	 */
633
	public function post_types_render() {
634
		$post_types = get_post_types( [ 'public' => true ], 'objects' );
635
636
		// Remove excluded post types from the list.
637
		$excluded_post_types = [ 'attachment', 'nav_menu_item', 'revision' ];
638
		foreach ( $excluded_post_types as $excluded ) {
639
			unset( $post_types[ $excluded ] );
640
		}
641
		?>
642
643
		<select id="enabled-post-types" multiple data-placeholder="<?php esc_attr_e( 'None', 'wp-to-diaspora' ); ?>" class="chosen" name="wp_to_diaspora_settings[enabled_post_types][]">
644
			<?php foreach ( $post_types as $post_type ) : ?>
645
				<option value="<?php echo esc_attr( $post_type->name ); ?>" <?php selected( in_array( $post_type->name, $this->get_option( 'enabled_post_types' ), true ) ); ?>><?php echo esc_html( $post_type->label ); ?></option>
646
			<?php endforeach; ?>
647
		</select>
648
649
		<p class="description"><?php esc_html_e( 'Choose which post types can be posted to diaspora*.', 'wp-to-diaspora' ); ?></p>
650
651
		<?php
652
	}
653
654
	/**
655
	 * Render the "Post to diaspora*" checkbox.
656
	 *
657
	 * @param bool $post_to_diaspora If this checkbox is checked or not.
658
	 */
659
	public function post_to_diaspora_render( $post_to_diaspora ) {
660
		$label = ( 'settings_page_wp_to_diaspora' === get_current_screen()->id ) ? __( 'Yes' ) : __( 'Post to diaspora*', 'wp-to-diaspora' );
661
		?>
662
		<label><input type="checkbox" id="post-to-diaspora" name="wp_to_diaspora_settings[post_to_diaspora]" value="1" <?php checked( $post_to_diaspora ); ?>><?php echo esc_html( $label ); ?></label>
663
		<?php
664
	}
665
666
	/**
667
	 * Render the "Show 'Posted at' link" checkbox.
668
	 *
669
	 * @param bool $show_link If the checkbox is checked or not.
670
	 */
671
	public function fullentrylink_render( $show_link ) {
672
		$description = __( 'Include a link back to your original post.', 'wp-to-diaspora' );
673
		$checkbox    = '<input type="checkbox" id="fullentrylink" name="wp_to_diaspora_settings[fullentrylink]" value="1"' . checked( $show_link, true, false ) . '>';
674
675
		if ( 'settings_page_wp_to_diaspora' === get_current_screen()->id ) : ?>
676
			<label><?php echo $checkbox; ?><?php esc_html_e( 'Yes' ); ?></label>
677
			<p class="description"><?php echo esc_html( $description ); ?></p>
678
		<?php else : ?>
679
			<label title="<?php echo esc_attr( $description ); ?>"><?php echo $checkbox; ?><?php esc_html_e( 'Show "Posted at" link?', 'wp-to-diaspora' ); ?></label>
680
		<?php endif;
681
	}
682
683
	/**
684
	 * Render the "Display" radio buttons.
685
	 *
686
	 * @param string $display The selected radio button.
687
	 */
688
	public function display_render( $display ) {
689
		?>
690
		<label><input type="radio" name="wp_to_diaspora_settings[display]" value="full" <?php checked( $display, 'full' ); ?>><?php esc_html_e( 'Full Post', 'wp-to-diaspora' ); ?></label><br/>
691
		<label><input type="radio" name="wp_to_diaspora_settings[display]" value="excerpt" <?php checked( $display, 'excerpt' ); ?>><?php esc_html_e( 'Excerpt' ); ?></label><br/>
692
		<label><input type="radio" name="wp_to_diaspora_settings[display]" value="none" <?php checked( $display, 'none' ); ?>><?php esc_html_e( 'None' ); ?></label>
693
		<?php
694
	}
695
696
	/**
697
	 * Render the "Tags to post" field.
698
	 *
699
	 * @param array $tags_to_post The types of tags to be posted.
700
	 */
701
	public function tags_to_post_render( $tags_to_post ) {
702
		$on_settings_page = ( 'settings_page_wp_to_diaspora' === get_current_screen()->id );
703
		$description      = esc_html__( 'Choose which tags should be posted to diaspora*.', 'wp-to-diaspora' );
704
705
		if ( ! $on_settings_page ) {
706
			echo '<label>' . esc_html( $description );
707
		}
708
709
		?>
710
		<select id="tags-to-post" multiple data-placeholder="<?php esc_attr_e( 'No tags', 'wp-to-diaspora' ); ?>" class="chosen" name="wp_to_diaspora_settings[tags_to_post][]">
711
			<option value="global" <?php selected( in_array( 'global', $tags_to_post, true ) ); ?>><?php esc_html_e( 'Global tags', 'wp-to-diaspora' ); ?></option>
712
			<option value="custom" <?php selected( in_array( 'custom', $tags_to_post, true ) ); ?>><?php esc_html_e( 'Custom tags', 'wp-to-diaspora' ); ?></option>
713
			<option value="post" <?php selected( in_array( 'post', $tags_to_post, true ) ); ?>><?php esc_html_e( 'Post tags', 'wp-to-diaspora' ); ?></option>
714
		</select>
715
716
		<?php if ( $on_settings_page ) : ?>
717
			<p class="description"><?php echo esc_html( $description ); ?></p>
718
		<?php else : ?>
719
			</label>
720
		<?php endif;
721
	}
722
723
	/**
724
	 * Render the "Global tags" field.
725
	 *
726
	 * @param array $tags The global tags to be posted.
727
	 */
728 View Code Duplication
	public function global_tags_render( $tags ) {
729
		WP2D_Helpers::arr_to_str( $tags );
730
		?>
731
		<input type="text" class="regular-text wp2d-tags" name="wp_to_diaspora_settings[global_tags]" value="<?php echo esc_attr( $tags ); ?>" placeholder="<?php esc_attr_e( 'Global tags', 'wp-to-diaspora' ); ?>">
732
		<p class="description"><?php esc_html_e( 'Custom tags to add to all posts being posted to diaspora*.', 'wp-to-diaspora' ); ?></p>
733
		<?php
734
	}
735
736
	/**
737
	 * Render the "Custom tags" field.
738
	 *
739
	 * @param array $tags The custom tags to be posted.
740
	 */
741 View Code Duplication
	public function custom_tags_render( $tags ) {
742
		WP2D_Helpers::arr_to_str( $tags );
743
		?>
744
		<label title="<?php esc_attr_e( 'Custom tags to add to this post when it\'s posted to diaspora*.', 'wp-to-diaspora' ); ?>">
745
			<?php esc_html_e( 'Custom tags', 'wp-to-diaspora' ); ?>
746
			<input type="text" class="widefat wp2d-tags" name="wp_to_diaspora_settings[custom_tags]" value="<?php echo esc_attr( $tags ); ?>">
747
		</label>
748
		<p class="description"><?php esc_html_e( 'Separate tags with commas' ); ?></p>
749
		<?php
750
	}
751
752
	/**
753
	 * Render the "Aspects" and "Services" checkboxes.
754
	 *
755
	 * @param array $args Array containing the type and items to output as checkboxes.
756
	 */
757
	public function aspects_services_render( $args ) {
758
		list( $type, $items ) = $args;
759
760
		// This is where the 2 types show their differences.
761
		switch ( $type ) {
762
			case 'aspects':
763
				$refresh_button = __( 'Refresh Aspects', 'wp-to-diaspora' );
764
				$description    = esc_html__( 'Choose which aspects to share to.', 'wp-to-diaspora' );
765
				$empty_label    = '<input type="checkbox" name="wp_to_diaspora_settings[aspects][]" value="public" checked="checked">' . esc_html__( 'Public' );
766
				break;
767
768
			case 'services':
769
				$refresh_button = __( 'Refresh Services', 'wp-to-diaspora' );
770
				$description    = sprintf(
771
					'%1$s<br><a href="%2$s" target="_blank">%3$s</a>',
772
					esc_html__( 'Choose which services to share to.', 'wp-to-diaspora' ),
773
					esc_url( 'https://' . $this->get_option( 'pod' ) . '/services' ),
774
					esc_html__( 'Show available services on my pod.', 'wp-to-diaspora' )
775
				);
776
				$empty_label    = esc_html__( 'No services connected yet.', 'wp-to-diaspora' );
777
				break;
778
779
			default:
780
				return;
781
		}
782
783
		$items = array_filter( (array) $items ) ?: [];
784
785
		// Special case for this field if it's displayed on the settings page.
786
		$on_settings_page = ( 'settings_page_wp_to_diaspora' === get_current_screen()->id );
787
788
		if ( ! $on_settings_page ) {
789
			echo $description;
790
			$description = '';
791
		}
792
793
		?>
794
		<div id="<?php echo esc_attr( $type ); ?>-container" data-<?php echo esc_attr( $type ); ?>-selected="<?php echo esc_attr( implode( ',', $items ) ); ?>">
795
			<?php if ( $list = (array) $this->get_option( $type . '_list' ) ) : ?>
796
				<?php foreach ( $list as $id => $name ) : ?>
797
					<label><input type="checkbox" name="wp_to_diaspora_settings[<?php echo esc_attr( $type ); ?>][]" value="<?php echo esc_attr( $id ); ?>" <?php checked( in_array( $id, $items ) ); ?>><?php echo esc_html( $name ); ?></label>
798
				<?php endforeach; ?>
799
			<?php else : ?>
800
				<label><?php echo $empty_label; ?></label>
801
			<?php endif; ?>
802
		</div>
803
		<p class="description">
804
			<?php echo $description; ?>
805
			<a id="refresh-<?php echo esc_attr( $type ); ?>-list" class="button hide-if-no-js"><?php echo esc_html( $refresh_button ); ?></a>
806
			<span class="spinner"></span>
807
			<span class="hide-if-js"><?php printf( esc_html_x( 'To update this list, %sre-save your login info%s.', 'placeholders are link tags to the settings page.', 'wp-to-diaspora' ), '<a href="' . admin_url( 'options-general.php?page=wp_to_diaspora' ) . '&amp;tab=setup" target="_blank">', '</a>' ); ?></span>
808
		</p>
809
		<?php
810
	}
811
812
813
	/**
814
	 * Get a specific option.
815
	 *
816
	 * @param null|string $option  ID of option to get.
817
	 * @param mixed       $default Override default value if option not found.
818
	 *
819
	 * @return mixed Requested option value.
820
	 */
821
	public function get_option( $option = null, $default = null ) {
822
		if ( null === self::$_options ) {
823
			self::$_options = get_option( 'wp_to_diaspora_settings', self::$_default_options );
824
		}
825
		if ( null !== $option ) {
826
			if ( isset( self::$_options[ $option ] ) ) {
827
				// Return found option value.
828
				return self::$_options[ $option ];
829
			} elseif ( null !== $default ) {
830
				// Return overridden default value.
831
				return $default;
832
			} elseif ( isset( self::$_default_options[ $option ] ) ) {
833
				// Return default option value.
834
				return self::$_default_options[ $option ];
835
			}
836
		}
837
838
		return $default;
839
	}
840
841
	/**
842
	 * Get all options.
843
	 *
844
	 * @return array All the options.
845
	 */
846
	public function get_options() {
847
		return self::$_options;
848
	}
849
850
	/**
851
	 * Set a certain option.
852
	 *
853
	 * @param string       $option ID of option to get.
854
	 * @param array|string $value  Value to be set for the passed option.
855
	 * @param bool         $save   Save the options immediately after setting them.
856
	 */
857
	public function set_option( $option, $value, $save = false ) {
858
		if ( null !== $option ) {
859
			if ( null !== $value ) {
860
				self::$_options[ $option ] = $value;
861
			} else {
862
				unset( self::$_options[ $option ] );
863
			}
864
		}
865
866
		$save && $this->save();
867
	}
868
869
	/**
870
	 * Save the options.
871
	 */
872
	public function save() {
873
		update_option( 'wp_to_diaspora_settings', self::$_options );
874
	}
875
876
	/**
877
	 * Get all valid input values for the passed field.
878
	 *
879
	 * @param string $field Field to get the valid values for.
880
	 *
881
	 * @return array List of valid values.
882
	 */
883
	public function get_valid_values( $field ) {
884
		if ( array_key_exists( $field, self::$_valid_values ) ) {
885
			return self::$_valid_values[ $field ];
886
		}
887
	}
888
889
	/**
890
	 * Check if a value is valid for the passed field.
891
	 *
892
	 * @param string $field Field to check the valid value for.
893
	 * @param mixed  $value Value to check validity.
894
	 *
895
	 * @return bool If the passed value is valid.
896
	 */
897
	public function is_valid_value( $field, $value ) {
898
		if ( $valids = $this->get_valid_values( $field ) ) {
899
			return in_array( $value, $valids, true );
900
		}
901
902
		return false;
903
	}
904
905
	/**
906
	 * Attempt to upgrade the password from using AUTH_KEY to using WP2D_AUTH_KEY.
907
	 *
908
	 * @since 2.2.0
909
	 *
910
	 * @param bool $save
911
	 */
912
	public function attempt_password_upgrade( $save = false ) {
913
		if ( AUTH_KEY !== WP2D_ENC_KEY ) {
914
			$old_pw = WP2D_Helpers::decrypt( (string) $this->get_option( 'password' ), AUTH_KEY );
915
			if ( $old_pw !== null ) {
916
				$new_pw = WP2D_Helpers::encrypt( $old_pw, WP2D_ENC_KEY );
917
				$this->set_option( 'password', $new_pw, $save );
918
			}
919
		}
920
	}
921
922
	/**
923
	 * Validate all settings.
924
	 *
925
	 * @param array $input RAW input values.
926
	 *
927
	 * @return array Validated input values.
928
	 */
929
	public function validate_settings( $input ) {
930
		/* Validate all settings before saving to the database. */
931
932
		// Saving the pod setup details.
933
		if ( isset( $input['submit_setup'] ) ) {
934
			$input['pod']      = trim( sanitize_text_field( $input['pod'] ), ' /' );
935
			$input['username'] = sanitize_text_field( $input['username'] );
936
			$input['password'] = sanitize_text_field( $input['password'] );
937
938
			// If password is blank, it hasn't been changed.
939
			// If new password is equal to the encrypted password already saved, it was just passed again. It happens everytime update_option('wp_to_diaspora_settings') is called.
940
			if ( '' === $input['password'] || $this->get_option( 'password' ) === $input['password'] ) {
941
				// Attempt a password upgrade if applicable.
942
				$this->attempt_password_upgrade( true );
943
				$input['password'] = $this->get_option( 'password' );
944
			} else {
945
				$input['password'] = WP2D_Helpers::encrypt( $input['password'] );
946
			}
947
948
			// Keep a note of the current AUTH_KEY.
949
			$this->set_option( 'auth_key_hash', md5( AUTH_KEY ), true );
950
951
			// This is for when JS in not enabled, to make sure that the aspects and services
952
			// are re-fetched when displaying the options page after saving.
953
			if ( isset( $input['no_js'] ) ) {
954
				set_transient( 'wp2d_no_js_force_refetch', true );
955
			}
956
		}
957
958
		// Saving the default options.
959
		if ( isset( $input['submit_defaults'] ) ) {
960
			if ( ! isset( $input['enabled_post_types'] ) ) {
961
				$input['enabled_post_types'] = [];
962
			}
963
964
			// Checkboxes.
965
			$this->validate_checkboxes( [ 'post_to_diaspora', 'fullentrylink' ], $input );
966
967
			// Single Selects.
968
			$this->validate_single_selects( 'display', $input );
969
970
			// Multiple Selects.
971
			$this->validate_multi_selects( 'tags_to_post', $input );
972
973
			// Get unique, non-empty, trimmed tags and clean them up.
974
			$this->validate_tags( $input['global_tags'] );
975
976
			// Clean up the list of aspects. If the list is empty, only use the 'Public' aspect.
977
			$this->validate_aspects_services( $input['aspects'], [ 'public' ] );
978
979
			// Clean up the list of services.
980
			$this->validate_aspects_services( $input['services'] );
981
		}
982
983
		// Reset to defaults.
984
		if ( isset( $input['reset_defaults'] ) ) {
985
			// Set the input to the default options.
986
			$input = self::$_default_options;
987
988
			// Don't reset the fetched lists of aspects and services.
989
			unset( $input['pod_list'], $input['aspects_list'], $input['services_list'] );
990
		}
991
992
		// Unset all unused input fields.
993
		unset( $input['submit_defaults'], $input['reset_defaults'], $input['submit_setup'] );
994
995
		// Parse inputs with default options and return.
996
		return wp_parse_args( $input, array_merge( self::$_default_options, self::$_options ) );
997
	}
998
999
	/**
1000
	 * Validate checkboxes, make them either true or false.
1001
	 *
1002
	 * @param string|array $checkboxes Checkboxes to validate.
1003
	 * @param array        $options    Options values themselves.
1004
	 *
1005
	 * @return array The validated options.
1006
	 */
1007
	public function validate_checkboxes( $checkboxes, &$options ) {
1008
		foreach ( WP2D_Helpers::str_to_arr( $checkboxes ) as $checkbox ) {
0 ignored issues
show
Bug introduced by
The expression \WP2D_Helpers::str_to_arr($checkboxes) of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1009
			$options[ $checkbox ] = isset( $options[ $checkbox ] );
1010
		}
1011
1012
		return $options;
1013
	}
1014
1015
	/**
1016
	 * Validate single-select fields and make sure their selected value are valid.
1017
	 *
1018
	 * @param string|array $selects Name(s) of the select fields.
1019
	 * @param array        $options Options values themselves.
1020
	 *
1021
	 * @return array The validated options.
1022
	 */
1023
	public function validate_single_selects( $selects, &$options ) {
1024
		foreach ( WP2D_Helpers::str_to_arr( $selects ) as $select ) {
0 ignored issues
show
Bug introduced by
The expression \WP2D_Helpers::str_to_arr($selects) of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1025
			if ( isset( $options[ $select ] ) && ! $this->is_valid_value( $select, $options[ $select ] ) ) {
1026
				unset( $options[ $select ] );
1027
			}
1028
		}
1029
1030
		return $options;
1031
	}
1032
1033
	/**
1034
	 * Validate multi-select fields and make sure their selected values are valid.
1035
	 *
1036
	 * @param string|array $selects Name(s) of the select fields.
1037
	 * @param array        $options Options values themselves.
1038
	 *
1039
	 * @return array The validated options.
1040
	 */
1041
	public function validate_multi_selects( $selects, &$options ) {
1042
		foreach ( WP2D_Helpers::str_to_arr( $selects ) as $select ) {
0 ignored issues
show
Bug introduced by
The expression \WP2D_Helpers::str_to_arr($selects) of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1043
			if ( isset( $options[ $select ] ) ) {
1044
				foreach ( (array) $options[ $select ] as $option_value ) {
1045
					if ( ! $this->is_valid_value( $select, $option_value ) ) {
1046
						unset( $options[ $select ] );
1047
						break;
1048
					}
1049
				}
1050
			} else {
1051
				$options[ $select ] = [];
1052
			}
1053
		}
1054
1055
		return $options;
1056
	}
1057
1058
	/**
1059
	 * Clean up the passed tags. Keep only alphanumeric, hyphen and underscore characters.
1060
	 *
1061
	 * @param array|string $tags Tags to be cleaned as array or comma seperated values.
1062
	 *
1063
	 * @return array The cleaned tags.
1064
	 */
1065
	public function validate_tags( &$tags ) {
1066
		WP2D_Helpers::str_to_arr( $tags );
1067
1068
		$tags = array_map( [ $this, 'validate_tag' ],
1069
			array_unique(
1070
				array_filter( $tags, 'trim' )
1071
			)
1072
		);
1073
1074
		return $tags;
1075
	}
1076
1077
	/**
1078
	 * Clean up the passed tag. Keep only alphanumeric, hyphen and underscore characters.
1079
	 *
1080
	 * @todo What about eastern characters? (chinese, indian, etc.)
1081
	 *
1082
	 * @param string $tag Tag to be cleaned.
1083
	 *
1084
	 * @return string The clean tag.
1085
	 */
1086
	public function validate_tag( &$tag ) {
1087
		$tag = preg_replace( '/[^\w $\-]/u', '', str_replace( ' ', '-', trim( $tag ) ) );
1088
1089
		return $tag;
1090
	}
1091
1092
	/**
1093
	 * Validate the passed aspects or services.
1094
	 *
1095
	 * @param array $aspects_services List of aspects or services that need to be validated.
1096
	 * @param array $default          Default value if not valid.
1097
	 *
1098
	 * @return array The validated list of aspects or services.
1099
	 */
1100
	public function validate_aspects_services( &$aspects_services, array $default = [] ) {
1101
		if ( empty( $aspects_services ) || ! is_array( $aspects_services ) ) {
1102
			$aspects_services = $default;
1103
		} else {
1104
			array_walk( $aspects_services, 'sanitize_text_field' );
1105
		}
1106
1107
		return $aspects_services;
1108
	}
1109
}
1110