Completed
Push — develop ( 1ae066...6cb57b )
by Armando
03:38
created

WP2D_Options::__wakeup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 1
rs 10
cc 1
eloc 1
nc 1
nop 0
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
		'pod_list'           => [],
31
		'aspects_list'       => [],
32
		'services_list'      => [],
33
		'post_to_diaspora'   => true,
34
		'enabled_post_types' => [ 'post' ],
35
		'fullentrylink'      => true,
36
		'display'            => 'full',
37
		'tags_to_post'       => [ 'global', 'custom', 'post' ],
38
		'global_tags'        => '',
39
		'aspects'            => [ 'public' ],
40
		'services'           => [],
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' ],
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() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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' ) {
1 ignored issue
show
Coding Style introduced by
_current_tab uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
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() {
1 ignored issue
show
Coding Style introduced by
admin_options_page uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
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
				// Attempt to get the cacert.pem file and save it to the plugin's root directory.
214
				if ( isset( $_GET['wp2d_temp_ssl_fix'] ) ) {
215
					$cacert_file = file_get_contents( 'http://curl.haxx.se/ca/cacert.pem' );
216
					if ( $cacert_file && file_put_contents( WP2D_DIR . '/cacert.pem', $cacert_file ) ) {
217
						add_settings_error(
218
							'wp_to_diaspora_settings',
219
							'wp_to_diaspora_temp_ssl_fix',
220
							__( 'Successfully saved cacert.pem!', 'wp-to-diaspora' ),
221
							'updated'
222
						);
223
					} else {
224
						add_settings_error(
225
							'wp_to_diaspora_settings',
226
							'wp_to_diaspora_temp_ssl_fix',
227
							__( 'Failed to save cacert.pem!', 'wp-to-diaspora' ),
228
							'error'
229
						);
230
					}
231
				}
232
			}
233
234
			// Output success or error message.
235
			settings_errors( 'wp_to_diaspora_settings' );
236
			?>
237
238
			<?php $page_tabs = array_keys( $this->_options_page_tabs( true ) ); ?>
239
240
			<form action="<?php echo admin_url( 'options.php' ); ?>" method="post">
241
				<input id="wp2d_no_js" type="hidden" name="wp_to_diaspora_settings[no_js]" value="1">
242
				<?php
243
				// Load the settings fields.
244
				settings_fields( 'wp_to_diaspora_settings' );
245
				do_settings_sections( 'wp_to_diaspora_settings' );
246
247
				// Get the name of the current tab, if set, else take the first one from the list.
248
				$tab = $this->_current_tab( $page_tabs[0] );
249
250
				// Add Save and Reset buttons.
251
				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;';
252
				if ( 'setup' !== $tab ) {
253
					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' ) . '" />';
254
				}
255
				?>
256
257
			</form>
258
		</div>
259
260
		<?php
261
	}
262
263
	/**
264
	 * Return if the settings for the pod setup have been entered.
265
	 *
266
	 * @return bool If the setup for the pod has been done.
267
	 */
268
	public function is_pod_set_up() {
269
		return ( $this->get_option( 'pod' ) && $this->get_option( 'username' ) && $this->get_option( 'password' ) );
270
	}
271
272
	/**
273
	 * Setup Contextual Help and Options pages.
274
	 */
275
	public function setup_wpadmin_pages() {
276
		// Add options page.
277
		$hook = add_options_page( 'WP to diaspora*', 'WP to diaspora*', 'manage_options', 'wp_to_diaspora', [ $this, 'admin_options_page' ] );
278
279
		// Setup the contextual help menu after the options page has been loaded.
280
		add_action( 'load-' . $hook, [ 'WP2D_Contextual_Help', 'instance' ] );
281
282
		// Setup the contextual help menu tab for post types. Checks are made there!
283
		add_action( 'load-post.php', [ 'WP2D_Contextual_Help', 'instance' ] );
284
		add_action( 'load-post-new.php', [ 'WP2D_Contextual_Help', 'instance' ] );
285
	}
286
287
	/**
288
	 * Initialise the settings sections and fields of the currently selected tab.
289
	 */
290
	public function register_settings() {
291
		// Register the settings with validation callback.
292
		register_setting( 'wp_to_diaspora_settings', 'wp_to_diaspora_settings', [ $this, 'validate_settings' ] );
293
294
		// Load only the sections of the selected tab.
295
		switch ( $this->_current_tab() ) {
296
			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...
297
				// Add a "Defaults" section that contains all posting settings to be used by default.
298
				add_settings_section( 'wp_to_diaspora_defaults_section', __( 'Posting Defaults', 'wp-to-diaspora' ), [ $this, 'defaults_section' ], 'wp_to_diaspora_settings' );
299
				break;
300
			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...
301
				// Add a "Setup" section that contains the Pod domain, Username and Password.
302
				add_settings_section( 'wp_to_diaspora_setup_section', __( 'diaspora* Setup', 'wp-to-diaspora' ), [ $this, 'setup_section' ], 'wp_to_diaspora_settings' );
303
				break;
304
		}
305
	}
306
307
308
	/**
309
	 * Callback for the "Setup" section.
310
	 */
311
	public function setup_section() {
312
		esc_html_e( 'Set up the connection to your diaspora* account.', 'wp-to-diaspora' );
313
314
		// Pod entry field.
315
		add_settings_field( 'pod', __( 'Diaspora* Pod', 'wp-to-diaspora' ), [ $this, 'pod_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_setup_section' );
316
317
		// Username entry field.
318
		add_settings_field( 'username', __( 'Username' ), [ $this, 'username_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_setup_section' );
319
320
		// Password entry field.
321
		add_settings_field( 'password', __( 'Password' ), [ $this, 'password_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_setup_section' );
322
	}
323
324
	/**
325
	 * Render the "Pod" field.
326
	 */
327
	public function pod_render() {
328
		?>
329
		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> <a id="refresh-pod-list" class="button hide-if-no-js"><?php esc_html_e( 'Refresh pod list', 'wp-to-diaspora' ); ?></a><span class="spinner"></span>
330
		<datalist id="pod-list">
331
			<?php foreach ( (array) $this->get_option( 'pod_list' ) as $pod ) : ?>
332
				<option data-secure="<?php echo esc_attr( $pod['secure'] ); ?>" value="<?php echo esc_attr( $pod['domain'] ); ?>"></option>
333
			<?php endforeach; ?>
334
		</datalist>
335
		<?php
336
	}
337
338
	/**
339
	 * Render the "Username" field.
340
	 */
341
	public function username_render() {
342
		?>
343
		<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>
344
		<?php
345
	}
346
347
	/**
348
	 * Render the "Password" field.
349
	 */
350
	public function password_render() {
351
		// Special case if we already have a password.
352
		$has_password = ( '' !== $this->get_option( 'password', '' ) );
353
		$placeholder  = $has_password ? __( 'Password already set.', 'wp-to-diaspora' ) : __( 'Password' );
354
		$required     = $has_password ? '' : ' required';
355
		?>
356
		<input type="password" name="wp_to_diaspora_settings[password]" value="" placeholder="<?php echo esc_attr( $placeholder ); ?>"<?php echo esc_attr( $required ); ?>>
357
		<?php if ( $has_password ) : ?>
358
			<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>
359
		<?php endif;
360
	}
361
362
363
	/**
364
	 * Callback for the "Defaults" section.
365
	 */
366
	public function defaults_section() {
367
		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' );
368
369
		// Post types field.
370
		add_settings_field( 'enabled_post_types', __( 'Post types', 'wp-to-diaspora' ), [ $this, 'post_types_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section' );
371
372
		// Post to diaspora* checkbox.
373
		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' ) );
374
375
		// Full entry link checkbox.
376
		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' ) );
377
378
		// Full text or excerpt radio buttons.
379
		add_settings_field( 'display', __( 'Display', 'wp-to-diaspora' ), [ $this, 'display_render' ], 'wp_to_diaspora_settings', 'wp_to_diaspora_defaults_section', $this->get_option( 'display' ) );
380
381
		// Tags to post dropdown.
382
		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' ) );
383
384
		// Global tags field.
385
		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' ) );
386
387
		// Aspects checkboxes.
388
		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' ) ] );
389
390
		// Services checkboxes.
391
		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' ) ] );
392
	}
393
394
	/**
395
	 * Render the "Post types" checkboxes.
396
	 */
397
	public function post_types_render() {
398
		$post_types = get_post_types( [ 'public' => true ], 'objects' );
399
400
		// Remove excluded post types from the list.
401
		$excluded_post_types = [ 'attachment', 'nav_menu_item', 'revision' ];
402
		foreach ( $excluded_post_types as $excluded ) {
403
			unset( $post_types[ $excluded ] );
404
		}
405
		?>
406
407
		<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][]">
408
			<?php foreach ( $post_types as $post_type ) : ?>
409
				<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>
410
			<?php endforeach; ?>
411
		</select>
412
413
		<p class="description"><?php esc_html_e( 'Choose which post types can be posted to diaspora*.', 'wp-to-diaspora' ); ?></p>
414
415
		<?php
416
	}
417
418
	/**
419
	 * Render the "Post to diaspora*" checkbox.
420
	 *
421
	 * @param bool $post_to_diaspora If this checkbox is checked or not.
422
	 */
423
	public function post_to_diaspora_render( $post_to_diaspora ) {
424
		$label = ( 'settings_page_wp_to_diaspora' === get_current_screen()->id ) ? __( 'Yes' ) : __( 'Post to diaspora*', 'wp-to-diaspora' );
425
		?>
426
		<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>
427
		<?php
428
	}
429
430
	/**
431
	 * Render the "Show 'Posted at' link" checkbox.
432
	 *
433
	 * @param bool $show_link If the checkbox is checked or not.
434
	 */
435
	public function fullentrylink_render( $show_link ) {
436
		$description = __( 'Include a link back to your original post.', 'wp-to-diaspora' );
437
		$checkbox    = '<input type="checkbox" id="fullentrylink" name="wp_to_diaspora_settings[fullentrylink]" value="1"' . checked( $show_link, true, false ) . '>';
438
439
		if ( 'settings_page_wp_to_diaspora' === get_current_screen()->id ) : ?>
440
			<label><?php echo $checkbox; ?><?php esc_html_e( 'Yes' ); ?></label>
441
			<p class="description"><?php echo esc_html( $description ); ?></p>
442
		<?php else : ?>
443
			<label title="<?php echo esc_attr( $description ); ?>"><?php echo $checkbox; ?><?php esc_html_e( 'Show "Posted at" link?', 'wp-to-diaspora' ); ?></label>
444
		<?php endif;
445
	}
446
447
	/**
448
	 * Render the "Display" radio buttons.
449
	 *
450
	 * @param string $display The selected radio button.
451
	 */
452
	public function display_render( $display ) {
453
		?>
454
		<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/>
455
		<label><input type="radio" name="wp_to_diaspora_settings[display]" value="excerpt" <?php checked( $display, 'excerpt' ); ?>><?php esc_html_e( 'Excerpt' ); ?></label>
456
		<?php
457
	}
458
459
	/**
460
	 * Render the "Tags to post" field.
461
	 *
462
	 * @param array $tags_to_post The types of tags to be posted.
463
	 */
464
	public function tags_to_post_render( $tags_to_post ) {
465
		$on_settings_page = ( 'settings_page_wp_to_diaspora' === get_current_screen()->id );
466
		$description      = esc_html__( 'Choose which tags should be posted to diaspora*.', 'wp-to-diaspora' );
467
468
		if ( ! $on_settings_page ) {
469
			echo '<label>' . esc_html( $description );
470
		}
471
472
		?>
473
		<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][]">
474
			<option value="global" <?php selected( in_array( 'global', $tags_to_post, true ) ); ?>><?php esc_html_e( 'Global tags', 'wp-to-diaspora' ); ?></option>
475
			<option value="custom" <?php selected( in_array( 'custom', $tags_to_post, true ) ); ?>><?php esc_html_e( 'Custom tags', 'wp-to-diaspora' ); ?></option>
476
			<option value="post" <?php selected( in_array( 'post', $tags_to_post, true ) ); ?>><?php esc_html_e( 'Post tags', 'wp-to-diaspora' ); ?></option>
477
		</select>
478
479
		<?php if ( $on_settings_page ) : ?>
480
			<p class="description"><?php echo esc_html( $description ); ?></p>
481
		<?php else : ?>
482
			</label>
483
		<?php endif;
484
	}
485
486
	/**
487
	 * Render the "Global tags" field.
488
	 *
489
	 * @param array $tags The global tags to be posted.
490
	 */
491 View Code Duplication
	public function global_tags_render( $tags ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
492
		WP2D_Helpers::arr_to_str( $tags );
493
		?>
494
		<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' ); ?>">
495
		<p class="description"><?php esc_html_e( 'Custom tags to add to all posts being posted to diaspora*.', 'wp-to-diaspora' ); ?></p>
496
		<?php
497
	}
498
499
	/**
500
	 * Render the "Custom tags" field.
501
	 *
502
	 * @param array $tags The custom tags to be posted.
503
	 */
504 View Code Duplication
	public function custom_tags_render( $tags ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
505
		WP2D_Helpers::arr_to_str( $tags );
506
		?>
507
		<label title="<?php esc_attr_e( 'Custom tags to add to this post when it\'s posted to diaspora*.', 'wp-to-diaspora' ); ?>">
508
			<?php esc_html_e( 'Custom tags', 'wp-to-diaspora' ); ?>
509
			<input type="text" class="widefat wp2d-tags" name="wp_to_diaspora_settings[custom_tags]" value="<?php echo esc_attr( $tags ); ?>">
510
		</label>
511
		<p class="description"><?php esc_html_e( 'Separate tags with commas' ); ?></p>
512
		<?php
513
	}
514
515
	/**
516
	 * Render the "Aspects" and "Services" checkboxes.
517
	 *
518
	 * @param array $args Array containing the type and items to output as checkboxes.
519
	 */
520
	public function aspects_services_render( $args ) {
521
		list( $type, $items ) = $args;
522
523
		// This is where the 2 types show their differences.
524
		switch ( $type ) {
525
			case 'aspects':
526
				$refresh_button = __( 'Refresh Aspects', 'wp-to-diaspora' );
527
				$description    = esc_html__( 'Choose which aspects to share to.', 'wp-to-diaspora' );
528
				$empty_label    = '<input type="checkbox" name="wp_to_diaspora_settings[aspects][]" value="public" checked="checked">' . esc_html__( 'Public' );
529
				break;
530
531
			case 'services':
532
				$refresh_button = __( 'Refresh Services', 'wp-to-diaspora' );
533
				$description    = sprintf(
534
					'%1$s<br><a href="%2$s" target="_blank">%3$s</a>',
535
					esc_html__( 'Choose which services to share to.', 'wp-to-diaspora' ),
536
					esc_url( 'https://' . $this->get_option( 'pod' ) . '/services' ),
537
					esc_html__( 'Show available services on my pod.', 'wp-to-diaspora' )
538
				);
539
				$empty_label    = esc_html__( 'No services connected yet.', 'wp-to-diaspora' );
540
				break;
541
542
			default:
543
				return;
544
		}
545
546
		$items = array_filter( (array) $items ) ?: [];
547
548
		// Special case for this field if it's displayed on the settings page.
549
		$on_settings_page = ( 'settings_page_wp_to_diaspora' === get_current_screen()->id );
550
551
		if ( ! $on_settings_page ) {
552
			echo $description;
553
			$description = '';
554
		}
555
556
		?>
557
		<div id="<?php echo esc_attr( $type ); ?>-container" data-<?php echo esc_attr( $type ); ?>-selected="<?php echo esc_attr( implode( ',', $items ) ); ?>">
558
			<?php if ( $list = (array) $this->get_option( $type . '_list' ) ) : ?>
559
				<?php foreach ( $list as $id => $name ) : ?>
560
					<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, true ) ); ?>><?php echo esc_html( $name ); ?></label>
561
				<?php endforeach; ?>
562
			<?php else : ?>
563
				<label><?php echo $empty_label; ?></label>
564
			<?php endif; ?>
565
		</div>
566
		<p class="description">
567
			<?php echo $description; ?>
568
			<a id="refresh-<?php echo esc_attr( $type ); ?>-list" class="button hide-if-no-js"><?php echo esc_html( $refresh_button ); ?></a>
569
			<span class="spinner"></span>
570
			<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>
571
		</p>
572
		<?php
573
	}
574
575
576
	/**
577
	 * Get a specific option.
578
	 *
579
	 * @param null|string $option  ID of option to get.
580
	 * @param mixed       $default Override default value if option not found.
581
	 *
582
	 * @return mixed Requested option value.
583
	 */
584
	public function get_option( $option = null, $default = null ) {
585
		if ( null === self::$_options ) {
586
			self::$_options = get_option( 'wp_to_diaspora_settings', self::$_default_options );
587
		}
588
		if ( null !== $option ) {
589
			if ( isset( self::$_options[ $option ] ) ) {
590
				// Return found option value.
591
				return self::$_options[ $option ];
592
			} elseif ( null !== $default ) {
593
				// Return overridden default value.
594
				return $default;
595
			} elseif ( isset( self::$_default_options[ $option ] ) ) {
596
				// Return default option value.
597
				return self::$_default_options[ $option ];
598
			}
599
		}
600
601
		return $default;
602
	}
603
604
	/**
605
	 * Get all options.
606
	 *
607
	 * @return array All the options.
608
	 */
609
	public function get_options() {
610
		return self::$_options;
611
	}
612
613
	/**
614
	 * Set a certain option.
615
	 *
616
	 * @param string       $option ID of option to get.
617
	 * @param array|string $value  Value to be set for the passed option.
618
	 * @param bool         $save   Save the options immediately after setting them.
619
	 */
620
	public function set_option( $option, $value, $save = false ) {
621
		if ( null !== $option ) {
622
			if ( null !== $value ) {
623
				self::$_options[ $option ] = $value;
624
			} else {
625
				unset( self::$_options[ $option ] );
626
			}
627
		}
628
629
		$save && $this->save();
630
	}
631
632
	/**
633
	 * Save the options.
634
	 */
635
	public function save() {
636
		update_option( 'wp_to_diaspora_settings', self::$_options );
637
	}
638
639
	/**
640
	 * Get all valid input values for the passed field.
641
	 *
642
	 * @param string $field Field to get the valid values for.
643
	 *
644
	 * @return array List of valid values.
645
	 */
646
	public function get_valid_values( $field ) {
647
		if ( array_key_exists( $field, self::$_valid_values ) ) {
648
			return self::$_valid_values[ $field ];
649
		}
650
	}
651
652
	/**
653
	 * Check if a value is valid for the passed field.
654
	 *
655
	 * @param string $field Field to check the valid value for.
656
	 * @param mixed  $value Value to check validity.
657
	 *
658
	 * @return bool If the passed value is valid.
659
	 */
660
	public function is_valid_value( $field, $value ) {
661
		if ( $valids = $this->get_valid_values( $field ) ) {
662
			return in_array( $value, $valids, true );
663
		}
664
665
		return false;
666
	}
667
668
	/**
669
	 * Validate all settings.
670
	 *
671
	 * @param array $input RAW input values.
672
	 *
673
	 * @return array Validated input values.
674
	 */
675
	public function validate_settings( $input ) {
676
		/* Validate all settings before saving to the database. */
677
678
		// Saving the pod setup details.
679
		if ( isset( $input['submit_setup'] ) ) {
680
			$input['pod']      = trim( sanitize_text_field( $input['pod'] ), ' /' );
681
			$input['username'] = sanitize_text_field( $input['username'] );
682
			$input['password'] = sanitize_text_field( $input['password'] );
683
684
			// If password is blank, it hasn't been changed.
685
			// 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.
686
			if ( '' === $input['password'] || $this->get_option( 'password' ) === $input['password'] ) {
687
				$input['password'] = $this->get_option( 'password' );
688
			} else {
689
				$input['password'] = WP2D_Helpers::encrypt( $input['password'] );
690
			}
691
692
			// This is for when JS in not enabled, to make sure that the aspects and services
693
			// are refetched when displaying the options page after saving.
694
			if ( isset( $input['no_js'] ) ) {
695
				set_transient( 'wp2d_no_js_force_refetch', true );
696
			}
697
		}
698
699
		// Saving the default options.
700
		if ( isset( $input['submit_defaults'] ) ) {
701
			if ( ! isset( $input['enabled_post_types'] ) ) {
702
				$input['enabled_post_types'] = [];
703
			}
704
705
			// Checkboxes.
706
			$this->validate_checkboxes( [ 'post_to_diaspora', 'fullentrylink' ], $input );
707
708
			// Single Selects.
709
			$this->validate_single_selects( 'display', $input );
710
711
			// Multiple Selects.
712
			$this->validate_multi_selects( 'tags_to_post', $input );
713
714
			// Get unique, non-empty, trimmed tags and clean them up.
715
			$this->validate_tags( $input['global_tags'] );
716
717
			// Clean up the list of aspects. If the list is empty, only use the 'Public' aspect.
718
			$this->validate_aspects_services( $input['aspects'], [ 'public' ] );
719
720
			// Clean up the list of services.
721
			$this->validate_aspects_services( $input['services'] );
722
		}
723
724
		// Reset to defaults.
725
		if ( isset( $input['reset_defaults'] ) ) {
726
			// Set the input to the default options.
727
			$input = self::$_default_options;
728
729
			// Don't reset the fetched lists of pods, aspects and services.
730
			unset( $input['pod_list'], $input['aspects_list'], $input['services_list'] );
731
		}
732
733
		// Unset all unused input fields.
734
		unset( $input['submit_defaults'], $input['reset_defaults'], $input['submit_setup'] );
735
736
		// Parse inputs with default options and return.
737
		return wp_parse_args( $input, array_merge( self::$_default_options, self::$_options ) );
738
	}
739
740
	/**
741
	 * Validate checkboxes, make them either true or false.
742
	 *
743
	 * @param string|array $checkboxes Checkboxes to validate.
744
	 * @param array        $options    Options values themselves.
745
	 *
746
	 * @return array The validated options.
747
	 */
748
	public function validate_checkboxes( $checkboxes, &$options ) {
749
		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...
750
			$options[ $checkbox ] = isset( $options[ $checkbox ] );
751
		}
752
753
		return $options;
754
	}
755
756
	/**
757
	 * Validate single-select fields and make sure their selected value are valid.
758
	 *
759
	 * @param string|array $selects Name(s) of the select fields.
760
	 * @param array        $options Options values themselves.
761
	 *
762
	 * @return array The validated options.
763
	 */
764
	public function validate_single_selects( $selects, &$options ) {
765
		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...
766
			if ( isset( $options[ $select ] ) && ! $this->is_valid_value( $select, $options[ $select ] ) ) {
767
				unset( $options[ $select ] );
768
			}
769
		}
770
771
		return $options;
772
	}
773
774
	/**
775
	 * Validate multi-select fields and make sure their selected values are valid.
776
	 *
777
	 * @param string|array $selects Name(s) of the select fields.
778
	 * @param array        $options Options values themselves.
779
	 *
780
	 * @return array The validated options.
781
	 */
782
	public function validate_multi_selects( $selects, &$options ) {
783
		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...
784
			if ( isset( $options[ $select ] ) ) {
785
				foreach ( (array) $options[ $select ] as $option_value ) {
786
					if ( ! $this->is_valid_value( $select, $option_value ) ) {
787
						unset( $options[ $select ] );
788
						break;
789
					}
790
				}
791
			} else {
792
				$options[ $select ] = [];
793
			}
794
		}
795
796
		return $options;
797
	}
798
799
	/**
800
	 * Clean up the passed tags. Keep only alphanumeric, hyphen and underscore characters.
801
	 *
802
	 * @param array|string $tags Tags to be cleaned as array or comma seperated values.
803
	 *
804
	 * @return array The cleaned tags.
805
	 */
806
	public function validate_tags( &$tags ) {
807
		WP2D_Helpers::str_to_arr( $tags );
808
809
		$tags = array_map( [ $this, 'validate_tag' ],
810
			array_unique(
811
				array_filter( $tags, 'trim' )
812
			)
813
		);
814
815
		return $tags;
816
	}
817
818
	/**
819
	 * Clean up the passed tag. Keep only alphanumeric, hyphen and underscore characters.
820
	 *
821
	 * @todo What about eastern characters? (chinese, indian, etc.)
822
	 *
823
	 * @param string $tag Tag to be cleaned.
824
	 *
825
	 * @return string The clean tag.
826
	 */
827
	public function validate_tag( &$tag ) {
828
		$tag = preg_replace( '/[^\w $\-]/u', '', str_replace( ' ', '-', trim( $tag ) ) );
829
830
		return $tag;
831
	}
832
833
	/**
834
	 * Validate the passed aspects or services.
835
	 *
836
	 * @param array $aspects_services List of aspects or services that need to be validated.
837
	 * @param array $default          Default value if not valid.
838
	 *
839
	 * @return array The validated list of aspects or services.
840
	 */
841
	public function validate_aspects_services( &$aspects_services, array $default = [] ) {
842
		if ( empty( $aspects_services ) || ! is_array( $aspects_services ) ) {
843
			$aspects_services = $default;
844
		} else {
845
			array_walk( $aspects_services, 'sanitize_text_field' );
846
		}
847
848
		return $aspects_services;
849
	}
850
}
851