PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.3.1
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.3.1
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.3.1, at central/listener.php

348 lines 14.7 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 //
221 // In addition, we need to attached the host plugin who is handling the UpdraftCentral requests
222 global $updraftcentral_host_plugin;
223 $extra = apply_filters('updraftcentral_get_updraftplus_status', array(
224 'is_updraftplus_installed' => false,
225 'is_updraftplus_active' => false,
226 'host_plugin' => $updraftcentral_host_plugin->plugin_name,
227 ));
228
229 $command_info = apply_filters('updraftcentral_get_command_info', false, $command);
230 if (!$command_info) {
231 if (isset($response['data']) && is_array($response['data'])) $response['data']['extra'] = $extra;
232 return $response;
233 }
234
235 $class_prefix = $command_info['class_prefix'];
236 $command = $command_info['command'];
237 $command_php_class = $command_info['command_php_class'];
238
239 if (empty($this->commands[$class_prefix])) {
240 if (class_exists($command_php_class)) {
241 $this->commands[$class_prefix] = new $command_php_class($this);
242 }
243 }
244
245 $command_class = isset($this->commands[$class_prefix]) ? $this->commands[$class_prefix] : new stdClass;
246
247 if ('_' == substr($command, 0, 1) || !is_a($command_class, $command_php_class) || (!method_exists($command_class, $command) && !method_exists($command_class, '__call'))) {
248 if (defined('UPDRAFTCENTRAL_UDRPC_FORCE_DEBUG') && UPDRAFTCENTRAL_UDRPC_FORCE_DEBUG) error_log("Unknown RPC command received: ".$command);
249
250 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))));
251 }
252
253 $extra_info = isset($this->extra_info[$key_name_indicator]) ? $this->extra_info[$key_name_indicator] : null;
254
255 // Make it so that current_user_can() checks can apply + work
256 if (!empty($extra_info['user_id'])) wp_set_current_user($extra_info['user_id']);
257
258 $this->current_udrpc = $ud_rpc;
259
260 do_action('updraftcentral_listener_pre_udrpc_action', $command, $command_class, $data, $extra_info);
261
262 // Allow the command class to perform any boiler-plate actions.
263 if (is_callable(array($command_class, '_pre_action'))) call_user_func(array($command_class, '_pre_action'), $command, $data, $extra_info);
264
265 // Despatch
266 $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);
267
268 if (is_callable(array($command_class, '_post_action'))) call_user_func(array($command_class, '_post_action'), $command, $data, $extra_info);
269
270 do_action('updraftcentral_listener_post_udrpc_action', $command, $command_class, $data, $extra_info);
271
272 if (isset($msg['data']) && is_array($msg['data'])) {
273 $msg['data']['extra'] = $extra;
274 }
275
276 return $this->return_rpc_message($msg);
277 } catch (Exception $e) {
278 $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().')';
279 error_log($log_message);
280
281 return $this->return_rpc_message(array('response' => 'rpcerror', 'data' => array('code' => 'rpc_fatal_error', 'data' => array('command' => $command, 'message' => $log_message))));
282 // @codingStandardsIgnoreLine
283 } catch (Error $e) {
284 $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().')';
285 error_log($log_message);
286
287 return $this->return_rpc_message(array('response' => 'rpcerror', 'data' => array('code' => 'rpc_fatal_error', 'data' => array('command' => $command, 'message' => $log_message))));
288 }
289 }
290
291 public function get_current_udrpc() {
292 return $this->current_udrpc;
293 }
294
295 private function initialise_listener_error_handling() {
296 global $updraftcentral_host_plugin;
297
298 $this->host->error_reporting_stop_when_logged = true;
299 set_error_handler(array($this->host, 'php_error'), E_ALL & ~E_STRICT);
300 $this->php_events = array();
301 @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?
302 add_filter($updraftcentral_host_plugin->get_logline_filter(), array($this, 'updraftcentral_logline'), 10, 4);
303 if (!$updraftcentral_host_plugin->get_debug_mode()) return;
304 }
305
306 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.
307 if ('notice' === $level && 'php_event' === $uniq_id) {
308 $this->php_events[] = $line;
309 }
310 return $line;
311 }
312
313 public function return_rpc_message($msg) {
314 if (is_array($msg) && isset($msg['response']) && 'error' == $msg['response']) {
315 $this->host->log('Unexpected response code in remote communications: '.serialize($msg));
316 }
317
318 $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?
319 @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?
320 // If turning output-catching off, turn this on instead:
321 // $caught_output = ''; @ob_end_flush();
322
323 // If there's higher-level output buffering going on, then get rid of that
324 if (ob_get_level()) ob_end_clean();
325
326 if ($caught_output) {
327 if (!isset($msg['data'])) $msg['data'] = null;
328 $msg['data'] = array('caught_output' => $caught_output, 'previous_data' => $msg['data']);
329 $already_rearranged_data = true;
330 }
331
332 if (!empty($this->php_events)) {
333 if (!isset($msg['data'])) $msg['data'] = null;
334 if (!empty($already_rearranged_data)) {
335 $msg['data']['php_events'] = array();
336 } else {
337 $msg['data'] = array('php_events' => array(), 'previous_data' => $msg['data']);
338 }
339 foreach ($this->php_events as $logline) {
340 $msg['data']['php_events'][] = $logline;
341 }
342 }
343 restore_error_handler();
344
345 return $msg;
346 }
347 }
348