PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.2.20
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.2.20
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / central / listener.php

listener.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.2.20, at central/listener.php

344 lines 14.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
4
5 /**
6 * This class is the basic glue between the lower-level Remote Communications (RPC) class in UpdraftCentral, and the host plugin. It does not contain actual commands themselves; the class names to use for actual commands are passed in as a parameter to the constructor.
7 */
8 class UpdraftCentral_Listener {
9
10 public $udrpc_version;
11
12 private $host = null;
13
14 private $receivers = array();
15
16 private $extra_info = array();
17
18 private $php_events = array();
19
20 private $commands = array();
21
22 private $current_udrpc = null;
23
24 private $command_classes;
25
26 /**
27 * Class constructor
28 *
29 * @param Array $keys - keys to set up listeners for
30 * @param Array $command_classes - commands
31 */
32 public function __construct($keys = array(), $command_classes = array()) {
33 global $updraftcentral_host_plugin;
34 $this->host = $updraftcentral_host_plugin;
35
36 // It seems impossible for this condition to result in a return; but it seems Plesk can do something odd within the control panel that causes a problem - see HS#6276
37 if (!is_a($this->host, 'UpdraftCentral_Host')) return;
38
39 $this->command_classes = $command_classes;
40
41 foreach ($keys as $name_hash => $key) {
42 // publickey_remote isn't necessarily set yet, depending on the key exchange method
43 if (!is_array($key) || empty($key['extra_info']) || empty($key['publickey_remote'])) continue;
44 $indicator = $name_hash.'.central.updraftplus.com';
45 $ud_rpc = $this->host->get_udrpc($indicator);
46 $this->udrpc_version = $ud_rpc->version;
47
48 // Only turn this on if you are comfortable with potentially anything appearing in your PHP error log
49 if (defined('UPDRAFTCENTRAL_UDRPC_FORCE_DEBUG') && UPDRAFTCENTRAL_UDRPC_FORCE_DEBUG) $ud_rpc->set_debug(true);
50
51 $this->receivers[$indicator] = $ud_rpc;
52 $this->extra_info[$indicator] = isset($key['extra_info']) ? $key['extra_info'] : null;
53 $ud_rpc->set_key_local($key['key']);
54 $ud_rpc->set_key_remote($key['publickey_remote']);
55 // Create listener (which causes WP actions to be fired when messages are received)
56 $ud_rpc->activate_replay_protection();
57 if (!empty($key['extra_info']) && isset($key['extra_info']['mothership'])) {
58 $mothership = $key['extra_info']['mothership'];
59 $url = '';
60 if ('__updraftpluscom' == $mothership) {
61 $url = 'https://updraftplus.com';
62 } elseif (false != ($parsed = parse_url($key['extra_info']['mothership'])) && is_array($parsed)) {
63 $url = $parsed['scheme'].'://'.$parsed['host'];
64 }
65 if (!empty($url)) $ud_rpc->set_allow_cors_from(array($url));
66 }
67 $ud_rpc->create_listener();
68 }
69
70 // If we ever need to expand beyond a single GET action, this can/should be generalised and put into the commands class
71 if (!empty($_GET['udcentral_action']) && 'login' == $_GET['udcentral_action']) {
72 // auth_redirect() does not return, according to the documentation; but the code shows that it can
73 // auth_redirect();
74
75 if (!empty($_GET['login_id']) && is_numeric($_GET['login_id']) && !empty($_GET['login_key'])) {
76 $login_user = get_user_by('id', $_GET['login_id']);
77
78 // THis is included so we can get $wp_version
79 include_once(ABSPATH.WPINC.'/version.php');
80
81 if (is_a($login_user, 'WP_User') || (version_compare($wp_version, '3.5', '<') && !empty($login_user->ID))) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- The variable is defined inside the ABSPATH.WPINC.'/version.php'.
82 // Allow site implementers to disable this functionality
83 $allow_autologin = apply_filters('updraftcentral_allow_autologin', true, $login_user);
84 if ($allow_autologin) {
85 $login_key = get_user_meta($login_user->ID, 'updraftcentral_login_key', true);
86 if (is_array($login_key) && !empty($login_key['created']) && $login_key['created'] > time() - 60 && !empty($login_key['key']) && $login_key['key'] == $_GET['login_key']) {
87 $autologin = empty($login_key['redirect_url']) ? network_admin_url() : $login_key['redirect_url'];
88 }
89 }
90 }
91 }
92 if (!empty($autologin)) {
93 // Allow use once only
94 delete_user_meta($login_user->ID, 'updraftcentral_login_key');
95 $this->autologin_user($login_user, $autologin);
96 }
97 }
98
99 add_filter('udrpc_action', array($this, 'udrpc_action'), 10, 5);
100 add_filter('updraftcentral_get_command_info', array($this, 'updraftcentral_get_command_info'), 10, 2);
101 add_filter('updraftcentral_get_updraftplus_status', array($this, 'get_updraftplus_status'), 10, 1);
102
103 }
104
105 /**
106 * Retrieves the UpdraftPlus plugin status whether it has been installed or activated
107 *
108 * @param mixed $data Default data to return
109 * @return array
110 */
111 public function get_updraftplus_status($data) {
112
113 // Handle cases of users who rename their plugin folders
114 if (class_exists('UpdraftPlus')) {
115 $data['is_updraftplus_installed'] = true;
116 $data['is_updraftplus_active'] = true;
117 } else {
118 if (!function_exists('get_plugins')) require_once(ABSPATH.'wp-admin/includes/plugin.php');
119 $plugins = get_plugins();
120 $key = 'updraftplus/updraftplus.php';
121
122 if (array_key_exists($key, $plugins)) {
123 $data['is_updraftplus_installed'] = true;
124 if (is_plugin_active($key)) $data['is_updraftplus_active'] = true;
125 }
126 }
127
128 return $data;
129 }
130
131 /**
132 * Retrieves command class information and includes class file if class
133 * is currently not available.
134 *
135 * @param mixed $response The default response to return if the submitted command does not exists
136 * @param string $command The command to parse and check
137 * @return array Contains the following command information "command_php_class", "class_prefix" and "command"
138 */
139 public function updraftcentral_get_command_info($response, $command) {
140 if (!preg_match('/^([a-z0-9]+)\.(.*)$/', $command, $matches)) return $response;
141 $class_prefix = $matches[1];
142 $command = $matches[2];
143
144 // Other plugins might have registered the filter rather later so we need to make
145 // sure that we get all the commands intended for UpdraftCentral.
146 $this->command_classes = apply_filters('updraftcentral_remotecontrol_command_classes', $this->command_classes);
147
148 // We only handle some commands - the others, we let something else deal with
149 if (!isset($this->command_classes[$class_prefix])) return $response;
150
151 $command_php_class = $this->command_classes[$class_prefix];
152 $command_base_class_at = apply_filters('updraftcentral_command_base_class_at', UPDRAFTCENTRAL_CLIENT_DIR.'/commands.php');
153
154 if (!class_exists('UpdraftCentral_Commands')) include_once($command_base_class_at);
155
156 // Second parameter has been passed since
157 do_action('updraftcentral_command_class_wanted', $command_php_class);
158
159 if (!class_exists($command_php_class)) {
160 if (file_exists(UPDRAFTCENTRAL_CLIENT_DIR.'/modules/'.$class_prefix.'.php')) {
161 include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/modules/'.$class_prefix.'.php');
162 }
163 }
164
165 return array(
166 'command_php_class' => $command_php_class,
167 'class_prefix' => $class_prefix,
168 'command' => $command
169 );
170 }
171
172 /**
173 * Do verification before calling this method
174 *
175 * @param WP_User|Object $user user object for autologin
176 * @param boolean $redirect_url Redirect URL
177 */
178 private function autologin_user($user, $redirect_url = false) {
179 if (!is_user_logged_in()) {
180 // $user = get_user_by('id', $user_id);
181 // Don't check that it's a WP_User - that's WP 3.5+ only
182 if (!is_object($user) || empty($user->ID)) return;
183 wp_set_current_user($user->ID, $user->user_login);
184 wp_set_auth_cookie($user->ID);
185 do_action('wp_login', $user->user_login, $user);
186 }
187 if ($redirect_url) {
188 wp_safe_redirect($redirect_url);
189 exit;
190 }
191 }
192
193 /**
194 * WP filter udrpc_action
195 *
196 * @param Array $response - the unfiltered response that will be returned
197 * @param String $command - the command being called
198 * @param Array $data - the parameters to the command
199 * @param String $key_name_indicator - the UC key that is in use
200 * @param Object $ud_rpc - the UDRP object
201 *
202 * @return Array - filtered response
203 */
204 public function udrpc_action($response, $command, $data, $key_name_indicator, $ud_rpc) {
205 try {
206
207 if (empty($this->receivers[$key_name_indicator])) return $response;
208
209 // This can be used to detect an UpdraftCentral context
210 if (!defined('UPDRAFTCENTRAL_COMMAND')) define('UPDRAFTCENTRAL_COMMAND', $command);
211
212 $this->initialise_listener_error_handling();
213
214 // UpdraftCentral needs this extra information especially now that the UpdraftCentral
215 // libraries can be totally embedded in other plugins (e.g. WP-Optimize, etc.) thus,
216 // that makes the UpdraftPlus plugin optional.
217 //
218 // This will give UpdraftCentral a proper way of disabling the backup feature
219 // for this site if the UpdraftPlus plugin is currently not installed or activated.
220 $extra = apply_filters('updraftcentral_get_updraftplus_status', array(
221 'is_updraftplus_installed' => false,
222 'is_updraftplus_active' => false
223 ));
224
225 $command_info = apply_filters('updraftcentral_get_command_info', false, $command);
226 if (!$command_info) {
227 if (isset($response['data']) && is_array($response['data'])) $response['data']['extra'] = $extra;
228 return $response;
229 }
230
231 $class_prefix = $command_info['class_prefix'];
232 $command = $command_info['command'];
233 $command_php_class = $command_info['command_php_class'];
234
235 if (empty($this->commands[$class_prefix])) {
236 if (class_exists($command_php_class)) {
237 $this->commands[$class_prefix] = new $command_php_class($this);
238 }
239 }
240
241 $command_class = isset($this->commands[$class_prefix]) ? $this->commands[$class_prefix] : new stdClass;
242
243 if ('_' == substr($command, 0, 1) || !is_a($command_class, $command_php_class) || (!method_exists($command_class, $command) && !method_exists($command_class, '__call'))) {
244 if (defined('UPDRAFTCENTRAL_UDRPC_FORCE_DEBUG') && UPDRAFTCENTRAL_UDRPC_FORCE_DEBUG) error_log("Unknown RPC command received: ".$command);
245
246 return $this->return_rpc_message(array('response' => 'rpcerror', 'data' => array('code' => 'unknown_rpc_command', 'data' => array('prefix' => $class_prefix, 'command' => $command, 'class' => $command_php_class))));
247 }
248
249 $extra_info = isset($this->extra_info[$key_name_indicator]) ? $this->extra_info[$key_name_indicator] : null;
250
251 // Make it so that current_user_can() checks can apply + work
252 if (!empty($extra_info['user_id'])) wp_set_current_user($extra_info['user_id']);
253
254 $this->current_udrpc = $ud_rpc;
255
256 do_action('updraftcentral_listener_pre_udrpc_action', $command, $command_class, $data, $extra_info);
257
258 // Allow the command class to perform any boiler-plate actions.
259 if (is_callable(array($command_class, '_pre_action'))) call_user_func(array($command_class, '_pre_action'), $command, $data, $extra_info);
260
261 // Despatch
262 $msg = apply_filters('updraftcentral_listener_udrpc_action', call_user_func(array($command_class, $command), $data, $extra_info), $command_class, $class_prefix, $command, $data, $extra_info);
263
264 if (is_callable(array($command_class, '_post_action'))) call_user_func(array($command_class, '_post_action'), $command, $data, $extra_info);
265
266 do_action('updraftcentral_listener_post_udrpc_action', $command, $command_class, $data, $extra_info);
267
268 if (isset($msg['data']) && is_array($msg['data'])) {
269 $msg['data']['extra'] = $extra;
270 }
271
272 return $this->return_rpc_message($msg);
273 } catch (Exception $e) {
274 $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during UpdraftCentral command execution. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
275 error_log($log_message);
276
277 return $this->return_rpc_message(array('response' => 'rpcerror', 'data' => array('code' => 'rpc_fatal_error', 'data' => array('command' => $command, 'message' => $log_message))));
278 // @codingStandardsIgnoreLine
279 } catch (Error $e) {
280 $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during UpdraftCentral command execution. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
281 error_log($log_message);
282
283 return $this->return_rpc_message(array('response' => 'rpcerror', 'data' => array('code' => 'rpc_fatal_error', 'data' => array('command' => $command, 'message' => $log_message))));
284 }
285 }
286
287 public function get_current_udrpc() {
288 return $this->current_udrpc;
289 }
290
291 private function initialise_listener_error_handling() {
292 global $updraftcentral_host_plugin;
293
294 $this->host->error_reporting_stop_when_logged = true;
295 set_error_handler(array($this->host, 'php_error'), E_ALL & ~E_STRICT);
296 $this->php_events = array();
297 @ob_start();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Might be a bigger picture that I am missing but do we need to silence errors here?
298 add_filter($updraftcentral_host_plugin->get_logline_filter(), array($this, 'updraftcentral_logline'), 10, 4);
299 if (!$updraftcentral_host_plugin->get_debug_mode()) return;
300 }
301
302 public function updraftcentral_logline($line, $nonce, $level, $uniq_id) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Unused parameter is present because the method is used as a WP filter.
303 if ('notice' === $level && 'php_event' === $uniq_id) {
304 $this->php_events[] = $line;
305 }
306 return $line;
307 }
308
309 public function return_rpc_message($msg) {
310 if (is_array($msg) && isset($msg['response']) && 'error' == $msg['response']) {
311 $this->host->log('Unexpected response code in remote communications: '.serialize($msg));
312 }
313
314 $caught_output = @ob_get_contents();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Might be a bigger picture that I am missing but do we need to silence errors here?
315 @ob_end_clean();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Might be a bigger picture that I am missing but do we need to silence errors here?
316 // If turning output-catching off, turn this on instead:
317 // $caught_output = ''; @ob_end_flush();
318
319 // If there's higher-level output buffering going on, then get rid of that
320 if (ob_get_level()) ob_end_clean();
321
322 if ($caught_output) {
323 if (!isset($msg['data'])) $msg['data'] = null;
324 $msg['data'] = array('caught_output' => $caught_output, 'previous_data' => $msg['data']);
325 $already_rearranged_data = true;
326 }
327
328 if (!empty($this->php_events)) {
329 if (!isset($msg['data'])) $msg['data'] = null;
330 if (!empty($already_rearranged_data)) {
331 $msg['data']['php_events'] = array();
332 } else {
333 $msg['data'] = array('php_events' => array(), 'previous_data' => $msg['data']);
334 }
335 foreach ($this->php_events as $logline) {
336 $msg['data']['php_events'][] = $logline;
337 }
338 }
339 restore_error_handler();
340
341 return $msg;
342 }
343 }
344