<?php

class Bug_Monitor_Server {

      public static function init(){
            foreach (array('activated_plugin', 'deactivated_plugin', 'upgrader_process_complete', 'switch_theme') as $hook){
                  add_action($hook, array(__CLASS__, 'log_wp_event'), 10, 3);
            }
            if (Bug_Monitor::check_option('php/error', 'on')){
                  register_shutdown_function(array(__CLASS__, 'error_handler'));
            }
            if (Bug_Monitor::check_option('php/warning', 'on')){
                  set_error_handler(array(__CLASS__, 'warning_handler'));
            }
            if (Bug_Monitor::check_option('php/redirect_loop', 'on') || Bug_Monitor::check_option('php/malicious_redirect', 'on')){
                  add_action('shutdown', array(__CLASS__, 'redirect'));
            }
            if (Bug_Monitor::check_option('php/email_sending_error', 'on')){
                  add_action( 'wp_mail_failed', array(__CLASS__, 'mail_error'), 10, 1 );
            }
            add_filter('wp_headers', function($headers) {
                  if (Bug_Monitor::check_option('server/uptime', 'on')) {
                        $headers['X-Bug-Monitor-Uptime'] = 'on';
                  }

                  return $headers;
            });
      }
      public static function log_wp_event($object, $primary = NULL, $secondary = NULL){
            $type = $url = $details = '';
            $current_action = current_action();
            switch ($current_action){
                  case 'activated_plugin':
                  case 'deactivated_plugin':
                        if ($object == BUG_MONITOR_PLUGIN_SLUG){
                              return;
                        }
                        $type = 'wp/' . $current_action;
                        $details = json_encode(array(
                              'plugin' => $object,
                        ));
                        break;
                  case 'upgrader_process_complete':
                        if ($primary['action'] != 'update' || empty($primary['type']) || $primary['type'] == 'translation'){
                              return;
                        }
                        $type = 'wp/update/' . $primary['type'];
                        $details = json_encode($primary['type'] == 'core' ? array() : ($primary['type'] == 'plugin' ? $primary['plugins'] : $primary['themes']));
                        break;
                  case 'switch_theme':
                        $type = 'wp/switch_theme';
                        $details = json_encode(array(
                              'theme' => $primary
                        ));
                        break;
            }
            if (empty($type)){
                  return;
            }

            $hash = md5($type . $details . time());
            $event_hash = md5($type . $details);

            self::insert_event($hash, $event_hash, 'info', $type, $url, $details);
      }
      private static function insert_event($id, $event_hash, $level, $type, $url, $details){
            $timestamp = gmdate('Y-m-d H:i:s');
            $ua = (!empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'none');

            Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("INSERT IGNORE INTO " . Bug_Monitor_Helper::$db->bug_monitor_events . " (id, event_hash, level, type, url, details, ua, timestamp, status) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 1);", $id, $event_hash, $level, $type, $url, $details, $ua, $timestamp));
      }

      public static function error_handler() {
            $error = error_get_last();
            if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_COMPILE_ERROR, E_CORE_ERROR])) {

                  $type = 'php/error';
                  $url = Bug_Monitor_Helper::get_current_url();
                  $details = json_encode(array(
                        'message' => $error['message'],
                        'file' => $error['file'],
                        'line' => $error['line'],
                        'component' => Bug_Monitor_Helper::get_file_origin($error['file'])
                  ));
                  $hash = Bug_Monitor_Log::get_event_hash(array('type' => $type, 'details' => $details), $url, time());
                  $event_hash = md5($type . $details);

                  self::insert_event($hash, $event_hash, 'critical', $type, $url, $details);
                  if (Bug_Monitor_Helper::is_admin() && Bug_Monitor::check_option('php/deactivate_plugin', 'on') && strpos($error['file'], WP_PLUGIN_DIR) === 0){
                        $active_plugins = get_option('active_plugins');
                        foreach ($active_plugins as $key => $plugin_file) {
                              $plugin_dir = dirname($plugin_file);
                              if (strpos($error['file'], WP_PLUGIN_DIR . "/{$plugin_dir}/") === 0){
                                    unset($active_plugins[$key]);
                                    update_option('active_plugins', $active_plugins);
                                    if (Bug_Monitor::check_option('notification/status', 'on')){
                                          $plugin_data = get_plugin_data(WP_PLUGIN_DIR . "/{$plugin_file}");
                                          $subject = sprintf(__('%s has been deactivated due to a 500 error.', 'bug-monitor'), $plugin_data['Name']);
                                          $body = sprintf(__('%s has been deactivated because it caused a 500 error in the admin area.', 'bug-monitor'), $plugin_data['Name']) . '<br><br>' . esc_html($error['message']);
                                          Bug_Monitor_Notification::instant($subject, $body);
                                    }
                                    break;
                              }
                        }
                  }
            }
      }

      public static function warning_handler($errno, $errstr, $errfile, $errline) {
            if (!in_array($errno, [E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING, E_NOTICE, E_USER_NOTICE, E_DEPRECATED, E_USER_DEPRECATED])) {
                  return false;
            }

            $type = 'php/warning';
            $url = Bug_Monitor_Helper::get_current_url();
            $details = json_encode(array(
                  'message' => $errstr,
                  'file' => $errfile,
                  'line' => $errline,
                  'component' => Bug_Monitor_Helper::get_file_origin($errfile)
            ));
            $hash = Bug_Monitor_Log::get_event_hash(array('type' => $type, 'details' => $details), $url, time());
            $event_hash = md5($type . $details);

            self::insert_event($hash, $event_hash, 'warning', $type, $url, $details);

            return false;
      }

      public static function mail_error($wp_error) {
            $type = 'php/email_sending_error';
            $url = Bug_Monitor_Helper::get_current_url();
            $mail_details = $wp_error->get_error_data();
            $details = json_encode(array(
                  'error' => $wp_error->get_error_message(),
                  'to' => (!empty($mail_details['to']) ? implode(', ', $mail_details['to']) : __('No recipient', 'bug-monitor')),
                  'subject' => (!empty($mail_details['subject']) ? $mail_details['subject'] : __('No subject', 'bug-monitor')),
                  'message' => (!empty($mail_details['message']) ? $mail_details['message'] : __('Empty message', 'bug-monitor'))
            ));
            $hash = Bug_Monitor_Log::get_event_hash(array('type' => $type, 'details' => $details), $url, time());
            $event_hash = md5($type . $details);

            self::insert_event($hash, $event_hash, 'error', $type, $url, $details);

            return false;
      }

      public static function redirect(){
            $headers = headers_list();
            foreach($headers as $header){
                  list($key, $value) = explode(':', $header, 2);
                  if (strtolower($key) == 'location'){
                        $redirect_to = untrailingslashit(trim($value));
                        $current_url = untrailingslashit(Bug_Monitor_Helper::get_current_url());

                        if (Bug_Monitor::check_option('php/malicious_redirect', 'on')){
                              $home_host = parse_url(home_url(), PHP_URL_HOST);
                              $redirect_host = parse_url($redirect_to, PHP_URL_HOST);
                              $home_domain = implode('.', array_slice(explode('.', $home_host), -2));
                              $redirect_domain = implode('.', array_slice(explode('.', $redirect_host), -2));

                              if (!empty($redirect_host) && $home_domain !== $redirect_domain){
                                    $type = 'php/malicious_redirect';
                                    $url = $current_url;
                                    $details = json_encode(array(
                                          'redirect_to' => $redirect_to,
                                          'cookies' => $_COOKIE,
                                          'headers' => $headers
                                    ));
                                    $hash = Bug_Monitor_Log::get_event_hash(array('type' => $type, 'details' => $details), $url, time());
                                    $event_hash = md5($type . $details);

                                    self::insert_event($hash, $event_hash, 'critical', $type, $url, $details);
                              }
                        }

                        if (Bug_Monitor::check_option('php/redirect_loop', 'on')){
                              $redirects = (!empty($_COOKIE['bm_redirects']) ? json_decode($_COOKIE['bm_redirects']) : array());
                              if (!headers_sent()){
                                    $redirects[] = $current_url;
                                    setcookie('bm_redirects', json_encode($redirects), array('expires' => time() + 30, 'path' => '/', 'secure' => true, 'httponly' => false, 'samesite' => 'None'));
                              }

                              if (!empty($redirects)){
                                    $url_occurances = array_count_values($redirects);

                                    if (!empty($url_occurances[$current_url]) && $url_occurances[$current_url] > 5){
                                          $type = 'php/redirect_loop';
                                          $url = $current_url;
                                          $details = array(
                                                'redirect_to' => $redirect_to,
                                                'cookies' => $_COOKIE,
                                                'headers' => $headers
                                          );
                                          $hash = Bug_Monitor_Log::get_event_hash(array('type' => $type, 'details' => $details), $url, time());
                                          $event_hash = Bug_Monitor_Log::get_event_hash(array('type' => $type, 'details' => $details), $url);

                                          self::insert_event($hash, $event_hash, 'critical', $type, $url, json_encode($details));
                                    }
                              }
                        }
                  }
            }
      }

}