Completed
Push — master ( 1149e8...e600a3 )
by Fulvio
08:43
created

Plugin::get_settings_page_url()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 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.5';
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
	 * @internal action hook callback
89
	 *
90
	 * @since 1.0.0
91
	 */
92
	public function set_locale() {
93
94
		load_plugin_textdomain(
95
			'wp-php-console',
96
			false,
97
			dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/'
98
		);
99
	}
100
101
102
	/**
103
	 * Loads admin.
104
	 *
105
	 * @since 1.5.0
106
	 */
107
	private function set_admin() {
108
109
		if ( is_admin() ) {
110
111
			add_filter( 'plugin_action_links_wp-php-console/wp-php-console.php', static function( $actions ) {
112
				return array_merge( [
113
					'<a href="' . esc_url( self::get_settings_page_url() ) . '">' . esc_html__( 'Settings', 'wp-php-console' ) . '</a>',
114
					'<a href="' . esc_url( self::get_project_page_url() ) . '">' . esc_html__( 'GitHub', 'wp-php-console' ) . '</a>',
115
					'<a href="' . esc_url( self::get_support_page_url() ) . '">' . esc_html__( 'Support', 'wp-php-console' ) . '</a>',
116
					'<a href="' . esc_url( self::get_reviews_page_url() ) . '">' . esc_html__( 'Review', 'wp-php-console' ) . '</a>',
117
				], $actions );
118
			} );
119
120
			if ( ! defined( 'DOING_AJAX' ) ) {
121
				new Settings( $this->options );
122
			}
123
		}
124
	}
125
126
127
	/**
128
	 * Sets plugin hooks.
129
	 *
130
	 * @since 1.5.4
131
	 */
132
	private function set_hooks() {
133
134
		// bail out if PHP Console can't be found
135
		if ( ! class_exists( 'PhpConsole\Connector' ) ) {
136
			return;
137
		}
138
139
		// handle translations
140
		add_action( 'plugins_loaded', [ $this, 'set_locale' ] );
141
		// connect to PHP Console
142
		add_action( 'init',           [ $this, 'connect' ], -1000 );
143
		// delay further PHP Console initialisation to have more context during Remote PHP execution
144
		add_action( 'wp_loaded',      [ $this, 'init' ], -1000 );
145
	}
146
147
148
	/**
149
	 * Connects to PHP Console.
150
	 *
151
	 * PHP Console needs to hook in session, in WordPress we need to be in 'init':
152
	 * @link http://silvermapleweb.com/using-the-php-session-in-wordpress/
153
	 *
154
	 * @internal action hook callback
155
	 *
156
	 * @since 1.4.0
157
	 */
158
	public function connect() {
159
160
		// workaround for avoiding headers already sent warnings
161
		@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...
162
163
		if ( ! @session_id() ) {
164
			@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...
165
		}
166
167
		$connected = true;
168
169
		if ( ! $this->connector instanceof PhpConsole\Connector ) {
170
			try {
171
				$this->connector = PhpConsole\Connector::getInstance();
172
			} catch ( \Exception $e ) {
173
				$connected = false;
174
			}
175
		}
176
177
		// restore error reporting
178
		@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...
179
180
		// apply PHP Console options
181
		if ( $connected ) {
182
			$this->apply_options();
183
		}
184
	}
185
186
187
	/**
188
	 * Get WP PHP Console settings options.
189
	 *
190
	 * @since  1.4.0
191
	 *
192
	 * @return array
193
	 */
194
	private function get_options() {
195
196
		$options = get_option( 'wp_php_console', [] );
197
198
		return wp_parse_args( $options, [
199
			'ip'       => '',
200
			'password' => '',
201
			'register' => false,
202
			'short'    => false,
203
			'ssl'      => false,
204
			'stack'    => false,
205
		] );
206
	}
207
208
209
	/**
210
	 * Applies options.
211
	 *
212
	 * @since 1.4.0
213
	 */
214
	private function apply_options() {
215
216
		// bail out if not connected yet to PHP Console
217
		if ( ! $this->connector instanceof PhpConsole\Connector ) {
218
			return;
219
		}
220
221
		// apply 'register' option to PHP Console...
222
		if ( true === $this->options['register'] && ! class_exists( 'PC', false ) ) {
223
			// ...only if PC not registered yet
224
			try {
225
				PhpConsole\Helper::register();
226
			} catch( \Exception $e ) {
227
				$this->print_notice_exception( $e );
228
			}
229
		}
230
231
		// apply 'stack' option to PHP Console
232
		if ( true === $this->options['stack'] ) {
233
			$this->connector->getDebugDispatcher()->detectTraceAndSource = true;
234
		}
235
236
		// apply 'short' option to PHP Console
237
		if ( true === $this->options['short'] ) {
238
			try {
239
				$this->connector->setSourcesBasePath( $_SERVER['DOCUMENT_ROOT'] );
240
			} catch ( \Exception $e ) {
241
				$this->print_notice_exception( $e );
242
			}
243
		}
244
	}
245
246
247
	/**
248
	 * Initializes PHP Console.
249
	 *
250
	 * @internal action hook callback
251
	 *
252
	 * @since 1.0.0
253
	 */
254
	public function init() {
255
256
		// get PHP Console extension password
257
		$password = trim( $this->options['password'] );
258
259
		if ( empty( $password ) ) {
260
261
			// display admin notice and abort if no password has been set
262
			add_action( 'admin_notices', [ $this, 'password_notice' ] );
263
			return;
264
		}
265
266
		// selectively remove slashes added by WordPress as expected by PHP Console
267
		if ( array_key_exists( PhpConsole\Connector::POST_VAR_NAME, $_POST ) ) {
268
			$_POST[ PhpConsole\Connector::POST_VAR_NAME ] = stripslashes_deep( $_POST[ PhpConsole\Connector::POST_VAR_NAME ] );
269
		}
270
271
		// get PHP Console instance if wasn't set yet
272
		if ( ! $this->connector instanceof PhpConsole\Connector ) {
273
274
			// workaround for avoiding headers already sent warnings
275
			@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...
276
277
			try {
278
				$this->connector = PhpConsole\Connector::getInstance();
279
				$connected       = true;
280
			} catch ( \Exception $e ) {
281
				$connected       = false;
282
			}
283
284
			// restore error reporting
285
			@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...
286
287
			if ( ! $connected ) {
288
				return;
289
			}
290
		}
291
292
		// set PHP Console password
293
		try {
294
			$this->connector->setPassword( $password );
295
		} catch ( \Exception $e ) {
296
			$this->print_notice_exception( $e );
297
		}
298
299
		// get PHP Console handler instance
300
		$handler = PhpConsole\Handler::getInstance();
301
302
		if ( true !== PhpConsole\Handler::getInstance()->isStarted() ) {
303
			try {
304
				$handler->start();
305
			} catch( \Exception $e ) {
306
				$this->print_notice_exception( $e );
307
				return;
308
			}
309
		}
310
311
		// enable SSL-only mode
312
		if ( true === $this->options['ssl'] ) {
313
			$this->connector->enableSslOnlyMode();
314
		}
315
316
		// restrict IP addresses
317
		$allowedIpMasks = ! empty( $this->options['ip'] ) ? explode( ',', $this->options['ip'] ) : '';
318
319
		if ( is_array( $allowedIpMasks ) && count( $allowedIpMasks ) > 0 ) {
320
			$this->connector->setAllowedIpMasks( $allowedIpMasks );
321
		}
322
323
		$evalProvider = $this->connector->getEvalDispatcher()->getEvalProvider();
324
325
		try {
326
			$evalProvider->addSharedVar( 'uri', $_SERVER['REQUEST_URI'] );
327
		} catch ( \Exception $e ) {
328
			$this->print_notice_exception( $e );
329
		}
330
331
		try {
332
			$evalProvider->addSharedVarReference( 'post', $_POST );
333
		} catch ( \Exception $e ) {
334
			$this->print_notice_exception( $e );
335
		}
336
337
		$openBaseDirs = [ ABSPATH, get_template_directory() ];
338
339
		try {
340
			$evalProvider->addSharedVarReference( 'dirs', $openBaseDirs );
341
		} catch ( \Exception $e ) {
342
			$this->print_notice_exception( $e );
343
		}
344
345
		$evalProvider->setOpenBaseDirs( $openBaseDirs );
346
347
		try {
348
			$this->connector->startEvalRequestsListener();
349
		} catch ( \Exception $e ) {
350
			$this->print_notice_exception( $e );
351
		}
352
	}
353
354
355
	/**
356
	 * Prints an exception message as WordPress admin notice.
357
	 *
358
	 * @since 1.4.0
359
	 *
360
	 * @param \Exception $e Exception object
361
	 */
362
	public function print_notice_exception( \Exception $e ) {
363
364
		add_action( 'admin_notices', static function() use ( $e ) {
365
366
			?>
367
			<div class="error">
368
				<p><?php printf( '%1$s: %2$s', self::NAME, $e->getMessage() ); ?></p>
369
			</div>
370
			<?php
371
372
		} );
373
	}
374
375
376
	/**
377
	 * Admin password notice.
378
	 *
379
	 * Prompts user to set a password for PHP Console upon plugin activation.
380
	 *
381
	 * @internal action hook callback
382
	 *
383
	 * @since 1.3.2
384
	 */
385
	public function password_notice() {
386
387
		?>
388
		<div class="update-nag">
389
			<p><?php printf(
390
				/* translators: Placeholders: %1$s - WP PHP Console name, %2$s - opening HTML <a> link tag; %3$s closing HTML </a> link tag */
391
				__( '%1$s: Please remember to %2$sset a password%3$s if you want to enable the terminal.', 'wp-php-console' ),
392
				'<strong>' . self::NAME . '</strong>',
393
				'<a href="' . esc_url( admin_url( 'options-general.php?page=wp-php-console' ) ) .'">',
394
				'</a>'
395
			); ?></p>
396
		</div>
397
		<?php
398
	}
399
400
401
	/**
402
	 * Gets the plugin path.
403
	 *
404
	 * @since 1.5.5
405
	 *
406
	 * @return string
407
	 */
408
	public static function get_plugin_path() {
409
410
		return untrailingslashit( dirname( __DIR__ ) );
411
	}
412
413
414
	/**
415
	 * Gets the plugin vendor path.
416
	 *
417
	 * @since 1.5.5
418
	 */
419
	public static function get_plugin_vendor_path() {
420
421
		return self::get_plugin_path() . '/vendor';
422
	}
423
424
425
	/**
426
	 * Gets the plugin page URL.
427
	 *
428
	 * @since 1.5.5
429
	 *
430
	 * @return string
431
	 */
432
	public static function get_plugin_page_url() {
433
434
		return 'https://wordpress.org/support/plugin/wp-php-console/';
435
	}
436
437
438
	/**
439
	 * Gets the GitHub repository page URL.
440
	 *
441
	 * @since 1.5.5
442
	 *
443
	 * @return string
444
	 */
445
	public static function get_project_page_url() {
446
447
		return 'https://github.com/unfulvio/wp-php-console';
448
	}
449
450
451
	/**
452
	 * Gets the plugin reviews page URL.
453
	 *
454
	 * @since 1.5.5
455
	 *
456
	 * @return string
457
	 */
458
	public static function get_reviews_page_url() {
459
460
		return 'https://wordpress.org/support/plugin/wp-php-console/reviews/';
461
	}
462
463
464
	/**
465
	 * Gets the plugin support page URL.
466
	 *
467
	 * @since 1.5.5
468
	 *
469
	 * @return string
470
	 */
471
	public static function get_support_page_url() {
472
473
		return 'https://wordpress.org/support/plugin/wp-php-console/';
474
	}
475
476
477
	/**
478
	 * Gets the admin settings page URL.
479
	 *
480
	 * @since 1.5.5
481
	 *
482
	 * @return string
483
	 */
484
	public static function get_settings_page_url() {
485
486
		return admin_url( 'options-general.php?page=wp-php-console' );
487
	}
488
489
490
	/**
491
	 * Determines if the current page is the settings page.
492
	 *
493
	 * @since 1.5.5
494
	 *
495
	 * @return bool
496
	 */
497
	public static function is_settings_page() {
498
499
		return is_admin() && isset( $_GET['page'] ) && 'page' === 'wp-php-console';
500
	}
501
502
503
}
504