Completed
Branch master (1fc640)
by Fulvio
12:07 queued 05:38
created

Plugin::print_notice_exception()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 12
rs 9.4286
cc 1
eloc 6
nc 1
nop 1
1
<?php
2
/**
3
 * WP PHP Console Plugin Core Class
4
 *
5
 * @link    https://github.com/nekojira/wp-php-console
6
 * @since   1.0.0
7
 * @package WP_PHP_Console
8
 */
9
namespace WP_PHP_Console;
10
11
use PhpConsole;
12
13
if ( ! defined( 'WPINC' ) ) {
14
	exit; // Exit if accessed directly
15
}
16
17
/**
18
 * WP PHP Console main class.
19
 *
20
 * @since   1.0.0
21
 * @package WP_PHP_Console
22
 */
23
class Plugin {
24
25
26
	/**
27
	 * The plugin name.
28
	 *
29
	 * @since  1.4.0
30
	 * @access public
31
	 * @var    string $plugin_name The name of this plugin.
32
	 */
33
	public $plugin_name = '';
34
35
	/**
36
	 * The unique identifier of this plugin.
37
	 *
38
	 * @since  1.0.0
39
	 * @access public
40
	 * @var    string $plugin_slug The string used to uniquely identify this plugin.
41
	 */
42
	public $plugin_slug = '';
43
44
	/**
45
	 * The current version of the plugin.
46
	 *
47
	 * @since  1.0.0
48
	 * @access public
49
	 * @var    string $version The current version of the plugin.
50
	 */
51
	public $version = '';
52
53
	/**
54
	 * This plugin's settings options.
55
	 *
56
	 * @since  1.0.0
57
	 * @access protected
58
	 * @var    array $options Array of this plugin settings options.
59
	 */
60
	protected $options = array();
61
62
63
	/**
64
	 * Load plugin and connect to PHP Console.
65
	 *
66
	 * @since 1.0.0
67
	 */
68
	public function __construct() {
69
70
		$this->plugin_name = 'WP PHP Console';
71
		$this->plugin_slug = 'wp-php-console';
72
		$this->version     = '1.4.1';
73
		$this->options     = $this->get_options();
74
75
		// Bail out if PHP Console can't be found
76
		if ( ! class_exists( 'PhpConsole\Connector' ) ) {
77
			return;
78
		}
79
80
		// Connect to PHP Console.
81
		$this->connect();
82
83
		// Apply PHP Console options.
84
		$this->apply_options();
85
86
		// Translations
87
		add_action( 'plugins_loaded', array( $this, 'set_locale' ) );
88
89
		// Admin menu
90
		add_action( 'admin_menu', array( $this, 'register_settings_page' ) );
91
92
		// Delay further PHP Console initialisation to have more context during Remote PHP execution
93
		add_action( 'wp_loaded', array( $this, 'init' ) );
94
95
	}
96
97
98
	/**
99
	 * Set plugin text domain.
100
	 *
101
	 * @since 1.0.0
102
	 */
103
	public function set_locale() {
104
105
		load_plugin_textdomain(
106
			$this->plugin_slug,
107
			false,
108
			dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
109
	}
110
111
112
	/**
113
	 * Connect to PHP Console.
114
	 *
115
	 * @since 1.4.1
116
	 */
117
	private function connect() {
118
119
		// By default PHP Console uses PhpConsole\Storage\Session for postponed responses,
120
		// so all temporary data will be stored in $_SESSION.
121
		// But there is some problem with frameworks like WordPress that override PHP session handler.
122
		// PHP Console has alternative storage drivers for this - we will write to a temporary file:
123
		$phpcdir  = dirname( __FILE__ ) . '/tmp';
124
		$make_dir = wp_mkdir_p( $phpcdir );
125
126
		if ( $make_dir === true ) {
127
128
			try {
129
				$storage = new PhpConsole\Storage\File( $phpcdir . '/' . md5( __FILE__ ) . '_pc.data' );
130
				PhpConsole\Connector::setPostponeStorage( $storage );
131
			} catch( \Exception $e ) {
132
				// TODO $storage is under DOCUMENT_ROOT - it's insecure but did not find another solution in WP
133
				// $this->print_notice_exception( $e );
134
			}
135
136
		}
137
138
		// Perform PHP Console initialisation required asap for other code to be able to output to the JavaScript console
139
		$connector = PhpConsole\Connector::getInstance();
0 ignored issues
show
Unused Code introduced by
$connector is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
140
141
	}
142
143
144
	/**
145
	 * Get WP PHP Console settings options.
146
	 *
147
	 * @since 1.4.0
148
	 * @return array
149
	 */
150
	protected function get_options() {
151
		return get_option( 'wp_php_console', array() );
152
	}
153
154
155
	/**
156
	 * Apply options.
157
	 *
158
	 * @since 1.4.1
159
	 */
160
	private function apply_options() {
0 ignored issues
show
Coding Style introduced by
apply_options uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
161
162
		// Apply 'register' option to PHP Console
163
		if ( ! empty( $this->options['register'] ) && ! class_exists( 'PC', false ) ) {
164
			// Only if PC not registered yet
165
			try {
166
				PhpConsole\Helper::register();
167
			} catch( \Exception $e ) {
168
				$this->print_notice_exception( $e );
169
			}
170
		}
171
172
		// Apply 'stack' option to PHP Console
173
		if ( ! empty( $this->options['stack'] ) ) {
174
			$connector->getDebugDispatcher()->detectTraceAndSource = true;
0 ignored issues
show
Bug introduced by
The variable $connector does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
175
		}
176
177
		// Apply 'short' option to PHP Console
178
		if ( ! empty( $this->options['short'] ) ) {
179
			try {
180
				$connector->setSourcesBasePath( $_SERVER['DOCUMENT_ROOT'] );
181
			} catch ( \Exception $e ) {
182
				$this->print_notice_exception( $e );
183
			}
184
		}
185
186
	}
187
188
189
	/**
190
	 * Initialize PHP Console.
191
	 *
192
	 * @since 1.0.0
193
	 */
194
	public function init() {
195
196
		// Display admin notice and abort if no password has been set
197
		$password = isset( $this->options['password'] ) ? $this->options['password'] : '';
198
		if ( ! $password ) {
199
			add_action( 'admin_notices', array( $this, 'password_notice' ) );
200
			return;
201
		}
202
203
		// Selectively remove slashes added by WordPress as expected by PhpConsole
204
		if ( isset( $_POST[PhpConsole\Connector::POST_VAR_NAME] ) ) {
205
			$_POST[ PhpConsole\Connector::POST_VAR_NAME ] = stripslashes_deep( $_POST[ PhpConsole\Connector::POST_VAR_NAME ] );
206
		}
207
208
		$connector = PhpConsole\Connector::getInstance();
209
210
		try {
211
			$connector->setPassword( $password );
212
		} catch ( \Exception $e ) {
213
			$this->print_notice_exception( $e );
214
		}
215
216
		// PhpConsole instance
217
		$handler = PhpConsole\Handler::getInstance();
218
219
		if ( PhpConsole\Handler::getInstance()->isStarted() !== true ) {
220
			try {
221
				$handler->start();
222
			} catch( \Exception $e ) {
223
				$this->print_notice_exception( $e );
224
			}
225
		}
226
227
		// Enable SSL-only mode
228
		$enableSslOnlyMode = isset( $this->options['ssl'] ) ? ( ! empty( $this->options['ssl'] ) ? $this->options['ssl'] : '' ) : '';
229
230
		if ( $enableSslOnlyMode ) {
231
			$connector->enableSslOnlyMode();
232
		}
233
234
		// Restrict IP addresses
235
		$allowedIpMasks = isset( $this->options['ip'] ) ? ( ! empty( $this->options['ip'] ) ? explode( ',', $this->options['ip'] ) : '' ) : '';
236
237
		if ( is_array( $allowedIpMasks ) && ! empty( $allowedIpMasks ) ) {
238
			$connector->setAllowedIpMasks( (array) $allowedIpMasks );
239
		}
240
241
		$evalProvider = $connector->getEvalDispatcher()->getEvalProvider();
242
243
		try {
244
			$evalProvider->addSharedVar( 'uri', $_SERVER['REQUEST_URI'] );
245
		} catch ( \Exception $e ) {
246
			$this->print_notice_exception( $e );
247
		}
248
249
		try {
250
			$evalProvider->addSharedVarReference( 'post', $_POST );
251
		} catch ( \Exception $e ) {
252
			$this->print_notice_exception( $e );
253
		}
254
255
		$openBaseDirs = array( ABSPATH, get_template_directory() );
256
257
		try {
258
			$evalProvider->addSharedVarReference( 'dirs', $openBaseDirs );
259
		} catch ( \Exception $e ) {
260
			$this->print_notice_exception( $e );
261
		}
262
263
		$evalProvider->setOpenBaseDirs( $openBaseDirs );
264
265
		try {
266
			$connector->startEvalRequestsListener();
267
		} catch ( \Exception $e ) {
268
			$this->print_notice_exception( $e );
269
		}
270
271
	}
272
273
274
	/**
275
	 * Prints an exception message as WordPress admin notice
276
	 *
277
	 * @since 1.4.0
278
	 * @param \Exception $e Exception object
279
	 * @return void
280
	 */
281
	public function print_notice_exception( \Exception $e ) {
282
283
		add_action( 'admin_notices', function() use ( $e ) {
284
285
			?>
286
			<div class="error">
287
				<p><?php printf( '%1$s: %2$s', $this->plugin_name, $e->getMessage() ); ?></p>
288
			</div>
289
			<?php
290
291
		} );
292
	}
293
294
295
296
	/**
297
	 * Admin password notice.
298
	 * Prompts user to set a password for PHP Console upon plugin activation.
299
	 *
300
	 * @since   1.3.2
301
	 */
302
	public function password_notice() {
303
304
		?>
305
		<div class="update-nag">
306
			<?php
307
308
			$settings_page = esc_url( admin_url( 'options-general.php?page=wp-php-console' ) );
309
310
			/* translators: Placeholders: %1$s - opening HTML <a> link tag; %2$s closing HTML </a> link tag */
311
			printf( $this->plugin_name . ': ' . __( 'Please remember to %1$s set a password %2$s if you want to enable terminal.', 'wp-php-console' ), '<a href="' . $settings_page .'">', '</a>' );
312
313
			?>
314
		</div>
315
		<?php
316
317
	}
318
319
320
	/**
321
	 * Plugin Settings menu.
322
	 *
323
	 * @since 1.0.0
324
	 */
325
	public function register_settings_page() {
326
327
		add_options_page(
328
			__( 'WP PHP Console', 'wp-php-console' ),
329
			__( 'WP PHP Console', 'wp-php-console' ),
330
			'manage_options',
331
			$this->plugin_slug,
332
			array( $this, 'settings_page' )
333
		);
334
335
		add_action( 'admin_init', array( $this, 'register_settings'  ) );
336
337
	}
338
339
340
	/**
341
	 * Register plugin settings.
342
	 *
343
	 * @since 1.0.0
344
	 */
345
	public function register_settings() {
346
347
		register_setting(
348
			'wp_php_console',
349
			'wp_php_console',
350
			array( $this, 'sanitize_field' )
351
		);
352
353
		add_settings_section(
354
			'wp_php_console',
355
			__( 'Settings', 'wp-php-console' ),
356
			array( $this, 'settings_info' ),
357
			$this->plugin_slug
358
		);
359
360
		add_settings_field(
361
			'password',
362
			__( 'Password', 'wp-php-console' ),
363
			array( $this, 'password_field' ),
364
			$this->plugin_slug,
365
			'wp_php_console'
366
		);
367
368
		add_settings_field(
369
			'ssl',
370
			__( 'Allow only on SSL', 'wp-php-console' ),
371
			array( $this, 'ssl_field' ),
372
			$this->plugin_slug,
373
			'wp_php_console'
374
		);
375
376
		add_settings_field(
377
			'ip',
378
			__( 'Allowed IP Masks', 'wp-php-console' ),
379
			array( $this, 'ip_field' ),
380
			$this->plugin_slug,
381
			'wp_php_console'
382
		);
383
384
		add_settings_field(
385
			'register',
386
			__( 'Register PC Class ', 'wp-php-console' ),
387
			array( $this, 'register_field' ),
388
			$this->plugin_slug,
389
			'wp_php_console'
390
		);
391
392
		add_settings_field(
393
			'stack',
394
			__( 'Show Call Stack', 'wp-php-console' ),
395
			array( $this, 'stack_field' ),
396
			$this->plugin_slug,
397
			'wp_php_console'
398
		);
399
400
		add_settings_field(
401
			'short',
402
			__( 'Short Path Names', 'wp-php-console' ),
403
			array( $this, 'short_field' ),
404
			$this->plugin_slug,
405
			'wp_php_console'
406
		);
407
408
	}
409
410
411
	/**
412
	 * Settings Page Password field.
413
	 *
414
	 * @since 1.0.0
415
	 */
416
	public function password_field() {
417
418
		printf (
419
			'<input type="password" id="wp-php-console-password" name="wp_php_console[password]" value="%s" />',
420
			isset( $this->options['password'] ) ? esc_attr( $this->options['password'] ) : ''
421
		);
422
		echo '<label for="wp-php-console-ip">' . esc_html__( 'Required', 'wp-php-console' ) . '</label>';
423
		echo '<br />';
424
		echo '<small class="description">' . esc_html__( 'The password for the eval terminal. If empty, the plugin will not work.', 'wp-php-console' ) . '</small>';
425
426
	}
427
428
429
	/**
430
	 * Settings Page SSL option field.
431
	 *
432
	 * @since 1.0.0
433
	 */
434
	public function ssl_field() {
435
436
		$ssl = isset( $this->options['ssl'] ) ? esc_attr( $this->options['ssl']) : '';
437
438
		printf (
439
			'<input type="checkbox" id="wp-php-console-ssl" name="wp_php_console[ssl]" value="1" %s /> ',
440
			$ssl ? 'checked="checked"' : ''
441
		);
442
		echo '<label for="wp-php-console-ssl">' . esc_html__( 'Yes', 'wp-php-console' ) . '</label>';
443
		echo '<br />';
444
		echo '<small class="description">' . esc_html__( 'Tick this option if you want the eval terminal to work only on a SSL connection.', 'wp-php-console' ) . '</small>';
445
446
	}
447
448
449
	/**
450
	 * Settings page IP Range field.
451
	 *
452
	 * @since 1.0.0
453
	 */
454
	public function ip_field() {
455
456
		printf (
457
			'<input type="text" class="regular-text" id="wp-php-console-ip" name="wp_php_console[ip]" value="%s" /> ',
458
			isset( $this->options['ip'] ) ? esc_attr( $this->options['ip']) : ''
459
		);
460
		echo '<label for="wp-php-console-ip">' . esc_html__( 'IP addresses (optional)', 'wp-php-console' ) . '</label>';
461
		echo '<br />';
462
		/* translators: VVV Varying Vagrant Vagrants default IP address */
463
		printf ( __( '<small class="description">' . __( 'You may specify an IP address (e.g. <code>192.168.50.4</code>, %s default IP address), a range of addresses (<code>192.168.*.*</code>) or multiple addresses, comma separated (<code>192.168.10.25,192.168.10.28</code>) to grant access to the eval terminal.', 'wp-php-console' ) . '</small>' ), '<a href="https://github.com/Varying-Vagrant-Vagrants/VVV">Varying Vagrant Vagrants</a>' );
464
465
	}
466
467
468
	/**
469
	 * Settings page Register PC Class field.
470
	 *
471
	 * @since 1.2.4
472
	 */
473 View Code Duplication
	public function register_field() {
474
475
		$register = ! empty( $this->options['register'] );
476
477
		printf (
478
			'<input type="checkbox" id="wp-php-console-register" name="wp_php_console[register]" value="1" %s /> ',
479
			$register ? 'checked="checked"' : ''
480
		);
481
		echo '<label for="wp-php-console-register">' . esc_html__( 'Yes', 'wp-php-console' ) . '</label>';
482
		echo '<br />';
483
		echo '<small class="description">' . __( 'Choose to register PC in the global namespace. Allows to write <code>PC::debug(&#36;var, &#36;tag)</code> or <code>PC::magic_tag(&#36;var)</code> instructions in PHP to inspect <code>&#36;var</code> in the JavaScript console.', 'wp-php-console' ) . '</small>';
484
485
	}
486
487
488
	/**
489
	 * Settings page Show Call Stack field.
490
	 *
491
	 * @since 1.2.4
492
	 */
493 View Code Duplication
	public function stack_field() {
494
495
		$stack = ! empty( $this->options['stack'] );
496
497
		printf (
498
			'<input type="checkbox" id="wp-php-console-stack" name="wp_php_console[stack]" value="1" %s /> ',
499
			$stack ? 'checked="checked"' : ''
500
		);
501
		echo '<label for="wp-php-console-stack">' . esc_html__( 'Yes', 'wp-php-console' ) . '</label>';
502
		echo '<br />';
503
		echo '<small class="description">' . __( "Choose to also see the call stack when PHP Console writes to the browser's JavaScript-console.", 'wp-php-console' ) . '</small>';
504
505
	}
506
507
508
	/**
509
	 * Settings page Show Short Paths field.
510
	 *
511
	 * @since 1.2.4
512
	 */
513 View Code Duplication
	public function short_field() {
514
515
		$short = ! empty( $this->options['short'] );
516
517
		printf (
518
			'<input type="checkbox" id="wp-php-console-short" name="wp_php_console[short]" value="1" %s /> ',
519
			$short ? 'checked="checked"' : ''
520
		);
521
		echo '<label for="wp-php-console-short">' . esc_html__( 'Yes', 'wp-php-console' ) . '</label>';
522
		echo '<br />';
523
		echo '<small class="description">' . __( "Choose to shorten PHP Console error sources and traces paths in browser's JavaScript-console. Paths like <code>/server/path/to/document/root/WP/wp-admin/admin.php:31</code> will be displayed as <code>/W/wp-admin/admin.php:31</code>", 'wp-php-console' ) . '</small>';
524
525
	}
526
527
528
	/**
529
	 * Sanitize user input in settings page.
530
	 *
531
	 * @since  1.0.0
532
	 * @param  string $input user input
533
	 * @return array  sanitized inputs
534
	 */
535
	public function sanitize_field( $input ) {
536
537
			$sanitized_input = array();
538
539
			if ( isset( $input['password'] ) ) {
540
				$sanitized_input['password'] = sanitize_text_field( $input['password'] );
541
			}
542
543
			if ( isset( $input['ssl'] ) ) {
544
				$sanitized_input['ssl'] = ! empty( $input['ssl'] ) ? 1 : '';
545
			}
546
547
			if ( isset( $input['ip'] ) ) {
548
				$sanitized_input['ip']  = sanitize_text_field( $input['ip'] );
549
			}
550
551
			$sanitized_input['register'] = empty( $input['register'] ) ? '' : 1;
552
			$sanitized_input['stack']    = empty( $input['stack'] )    ? '' : 1;
553
			$sanitized_input['short']    = empty( $input['short'] )    ? '' : 1;
554
555
			return $sanitized_input;
556
	}
557
558
559
	/**
560
	 * Settings page.
561
	 *
562
	 * @since 1.0.0
563
	 */
564
	public function settings_page() {
565
566
		?>
567
		<div class="wrap">
568
			<h2><?php esc_html_e( 'WP PHP Console', 'wp-php-console' ); ?></h2>
569
			<hr />
570
			<form method="post" action="options.php">
571
				<?php
572
				settings_fields( 'wp_php_console' );
573
				do_settings_sections( $this->plugin_slug );
574
				submit_button();
575
				?>
576
			</form>
577
		</div>
578
		<?php
579
580
	}
581
582
583
	/**
584
	 * Settings page additional info.
585
	 * Prints more details on the plugin settings page.
586
	 *
587
	 * @since 1.3.0
588
	 */
589
	public function settings_info() {
590
591
		?>
592
		<p><?php /* translators: %s refers to 'PHP Console' Chrome extension, will print download link for the Chrome extension */
593
			printf( _x( 'This plugin allows you to use %s within your WordPress installation for testing, debugging and development purposes.<br/>Usage instructions:', 'PHP Console, the Chrome Extension', 'wp-php-console' ), '<a href="https://github.com/barbushin/php-console" target="_blank">PHP Console</a>' ); ?></p>
594
		<ol>
595
			<li><?php /* translators: Install PHP Console extension for Google Chrome download link */
596
				printf( _x( 'Make sure you have downloaded and installed %s.', 'PHP Console, the Chrome Extension', 'wp-php-console' ), '<a target="_blank" href="https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef">PHP Console extension for Google Chrome</a>' ); ?></li>
597
			<li><?php _e( 'Set a password for the eval terminal in the options below and hit <code>save changes</code>.', 'wp-php-console' ); ?></li>
598
			<li><?php _e( 'Reload any page of your installation and click on the key icon in your Chrome browser address bar, enter your password and access the terminal.', 'wp-php-console' ); ?></li>
599
			<li><?php _e( 'From the eval terminal you can execute any PHP or WordPress specific function, including functions from your plugins and active theme.', 'wp-php-console' ); ?></li>
600
			<li><?php _e( "In your PHP code, you can call PHP Console debug statements like <code>debug(&#36;var, &#36;tag)</code> to display PHP variables in the browser's JavaScript-console (<code>Ctrl Shift J </code>) and optionally filter selected tags through the browser's Remote PHP Eval Terminal screen's Ignore Debug options.", 'wp-php-console' ); ?></li>
601
		</ol>
602
		<?php
603
604
	}
605
606
607
}
608