Passed
Push — develop ( 017b4f...b34b2a )
by Paul
03:08
created

GateKeeper::canActivate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 4
rs 10
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 hasValidPHPVersion()
141
	{
142
		return version_compare( PHP_VERSION, self::MIN_PHP_VERSION, '>=' );
143
	}
144
145
	/**
146
	 * @return bool
147
	 */
148
	public function hasValidWPVersion()
149
	{
150
		global $wp_version;
151
		return version_compare( $wp_version, self::MIN_WORDPRESS_VERSION, '>=' );
152
	}
153
154
	/**
155
	 * @return bool
156
	 */
157
	public function isPluginActive( $plugin )
158
	{
159
		return $this->catchError( $plugin, 'inactive',
160
			is_plugin_active( $plugin ) || array_key_exists( $plugin, $this->getMustUsePlugins() )
161
		);
162
	}
163
164
	/**
165
	 * @return bool
166
	 */
167
	public function isPluginDependency( $plugin )
168
	{
169
		return array_key_exists( $plugin, static::DEPENDENCIES );
170
	}
171
172
	/**
173
	 * @return bool
174
	 */
175
	public function isPluginInstalled( $plugin )
176
	{
177
		return $this->catchError( $plugin, 'not_found',
178
			array_key_exists( $plugin, $this->getAllPlugins() )
179
		);
180
	}
181
182
	/**
183
	 * @return bool
184
	 */
185
	public function isPluginValid( $plugin )
186
	{
187
		return $this->isPluginActive( $plugin ) && $this->isPluginVersionValid( $plugin );
188
	}
189
190
	/**
191
	 * @return bool
192
	 */
193
	public function isPluginVersionValid( $plugin )
194
	{
195
		if( !$this->isPluginDependency( $plugin )) {
196
			return true;
197
		}
198
		if( !$this->isPluginInstalled( $plugin )) {
199
			return false;
200
		}
201
		return $this->catchError( $plugin, 'wrong_version', version_compare(
202
			$this->getPluginRequirements( $plugin, 'version' ),
203
			$this->getAllPlugins()[$plugin]['Version'],
204
			'<='
205
		));
206
	}
207
208
	/**
209
	 * @return void
210
	 */
211
	public function printNotices()
212
	{
213
		foreach( $this->notice->all as $notice ) {
214
			echo $this->notice->generate( $notice );
215
		}
216
	}
217
218
	/**
219
	 * @return bool
220
	 */
221
	public function hasPendingDependencies()
222
	{
223
		foreach( static::DEPENDENCIES as $plugin => $data ) {
224
			if( !$this->isPluginDependency( $plugin ))continue;
225
			$this->isPluginActive( $plugin );
226
			$this->isPluginVersionValid( $plugin );
227
		}
228
		return !empty( $this->errors );
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
241
		$plugins = '';
242
		$actions = '';
243
244
		foreach( $this->errors as $plugin => $errors ) {
245
			$plugins .= $this->getPluginLink( $plugin );
246
			if( in_array( 'not_found', $errors ) && current_user_can( 'install_plugins' )) {
247
				$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...
248
			}
249
			else if( in_array( 'wrong_version', $errors ) && current_user_can( 'update_plugins' )) {
250
				$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...
251
			}
252
			else if( in_array( 'inactive', $errors ) && current_user_can( 'activate_plugins' )) {
253
				$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...
254
			}
255
		}
256
		$this->notice->addWarning([
0 ignored issues
show
Bug introduced by
The call to addWarning() misses a required argument $args.

This check looks for function calls that miss required arguments.

Loading history...
257
			sprintf( '<strong>%s</strong> %s', __( 'Pollux requires the latest version of the following plugins:', 'pollux' ), $plugins ),
258
			$actions,
259
		]);
260
	}
261
262
	/**
263
	 * @return void
264
	 */
265 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...
266
	{
267
		$this->notice->addError([
0 ignored issues
show
Bug introduced by
The call to addError() misses a required argument $args.

This check looks for function calls that miss required arguments.

Loading history...
268
			$this->notice->title( __( 'The Pollux plugin was deactivated.', 'pollux' )),
269
			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 ),
270
			__( 'Please contact your hosting provider or server administrator to upgrade the version of PHP running on your server, or find an alternate plugin.', 'pollux' ),
271
		]);
272
	}
273
274
	/**
275
	 * @return void
276
	 */
277 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...
278
	{
279
		$this->notice->addError([
0 ignored issues
show
Bug introduced by
The call to addError() misses a required argument $args.

This check looks for function calls that miss required arguments.

Loading history...
280
			$this->notice->title( __( 'The Pollux plugin was deactivated.', 'pollux' )),
281
			sprintf( __( 'Sorry, Pollux requires WordPress %s or greater in order to work properly.', 'pollux' ), self::MIN_WORDPRESS_VERSION ),
282
			$this->notice->button( __( 'Update WordPress', 'pollux' ), self_admin_url( 'update-core.php' )),
283
		]);
284
	}
285
286
	/**
287
	 * @param string $plugin
288
	 * @param string $error
289
	 * @param bool $isValid
290
	 * @return bool
291
	 */
292
	protected function catchError( $plugin, $error, $isValid )
293
	{
294
		if( !$isValid ) {
295
			if( !isset( $this->errors[$plugin] )) {
296
				$this->errors[$plugin] = [];
297
			}
298
			$this->errors[$plugin] = array_keys( array_flip(
299
				array_merge( $this->errors[$plugin], [$error] )
300
			));
301
		}
302
		return $isValid;
303
	}
304
305
	/**
306
	 * @return array
307
	 */
308
	protected function getAllPlugins()
309
	{
310
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
311
		return array_merge( get_plugins(), $this->getMustUsePlugins() );
312
	}
313
314
	/**
315
	 * @return array
316
	 */
317
	protected function getMustUsePlugins()
318
	{
319
		$plugins = get_mu_plugins();
320
		if( in_array( 'Bedrock Autoloader', array_column( $plugins, 'Name' ))) {
321
			$autoloadedPlugins = get_site_option( 'bedrock_autoloader' );
322
			if( !empty( $autoloadedPlugins['plugins'] )) {
323
				return array_merge( $plugins, $autoloadedPlugins['plugins'] );
324
			}
325
		}
326
		return $plugins;
327
	}
328
329
	/**
330
	 * @return array|false
331
	 */
332
	protected function getPlugin( $plugin )
333
	{
334
		if( $this->isPluginInstalled( $plugin )) {
335
			return $this->getAllPlugins()[$plugin];
336
		}
337
		return false;
338
	}
339
340
	/**
341
	 * @return array|string
342
	 */
343
	protected function getPluginData( $plugin, $data, $key = null )
344
	{
345
		if( !is_array( $data )) {
346
			throw new Exception( sprintf( 'Plugin information not found for: %s', $plugin ));
347
		}
348
		$data['plugin'] = $plugin;
349
		$data['slug'] = $this->getPluginSlug( $plugin );
350
		$data = array_change_key_case( $data );
351
		if( is_null( $key )) {
352
			return $data;
353
		}
354
		$key = strtolower( $key );
355
		return isset( $data[$key] )
356
			? $data[$key]
357
			: '';
358
	}
359
360
	/**
361
	 * @return array|string
362
	 */
363
	protected function getPluginInformation( $plugin, $key = null )
364
	{
365
		return $this->getPluginData( $plugin, $this->getPlugin( $plugin ), $key );
366
	}
367
368
	/**
369
	 * @return string
370
	 */
371
	protected function getPluginLink( $plugin )
372
	{
373
		try {
374
			$data = $this->getPluginInformation( $plugin );
375
		}
376
		catch( Exception $e ) {
377
			$data = $this->getPluginRequirements( $plugin );
378
		}
379
		return sprintf( '<span class="plugin-%s"><a href="%s">%s</a></span>',
380
			$data['slug'],
381
			$data['pluginuri'],
382
			$data['name']
383
		);
384
	}
385
386
	/**
387
	 * @return array|string
388
	 */
389
	protected function getPluginRequirements( $plugin, $key = null )
390
	{
391
		$keys = ['Name', 'Version', 'PluginURI'];
392
		$requirements = $this->isPluginDependency( $plugin )
393
			? array_pad( explode( '|', static::DEPENDENCIES[$plugin] ), 3, '' )
394
			: array_fill( 0, 3, '' );
395
		return $this->getPluginData( $plugin, array_combine( $keys, $requirements ), $key );
396
	}
397
398
	/**
399
	 * @return string
400
	 */
401
	protected function getPluginSlug( $plugin )
402
	{
403
		return substr( $plugin, 0, strrpos( $plugin, '/' ));
404
	}
405
406
	/**
407
	 * @return void
408
	 */
409
	protected function redirect()
410
	{
411
		wp_safe_redirect( self_admin_url( sprintf( 'plugins.php?plugin_status=%s&paged=%s&s=%s',
412
			filter_input( INPUT_GET, 'plugin_status' ),
413
			filter_input( INPUT_GET, 'paged' ),
414
			filter_input( INPUT_GET, 's' )
415
		)));
416
		exit;
417
	}
418
}
419