Test Setup Failed
Push — master ( 9b1a69...300ef2 )
by Paul
03:03
created

GateKeeper::deactivate()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
nc 4
nop 1
dl 0
loc 11
ccs 0
cts 9
cp 0
crap 12
rs 9.4285
c 1
b 0
f 0
1
<?php
2
3
namespace GeminiLabs\Pollux;
4
5
use Exception;
6
use ReflectionClass;
7
8
class GateKeeper
9
{
10
	/**
11
	 * [plugin_file_path] => [plugin_name]|[plugin_version]|[plugin_url]
12
	 */
13
	const DEPENDENCIES = [
14
		'meta-box/meta-box.php' => 'Meta Box|4.11|https://wordpress.org/plugins/meta-box/',
15
	];
16
17
	public $errors = [];
18
19
	/**
20
	 * @var Notice
21
	 */
22
	protected $notice;
23
24
	/**
25
	 * @var string
26
	 */
27
	protected $plugin;
28
29
	public function __construct( $plugin )
30
	{
31
		$this->plugin = $plugin;
32
		add_action( 'admin_init', [$this, 'init'] );
33
	}
34
35
	public function init()
36
	{
37
		$this->notice = pollux_app()->make( 'Notice' );
38
39
		add_action( 'current_screen',                         [$this, 'activatePlugin'] );
40
		add_action( 'wp_ajax_pollux/dependency/activate_url', [$this, 'ajaxActivatePluginLink'] );
41
		add_action( 'admin_notices',                          [$this, 'printNotices'] );
42
		add_action( 'current_screen',                         [$this, 'setDependencyNotice'] );
43
	}
44
45
	/**
46
	 * @return void
47
	 */
48
	public function activatePlugin()
49
	{
50
		if( get_current_screen()->id != sprintf( 'settings_page_%s', pollux_app()->id )
51
			|| filter_input( INPUT_GET, 'action' ) != 'activate'
52
		)return;
53
		$plugin = filter_input( INPUT_GET, 'plugin' );
54
		check_admin_referer( 'activate-plugin_' . $plugin );
55
		$result = activate_plugin( $plugin, null, is_network_admin(), true );
56
		if( is_wp_error( $result )) {
57
			wp_die( $result->get_error_message() );
58
		}
59
		wp_safe_redirect( wp_get_referer() );
60
		exit;
61
	}
62
63
	/**
64
	 * @return void
65
	 */
66
	public function ajaxActivatePluginLink()
67
	{
68
		check_ajax_referer( 'updates' );
69
		$plugin = filter_input( INPUT_POST, 'plugin' );
70
		if( !$this->isPluginDependency( $plugin )) {
71
			wp_send_json_error();
72
		}
73
		$activateUrl = add_query_arg([
74
			'_wpnonce' => wp_create_nonce( sprintf( 'activate-plugin_%s', $plugin )),
75
			'action' => 'activate',
76
			'page' => pollux_app()->id,
77
			'plugin' => $plugin,
78
		], self_admin_url( 'options-general.php' ));
79
		wp_send_json_success([
80
			'activate_url' => $activateUrl,
81
			filter_input( INPUT_POST, 'type' ) => $plugin,
82
		]);
83
	}
84
85
	/**
86
	 * @return bool
87
	 */
88
	public function hasDependency( $plugin )
89
	{
90
		if( !$this->isPluginDependency( $plugin )) {
91
			return true;
92
		}
93
		return $this->isPluginInstalled( $plugin ) && $this->isPluginValid( $plugin );
94
	}
95
96
	/**
97
	 * @return bool
98
	 */
99
	public function hasPendingDependencies()
100
	{
101
		foreach( static::DEPENDENCIES as $plugin => $data ) {
102
			if( !$this->isPluginDependency( $plugin ))continue;
103
			$this->isPluginActive( $plugin );
104
			$this->isPluginVersionValid( $plugin );
105
		}
106
		return !empty( $this->errors );
107
	}
108
109
	/**
110
	 * @return bool
111
	 */
112
	public function isPluginActive( $plugin )
113
	{
114
		return $this->catchError( $plugin, 'inactive',
115
			is_plugin_active( $plugin ) || array_key_exists( $plugin, $this->getMustUsePlugins() )
116
		);
117
	}
118
119
	/**
120
	 * @return bool
121
	 */
122
	public function isPluginDependency( $plugin )
123
	{
124
		return array_key_exists( $plugin, static::DEPENDENCIES );
125
	}
126
127
	/**
128
	 * @return bool
129
	 */
130
	public function isPluginInstalled( $plugin )
131
	{
132
		return $this->catchError( $plugin, 'not_found',
133
			array_key_exists( $plugin, $this->getAllPlugins() )
134
		);
135
	}
136
137
	/**
138
	 * @return bool
139
	 */
140
	public function isPluginValid( $plugin )
141
	{
142
		return $this->isPluginActive( $plugin ) && $this->isPluginVersionValid( $plugin );
143
	}
144
145
	/**
146
	 * @return bool
147
	 */
148
	public function isPluginVersionValid( $plugin )
149
	{
150
		if( !$this->isPluginDependency( $plugin )) {
151
			return true;
152
		}
153
		if( !$this->isPluginInstalled( $plugin )) {
154
			return false;
155
		}
156
		return $this->catchError( $plugin, 'wrong_version', version_compare(
157
			$this->getPluginRequirements( $plugin, 'version' ),
158
			$this->getAllPlugins()[$plugin]['Version'],
159
			'<='
160
		));
161
	}
162
163
	/**
164
	 * @return void
165
	 */
166
	public function printNotices()
167
	{
168
		foreach( $this->notice->all as $notice ) {
169
			echo $this->notice->generate( $notice );
170
		}
171
	}
172
173
	/**
174
	 * @return void|null
175
	 */
176
	public function setDependencyNotice()
177
	{
178
		if( get_current_screen()->id != 'settings_page_pollux'
179
			|| pollux_app()->config->disable_config
180
			|| !$this->hasPendingDependencies()
181
		)return;
182
		$message = sprintf( '<strong>%s:</strong> %s',
183
			__( 'Pollux requires the latest version of the following plugins', 'pollux' ),
184
			$this->getDependencyLinks()
185
		);
186
		$this->notice->addWarning( [$message, $this->getDependencyActions()] );
187
	}
188
189
	/**
190
	 * @param string $plugin
191
	 * @param string $error
192
	 * @param bool $isValid
193
	 * @return bool
194
	 */
195
	protected function catchError( $plugin, $error, $isValid )
196
	{
197
		if( !$isValid && $this->isPluginDependency( $plugin )) {
198
			if( !isset( $this->errors[$plugin] )) {
199
				$this->errors[$plugin] = [];
200
			}
201
			$this->errors[$plugin] = array_keys( array_flip(
202
				array_merge( $this->errors[$plugin], [$error] )
203
			));
204
		}
205
		return $isValid;
206
	}
207
208
	/**
209
	 * @return array
210
	 */
211
	protected function getAllPlugins()
212
	{
213
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
214
		return array_merge( get_plugins(), $this->getMustUsePlugins() );
215
	}
216
217
	/**
218
	 * @return string
219
	 */
220
	protected function getDependencyActions()
221
	{
222
		$actions = '';
223
		foreach( $this->errors as $plugin => $errors ) {
224
			if( in_array( 'not_found', $errors ) && current_user_can( 'install_plugins' )) {
225
				$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...
226
			}
227
			else if( in_array( 'wrong_version', $errors ) && current_user_can( 'update_plugins' )) {
228
				$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...
229
			}
230
			else if( in_array( 'inactive', $errors ) && current_user_can( 'activate_plugins' )) {
231
				$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...
232
			}
233
		}
234
		return $actions;
235
	}
236
237
	/**
238
	 * @return string
239
	 */
240
	protected function getDependencyLinks()
241
	{
242
		return array_reduce( array_keys( $this->errors ), function( $carry, $plugin ) {
243
			return $carry . $this->getPluginLink( $plugin );
244
		});
245
	}
246
247
	/**
248
	 * @return array
249
	 */
250
	protected function getMustUsePlugins()
251
	{
252
		$plugins = get_mu_plugins();
253
		if( in_array( 'Bedrock Autoloader', array_column( $plugins, 'Name' ))) {
254
			$autoloadedPlugins = get_site_option( 'bedrock_autoloader' );
255
			if( !empty( $autoloadedPlugins['plugins'] )) {
256
				return array_merge( $plugins, $autoloadedPlugins['plugins'] );
257
			}
258
		}
259
		return $plugins;
260
	}
261
262
	/**
263
	 * @return array|false
264
	 */
265
	protected function getPlugin( $plugin )
266
	{
267
		if( $this->isPluginInstalled( $plugin )) {
268
			return $this->getAllPlugins()[$plugin];
269
		}
270
		return false;
271
	}
272
273
	/**
274
	 * @return array|string
275
	 */
276
	protected function getPluginData( $plugin, $data, $key = null )
277
	{
278
		if( !is_array( $data )) {
279
			throw new Exception( sprintf( 'Plugin information not found for: %s', $plugin ));
280
		}
281
		$data['plugin'] = $plugin;
282
		$data['slug'] = $this->getPluginSlug( $plugin );
283
		$data = array_change_key_case( $data );
284
		if( is_null( $key )) {
285
			return $data;
286
		}
287
		$key = strtolower( $key );
288
		return isset( $data[$key] )
289
			? $data[$key]
290
			: '';
291
	}
292
293
	/**
294
	 * @return array|string
295
	 */
296
	protected function getPluginInformation( $plugin, $key = null )
297
	{
298
		return $this->getPluginData( $plugin, $this->getPlugin( $plugin ), $key );
299
	}
300
301
	/**
302
	 * @return string
303
	 */
304
	protected function getPluginLink( $plugin )
305
	{
306
		try {
307
			$data = $this->getPluginInformation( $plugin );
308
		}
309
		catch( Exception $e ) {
310
			$data = $this->getPluginRequirements( $plugin );
311
		}
312
		return sprintf( '<span class="plugin-%s"><a href="%s">%s</a></span>',
313
			$data['slug'],
314
			$data['pluginuri'],
315
			$data['name']
316
		);
317
	}
318
319
	/**
320
	 * @return array|string
321
	 */
322
	protected function getPluginRequirements( $plugin, $key = null )
323
	{
324
		$keys = ['Name', 'Version', 'PluginURI'];
325
		$requirements = $this->isPluginDependency( $plugin )
326
			? array_pad( explode( '|', static::DEPENDENCIES[$plugin] ), 3, '' )
327
			: array_fill( 0, 3, '' );
328
		return $this->getPluginData( $plugin, array_combine( $keys, $requirements ), $key );
329
	}
330
331
	/**
332
	 * @return string
333
	 */
334
	protected function getPluginSlug( $plugin )
335
	{
336
		return substr( $plugin, 0, strrpos( $plugin, '/' ));
337
	}
338
}
339