1
|
|
|
<?php |
|
|
|
|
2
|
|
|
/** |
3
|
|
|
* WP PHP Console Plugin Core Class |
4
|
|
|
* |
5
|
|
|
* @link https://github.com/unfulvio/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
|
|
|
defined( 'ABSPATH' ) or exit; |
|
|
|
|
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* WP PHP Console main class. |
17
|
|
|
* |
18
|
|
|
* @since 1.0.0 |
19
|
|
|
* @package WP_PHP_Console |
20
|
|
|
*/ |
21
|
|
|
class Plugin { |
22
|
|
|
|
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* The plugin version. |
26
|
|
|
* |
27
|
|
|
* @since 1.5.0 |
28
|
|
|
* @const string |
29
|
|
|
*/ |
30
|
|
|
CONST VERSION = '1.5.0'; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* The plugin name. |
34
|
|
|
* |
35
|
|
|
* @since 1.5.0 |
36
|
|
|
* @const string |
37
|
|
|
*/ |
38
|
|
|
CONST NAME = 'WP PHP Console'; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* This plugin's settings options. |
42
|
|
|
* |
43
|
|
|
* @since 1.0.0 |
44
|
|
|
* @access protected |
45
|
|
|
* @var array $options Array of this plugin settings options. |
46
|
|
|
*/ |
47
|
|
|
protected $options = array(); |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Instance of PHP Console connector object. |
51
|
|
|
* |
52
|
|
|
* @since 1.4.1 |
53
|
|
|
* @access public |
54
|
|
|
* @var PhpConsole\Connector $connector Instance. |
55
|
|
|
*/ |
56
|
|
|
public $connector; |
57
|
|
|
|
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Load plugin and connect to PHP Console. |
61
|
|
|
* |
62
|
|
|
* @since 1.0.0 |
63
|
|
|
*/ |
64
|
|
|
public function __construct() { |
65
|
|
|
|
66
|
|
|
// Translations. |
67
|
|
|
add_action( 'plugins_loaded', array( $this, 'set_locale' ) ); |
68
|
|
|
|
69
|
|
|
// Set options. |
70
|
|
|
$this->options = $this->get_options(); |
71
|
|
|
|
72
|
|
|
// Load admin. |
73
|
|
|
if ( ! defined( 'DOING_AJAX' ) && is_admin() ) { |
74
|
|
|
require_once __DIR__ . '/class-wp-php-console-settings.php'; |
75
|
|
|
new Settings( $this->options ); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
// Bail out if PHP Console can't be found. |
79
|
|
|
if ( ! class_exists( 'PhpConsole\Connector' ) ) { |
80
|
|
|
return; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
// Connect to PHP Console. |
84
|
|
|
$this->connect(); |
85
|
|
|
|
86
|
|
|
// Apply PHP Console options. |
87
|
|
|
$this->apply_options(); |
88
|
|
|
|
89
|
|
|
// Delay further PHP Console initialisation to have more context during Remote PHP execution. |
90
|
|
|
add_action( 'wp_loaded', array( $this, 'init' ) ); |
91
|
|
|
|
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Set plugin text domain. |
97
|
|
|
* |
98
|
|
|
* @since 1.0.0 |
99
|
|
|
*/ |
100
|
|
|
public function set_locale() { |
101
|
|
|
|
102
|
|
|
load_plugin_textdomain( |
103
|
|
|
'wp-php-console', |
104
|
|
|
false, |
105
|
|
|
dirname( plugin_basename( __FILE__ ) ) . '/languages/' |
106
|
|
|
); |
107
|
|
|
|
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Connect to PHP Console. |
113
|
|
|
* |
114
|
|
|
* @since 1.4.0 |
115
|
|
|
*/ |
116
|
|
|
private function connect() { |
117
|
|
|
|
118
|
|
|
// By default PHP Console uses PhpConsole\Storage\Session for postponed responses, |
119
|
|
|
// so all temporary data will be stored in $_SESSION. |
120
|
|
|
// But there is some problem with frameworks like WordPress that override PHP session handler. |
121
|
|
|
// PHP Console has alternative storage drivers for this - we will write to a temporary file: |
122
|
|
|
|
123
|
|
|
// TODO this generates "PHP Warning: session_start(): Cannot send session cache limiter - headers already sent" |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
$phpcdir = __DIR__ . '/tmp'; |
127
|
|
|
$make_dir = wp_mkdir_p( $phpcdir ); |
128
|
|
|
|
129
|
|
|
if ( true === $make_dir ) { |
130
|
|
|
|
131
|
|
|
try { |
132
|
|
|
$storage = @new PhpConsole\Storage\File( $phpcdir . '/' . md5( __FILE__ ) . '_pc.data' ); |
133
|
|
|
PhpConsole\Connector::setPostponeStorage( $storage ); |
134
|
|
|
} catch( \Exception $e ) { |
135
|
|
|
// TODO $storage is under DOCUMENT_ROOT - it's insecure but did not find another solution in WP |
136
|
|
|
$this->print_notice_exception( $e ); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
} |
140
|
|
|
**/ |
141
|
|
|
|
142
|
|
|
// Perform PHP Console initialisation required asap for other code to be able to output to the JavaScript console. |
143
|
|
|
if ( ! $this->connector instanceof PhpConsole\Connector ) { |
144
|
|
|
$this->connector = PhpConsole\Connector::getInstance(); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Get WP PHP Console settings options. |
152
|
|
|
* |
153
|
|
|
* @since 1.4.0 |
154
|
|
|
* @return array |
155
|
|
|
*/ |
156
|
|
|
protected function get_options() { |
157
|
|
|
|
158
|
|
|
$options = get_option( 'wp_php_console', array() ); |
159
|
|
|
|
160
|
|
|
return wp_parse_args( $options, array( |
161
|
|
|
'ip' => '', |
162
|
|
|
'password' => '', |
163
|
|
|
'register' => false, |
164
|
|
|
'short' => false, |
165
|
|
|
'ssl' => false, |
166
|
|
|
'stack' => false, |
167
|
|
|
) ); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* Apply options. |
173
|
|
|
* |
174
|
|
|
* @since 1.4.1 |
175
|
|
|
*/ |
176
|
|
|
private function apply_options() { |
|
|
|
|
177
|
|
|
|
178
|
|
|
// Bail out if not connected yet to PHP Console. |
179
|
|
|
if ( ! $this->connector instanceof PhpConsole\Connector ) { |
180
|
|
|
return; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
// Apply 'register' option to PHP Console |
184
|
|
|
if ( true === $this->options['register'] && ! class_exists( 'PC', false ) ) { |
185
|
|
|
// Only if PC not registered yet |
186
|
|
|
try { |
187
|
|
|
PhpConsole\Helper::register(); |
188
|
|
|
} catch( \Exception $e ) { |
189
|
|
|
$this->print_notice_exception( $e ); |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
// Apply 'stack' option to PHP Console |
194
|
|
|
if ( true === $this->options['stack'] ) { |
195
|
|
|
$this->connector->getDebugDispatcher()->detectTraceAndSource = true; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
// Apply 'short' option to PHP Console |
199
|
|
|
if ( true === $this->options['short'] ) { |
200
|
|
|
try { |
201
|
|
|
$this->connector->setSourcesBasePath( $_SERVER['DOCUMENT_ROOT'] ); |
202
|
|
|
} catch ( \Exception $e ) { |
203
|
|
|
$this->print_notice_exception( $e ); |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Initialize PHP Console. |
212
|
|
|
* |
213
|
|
|
* @since 1.0.0 |
214
|
|
|
*/ |
215
|
|
|
public function init() { |
216
|
|
|
|
217
|
|
|
// Set PHP Console extension password. |
218
|
|
|
$password = $this->options['password']; |
219
|
|
|
|
220
|
|
|
if ( ! $password ) { |
221
|
|
|
// Display admin notice and abort if no password has been set. |
222
|
|
|
add_action( 'admin_notices', array( $this, 'password_notice' ) ); |
223
|
|
|
return; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
// Selectively remove slashes added by WordPress as expected by PhpConsole. |
227
|
|
|
if ( array_key_exists( PhpConsole\Connector::POST_VAR_NAME, $_POST ) ) { |
228
|
|
|
$_POST[ PhpConsole\Connector::POST_VAR_NAME ] = stripslashes_deep( $_POST[ PhpConsole\Connector::POST_VAR_NAME ] ); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
if ( ! $this->connector instanceof PhpConsole\Connector ) { |
232
|
|
|
$this->connector = PhpConsole\Connector::getInstance(); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
try { |
236
|
|
|
$this->connector->setPassword( $password ); |
237
|
|
|
} catch ( \Exception $e ) { |
238
|
|
|
$this->print_notice_exception( $e ); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
// PhpConsole instance. |
242
|
|
|
$handler = PhpConsole\Handler::getInstance(); |
243
|
|
|
|
244
|
|
|
if ( true !== PhpConsole\Handler::getInstance()->isStarted() ) { |
245
|
|
|
try { |
246
|
|
|
$handler->start(); |
247
|
|
|
} catch( \Exception $e ) { |
248
|
|
|
$this->print_notice_exception( $e ); |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
// Enable SSL-only mode. |
253
|
|
|
if ( true === $this->options['ssl'] ) { |
254
|
|
|
$this->connector->enableSslOnlyMode(); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
// Restrict IP addresses. |
258
|
|
|
$allowedIpMasks = ! empty( $this->options['ip'] ) ? explode( ',', $this->options['ip'] ) : ''; |
259
|
|
|
|
260
|
|
|
if ( is_array( $allowedIpMasks ) && count( $allowedIpMasks ) > 0 ) { |
261
|
|
|
$this->connector->setAllowedIpMasks( $allowedIpMasks ); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
$evalProvider = $this->connector->getEvalDispatcher()->getEvalProvider(); |
265
|
|
|
|
266
|
|
|
try { |
267
|
|
|
$evalProvider->addSharedVar( 'uri', $_SERVER['REQUEST_URI'] ); |
268
|
|
|
} catch ( \Exception $e ) { |
269
|
|
|
$this->print_notice_exception( $e ); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
try { |
273
|
|
|
$evalProvider->addSharedVarReference( 'post', $_POST ); |
274
|
|
|
} catch ( \Exception $e ) { |
275
|
|
|
$this->print_notice_exception( $e ); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
$openBaseDirs = array( ABSPATH, get_template_directory() ); |
279
|
|
|
|
280
|
|
|
try { |
281
|
|
|
$evalProvider->addSharedVarReference( 'dirs', $openBaseDirs ); |
282
|
|
|
} catch ( \Exception $e ) { |
283
|
|
|
$this->print_notice_exception( $e ); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
$evalProvider->setOpenBaseDirs( $openBaseDirs ); |
287
|
|
|
|
288
|
|
|
try { |
289
|
|
|
$this->connector->startEvalRequestsListener(); |
290
|
|
|
} catch ( \Exception $e ) { |
291
|
|
|
$this->print_notice_exception( $e ); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Prints an exception message as WordPress admin notice. |
299
|
|
|
* |
300
|
|
|
* @since 1.4.0 |
301
|
|
|
* @param \Exception $e Exception object |
302
|
|
|
*/ |
303
|
|
|
public function print_notice_exception( \Exception $e ) { |
304
|
|
|
|
305
|
|
|
add_action( 'admin_notices', function() use ( $e ) { |
306
|
|
|
|
307
|
|
|
?> |
308
|
|
|
<div class="error"> |
309
|
|
|
<p><?php printf( '%1$s: %2$s', self::NAME, $e->getMessage() ); ?></p> |
310
|
|
|
</div> |
311
|
|
|
<?php |
312
|
|
|
|
313
|
|
|
} ); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
|
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Admin password notice. |
320
|
|
|
* Prompts user to set a password for PHP Console upon plugin activation. |
321
|
|
|
* |
322
|
|
|
* @since 1.3.2 |
323
|
|
|
*/ |
324
|
|
|
public function password_notice() { |
325
|
|
|
|
326
|
|
|
?> |
327
|
|
|
<div class="update-nag"> |
328
|
|
|
<?php |
329
|
|
|
|
330
|
|
|
/* translators: Placeholders: %1$s - WP Php Console name, %2$s - opening HTML <a> link tag; %3$s closing HTML </a> link tag */ |
331
|
|
|
printf( __( '%1$s: Please remember to %2$s set a password %3$s if you want to enable the terminal.', 'wp-php-console' ), |
332
|
|
|
'<strong>' . self::NAME . '</strong>', |
333
|
|
|
'<a href="' . esc_url( admin_url( 'options-general.php?page=wp-php-console' ) ) .'">', |
334
|
|
|
'</a>' |
335
|
|
|
); |
336
|
|
|
|
337
|
|
|
?> |
338
|
|
|
</div> |
339
|
|
|
<?php |
340
|
|
|
|
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
|
344
|
|
|
} |
345
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.