Completed
Push — master ( 385022...eefc26 )
by Fulvio
03:54
created

Plugin::get_options()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
/**
3
 * WP PHP Console
4
 *
5
 * This source file is subject to the GNU General Public License v3.0
6
 * that is bundled with this package in the file license.txt.
7
 * It is also available through the world-wide-web at this URL:
8
 * http://www.gnu.org/licenses/gpl-3.0.html
9
 *
10
 * @author    Fulvio Notarstefano <[email protected]>
11
 * @copyright Copyright (c) 2014-2019 Fulvio Notarstefano
12
 * @license   http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
13
 */
14
15
namespace WP_PHP_Console;
16
17
use PhpConsole;
18
19
defined( 'ABSPATH' ) or exit;
20
21
/**
22
 * WP PHP Console main class.
23
 *
24
 * @since 1.0.0
25
 */
26
class Plugin {
27
28
29
	/** @var string plugin version */
30
	CONST VERSION = '1.5.4';
31
32
	/** @var string plugin name */
33
	CONST NAME = 'WP PHP Console';
34
35
36
	/** @var array settings options */
37
	private $options = [];
38
39
	/** @var PhpConsole\Connector instance */
40
	public $connector;
41
42
43
	/**
44
	 * Loads plugin and connects to PHP Console.
45
	 *
46
	 * @since 1.0.0
47
	 */
48
	public function __construct() {
49
50
		$this->set_debug_mode();
51
		$this->set_options();
52
		$this->set_admin();
53
		$this->set_hooks();
54
	}
55
56
57
	/**
58
	 * Sets constants and elevates PHP error reporting.
59
	 *
60
	 * @since 1.5.4
61
	 */
62
	private function set_debug_mode() {
63
64
		@error_reporting( E_ALL );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
65
66
		foreach ( [ 'WP_DEBUG',	'WP_DEBUG_LOG', 'WP_DEBUG_DISPLAY', ] as $wp_debug_constant ) {
67
			if ( ! defined( $wp_debug_constant ) ) {
68
				define ( $wp_debug_constant, true );
69
			}
70
		}
71
	}
72
73
74
	/**
75
	 * Sets the plugin options.
76
	 *
77
	 * @since 1.5.4
78
	 */
79
	private function set_options() {
80
81
		$this->options = $this->get_options();
82
	}
83
84
85
	/**
86
	 * Sets plugin text domain.
87
	 *
88
	 * @since 1.0.0
89
	 */
90
	public function set_locale() {
91
92
		load_plugin_textdomain(
93
			'wp-php-console',
94
			false,
95
			dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/'
96
		);
97
	}
98
99
100
	/**
101
	 * Loads admin.
102
	 *
103
	 * @since 1.5.0
104
	 */
105
	private function set_admin() {
106
107
		if ( ! defined( 'DOING_AJAX' ) && is_admin() ) {
108
109
			// add a settings link to the plugins admin screen
110
			$plugin_name = str_replace( 'includes/class-', '', plugin_basename( __FILE__ ) );
111
			add_filter( "plugin_action_links_{$plugin_name}", static function( $actions ) {
112
				return array_merge( [
113
					'<a href="' . esc_url( admin_url( 'options-general.php?page=wp-php-console' ) ) . '">' . __( 'Settings', 'wp-php-console' ) . '</a>',
114
				], $actions );
115
			} );
116
117
			new Settings( $this->options );
118
		}
119
	}
120
121
122
	/**
123
	 * Sets plugin hooks.
124
	 *
125
	 * @since 1.5.4
126
	 */
127
	private function set_hooks() {
128
129
		// bail out if PHP Console can't be found
130
		if ( ! class_exists( 'PhpConsole\Connector' ) ) {
131
			return;
132
		}
133
134
		// handle translations
135
		add_action( 'plugins_loaded', [ $this, 'set_locale' ] );
136
		// connect to PHP Console
137
		add_action( 'init',           [ $this, 'connect' ], -1000 );
138
		// delay further PHP Console initialisation to have more context during Remote PHP execution
139
		add_action( 'wp_loaded',      [ $this, 'init' ], -1000 );
140
	}
141
142
143
	/**
144
	 * Connects to PHP Console.
145
	 *
146
	 * PHP Console needs to hook in session, in WordPress we need to be in 'init':
147
	 * @link http://silvermapleweb.com/using-the-php-session-in-wordpress/
148
	 * @internal action hook callback
149
	 *
150
	 * @since 1.4.0
151
	 */
152
	public function connect() {
153
154
		// workaround for avoiding headers already sent warnings
155
		@error_reporting( E_ALL & ~E_WARNING );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
156
157
		if ( ! @session_id() ) {
158
			@session_start();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
159
		}
160
161
		$connected = true;
162
163
		if ( ! $this->connector instanceof PhpConsole\Connector ) {
164
			try {
165
				$this->connector = PhpConsole\Connector::getInstance();
166
			} catch ( \Exception $e ) {
167
				$connected = false;
168
			}
169
		}
170
171
		// restore error reporting
172
		@error_reporting( E_ALL );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
173
174
		// apply PHP Console options
175
		if ( $connected ) {
176
			$this->apply_options();
177
		}
178
	}
179
180
181
	/**
182
	 * Get WP PHP Console settings options.
183
	 *
184
	 * @since  1.4.0
185
	 *
186
	 * @return array
187
	 */
188
	protected function get_options() {
189
190
		$options = get_option( 'wp_php_console', [] );
191
192
		return wp_parse_args( $options, [
193
			'ip'       => '',
194
			'password' => '',
195
			'register' => false,
196
			'short'    => false,
197
			'ssl'      => false,
198
			'stack'    => false,
199
		] );
200
	}
201
202
203
	/**
204
	 * Applies options.
205
	 *
206
	 * @since 1.4.0
207
	 */
208
	private function apply_options() {
209
210
		// bail out if not connected yet to PHP Console
211
		if ( ! $this->connector instanceof PhpConsole\Connector ) {
212
			return;
213
		}
214
215
		// apply 'register' option to PHP Console...
216
		if ( true === $this->options['register'] && ! class_exists( 'PC', false ) ) {
217
			// ...only if PC not registered yet
218
			try {
219
				PhpConsole\Helper::register();
220
			} catch( \Exception $e ) {
221
				$this->print_notice_exception( $e );
222
			}
223
		}
224
225
		// apply 'stack' option to PHP Console
226
		if ( true === $this->options['stack'] ) {
227
			$this->connector->getDebugDispatcher()->detectTraceAndSource = true;
228
		}
229
230
		// apply 'short' option to PHP Console
231
		if ( true === $this->options['short'] ) {
232
			try {
233
				$this->connector->setSourcesBasePath( $_SERVER['DOCUMENT_ROOT'] );
234
			} catch ( \Exception $e ) {
235
				$this->print_notice_exception( $e );
236
			}
237
		}
238
	}
239
240
241
	/**
242
	 * Initializes PHP Console.
243
	 *
244
	 * @internal action hook callback
245
	 *
246
	 * @since 1.0.0
247
	 */
248
	public function init() {
249
250
		// get PHP Console extension password
251
		$password = trim( $this->options['password'] );
252
253
		if ( empty( $password ) ) {
254
255
			// display admin notice and abort if no password has been set
256
			add_action( 'admin_notices', [ $this, 'password_notice' ] );
257
			return;
258
		}
259
260
		// selectively remove slashes added by WordPress as expected by PHP Console
261
		if ( array_key_exists( PhpConsole\Connector::POST_VAR_NAME, $_POST ) ) {
262
			$_POST[ PhpConsole\Connector::POST_VAR_NAME ] = stripslashes_deep( $_POST[ PhpConsole\Connector::POST_VAR_NAME ] );
263
		}
264
265
		// get PHP Console instance if wasn't set yet
266
		if ( ! $this->connector instanceof PhpConsole\Connector ) {
267
268
			// workaround for avoiding headers already sent warnings
269
			@error_reporting( E_ALL & ~E_WARNING );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
270
271
			try {
272
				$this->connector = PhpConsole\Connector::getInstance();
273
				$connected       = true;
274
			} catch ( \Exception $e ) {
275
				$connected       = false;
276
			}
277
278
			// restore error reporting
279
			@error_reporting( E_ALL );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
280
281
			if ( ! $connected ) {
282
				return;
283
			}
284
		}
285
286
		// set PHP Console password
287
		try {
288
			$this->connector->setPassword( $password );
289
		} catch ( \Exception $e ) {
290
			$this->print_notice_exception( $e );
291
		}
292
293
		// get PHP Console handler instance
294
		$handler = PhpConsole\Handler::getInstance();
295
296
		if ( true !== PhpConsole\Handler::getInstance()->isStarted() ) {
297
			try {
298
				$handler->start();
299
			} catch( \Exception $e ) {
300
				$this->print_notice_exception( $e );
301
				return;
302
			}
303
		}
304
305
		// enable SSL-only mode
306
		if ( true === $this->options['ssl'] ) {
307
			$this->connector->enableSslOnlyMode();
308
		}
309
310
		// restrict IP addresses
311
		$allowedIpMasks = ! empty( $this->options['ip'] ) ? explode( ',', $this->options['ip'] ) : '';
312
313
		if ( is_array( $allowedIpMasks ) && count( $allowedIpMasks ) > 0 ) {
314
			$this->connector->setAllowedIpMasks( $allowedIpMasks );
315
		}
316
317
		$evalProvider = $this->connector->getEvalDispatcher()->getEvalProvider();
318
319
		try {
320
			$evalProvider->addSharedVar( 'uri', $_SERVER['REQUEST_URI'] );
321
		} catch ( \Exception $e ) {
322
			$this->print_notice_exception( $e );
323
		}
324
325
		try {
326
			$evalProvider->addSharedVarReference( 'post', $_POST );
327
		} catch ( \Exception $e ) {
328
			$this->print_notice_exception( $e );
329
		}
330
331
		$openBaseDirs = [ ABSPATH, get_template_directory() ];
332
333
		try {
334
			$evalProvider->addSharedVarReference( 'dirs', $openBaseDirs );
335
		} catch ( \Exception $e ) {
336
			$this->print_notice_exception( $e );
337
		}
338
339
		$evalProvider->setOpenBaseDirs( $openBaseDirs );
340
341
		try {
342
			$this->connector->startEvalRequestsListener();
343
		} catch ( \Exception $e ) {
344
			$this->print_notice_exception( $e );
345
		}
346
	}
347
348
349
	/**
350
	 * Prints an exception message as WordPress admin notice.
351
	 *
352
	 * @since 1.4.0
353
	 *
354
	 * @param \Exception $e Exception object
355
	 */
356
	public function print_notice_exception( \Exception $e ) {
357
358
		add_action( 'admin_notices', static function() use ( $e ) {
359
360
			?>
361
			<div class="error">
362
				<p><?php printf( '%1$s: %2$s', self::NAME, $e->getMessage() ); ?></p>
363
			</div>
364
			<?php
365
366
		} );
367
	}
368
369
370
371
	/**
372
	 * Admin password notice.
373
	 *
374
	 * Prompts user to set a password for PHP Console upon plugin activation.
375
	 *
376
	 * @internal action hook callback
377
	 *
378
	 * @since 1.3.2
379
	 */
380
	public function password_notice() {
381
382
		?>
383
		<div class="update-nag">
384
			<p><?php printf(
385
				/* translators: Placeholders: %1$s - WP PHP Console name, %2$s - opening HTML <a> link tag; %3$s closing HTML </a> link tag */
386
				__( '%1$s: Please remember to %2$sset a password%3$s if you want to enable the terminal.', 'wp-php-console' ),
387
				'<strong>' . self::NAME . '</strong>',
388
				'<a href="' . esc_url( admin_url( 'options-general.php?page=wp-php-console' ) ) .'">',
389
				'</a>'
390
			); ?></p>
391
		</div>
392
		<?php
393
	}
394
395
396
}
397