Passed
Push — develop ( cc7485...bdcc19 )
by Paul
03:28
created

GateKeeper::getPluginRequirements()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 2
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace GeminiLabs\Pollux;
4
5
use Exception;
6
use GeminiLabs\Pollux\Config\Config;
7
use ReflectionClass;
8
9
class GateKeeper
10
{
11
	/**
12
	 * [plugin_file_path] => [plugin_name]|[plugin_version]|[plugin_url]
13
	 */
14
	const DEPENDENCIES = [
15
		'meta-box/meta-box.php' => 'Meta Box|4.11|https://wordpress.org/plugins/meta-box/',
16
	];
17
	const MIN_PHP_VERSION = '5.6.0';
18
	const MIN_WORDPRESS_VERSION = '4.7';
19
20
	public $errors = [];
21
22
	/**
23
	 * @var Application
24
	 */
25
	protected $app;
26
27
	/**
28
	 * @var Notice
29
	 */
30
	protected $notice;
31
32
	/**
33
	 * @var string
34
	 */
35
	protected $plugin;
36
37
	public function __construct( $plugin )
38
	{
39
		$this->plugin = $plugin;
40
41
		if( $this->canActivate() ) {
42
			add_action( 'admin_init', array( $this, 'init' ));
43
		}
44
		else {
45
			add_action( 'activated_plugin', array( $this, 'deactivate' ));
46
			add_action( 'admin_notices',    array( $this, 'deactivate' ));
47
		}
48
	}
49
50
	public function init()
51
	{
52
		$this->app = pollux_app();
53
		$this->notice = pollux_app()->make( 'Notice' );
54
55
		add_action( 'current_screen',                         array( $this, 'activatePlugin' ));
56
		add_action( 'wp_ajax_pollux/dependency/activate_url', array( $this, 'ajaxActivatePluginLink' ));
57
		add_action( 'admin_notices',                          array( $this, 'printNotices' ));
58
		add_action( 'current_screen',                         array( $this, 'setDependencyNotice' ));
59
	}
60
61
	/**
62
	 * @return void
63
	 */
64
	public function activatePlugin()
65
	{
66
		if( get_current_screen()->id != sprintf( 'settings_page_%s', $this->app->id )
67
			|| filter_input( INPUT_GET, 'action' ) != 'activate'
68
		)return;
69
		$plugin = filter_input( INPUT_GET, 'plugin' );
70
		check_admin_referer( 'activate-plugin_' . $plugin );
71
		$result = activate_plugin( $plugin, null, is_network_admin(), true );
72
		if( is_wp_error( $result )) {
73
			wp_die( $result->get_error_message() );
74
		}
75
		wp_safe_redirect( wp_get_referer() );
76
		exit;
77
	}
78
79
	/**
80
	 * @return void
81
	 */
82
	public function ajaxActivatePluginLink()
83
	{
84
		check_ajax_referer( 'updates' );
85
		$plugin = filter_input( INPUT_POST, 'plugin' );
86
		if( !$this->isPluginDependency( $plugin )) {
87
			wp_send_json_error();
88
		}
89
		$activateUrl = add_query_arg([
90
			'_wpnonce' => wp_create_nonce( sprintf( 'activate-plugin_%s', $plugin )),
91
			'action' => 'activate',
92
			'page' => $this->app->id,
93
			'plugin' => $plugin,
94
		], self_admin_url( 'options-general.php' ));
95
		wp_send_json_success([
96
			'activate_url' => $activateUrl,
97
			filter_input( INPUT_POST, 'type' ) => $plugin,
98
		]);
99
	}
100
101
	/**
102
	 * @return bool
103
	 */
104
	public function canActivate()
105
	{
106
		return $this->hasValidPHPVersion() && $this->hasValidWPVersion();
107
	}
108
109
	/**
110
	 * @return void
111
	 * @action activated_plugin
112
	 * @action admin_notices
113
	 */
114
	public function deactivate( $plugin )
115
	{
116
		if( $plugin == $this->plugin ) {
117
			$this->redirect();
118
		}
119
		deactivate_plugins( $this->plugin );
120
		$addNotice = $this->hasValidPHPVersion()
121
			? 'addInvalidWPVersionNotice'
122
			: 'addInvalidPHPVersionNotice';
123
		$this->$addNotice();
124
	}
125
126
	/**
127
	 * @return bool
128
	 */
129
	public function hasDependency( $plugin )
130
	{
131
		if( !$this->isPluginDependency( $plugin )) {
132
			return true;
133
		}
134
		return $this->isPluginInstalled( $plugin ) && $this->isPluginValid( $plugin );
135
	}
136
137
	/**
138
	 * @return bool
139
	 */
140
	public function hasPendingDependencies()
141
	{
142
		foreach( static::DEPENDENCIES as $plugin => $data ) {
143
			if( !$this->isPluginDependency( $plugin ))continue;
144
			$this->isPluginActive( $plugin );
145
			$this->isPluginVersionValid( $plugin );
146
		}
147
		return !empty( $this->errors );
148
	}
149
150
	/**
151
	 * @return bool
152
	 */
153
	public function hasValidPHPVersion()
154
	{
155
		return version_compare( PHP_VERSION, self::MIN_PHP_VERSION, '>=' );
156
	}
157
158
	/**
159
	 * @return bool
160
	 */
161
	public function hasValidWPVersion()
162
	{
163
		global $wp_version;
164
		return version_compare( $wp_version, self::MIN_WORDPRESS_VERSION, '>=' );
165
	}
166
167
	/**
168
	 * @return bool
169
	 */
170
	public function isPluginActive( $plugin )
171
	{
172
		return $this->catchError( $plugin, 'inactive',
173
			is_plugin_active( $plugin ) || array_key_exists( $plugin, $this->getMustUsePlugins() )
174
		);
175
	}
176
177
	/**
178
	 * @return bool
179
	 */
180
	public function isPluginDependency( $plugin )
181
	{
182
		return array_key_exists( $plugin, static::DEPENDENCIES );
183
	}
184
185
	/**
186
	 * @return bool
187
	 */
188
	public function isPluginInstalled( $plugin )
189
	{
190
		return $this->catchError( $plugin, 'not_found',
191
			array_key_exists( $plugin, $this->getAllPlugins() )
192
		);
193
	}
194
195
	/**
196
	 * @return bool
197
	 */
198
	public function isPluginValid( $plugin )
199
	{
200
		return $this->isPluginActive( $plugin ) && $this->isPluginVersionValid( $plugin );
201
	}
202
203
	/**
204
	 * @return bool
205
	 */
206
	public function isPluginVersionValid( $plugin )
207
	{
208
		if( !$this->isPluginDependency( $plugin )) {
209
			return true;
210
		}
211
		if( !$this->isPluginInstalled( $plugin )) {
212
			return false;
213
		}
214
		return $this->catchError( $plugin, 'wrong_version', version_compare(
215
			$this->getPluginRequirements( $plugin, 'version' ),
216
			$this->getAllPlugins()[$plugin]['Version'],
217
			'<='
218
		));
219
	}
220
221
	/**
222
	 * @return void
223
	 */
224
	public function printNotices()
225
	{
226
		foreach( $this->notice->all as $notice ) {
227
			echo $this->notice->generate( $notice );
228
		}
229
	}
230
231
	/**
232
	 * @return void|null
233
	 */
234
	public function setDependencyNotice()
235
	{
236
		if( get_current_screen()->id != 'settings_page_pollux'
237
			|| $this->app->config->disable_config
238
			|| !$this->hasPendingDependencies()
239
		)return;
240
		$message = sprintf( '<strong>%s:</strong> %s',
241
			__( 'Pollux requires the latest version of the following plugins', 'pollux' ),
242
			$this->getDependencyLinks()
243
		);
244
		$this->notice->addWarning( [$message, $this->getDependencyActions()] );
245
	}
246
247
	/**
248
	 * @return void
249
	 */
250 View Code Duplication
	protected function addInvalidPHPVersionNotice()
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...
251
	{
252
		$this->notice->addError([
253
			$this->notice->title( __( 'The Pollux plugin was deactivated.', 'pollux' )),
254
			sprintf( __( 'Sorry, Pollux requires PHP %s or greater in order to work properly (your server is running PHP %s).', 'pollux' ), self::MIN_PHP_VERSION, PHP_VERSION ),
255
			__( 'Please contact your hosting provider or server administrator to upgrade the version of PHP running on your server, or find an alternate plugin.', 'pollux' ),
256
		]);
257
	}
258
259
	/**
260
	 * @return void
261
	 */
262 View Code Duplication
	protected function addInvalidWPVersionNotice()
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...
263
	{
264
		$this->notice->addError([
265
			$this->notice->title( __( 'The Pollux plugin was deactivated.', 'pollux' )),
266
			sprintf( __( 'Sorry, Pollux requires WordPress %s or greater in order to work properly.', 'pollux' ), self::MIN_WORDPRESS_VERSION ),
267
			$this->notice->button( __( 'Update WordPress', 'pollux' ), self_admin_url( 'update-core.php' )),
268
		]);
269
	}
270
271
	/**
272
	 * @param string $plugin
273
	 * @param string $error
274
	 * @param bool $isValid
275
	 * @return bool
276
	 */
277
	protected function catchError( $plugin, $error, $isValid )
278
	{
279
		if( !$isValid ) {
280
			if( !isset( $this->errors[$plugin] )) {
281
				$this->errors[$plugin] = [];
282
			}
283
			$this->errors[$plugin] = array_keys( array_flip(
284
				array_merge( $this->errors[$plugin], [$error] )
285
			));
286
		}
287
		return $isValid;
288
	}
289
290
	/**
291
	 * @return array
292
	 */
293
	protected function getAllPlugins()
294
	{
295
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
296
		return array_merge( get_plugins(), $this->getMustUsePlugins() );
297
	}
298
299
	/**
300
	 * @return string
301
	 */
302
	protected function getDependencyActions()
303
	{
304
		$actions = '';
305
		foreach( $this->errors as $plugin => $errors ) {
306
			if( in_array( 'not_found', $errors ) && current_user_can( 'install_plugins' )) {
307
				$actions .= $this->notice->installButton( $this->getPluginRequirements( $plugin ));
0 ignored issues
show
Bug introduced by
It seems like $this->getPluginRequirements($plugin) targeting GeminiLabs\Pollux\GateKe...getPluginRequirements() can also be of type string; however, GeminiLabs\Pollux\Notice::installButton() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
308
			}
309
			else if( in_array( 'wrong_version', $errors ) && current_user_can( 'update_plugins' )) {
310
				$actions .= $this->notice->updateButton( $this->getPluginInformation( $plugin ));
0 ignored issues
show
Bug introduced by
It seems like $this->getPluginInformation($plugin) targeting GeminiLabs\Pollux\GateKe...:getPluginInformation() can also be of type string; however, GeminiLabs\Pollux\Notice::updateButton() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
311
			}
312
			else if( in_array( 'inactive', $errors ) && current_user_can( 'activate_plugins' )) {
313
				$actions .= $this->notice->activateButton( $this->getPluginInformation( $plugin ));
0 ignored issues
show
Bug introduced by
It seems like $this->getPluginInformation($plugin) targeting GeminiLabs\Pollux\GateKe...:getPluginInformation() can also be of type string; however, GeminiLabs\Pollux\Notice::activateButton() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
314
			}
315
		}
316
		return $actions;
317
	}
318
319
	/**
320
	 * @return string
321
	 */
322
	protected function getDependencyLinks()
323
	{
324
		return array_reduce( array_keys( $this->errors ), function( $carry, $plugin ) {
325
			return $carry . $this->getPluginLink( $plugin );
326
		});
327
	}
328
329
	/**
330
	 * @return array
331
	 */
332
	protected function getMustUsePlugins()
333
	{
334
		$plugins = get_mu_plugins();
335
		if( in_array( 'Bedrock Autoloader', array_column( $plugins, 'Name' ))) {
336
			$autoloadedPlugins = get_site_option( 'bedrock_autoloader' );
337
			if( !empty( $autoloadedPlugins['plugins'] )) {
338
				return array_merge( $plugins, $autoloadedPlugins['plugins'] );
339
			}
340
		}
341
		return $plugins;
342
	}
343
344
	/**
345
	 * @return array|false
346
	 */
347
	protected function getPlugin( $plugin )
348
	{
349
		if( $this->isPluginInstalled( $plugin )) {
350
			return $this->getAllPlugins()[$plugin];
351
		}
352
		return false;
353
	}
354
355
	/**
356
	 * @return array|string
357
	 */
358
	protected function getPluginData( $plugin, $data, $key = null )
359
	{
360
		if( !is_array( $data )) {
361
			throw new Exception( sprintf( 'Plugin information not found for: %s', $plugin ));
362
		}
363
		$data['plugin'] = $plugin;
364
		$data['slug'] = $this->getPluginSlug( $plugin );
365
		$data = array_change_key_case( $data );
366
		if( is_null( $key )) {
367
			return $data;
368
		}
369
		$key = strtolower( $key );
370
		return isset( $data[$key] )
371
			? $data[$key]
372
			: '';
373
	}
374
375
	/**
376
	 * @return array|string
377
	 */
378
	protected function getPluginInformation( $plugin, $key = null )
379
	{
380
		return $this->getPluginData( $plugin, $this->getPlugin( $plugin ), $key );
381
	}
382
383
	/**
384
	 * @return string
385
	 */
386
	protected function getPluginLink( $plugin )
387
	{
388
		try {
389
			$data = $this->getPluginInformation( $plugin );
390
		}
391
		catch( Exception $e ) {
392
			$data = $this->getPluginRequirements( $plugin );
393
		}
394
		return sprintf( '<span class="plugin-%s"><a href="%s">%s</a></span>',
395
			$data['slug'],
396
			$data['pluginuri'],
397
			$data['name']
398
		);
399
	}
400
401
	/**
402
	 * @return array|string
403
	 */
404
	protected function getPluginRequirements( $plugin, $key = null )
405
	{
406
		$keys = ['Name', 'Version', 'PluginURI'];
407
		$requirements = $this->isPluginDependency( $plugin )
408
			? array_pad( explode( '|', static::DEPENDENCIES[$plugin] ), 3, '' )
409
			: array_fill( 0, 3, '' );
410
		return $this->getPluginData( $plugin, array_combine( $keys, $requirements ), $key );
411
	}
412
413
	/**
414
	 * @return string
415
	 */
416
	protected function getPluginSlug( $plugin )
417
	{
418
		return substr( $plugin, 0, strrpos( $plugin, '/' ));
419
	}
420
421
	/**
422
	 * @return void
423
	 */
424
	protected function redirect()
425
	{
426
		wp_safe_redirect( self_admin_url( sprintf( 'plugins.php?plugin_status=%s&paged=%s&s=%s',
427
			filter_input( INPUT_GET, 'plugin_status' ),
428
			filter_input( INPUT_GET, 'paged' ),
429
			filter_input( INPUT_GET, 's' )
430
		)));
431
		exit;
432
	}
433
}
434