WP2D::upgrade()   B
last analyzed

Complexity

Conditions 9
Paths 17

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 49
rs 7.5571
c 0
b 0
f 0
cc 9
nc 17
nop 0
1
<?php
2
/**
3
 * Main plugin initialisation class.
4
 *
5
 * @package WP_To_Diaspora\Core
6
 */
7
8
// Exit if accessed directly.
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * WP to diaspora* main plugin class.
13
 */
14
class WP2D {
15
16
	/**
17
	 * Only instance of this class.
18
	 *
19
	 * @var WP2D
20
	 */
21
	private static $instance;
22
23
	/**
24
	 * The minimum required WordPress version.
25
	 *
26
	 * @since 1.5.4
27
	 *
28
	 * @var string
29
	 */
30
	private $min_wp = '4.6-src';
31
32
	/**
33
	 * The minimum required PHP version.
34
	 *
35
	 * @since 1.5.4
36
	 *
37
	 * @var string
38
	 */
39
	private $min_php = '7.2';
40
41
	/**
42
	 * Instance of the API class.
43
	 *
44
	 * @var WP2D_API
45
	 */
46
	private $api;
47
48
	/**
49
	 * Create / Get the instance of this class.
50
	 *
51
	 * @return WP2D Instance of this class.
52
	 */
53
	public static function instance() {
54
		if ( null === self::$instance ) {
55
			self::$instance = new self();
56 View Code Duplication
			if ( self::$instance->version_check() ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
57
				self::$instance->constants();
58
				self::$instance->setup();
59
			} else {
60
				self::$instance = null;
61
			}
62
		}
63
64
		return self::$instance;
65
	}
66
67
	/**
68
	 * Define all the required constants.
69
	 *
70
	 * @since 1.5.0
71
	 */
72
	private function constants() {
73
		// Are we in debugging mode?
74
		if ( isset( $_GET['debugging'] ) ) { // phpcs:ignore
75
			define( 'WP2D_DEBUGGING', true );
76
		}
77
78
		define( 'WP2D_DIR', dirname( __DIR__ ) );
79
		define( 'WP2D_LIB_DIR', WP2D_DIR . '/lib' );
80
81
		// Fall back to WordPress AUTH_KEY for password encryption.
82
		defined( 'WP2D_ENC_KEY' ) || define( 'WP2D_ENC_KEY', AUTH_KEY );
83
	}
84
85
	/**
86
	 * Check the minimum WordPress and PHP requirements.
87
	 *
88
	 * @since 1.5.4
89
	 *
90
	 * @return bool If version requirements are met.
91
	 */
92
	private function version_check() {
93
		// Check for version requirements.
94
		if ( version_compare( PHP_VERSION, $this->min_php, '<' ) || version_compare( $GLOBALS['wp_version'], $this->min_wp, '<' ) ) {
95
			add_action( 'admin_notices', [ $this, 'deactivate' ] );
96
97
			return false;
98
		}
99
100
		return true;
101
	}
102
103
	/**
104
	 * Callback to deactivate plugin and display admin notice.
105
	 *
106
	 * @since 1.5.4
107
	 */
108
	public function deactivate() {
109
		// First of all, deactivate the plugin.
110
		deactivate_plugins( WP2D_BASENAME );
111
112
		// Get rid of the "Plugin activated" message.
113
		unset( $_GET['activate'] ); // phpcs:ignore
114
115
		// Then display the admin notice.
116
		?>
117
		<div class="error">
118
			<p><?php echo esc_html( sprintf( 'WP to diaspora* requires at least WordPress %1$s (you have %2$s) and PHP %3$s (you have %4$s)!', $this->min_wp, $GLOBALS['wp_version'], $this->min_php, PHP_VERSION ) ); ?></p>
119
		</div>
120
		<?php
121
	}
122
123
	/**
124
	 * Set up the plugin.
125
	 */
126
	private function setup() {
127
		// Add "Settings" link to plugin page.
128
		add_filter( 'plugin_action_links_' . WP2D_BASENAME, [ $this, 'settings_link' ] );
129
130
		// Perform any necessary data upgrades.
131
		add_action( 'admin_init', [ $this, 'upgrade' ] );
132
133
		// Admin notice when the AUTH_KEY has changed and credentials need to be re-saved.
134
		add_action( 'admin_notices', [ $this, 'admin_notices' ] );
135
136
		// Enqueue CSS and JS scripts.
137
		add_action( 'admin_enqueue_scripts', [ $this, 'admin_load_scripts' ] );
138
139
		// Set up the options.
140
		add_action( 'init', [ 'WP2D_Options', 'instance' ] );
141
142
		// WP2D Post.
143
		add_action( 'init', [ 'WP2D_Post', 'setup' ] );
144
145
		// AJAX actions for loading aspects and services.
146
		add_action( 'wp_ajax_wp_to_diaspora_update_aspects_list', [ $this, 'update_aspects_list_callback' ] );
147
		add_action( 'wp_ajax_wp_to_diaspora_update_services_list', [ $this, 'update_services_list_callback' ] );
148
149
		// Check the pod connection status on the options page.
150
		add_action( 'wp_ajax_wp_to_diaspora_check_pod_connection_status', [ $this, 'check_pod_connection_status_callback' ] );
151
	}
152
153
	/**
154
	 * Load the diaspora* API for ease of use.
155
	 *
156
	 * @return WP2D_API The API object.
157
	 */
158
	private function load_api() {
159
		if ( null === $this->api ) {
160
			$this->api = WP2D_Helpers::api_quick_connect();
161
		}
162
163
		return $this->api;
164
	}
165
166
	/**
167
	 * Initialise upgrade sequence.
168
	 */
169
	public function upgrade() {
170
		// Get the current options, or assign defaults.
171
		$options = WP2D_Options::instance();
172
		$version = $options->get_option( 'version' );
173
174
		// If the versions differ, this is probably an update. Need to save updated options.
175
		if ( WP2D_VERSION !== $version ) {
176
177
			// Password is stored encrypted since version 1.2.7.
178
			// When upgrading to it, the plain text password is encrypted and saved again.
179
			if ( version_compare( $version, '1.2.7', '<' ) ) {
180
				$options->set_option( 'password', WP2D_Helpers::encrypt( (string) $options->get_option( 'password' ) ) );
181
			}
182
183
			if ( version_compare( $version, '1.3.0', '<' ) ) {
184
				// The 'user' setting is renamed to 'username'.
185
				$options->set_option( 'username', $options->get_option( 'user' ) );
186
				$options->set_option( 'user', null );
187
188
				// Save tags as arrays instead of comma seperated values.
189
				$global_tags = $options->get_option( 'global_tags' );
190
				$options->set_option( 'global_tags', $options->validate_tags( $global_tags ) );
191
			}
192
193
			if ( version_compare( $version, '1.4.0', '<' ) ) {
194
				// Turn tags_to_post string into an array.
195
				$tags_to_post_old = $options->get_option( 'tags_to_post' );
196
				$tags_to_post     = array_filter( [
197
					( false !== strpos( $tags_to_post_old, 'g' ) ) ? 'global' : null,
198
					( false !== strpos( $tags_to_post_old, 'c' ) ) ? 'custom' : null,
199
					( false !== strpos( $tags_to_post_old, 'p' ) ) ? 'post' : null,
200
				] );
201
				$options->set_option( 'tags_to_post', $tags_to_post );
202
			}
203
204
			// Encryption key is set in WP2D_ENC_KEY since version 2.2.0.
205
			if ( version_compare( $version, '2.2.0', '<' ) ) {
206
				// Remember AUTH_KEY hash to notice a change.
207
				$options->set_option( 'auth_key_hash', md5( AUTH_KEY ) );
208
209
				// Upgrade encrypted password if new WP2D_ENC_KEY is used.
210
				$options->attempt_password_upgrade();
211
			}
212
213
			// Update version.
214
			$options->set_option( 'version', WP2D_VERSION );
215
			$options->save();
216
		}
217
	}
218
219
	/**
220
	 * Load scripts and styles for Settings and Post pages of allowed post types.
221
	 */
222
	public function admin_load_scripts() {
223
		// Get the enabled post types to load the script for.
224
		$enabled_post_types = WP2D_Options::instance()->get_option( 'enabled_post_types', [] );
225
226
		// Get the screen to find out where we are.
227
		$screen = get_current_screen();
228
229
		// Only load the styles and scripts on the settings page and the allowed post types.
230
		if ( 'settings_page_wp_to_diaspora' === $screen->id || ( in_array( $screen->post_type, $enabled_post_types, true ) && 'post' === $screen->base ) ) {
231
			wp_enqueue_style( 'tag-it', plugins_url( '/css/tag-it.min.css', WP2D_BASENAME ), [], WP2D_VERSION );
232
			wp_enqueue_style( 'chosen', plugins_url( '/css/chosen.min.css', WP2D_BASENAME ), [], WP2D_VERSION );
233
			wp_enqueue_style( 'wp-to-diaspora-admin', plugins_url( '/css/wp-to-diaspora.css', WP2D_BASENAME ), [], WP2D_VERSION );
234
			wp_enqueue_script( 'chosen', plugins_url( '/js/chosen.jquery.min.js', WP2D_BASENAME ), [ 'jquery' ], WP2D_VERSION, true );
235
			wp_enqueue_script( 'tag-it', plugins_url( '/js/tag-it.jquery.min.js', WP2D_BASENAME ), [ 'jquery', 'jquery-ui-autocomplete' ], WP2D_VERSION, true );
236
			wp_enqueue_script( 'wp-to-diaspora-admin', plugins_url( '/js/wp-to-diaspora.js', WP2D_BASENAME ), [ 'jquery' ], WP2D_VERSION, true );
237
			// Javascript-specific l10n.
238
			wp_localize_script( 'wp-to-diaspora-admin', 'WP2D', [
239
				'_nonce'                => wp_create_nonce( 'wp2d' ),
240
				'nonce_failure'         => __( 'AJAX Nonce failure.', 'wp-to-diaspora' ),
241
				'resave_credentials'    => __( 'Resave your credentials and try again.', 'wp-to-diaspora' ),
242
				'no_services_connected' => __( 'No services connected yet.', 'wp-to-diaspora' ),
243
				'sure_reset_defaults'   => __( 'Are you sure you want to reset to default values?', 'wp-to-diaspora' ),
244
				'conn_testing'          => __( 'Testing connection...', 'wp-to-diaspora' ),
245
				'conn_successful'       => __( 'Connection successful.', 'wp-to-diaspora' ),
246
				'conn_failed'           => __( 'Connection failed.', 'wp-to-diaspora' ),
247
			] );
248
		}
249
	}
250
251
	/**
252
	 * Add "AUTH_KEY" changed admin notice.
253
	 *
254
	 * @since 2.2.0
255
	 */
256
	public function admin_notices() {
257
		// If a custom WP2D_ENC_KEY is set, it doesn't matter if the AUTH_KEY has changed.
258
		if ( AUTH_KEY !== WP2D_ENC_KEY ) {
259
			return;
260
		}
261
262
		$options = WP2D_Options::instance();
263
		if ( md5( AUTH_KEY ) !== $options->get_option( 'auth_key_hash' ) ) {
264
			printf( '<div class="error notice is-dismissible"><p>%1$s</p></div>',
265
				sprintf(
266
					esc_html_x( 'Looks like your WordPress secret keys have changed! Please %1$sre-save your login info%2$s.', 'placeholders are link tags to the settings page.', 'wp-to-diaspora' ),
267
					'<a href="' . esc_url( admin_url( 'options-general.php?page=wp_to_diaspora' ) ) . '&amp;tab=setup" target="_blank">',
268
					'</a>'
269
				)
270
			);
271
		}
272
	}
273
274
	/**
275
	 * Add the "Settings" link to the plugins page.
276
	 *
277
	 * @param array $links Links to display for plugin on plugins page.
278
	 *
279
	 * @return array Links to display for plugin on plugins page.
280
	 */
281
	public function settings_link( $links ) {
282
		$links[] = '<a href="' . esc_url( admin_url( 'options-general.php?page=wp_to_diaspora' ) ) . '">' . __( 'Settings', 'wp-to-diaspora' ) . '</a>';
283
284
		return $links;
285
	}
286
287
	/**
288
	 * Fetch the list of aspects or services and save them to the settings.
289
	 *
290
	 * NOTE: When updating the lists, always force a fresh fetch.
291
	 *
292
	 * @param string $type Type of list to update.
293
	 *
294
	 * @return array|bool The list of aspects or services, false if an illegal parameter is passed.
295
	 */
296
	private function update_aspects_services_list( $type ) {
297
		// Check for correct argument value.
298
		if ( ! in_array( $type, [ 'aspects', 'services' ], true ) ) {
299
			return false;
300
		}
301
302
		$options = WP2D_Options::instance();
303
		$list    = $options->get_option( $type . '_list' );
304
305
		// Make sure that we have at least the 'Public' aspect.
306
		if ( 'aspects' === $type && empty( $list ) ) {
307
			$list = [ 'public' => __( 'Public', 'wp-to-diaspora' ) ];
308
		}
309
310
		// Set up the connection to diaspora*.
311
		$api = $this->load_api();
312
313
		// If there was a problem loading the API, return false.
314
		if ( $api->has_last_error() ) {
315
			return false;
316
		}
317
318
		$list_new = $list;
319
		if ( 'aspects' === $type ) {
320
			$list_new = $api->get_aspects( true );
321
		} elseif ( 'services' === $type ) {
322
			$list_new = $api->get_services( true );
323
		}
324
325
		// If the new list couldn't be fetched successfully, return false.
326
		if ( $api->has_last_error() ) {
327
			return false;
328
		}
329
330
		// We have a new list to save and return!
331
		$options->set_option( $type . '_list', $list_new );
332
		$options->save();
333
334
		return $list_new;
335
	}
336
337
	/**
338
	 * Update the list of aspects and return them for use with AJAX.
339
	 */
340
	public function update_aspects_list_callback() {
341
		check_ajax_referer( 'wp2d', 'nonce' );
342
		wp_send_json( $this->update_aspects_services_list( 'aspects' ) );
343
	}
344
345
	/**
346
	 * Update the list of services and return them for use with AJAX.
347
	 */
348
	public function update_services_list_callback() {
349
		check_ajax_referer( 'wp2d', 'nonce' );
350
		wp_send_json( $this->update_aspects_services_list( 'services' ) );
351
	}
352
353
	/**
354
	 * Check the pod connection status.
355
	 *
356
	 * @return bool The status of the connection.
357
	 */
358
	private function check_pod_connection_status() {
359
		$options = WP2D_Options::instance();
360
361
		$status = null;
362
363
		if ( $options->is_pod_set_up() ) {
364
			$status = ! $this->load_api()->has_last_error();
365
		}
366
367
		return $status;
368
	}
369
370
	/**
371
	 * Check the connection to the pod and return the status for use with AJAX.
372
	 *
373
	 * @todo esc_html
374
	 */
375
	public function check_pod_connection_status_callback() {
376
		if ( ! defined( 'WP2D_DEBUGGING' ) && isset( $_REQUEST['debugging'] ) ) { // phpcs:ignore
377
			define( 'WP2D_DEBUGGING', true );
378
		}
379
380 View Code Duplication
		if ( ! check_ajax_referer( 'wp2d', 'nonce', false ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
381
			wp_send_json_error( [ 'message' => __( 'Invalid AJAX nonce', 'wp-to-diaspora' ) ] );
382
		}
383
384
		$status = $this->check_pod_connection_status();
385
386
		$data = [
387
			'debug'   => esc_textarea( WP2D_Helpers::get_debugging() ),
388
			'message' => __( 'Connection successful.', 'wp-to-diaspora' ),
389
		];
390
391
		if ( true === $status ) {
392
			wp_send_json_success( $data );
393
		} elseif ( false === $status && $this->load_api()->has_last_error() ) {
394
			$data['message'] = $this->load_api()->get_last_error() . ' ' . WP2D_Contextual_Help::get_help_tab_quick_link( $this->load_api()->get_last_error_object() );
0 ignored issues
show
Bug introduced by
It seems like $this->load_api()->get_last_error_object() can be null; however, get_help_tab_quick_link() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
395
			wp_send_json_error( $data );
396
		}
397
		// If $status === null, do nothing.
398
	}
399
}
400