WP2D_Options::attempt_password_upgrade()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 3
nc 3
nop 1
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
	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 = sanitize_key( $_GET['tab'] ?? $default ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
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; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
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 esc_url( 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', 'wp-to-diaspora' ) . '" />&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':
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':
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', 'wp-to-diaspora' ), [ $this, 'username_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_setup_section' );
299
300
		// Password entry field.
301
		add_settings_field( 'password', __( 'Password', 'wp-to-diaspora' ), [ $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
		/**
309
		 * Update entries:
310
		 * curl -G 'https://the-federation.info/graphql?raw' --data-urlencode 'query={nodes(platform:"diaspora"){host}}' | jq '.data.nodes[].host'
311
		 */
312
313
		$pod_list = [
314
			'20190827.club',
315
			'a.grumpy.world',
316
			'azazel.ultragreen.net',
317
			'berlinspora.de',
318
			'bobspora.com',
319
			'borg.zbkbie.com',
320
			'brighton.social',
321
			'catpod.cat.scot',
322
			'd-resources.hopto.org',
323
			'd.consumium.org',
324
			'd.kretschmann.social',
325
			'deko.cloud',
326
			'despora.de',
327
			'dia.delaregula.fr',
328
			'dia.goexchange.de',
329
			'dia.gordons.gen.nz',
330
			'diapod.org',
331
			'diasp.de',
332
			'diasp.eu',
333
			'diasp.eu.com',
334
			'diasp.in',
335
			'diasp.nl',
336
			'diasp.org',
337
			'diaspod.de',
338
			'diaspora-fr.org',
339
			'diaspora.animalnet.de',
340
			'diaspora.anjara.eu',
341
			'diaspora.asrun.eu',
342
			'diaspora.baucum.me',
343
			'diaspora.club',
344
			'diaspora.conxtor.com',
345
			'diaspora.counterspin.org.nz',
346
			'diaspora.cyber-tribal.com',
347
			'diaspora.flyar.net',
348
			'diaspora.gegeweb.eu',
349
			'diaspora.goethe12.de',
350
			'diaspora.goethe20.de',
351
			'diaspora.hofud.com',
352
			'diaspora.immae.eu',
353
			'diaspora.itopie.ch',
354
			'diaspora.koehn.com',
355
			'diaspora.laka.lv',
356
			'diaspora.lamsade.fr',
357
			'diaspora.mathematicon.com',
358
			'diaspora.microdata.co.uk',
359
			'diaspora.mifritscher.de',
360
			'diaspora.normandie-libre.fr',
361
			'diaspora.odat.xyz',
362
			'diaspora.ofstad.xyz',
363
			'diaspora.permutationsofchaos.com',
364
			'diaspora.psyco.fr',
365
			'diaspora.ruhrmail.de',
366
			'diaspora.sceal.ie',
367
			'diaspora.schoenf.de',
368
			'diaspora.snakenode.eu',
369
			'diaspora.solusar.de',
370
			'diaspora.somethinghub.com',
371
			'diaspora.stevesullam.com',
372
			'diaspora.thus.ch',
373
			'diaspora.town',
374
			'diaspora.trancart.eu',
375
			'diaspora.vrije-mens.org',
376
			'diaspora.williamsonday.org',
377
			'diaspora.willispickering.com',
378
			'diaspora.yuais.net',
379
			'diasporabr.com.br',
380
			'diasporapod.no',
381
			'diasporing.ch',
382
			'dicespora.net',
383
			'dorf-post.de',
384
			'expod.de',
385
			'eyepod.oksocial.net',
386
			'failure.net',
387
			'federatica.space',
388
			'framasphere.org',
389
			'freehuman.fr',
390
			'friendsmeet.win',
391
			'iitians.xyz',
392
			'iliketoast.net',
393
			'ingtech.net',
394
			'jardin.umaneti.net',
395
			'jochem.name',
396
			'joindiaspora.com',
397
			'kitsune.click',
398
			'lanx.fr',
399
			'librenet.gr',
400
			'livepods.net',
401
			'lstoll-diaspora.herokuapp.com',
402
			'manapod.space',
403
			'mastodon.webseed.com',
404
			'mindfeed.herokuapp.com',
405
			'mondiaspora.net',
406
			'mondiaspora.org',
407
			'nerdpol.ch',
408
			'nodewatch.ninja',
409
			'nota.404.mn',
410
			'parlote.facil.services',
411
			'paxation.org',
412
			'pekospora.pekoyama.com',
413
			'pluspora.com',
414
			'pod.aevl.us',
415
			'pod.afox.me',
416
			'pod.alhague.net',
417
			'pod.asap-soft.com',
418
			'pod.automat.one',
419
			'pod.buergi.lugs.ch',
420
			'pod.chabotsi.fr',
421
			'pod.dapor.net',
422
			'pod.ddna.co',
423
			'pod.diaspora.software',
424
			'pod.dukun.de',
425
			'pod.fab-l3.org',
426
			'pod.ferner-online.de',
427
			'pod.fulll.name',
428
			'pod.g3l.org',
429
			'pod.geraspora.de',
430
			'pod.gothic.net.au',
431
			'pod.haxxors.com',
432
			'pod.hoizi.net',
433
			'pod.interlin.nl',
434
			'pod.jamidisi-edu.de',
435
			'pod.jns.im',
436
			'pod.jotoma.de',
437
			'pod.libreplanetbr.org',
438
			'pod.mttv.it',
439
			'pod.omgsrsly.net',
440
			'pod.orkz.net',
441
			'pod.pc-tiede.de',
442
			'pod.reckord.de',
443
			'pod.redfish.ca',
444
			'pod.rusa.fr',
445
			'pod.tchncs.de',
446
			'pod.thing.org',
447
			'podbay.net',
448
			'poddery.com',
449
			'podington.oksocial.net',
450
			'podling.oksocial.net',
451
			'podricing.pw',
452
			'protagio.social',
453
			'pubpod.alqualonde.org',
454
			'revreso.de',
455
			'rhizome.hfbk.net',
456
			'ruhrspora.de',
457
			'sechat.org',
458
			'shrekislove.us',
459
			'sn.xxz.su',
460
			'social.blackrosehosting.com',
461
			'social.cooleysekula.net',
462
			'social.daxbau.net',
463
			'social.dbernhardt.com',
464
			'social.elinux.org',
465
			'social.gibberfish.org',
466
			'social.hiernaux.eu',
467
			'social.milhousfamily.com',
468
			'social.mrzyx.de',
469
			'social.prien.xyz',
470
			'social.sejourne.info',
471
			'social.sip247.com',
472
			'spora.grin.hu',
473
			'spyurk.am',
474
			'sysad.org',
475
			'test.poddery.com',
476
			'tovari.ch',
477
			'twitt-dev.opencaribbean.org',
478
			'tyrell.marway.org',
479
			'wertier.de',
480
			'wgserv.eu',
481
			'whatsnewz.com',
482
			'wk3.org',
483
			'www.diasporaix.de',
484
			'www.fessebouc.fr',
485
			'www.reteasociala.ro',
486
			'xn--b1aafeapdba0ada1es.xn--p1ai',
487
		];
488
		?>
489
		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>
490
		<datalist id="pod-list">
491
			<?php foreach ( $pod_list as $pod ) : ?>
492
				<option value="<?php echo esc_attr( $pod ); ?>"></option>
493
			<?php endforeach; ?>
494
		</datalist>
495
		<?php
496
	}
497
498
	/**
499
	 * Render the "Username" field.
500
	 */
501
	public function username_render() {
502
		?>
503
		<input type="text" name="wp_to_diaspora_settings[username]" value="<?php echo esc_attr( $this->get_option( 'username' ) ); ?>" placeholder="<?php esc_attr_e( 'Username', 'wp-to-diaspora' ); ?>" required>
504
		<?php
505
	}
506
507
	/**
508
	 * Render the "Password" field.
509
	 */
510
	public function password_render() {
511
		// Special case if we already have a password.
512
		$has_password = ( '' !== $this->get_option( 'password', '' ) );
513
		$placeholder  = $has_password ? __( 'Password already set.', 'wp-to-diaspora' ) : __( 'Password', 'wp-to-diaspora' );
514
		$required     = $has_password ? '' : ' required';
515
		?>
516
		<input type="password" name="wp_to_diaspora_settings[password]" value="" placeholder="<?php echo esc_attr( $placeholder ); ?>"<?php echo esc_attr( $required ); ?>>
517
		<?php if ( $has_password ) : ?>
518
			<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>
519
		<?php endif;
520
	}
521
522
523
	/**
524
	 * Callback for the "Defaults" section.
525
	 */
526
	public function defaults_section() {
527
		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' );
528
529
		// Post types field.
530
		add_settings_field( 'enabled_post_types', __( 'Post types', 'wp-to-diaspora' ), [ $this, 'post_types_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section' );
531
532
		// Post to diaspora* checkbox.
533
		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' ) );
534
535
		// Full entry link checkbox.
536
		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' ) );
537
538
		// Full text, excerpt or none radio buttons.
539
		add_settings_field( 'display', __( 'Display', 'wp-to-diaspora' ), [ $this, 'display_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section', $this->get_option( 'display' ) );
540
541
		// Tags to post dropdown.
542
		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' ) );
543
544
		// Global tags field.
545
		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' ) );
546
547
		// Aspects checkboxes.
548
		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' ) ] );
549
550
		// Services checkboxes.
551
		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' ) ] );
552
	}
553
554
	/**
555
	 * Render the "Post types" checkboxes.
556
	 */
557
	public function post_types_render() {
558
		$post_types = get_post_types( [ 'public' => true ], 'objects' );
559
560
		// Remove excluded post types from the list.
561
		$excluded_post_types = [ 'attachment', 'nav_menu_item', 'revision' ];
562
		foreach ( $excluded_post_types as $excluded ) {
563
			unset( $post_types[ $excluded ] );
564
		}
565
		?>
566
567
		<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][]">
568
			<?php foreach ( $post_types as $post_type ) : ?>
569
				<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>
570
			<?php endforeach; ?>
571
		</select>
572
573
		<p class="description"><?php esc_html_e( 'Choose which post types can be posted to diaspora*.', 'wp-to-diaspora' ); ?></p>
574
575
		<?php
576
	}
577
578
	/**
579
	 * Render the "Post to diaspora*" checkbox.
580
	 *
581
	 * @param bool $post_to_diaspora If this checkbox is checked or not.
582
	 */
583
	public function post_to_diaspora_render( $post_to_diaspora ) {
584
		$label = ( 'settings_page_wp_to_diaspora' === get_current_screen()->id ) ? __( 'Yes', 'wp-to-diaspora' ) : __( 'Post to diaspora*', 'wp-to-diaspora' );
585
		?>
586
		<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>
587
		<?php
588
	}
589
590
	/**
591
	 * Render the "Show 'Posted at' link" checkbox.
592
	 *
593
	 * @param bool $show_link If the checkbox is checked or not.
594
	 */
595
	public function fullentrylink_render( $show_link ) {
596
		$description = __( 'Include a link back to your original post.', 'wp-to-diaspora' );
597
		$checkbox    = '<input type="checkbox" id="fullentrylink" name="wp_to_diaspora_settings[fullentrylink]" value="1"' . checked( $show_link, true, false ) . '>';
598
599
		if ( 'settings_page_wp_to_diaspora' === get_current_screen()->id ) : ?>
600
			<label><?php echo $checkbox; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><?php esc_html_e( 'Yes', 'wp-to-diaspora' ); ?></label>
601
			<p class="description"><?php echo esc_html( $description ); ?></p>
602
		<?php else : ?>
603
			<label title="<?php echo esc_attr( $description ); ?>"><?php echo $checkbox; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><?php esc_html_e( 'Show "Posted at" link?', 'wp-to-diaspora' ); ?></label>
604
		<?php endif;
605
	}
606
607
	/**
608
	 * Render the "Display" radio buttons.
609
	 *
610
	 * @param string $display The selected radio button.
611
	 */
612
	public function display_render( $display ) {
613
		?>
614
		<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/>
615
		<label><input type="radio" name="wp_to_diaspora_settings[display]" value="excerpt" <?php checked( $display, 'excerpt' ); ?>><?php esc_html_e( 'Excerpt', 'wp-to-diaspora' ); ?></label><br/>
616
		<label><input type="radio" name="wp_to_diaspora_settings[display]" value="none" <?php checked( $display, 'none' ); ?>><?php esc_html_e( 'None', 'wp-to-diaspora' ); ?></label>
617
		<?php
618
	}
619
620
	/**
621
	 * Render the "Tags to post" field.
622
	 *
623
	 * @param array $tags_to_post The types of tags to be posted.
624
	 */
625
	public function tags_to_post_render( $tags_to_post ) {
626
		$on_settings_page = ( 'settings_page_wp_to_diaspora' === get_current_screen()->id );
627
		$description      = esc_html__( 'Choose which tags should be posted to diaspora*.', 'wp-to-diaspora' );
628
629
		if ( ! $on_settings_page ) {
630
			echo '<label>' . esc_html( $description );
631
		}
632
633
		?>
634
		<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][]">
635
			<option value="global" <?php selected( in_array( 'global', $tags_to_post, true ) ); ?>><?php esc_html_e( 'Global tags', 'wp-to-diaspora' ); ?></option>
636
			<option value="custom" <?php selected( in_array( 'custom', $tags_to_post, true ) ); ?>><?php esc_html_e( 'Custom tags', 'wp-to-diaspora' ); ?></option>
637
			<option value="post" <?php selected( in_array( 'post', $tags_to_post, true ) ); ?>><?php esc_html_e( 'Post tags', 'wp-to-diaspora' ); ?></option>
638
		</select>
639
640
		<?php if ( $on_settings_page ) : ?>
641
			<p class="description"><?php echo esc_html( $description ); ?></p>
642
		<?php else : ?>
643
			</label>
644
		<?php endif;
645
	}
646
647
	/**
648
	 * Render the "Global tags" field.
649
	 *
650
	 * @param array $tags The global tags to be posted.
651
	 */
652
	public function global_tags_render( $tags ) {
653
		WP2D_Helpers::arr_to_str( $tags );
654
		?>
655
		<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' ); ?>">
656
		<p class="description"><?php esc_html_e( 'Custom tags to add to all posts being posted to diaspora*.', 'wp-to-diaspora' ); ?></p>
657
		<?php
658
	}
659
660
	/**
661
	 * Render the "Custom tags" field.
662
	 *
663
	 * @param array $tags The custom tags to be posted.
664
	 */
665
	public function custom_tags_render( $tags ) {
666
		WP2D_Helpers::arr_to_str( $tags );
667
		?>
668
		<label title="<?php esc_attr_e( 'Custom tags to add to this post when it\'s posted to diaspora*.', 'wp-to-diaspora' ); ?>">
669
			<?php esc_html_e( 'Custom tags', 'wp-to-diaspora' ); ?>
670
			<input type="text" class="widefat wp2d-tags" name="wp_to_diaspora_settings[custom_tags]" value="<?php echo esc_attr( $tags ); ?>">
671
		</label>
672
		<p class="description"><?php esc_html_e( 'Separate tags with commas', 'wp-to-diaspora' ); ?></p>
673
		<?php
674
	}
675
676
	/**
677
	 * Render the "Aspects" and "Services" checkboxes.
678
	 *
679
	 * @param array $args Array containing the type and items to output as checkboxes.
680
	 */
681
	public function aspects_services_render( $args ) {
682
		[ $type, $items ] = $args;
0 ignored issues
show
Bug introduced by
The variable $type does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $items seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
683
684
		// This is where the 2 types show their differences.
685
		switch ( $type ) {
686
			case 'aspects':
687
				$refresh_button = __( 'Refresh Aspects', 'wp-to-diaspora' );
688
				$description    = esc_html__( 'Choose which aspects to share to.', 'wp-to-diaspora' );
689
				$empty_label    = '<input type="checkbox" name="wp_to_diaspora_settings[aspects][]" value="public" checked="checked">' . esc_html__( 'Public', 'wp-to-diaspora' );
690
				break;
691
692
			case 'services':
693
				$refresh_button = __( 'Refresh Services', 'wp-to-diaspora' );
694
				$description    = sprintf(
695
					'%1$s<br><a href="%2$s" target="_blank">%3$s</a>',
696
					esc_html__( 'Choose which services to share to.', 'wp-to-diaspora' ),
697
					esc_url( 'https://' . $this->get_option( 'pod' ) . '/services' ),
698
					esc_html__( 'Show available services on my pod.', 'wp-to-diaspora' )
699
				);
700
				$empty_label    = esc_html__( 'No services connected yet.', 'wp-to-diaspora' );
701
				break;
702
703
			default:
704
				return;
705
		}
706
707
		$items = array_filter( (array) $items ) ?: [];
0 ignored issues
show
Bug introduced by
The variable $items seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
708
709
		// Special case for this field if it's displayed on the settings page.
710
		$on_settings_page = ( 'settings_page_wp_to_diaspora' === get_current_screen()->id );
711
712
		if ( ! $on_settings_page ) {
713
			echo $description; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
714
			$description = '';
715
		}
716
717
		?>
718
		<div id="<?php echo esc_attr( $type ); ?>-container" data-<?php echo esc_attr( $type ); ?>-selected="<?php echo esc_attr( implode( ',', $items ) ); ?>">
719
			<?php if ( $list = (array) $this->get_option( $type . '_list' ) ) : ?>
720
				<?php foreach ( $list as $id => $name ) : ?>
721
					<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, false ) ); // phpcs:ignore WordPress.PHP.StrictInArray.FoundNonStrictFalse ?>><?php echo esc_html( $name ); ?></label>
722
				<?php endforeach; ?>
723
			<?php else : ?>
724
				<label><?php echo $empty_label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></label>
725
			<?php endif; ?>
726
		</div>
727
		<p class="description">
728
			<?php echo $description; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
729
			<a id="refresh-<?php echo esc_attr( $type ); ?>-list" class="button hide-if-no-js"><?php echo esc_html( $refresh_button ); ?></a>
730
			<span class="spinner"></span>
731
			<span class="hide-if-js"><?php printf( esc_html_x( 'To update this list, %1$sre-save your login info%2$s.', 'placeholders are link tags to the settings page.', 'wp-to-diaspora' ), '<a href="' . esc_url( admin_url( 'options-general.php?page=wp_to_diaspora' ) ) . '&amp;tab=setup" target="_blank">', '</a>' ); ?></span>
732
		</p>
733
		<?php
734
	}
735
736
737
	/**
738
	 * Get a specific option.
739
	 *
740
	 * @param null|string $option  ID of option to get.
741
	 * @param mixed       $default Override default value if option not found.
742
	 *
743
	 * @return mixed Requested option value.
744
	 */
745
	public function get_option( $option = null, $default = null ) {
746
		if ( null === self::$options ) {
747
			self::$options = get_option( 'wp_to_diaspora_settings', self::$default_options );
748
		}
749
		if ( null !== $option ) {
750
			if ( isset( self::$options[ $option ] ) ) {
751
				// Return found option value.
752
				return self::$options[ $option ];
753
			} elseif ( null !== $default ) {
754
				// Return overridden default value.
755
				return $default;
756
			} elseif ( isset( self::$default_options[ $option ] ) ) {
757
				// Return default option value.
758
				return self::$default_options[ $option ];
759
			}
760
		}
761
762
		return $default;
763
	}
764
765
	/**
766
	 * Get all options.
767
	 *
768
	 * @return array All the options.
769
	 */
770
	public function get_options() {
771
		return self::$options;
772
	}
773
774
	/**
775
	 * Set a certain option.
776
	 *
777
	 * @param string       $option ID of option to get.
778
	 * @param array|string $value  Value to be set for the passed option.
779
	 * @param bool         $save   Save the options immediately after setting them.
780
	 */
781
	public function set_option( $option, $value, $save = false ) {
782
		if ( null !== $option ) {
783
			if ( null !== $value ) {
784
				self::$options[ $option ] = $value;
785
			} else {
786
				unset( self::$options[ $option ] );
787
			}
788
		}
789
790
		$save && $this->save();
791
	}
792
793
	/**
794
	 * Save the options.
795
	 */
796
	public function save() {
797
		update_option( 'wp_to_diaspora_settings', self::$options );
798
	}
799
800
	/**
801
	 * Get all valid input values for the passed field.
802
	 *
803
	 * @param string $field Field to get the valid values for.
804
	 *
805
	 * @return array List of valid values.
806
	 */
807
	public function get_valid_values( $field ) {
808
		if ( array_key_exists( $field, self::$valid_values ) ) {
809
			return self::$valid_values[ $field ];
810
		}
811
	}
812
813
	/**
814
	 * Check if a value is valid for the passed field.
815
	 *
816
	 * @param string $field Field to check the valid value for.
817
	 * @param mixed  $value Value to check validity.
818
	 *
819
	 * @return bool If the passed value is valid.
820
	 */
821
	public function is_valid_value( $field, $value ) {
822
		if ( $valids = $this->get_valid_values( $field ) ) {
823
			return in_array( $value, $valids, true );
824
		}
825
826
		return false;
827
	}
828
829
	/**
830
	 * Attempt to upgrade the password from using AUTH_KEY to using WP2D_AUTH_KEY.
831
	 *
832
	 * @since 2.2.0
833
	 *
834
	 * @param bool $save If the password should be saved to the options immediately.
835
	 */
836
	public function attempt_password_upgrade( $save = false ) {
837
		if ( AUTH_KEY !== WP2D_ENC_KEY ) {
838
			$old_pw = WP2D_Helpers::decrypt( (string) $this->get_option( 'password' ), AUTH_KEY );
839
			if ( null !== $old_pw ) {
840
				$new_pw = WP2D_Helpers::encrypt( $old_pw, WP2D_ENC_KEY );
841
				$this->set_option( 'password', $new_pw, $save );
842
			}
843
		}
844
	}
845
846
	/**
847
	 * Validate all settings.
848
	 *
849
	 * @param array $input RAW input values.
850
	 *
851
	 * @return array Validated input values.
852
	 */
853
	public function validate_settings( $input ) {
854
		/* Validate all settings before saving to the database. */
855
856
		// Saving the pod setup details.
857
		if ( isset( $input['submit_setup'] ) ) {
858
			$input['pod']      = trim( sanitize_text_field( $input['pod'] ), ' /' );
859
			$input['username'] = sanitize_text_field( $input['username'] );
860
			$input['password'] = sanitize_text_field( $input['password'] );
861
862
			// If password is blank, it hasn't been changed.
863
			// 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.
864
			if ( '' === $input['password'] || $this->get_option( 'password' ) === $input['password'] ) {
865
				// Attempt a password upgrade if applicable.
866
				$this->attempt_password_upgrade( true );
867
				$input['password'] = $this->get_option( 'password' );
868
			} else {
869
				$input['password'] = WP2D_Helpers::encrypt( $input['password'] );
870
			}
871
872
			// Keep a note of the current AUTH_KEY.
873
			$this->set_option( 'auth_key_hash', md5( AUTH_KEY ), true );
874
875
			// This is for when JS in not enabled, to make sure that the aspects and services
876
			// are re-fetched when displaying the options page after saving.
877
			if ( isset( $input['no_js'] ) ) {
878
				set_transient( 'wp2d_no_js_force_refetch', true );
879
			}
880
		}
881
882
		// Saving the default options.
883
		if ( isset( $input['submit_defaults'] ) ) {
884
			if ( ! isset( $input['enabled_post_types'] ) ) {
885
				$input['enabled_post_types'] = [];
886
			}
887
888
			// Checkboxes.
889
			$this->validate_checkboxes( [ 'post_to_diaspora', 'fullentrylink' ], $input );
890
891
			// Single Selects.
892
			$this->validate_single_selects( 'display', $input );
893
894
			// Multiple Selects.
895
			$this->validate_multi_selects( 'tags_to_post', $input );
896
897
			// Get unique, non-empty, trimmed tags and clean them up.
898
			$this->validate_tags( $input['global_tags'] );
899
900
			// Clean up the list of aspects. If the list is empty, only use the 'Public' aspect.
901
			$this->validate_aspects_services( $input['aspects'], [ 'public' ] );
902
903
			// Clean up the list of services.
904
			$this->validate_aspects_services( $input['services'] );
905
		}
906
907
		// Reset to defaults.
908
		if ( isset( $input['reset_defaults'] ) ) {
909
			// Set the input to the default options.
910
			$input = self::$default_options;
911
912
			// Don't reset the fetched lists of aspects and services.
913
			unset( $input['pod_list'], $input['aspects_list'], $input['services_list'] );
914
		}
915
916
		// Unset all unused input fields.
917
		unset( $input['submit_defaults'], $input['reset_defaults'], $input['submit_setup'] );
918
919
		// Parse inputs with default options and return.
920
		return wp_parse_args( $input, array_merge( self::$default_options, self::$options ) );
921
	}
922
923
	/**
924
	 * Validate checkboxes, make them either true or false.
925
	 *
926
	 * @param string|array $checkboxes Checkboxes to validate.
927
	 * @param array        $options    Options values themselves.
928
	 *
929
	 * @return array The validated options.
930
	 */
931
	public function validate_checkboxes( $checkboxes, &$options ) {
932
		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...
933
			$options[ $checkbox ] = isset( $options[ $checkbox ] );
934
		}
935
936
		return $options;
937
	}
938
939
	/**
940
	 * Validate single-select fields and make sure their selected value are valid.
941
	 *
942
	 * @param string|array $selects Name(s) of the select fields.
943
	 * @param array        $options Options values themselves.
944
	 *
945
	 * @return array The validated options.
946
	 */
947
	public function validate_single_selects( $selects, &$options ) {
948
		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...
949
			if ( isset( $options[ $select ] ) && ! $this->is_valid_value( $select, $options[ $select ] ) ) {
950
				unset( $options[ $select ] );
951
			}
952
		}
953
954
		return $options;
955
	}
956
957
	/**
958
	 * Validate multi-select fields and make sure their selected values are valid.
959
	 *
960
	 * @param string|array $selects Name(s) of the select fields.
961
	 * @param array        $options Options values themselves.
962
	 *
963
	 * @return array The validated options.
964
	 */
965
	public function validate_multi_selects( $selects, &$options ) {
966
		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...
967
			if ( isset( $options[ $select ] ) ) {
968
				foreach ( (array) $options[ $select ] as $option_value ) {
969
					if ( ! $this->is_valid_value( $select, $option_value ) ) {
970
						unset( $options[ $select ] );
971
						break;
972
					}
973
				}
974
			} else {
975
				$options[ $select ] = [];
976
			}
977
		}
978
979
		return $options;
980
	}
981
982
	/**
983
	 * Clean up the passed tags. Keep only alphanumeric, hyphen and underscore characters.
984
	 *
985
	 * @param array|string $tags Tags to be cleaned as array or comma seperated values.
986
	 *
987
	 * @return array The cleaned tags.
988
	 */
989
	public function validate_tags( &$tags ) {
990
		WP2D_Helpers::str_to_arr( $tags );
991
992
		$tags = array_map( [ $this, 'validate_tag' ],
993
			array_unique(
994
				array_filter( $tags, 'trim' )
995
			)
996
		);
997
998
		return $tags;
999
	}
1000
1001
	/**
1002
	 * Clean up the passed tag. Keep only alphanumeric, hyphen and underscore characters.
1003
	 *
1004
	 * @todo What about eastern characters? (chinese, indian, etc.)
1005
	 *
1006
	 * @param string $tag Tag to be cleaned.
1007
	 *
1008
	 * @return string The clean tag.
1009
	 */
1010
	public function validate_tag( &$tag ) {
1011
		$tag = preg_replace( '/[^\w $\-]/u', '', str_replace( ' ', '-', trim( $tag ) ) );
1012
1013
		return $tag;
1014
	}
1015
1016
	/**
1017
	 * Validate the passed aspects or services.
1018
	 *
1019
	 * @param array $aspects_services List of aspects or services that need to be validated.
1020
	 * @param array $default          Default value if not valid.
1021
	 *
1022
	 * @return array The validated list of aspects or services.
1023
	 */
1024
	public function validate_aspects_services( &$aspects_services, array $default = [] ) {
1025
		if ( empty( $aspects_services ) || ! is_array( $aspects_services ) ) {
1026
			$aspects_services = $default;
1027
		} else {
1028
			array_walk( $aspects_services, 'sanitize_text_field' );
1029
		}
1030
1031
		return $aspects_services;
1032
	}
1033
}
1034