Passed
Push — develop ( bdcc19...bd72f8 )
by Paul
03:05
created

GateKeeper::getPluginSlug()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
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 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
	protected function addInvalidPHPVersionNotice()
251
	{
252
		$message1 = sprintf( __( 'Pollux requires PHP %s or greater in order to work properly (your server is running PHP %s).', 'pollux' ), self::MIN_PHP_VERSION, PHP_VERSION );
253
		$message2 = __( 'Please contact your webhosting provider or server administrator to upgrade the version of PHP running on your server, or use a different plugin.', 'pollux' );
254
		$this->printDeactivationNotice( sprintf( '%s %s',
255
			$message1,
256
			$message2
257
		));
258
	}
259
260
	/**
261
	 * @return void
262
	 */
263
	protected function addInvalidWPVersionNotice()
264
	{
265
		$message = sprintf( __( 'Pollux requires WordPress %s or greater in order to work properly.', 'pollux' ), self::MIN_WORDPRESS_VERSION );
266
		if( current_user_can( 'update_core' )) {
267
			$message .= PHP_EOL . PHP_EOL . sprintf( '<a href="%s" class="button button-small">%s</a>',
268
				self_admin_url( 'update-core.php' ),
269
				__( 'Update WordPress', 'pollux' )
270
			);
271
		}
272
		$this->printDeactivationNotice( $message );
273
	}
274
275
	/**
276
	 * @param string $plugin
277
	 * @param string $error
278
	 * @param bool $isValid
279
	 * @return bool
280
	 */
281
	protected function catchError( $plugin, $error, $isValid )
282
	{
283
		if( !$isValid ) {
284
			if( !isset( $this->errors[$plugin] )) {
285
				$this->errors[$plugin] = [];
286
			}
287
			$this->errors[$plugin] = array_keys( array_flip(
288
				array_merge( $this->errors[$plugin], [$error] )
289
			));
290
		}
291
		return $isValid;
292
	}
293
294
	/**
295
	 * @return array
296
	 */
297
	protected function getAllPlugins()
298
	{
299
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
300
		return array_merge( get_plugins(), $this->getMustUsePlugins() );
301
	}
302
303
	/**
304
	 * @return string
305
	 */
306
	protected function getDependencyActions()
307
	{
308
		$actions = '';
309
		foreach( $this->errors as $plugin => $errors ) {
310
			if( in_array( 'not_found', $errors ) && current_user_can( 'install_plugins' )) {
311
				$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...
312
			}
313
			else if( in_array( 'wrong_version', $errors ) && current_user_can( 'update_plugins' )) {
314
				$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...
315
			}
316
			else if( in_array( 'inactive', $errors ) && current_user_can( 'activate_plugins' )) {
317
				$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...
318
			}
319
		}
320
		return $actions;
321
	}
322
323
	/**
324
	 * @return string
325
	 */
326
	protected function getDependencyLinks()
327
	{
328
		return array_reduce( array_keys( $this->errors ), function( $carry, $plugin ) {
329
			return $carry . $this->getPluginLink( $plugin );
330
		});
331
	}
332
333
	/**
334
	 * @return array
335
	 */
336
	protected function getMustUsePlugins()
337
	{
338
		$plugins = get_mu_plugins();
339
		if( in_array( 'Bedrock Autoloader', array_column( $plugins, 'Name' ))) {
340
			$autoloadedPlugins = get_site_option( 'bedrock_autoloader' );
341
			if( !empty( $autoloadedPlugins['plugins'] )) {
342
				return array_merge( $plugins, $autoloadedPlugins['plugins'] );
343
			}
344
		}
345
		return $plugins;
346
	}
347
348
	/**
349
	 * @return array|false
350
	 */
351
	protected function getPlugin( $plugin )
352
	{
353
		if( $this->isPluginInstalled( $plugin )) {
354
			return $this->getAllPlugins()[$plugin];
355
		}
356
		return false;
357
	}
358
359
	/**
360
	 * @return array|string
361
	 */
362
	protected function getPluginData( $plugin, $data, $key = null )
363
	{
364
		if( !is_array( $data )) {
365
			throw new Exception( sprintf( 'Plugin information not found for: %s', $plugin ));
366
		}
367
		$data['plugin'] = $plugin;
368
		$data['slug'] = $this->getPluginSlug( $plugin );
369
		$data = array_change_key_case( $data );
370
		if( is_null( $key )) {
371
			return $data;
372
		}
373
		$key = strtolower( $key );
374
		return isset( $data[$key] )
375
			? $data[$key]
376
			: '';
377
	}
378
379
	/**
380
	 * @return array|string
381
	 */
382
	protected function getPluginInformation( $plugin, $key = null )
383
	{
384
		return $this->getPluginData( $plugin, $this->getPlugin( $plugin ), $key );
385
	}
386
387
	/**
388
	 * @return string
389
	 */
390
	protected function getPluginLink( $plugin )
391
	{
392
		try {
393
			$data = $this->getPluginInformation( $plugin );
394
		}
395
		catch( Exception $e ) {
396
			$data = $this->getPluginRequirements( $plugin );
397
		}
398
		return sprintf( '<span class="plugin-%s"><a href="%s">%s</a></span>',
399
			$data['slug'],
400
			$data['pluginuri'],
401
			$data['name']
402
		);
403
	}
404
405
	/**
406
	 * @return array|string
407
	 */
408
	protected function getPluginRequirements( $plugin, $key = null )
409
	{
410
		$keys = ['Name', 'Version', 'PluginURI'];
411
		$requirements = $this->isPluginDependency( $plugin )
412
			? array_pad( explode( '|', static::DEPENDENCIES[$plugin] ), 3, '' )
413
			: array_fill( 0, 3, '' );
414
		return $this->getPluginData( $plugin, array_combine( $keys, $requirements ), $key );
415
	}
416
417
	/**
418
	 * @return string
419
	 */
420
	protected function getPluginSlug( $plugin )
421
	{
422
		return substr( $plugin, 0, strrpos( $plugin, '/' ));
423
	}
424
425
	/**
426
	 * @return void
427
	 */
428
	protected function printDeactivationNotice( $message )
429
	{
430
		printf( '<div class="notice notice-error is-dismissible"><p><strong>%s</strong></p>%s</div>',
431
			__( 'The Pollux plugin was deactivated.', 'pollux' ),
432
			wpautop( $message )
433
		);
434
	}
435
436
	/**
437
	 * @return void
438
	 */
439
	protected function redirect()
440
	{
441
		wp_safe_redirect( self_admin_url( sprintf( 'plugins.php?plugin_status=%s&paged=%s&s=%s',
442
			filter_input( INPUT_GET, 'plugin_status' ),
443
			filter_input( INPUT_GET, 'paged' ),
444
			filter_input( INPUT_GET, 's' )
445
		)));
446
		exit;
447
	}
448
}
449