verygoodplugins

Untitled

Jun 5th, 2020
241
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 14.10 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4.  * Handles log entries by writing to database.
  5.  *
  6.  * @class          WPF_Log_Handler
  7.  */
  8.  
  9. class WPF_Log_Handler {
  10.  
  11.     /**
  12.      * Log Levels
  13.      *
  14.      * Description of levels:.
  15.      *     'error': Error conditions.
  16.      *     'warning': Warning conditions.
  17.      *     'notice': Normal but significant condition.
  18.      *     'info': Informational messages.
  19.      *
  20.      * @see @link {https://tools.ietf.org/html/rfc5424}
  21.      */
  22.     const ERROR   = 'error';
  23.     const WARNING = 'warning';
  24.     const NOTICE  = 'notice';
  25.     const INFO    = 'info';
  26.  
  27.     /**
  28.      * Level strings mapped to integer severity.
  29.      *
  30.      * @var array
  31.      */
  32.     protected static $level_to_severity = array(
  33.         self::ERROR   => 500,
  34.         self::WARNING => 400,
  35.         self::NOTICE  => 300,
  36.         self::INFO    => 200,
  37.     );
  38.  
  39.     /**
  40.      * Severity integers mapped to level strings.
  41.      *
  42.      * This is the inverse of $level_severity.
  43.      *
  44.      * @var array
  45.      */
  46.     protected static $severity_to_level = array(
  47.         500 => self::ERROR,
  48.         400 => self::WARNING,
  49.         300 => self::NOTICE,
  50.         200 => self::INFO,
  51.     );
  52.  
  53.     /**
  54.      * Constructor for the logger.
  55.      */
  56.     public function __construct() {
  57.  
  58.         add_action( 'init', array( $this, 'init' ) );
  59.  
  60.     }
  61.  
  62.     /**
  63.      * Prepares logging functionalty if enabled
  64.      *
  65.      * @access public
  66.      * @return void
  67.      */
  68.  
  69.     public function init() {
  70.  
  71.         if ( wp_fusion()->settings->get( 'enable_logging' ) != true ) {
  72.             return;
  73.         }
  74.  
  75.         add_filter( 'wpf_configure_sections', array( $this, 'configure_sections' ), 10, 2 );
  76.  
  77.         add_action( 'admin_menu', array( $this, 'register_logger_subpage' ) );
  78.         add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
  79.  
  80.         // Screen options
  81.         add_action( 'load-tools_page_wpf-settings-logs', array( $this, 'add_screen_options' ) );
  82.         add_filter( 'set-screen-option', array( $this, 'set_screen_option' ), 10, 3 );
  83.  
  84.         // Error handling
  85.         add_action( 'shutdown', array( $this, 'shutdown' ) );
  86.  
  87.         $this->create_update_table();
  88.  
  89.     }
  90.  
  91.     /**
  92.      * Adds standalone log management page
  93.      *
  94.      * @access public
  95.      * @return void
  96.      */
  97.  
  98.     public function register_logger_subpage() {
  99.  
  100.         $page = add_submenu_page(
  101.             'tools.php',
  102.             'WP Fusion Activity Logs',
  103.             'WP Fusion Logs',
  104.             'manage_options',
  105.             'wpf-settings-logs',
  106.             array( $this, 'show_logs_section' )
  107.         );
  108.  
  109.     }
  110.  
  111.     /**
  112.      * Enqueues logger styles
  113.      *
  114.      * @access public
  115.      * @return void
  116.      */
  117.  
  118.     public function enqueue_scripts() {
  119.  
  120.         $screen = get_current_screen();
  121.  
  122.         if ( 'tools_page_wpf-settings-logs' !== $screen->id ) {
  123.             return;
  124.         }
  125.  
  126.         wp_enqueue_style( 'wpf-options', WPF_DIR_URL . 'assets/css/wpf-options.css', array(), WP_FUSION_VERSION );
  127.         wp_enqueue_style( 'wpf-admin', WPF_DIR_URL . 'assets/css/wpf-admin.css', array(), WP_FUSION_VERSION );
  128.  
  129.     }
  130.  
  131.     /**
  132.      * Adds per-page screen option
  133.      *
  134.      * @access public
  135.      * @return void
  136.      */
  137.  
  138.     public function add_screen_options() {
  139.  
  140.         $args = array(
  141.             'label'   => __( 'Entries per page', 'wp-fusion' ),
  142.             'default' => 20,
  143.             'option'  => 'wpf_status_log_items_per_page',
  144.         );
  145.  
  146.         add_screen_option( 'per_page', $args );
  147.  
  148.     }
  149.  
  150.     /**
  151.      * Save screen options
  152.      *
  153.      * @access public
  154.      * @return int Value
  155.      */
  156.  
  157.     public function set_screen_option( $status, $option, $value ) {
  158.  
  159.         if ( 'wpf_status_log_items_per_page' == $option ) {
  160.             return $value;
  161.         }
  162.  
  163.         return $status;
  164.  
  165.     }
  166.  
  167.     /**
  168.      * Adds logging tab to main settings for access
  169.      *
  170.      * @access public
  171.      * @return array Page
  172.      */
  173.  
  174.     public function configure_sections( $page, $options ) {
  175.  
  176.         $page['sections'] = wp_fusion()->settings->insert_setting_after(
  177.             'advanced', $page['sections'], array(
  178.                 'logs' => array(
  179.                     'title' => __( 'Logs', 'wp-fusion' ),
  180.                     'slug'  => 'wpf-settings-logs',
  181.                 ),
  182.             )
  183.         );
  184.  
  185.         return $page;
  186.  
  187.     }
  188.  
  189.     /**
  190.      * Creates logging table if logging enabled
  191.      *
  192.      * @access public
  193.      * @return void
  194.      */
  195.  
  196.     public function create_update_table() {
  197.  
  198.         global $wpdb;
  199.         $table_name = $wpdb->prefix . 'wpf_logging';
  200.  
  201.         if ( $wpdb->get_var( "show tables like '$table_name'" ) != $table_name ) {
  202.  
  203.             require_once ABSPATH . 'wp-admin/includes/upgrade.php';
  204.  
  205.             $collate = '';
  206.  
  207.             if ( $wpdb->has_cap( 'collation' ) ) {
  208.                 $collate = $wpdb->get_charset_collate();
  209.             }
  210.  
  211.             $sql = 'CREATE TABLE ' . $table_name . " (
  212.                 log_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  213.                 timestamp datetime NOT NULL,
  214.                 level smallint(4) NOT NULL,
  215.                 user bigint(8) NOT NULL,
  216.                 source varchar(200) NOT NULL,
  217.                 message longtext NOT NULL,
  218.                 context longtext NULL,
  219.                 PRIMARY KEY (log_id),
  220.                 KEY level (level)
  221.             ) $collate;";
  222.  
  223.             dbDelta( $sql );
  224.  
  225.         }
  226.  
  227.     }
  228.  
  229.     /**
  230.      * Logging tab content
  231.      *
  232.      * @access public
  233.      * @return void
  234.      */
  235.  
  236.     public function show_logs_section() {
  237.  
  238.         include_once WPF_DIR_PATH . 'includes/admin/logging/class-log-table-list.php';
  239.  
  240.         // Flush
  241.         if ( ! empty( $_REQUEST['flush-logs'] ) ) {
  242.             self::flush();
  243.         }
  244.  
  245.         // Bulk actions
  246.         if ( isset( $_REQUEST['action'] ) && isset( $_REQUEST['log'] ) ) {
  247.             self::log_table_bulk_actions();
  248.         }
  249.  
  250.         $log_table_list = new WPF_Log_Table_List();
  251.         $log_table_list->prepare_items();
  252.  
  253.         // Stop _wp_http_referer getting appended to the logs URL, so it doesn't get too long
  254.         add_filter(
  255.             'removable_query_args', function( $query_args ) {
  256.  
  257.                 $query_args[] = '_wp_http_referer';
  258.                 return $query_args;
  259.  
  260.             }
  261.         );
  262.  
  263.         ?>
  264.  
  265.         <div class="wrap">
  266.             <h1><?php _e( 'WP Fusion Activity Log', 'wp-fusion' ); ?></h1>
  267.  
  268.             <form method="get" id="mainform">
  269.  
  270.                 <input type="hidden" name="page" value="wpf-settings-logs">
  271.  
  272.                 <?php $log_table_list->display(); ?>
  273.  
  274.                 <?php submit_button( __( 'Flush all logs', 'wp-fusion' ), 'delete', 'flush-logs' ); ?>
  275.                 <?php wp_nonce_field( 'wp-fusion-status-logs' ); ?>
  276.  
  277.             </form>
  278.         </div>
  279.  
  280.         <?php
  281.  
  282.     }
  283.  
  284.  
  285.     /**
  286.      * Validate a level string.
  287.      *
  288.      * @param string $level
  289.      * @return bool True if $level is a valid level.
  290.      */
  291.     public static function is_valid_level( $level ) {
  292.         return array_key_exists( strtolower( $level ), self::$level_to_severity );
  293.     }
  294.  
  295.     /**
  296.      * Translate level string to integer.
  297.      *
  298.      * @param string $level emergency|alert|critical|error|warning|notice|info|debug
  299.      * @return int 100 (debug) - 800 (emergency) or 0 if not recognized
  300.      */
  301.     public static function get_level_severity( $level ) {
  302.         if ( self::is_valid_level( $level ) ) {
  303.             $severity = self::$level_to_severity[ strtolower( $level ) ];
  304.         } else {
  305.             $severity = 0;
  306.         }
  307.         return $severity;
  308.     }
  309.  
  310.     /**
  311.      * Translate severity integer to level string.
  312.      *
  313.      * @param int $severity
  314.      * @return bool|string False if not recognized. Otherwise string representation of level.
  315.      */
  316.     public static function get_severity_level( $severity ) {
  317.         if ( array_key_exists( $severity, self::$severity_to_level ) ) {
  318.             return self::$severity_to_level[ $severity ];
  319.         } else {
  320.             return false;
  321.         }
  322.     }
  323.  
  324.     /**
  325.      * Handle a log entry.
  326.      *
  327.      * @param int    $timestamp Log timestamp.
  328.      * @param string $level emergency|alert|critical|error|warning|notice|info|debug
  329.      * @param string $message Log message.
  330.      * @param array  $context {
  331.      *      Additional information for log handlers.
  332.      *
  333.      *     @type string $source Optional. Source will be available in log table.
  334.      *                  If no source is provided, attempt to provide sensible default.
  335.      * }
  336.      *
  337.      * @see WPF_Log_Handler::get_log_source() for default source.
  338.      *
  339.      * @return bool False if value was not handled and true if value was handled.
  340.      */
  341.     public function handle( $level, $user, $message, $context = array() ) {
  342.  
  343.         $timestamp = current_time( 'timestamp' );
  344.  
  345.         do_action( 'wpf_handle_log', $timestamp, $level, $user, $message, $context );
  346.  
  347.         if ( wp_fusion()->settings->get( 'enable_logging' ) != true ) {
  348.             return;
  349.         }
  350.  
  351.         if ( wp_fusion()->settings->get( 'logging_errors_only' ) == true && $level != 'error' ) {
  352.             return;
  353.         }
  354.  
  355.         if ( isset( $context['source'] ) && $context['source'] ) {
  356.             $source = $context['source'];
  357.         } else {
  358.             $source = $this->get_log_source();
  359.         }
  360.  
  361.         // Filter out irrelevant meta fields
  362.         if ( isset( $context['meta_array'] ) && $context['meta_array'] ) {
  363.  
  364.             $contact_fields = wp_fusion()->settings->get( 'contact_fields' );
  365.  
  366.             foreach ( $context['meta_array'] as $key => $data ) {
  367.  
  368.                 if ( ! isset( $contact_fields[ $key ] ) || $contact_fields[ $key ]['active'] == false ) {
  369.                     unset( $context['meta_array'][ $key ] );
  370.                 }
  371.             }
  372.         }
  373.  
  374.         if ( empty( $user ) ) {
  375.             $user = 0;
  376.         }
  377.  
  378.         // Don't log meta data pushes where no enabled fields are being synced
  379.         if ( isset( $context['meta_array'] ) && empty( $context['meta_array'] ) ) {
  380.             return;
  381.         }
  382.  
  383.         do_action( 'wpf_log_handled', $timestamp, $level, $user, $message, $source, $context );
  384.  
  385.         return $this->add( $timestamp, $level, $user, $message, $source, $context );
  386.     }
  387.  
  388.     /**
  389.      * Add a log entry to chosen file.
  390.      *
  391.      * @param string $level emergency|alert|critical|error|warning|notice|info|debug
  392.      * @param string $message Log message.
  393.      * @param string $source Log source. Useful for filtering and sorting.
  394.      * @param array  $context {
  395.      *      Context will be serialized and stored in database.
  396.      *  }
  397.      *
  398.      * @return bool True if write was successful.
  399.      */
  400.     protected static function add( $timestamp, $level, $user, $message, $source, $context ) {
  401.         global $wpdb;
  402.  
  403.         $insert = array(
  404.             'timestamp' => date( 'Y-m-d H:i:s', $timestamp ),
  405.             'level'     => self::get_level_severity( $level ),
  406.             'user'      => $user,
  407.             'message'   => $message,
  408.             'source'    => $source,
  409.         );
  410.  
  411.         $format = array(
  412.             '%s',
  413.             '%d',
  414.             '%d',
  415.             '%s',
  416.             '%s',
  417.             '%s', // possible serialized context
  418.         );
  419.  
  420.         if ( ! empty( $context ) ) {
  421.             $insert['context'] = serialize( $context );
  422.         }
  423.  
  424.         $result = $wpdb->insert( "{$wpdb->prefix}wpf_logging", $insert, $format );
  425.  
  426.         if ( $result === false ) {
  427.             return false;
  428.         }
  429.  
  430.         $rowcount = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wpf_logging" );
  431.  
  432.         $max_log_size = apply_filters( 'wpf_log_max_entries', 10000 );
  433.  
  434.         if ( $rowcount > $max_log_size ) {
  435.             $wpdb->query( "DELETE FROM {$wpdb->prefix}wpf_logging ORDER BY log_id ASC LIMIT 1" );
  436.         }
  437.  
  438.         return $result;
  439.  
  440.     }
  441.  
  442.     /**
  443.      * Clear all logs from the DB.
  444.      *
  445.      * @return bool True if flush was successful.
  446.      */
  447.     public static function flush() {
  448.         global $wpdb;
  449.  
  450.         return $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}wpf_logging" );
  451.     }
  452.  
  453.     /**
  454.      * Bulk DB log table actions.
  455.      *
  456.      * @since 3.0.0
  457.      */
  458.     private function log_table_bulk_actions() {
  459.  
  460.         if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'wp-fusion-status-logs' ) ) {
  461.             wp_die( __( 'Action failed. Please refresh the page and retry.', 'wp-fusion' ) );
  462.         }
  463.  
  464.         $log_ids = array_map( 'absint', (array) $_REQUEST['log'] );
  465.  
  466.         if ( 'delete' === $_REQUEST['action'] || 'delete' === $_REQUEST['action2'] ) {
  467.             self::delete( $log_ids );
  468.         }
  469.     }
  470.  
  471.     /**
  472.      * Delete selected logs from DB.
  473.      *
  474.      * @param int|string|array Log ID or array of Log IDs to be deleted.
  475.      *
  476.      * @return bool
  477.      */
  478.     public static function delete( $log_ids ) {
  479.         global $wpdb;
  480.  
  481.         if ( ! is_array( $log_ids ) ) {
  482.             $log_ids = array( $log_ids );
  483.         }
  484.  
  485.         $format = array_fill( 0, count( $log_ids ), '%d' );
  486.  
  487.         $query_in = '(' . implode( ',', $format ) . ')';
  488.  
  489.         $query = $wpdb->prepare(
  490.             "DELETE FROM {$wpdb->prefix}wpf_logging WHERE log_id IN {$query_in}",
  491.             $log_ids
  492.         );
  493.  
  494.         return $wpdb->query( $query );
  495.     }
  496.  
  497.  
  498.     /**
  499.      * Get appropriate source based on file name.
  500.      *
  501.      * Try to provide an appropriate source in case none is provided.
  502.      *
  503.      * @return string Text to use as log source. "" (empty string) if none is found.
  504.      */
  505.  
  506.     protected static function get_log_source() {
  507.  
  508.         static $ignore_files = array( 'class-log-handler' );
  509.  
  510.         /**
  511.          * PHP < 5.3.6 correct behavior
  512.          *
  513.          * @see http://php.net/manual/en/function.debug-backtrace.php#refsect1-function.debug-backtrace-parameters
  514.          */
  515.  
  516.         if ( defined( 'DEBUG_BACKTRACE_IGNORE_ARGS' ) ) {
  517.             $debug_backtrace_arg = DEBUG_BACKTRACE_IGNORE_ARGS;
  518.         } else {
  519.             $debug_backtrace_arg = false;
  520.         }
  521.  
  522.         $full_trace = debug_backtrace( $debug_backtrace_arg );
  523.  
  524.         $slugs = array( 'user-profile', 'api', 'access-control', 'class-auto-login', 'class-ajax' );
  525.  
  526.         foreach ( wp_fusion()->get_integrations() as $slug => $integration ) {
  527.             $slugs[] = $slug;
  528.         }
  529.  
  530.         $found_integrations = array();
  531.  
  532.         foreach ( $full_trace as $i => $trace ) {
  533.  
  534.             if ( isset( $trace['file'] ) ) {
  535.  
  536.                 foreach ( $slugs as $slug ) {
  537.  
  538.                     if ( empty( $slug ) ) {
  539.                         continue;
  540.                     }
  541.  
  542.                     if ( strpos( $trace['file'], $slug ) !== false ) {
  543.  
  544.                         $found_integrations[] = $slug;
  545.                     }
  546.                 }
  547.             }
  548.         }
  549.  
  550.         // Figure out most likely integration
  551.         if ( ! empty( $found_integrations ) ) {
  552.  
  553.             $source = serialize( array_reverse( array_unique( $found_integrations ) ) );
  554.  
  555.         } else {
  556.             $source = 'unknown';
  557.         }
  558.  
  559.         return $source;
  560.     }
  561.  
  562.  
  563.     /**
  564.      * Check for PHP errors on shutdown and log them
  565.      *
  566.      * @access public
  567.      * @return void
  568.      */
  569.     public function shutdown() {
  570.  
  571.         $error = error_get_last();
  572.  
  573.         if ( is_null( $error ) ) {
  574.             return;
  575.         }
  576.  
  577.         if ( false !== strpos( $error['file'], 'wp-fusion' ) || false !== strpos( $error['message'], 'wp-fusion' ) ) {
  578.  
  579.             if ( E_ERROR == $error['type'] || E_WARNING == $error['type'] ) {
  580.  
  581.                 // Get the source
  582.  
  583.                 $source = 'unknown';
  584.  
  585.                 $slugs = array( 'user-profile', 'api', 'access-control', 'class-auto-login', 'class-ajax', 'class-user' );
  586.  
  587.                 foreach ( wp_fusion()->get_integrations() as $slug => $integration ) {
  588.                     $slugs[] = $slug;
  589.                 }
  590.  
  591.                 foreach ( $slugs as $slug ) {
  592.  
  593.                     if ( empty( $slug ) ) {
  594.                         continue;
  595.                     }
  596.  
  597.                     if ( strpos( $error['file'], $slug ) !== false ) {
  598.  
  599.                         $source = $slug;
  600.                         break;
  601.  
  602.                     }
  603.                 }
  604.  
  605.                 if ( E_ERROR == $error['type'] ) {
  606.                     $level = 'error';
  607.                 } elseif ( E_WARNING == $error['type'] ) {
  608.                     $level = 'warning';
  609.                 }
  610.  
  611.                 $this->handle( $level, wpf_get_current_user_id(), '<strong>PHP error:</strong> ' . nl2br( $error['message'] ) . '<br /><br />' . $error['file'] . ':' . $error['line'], array( 'source' => $source ) );
  612.  
  613.             }
  614.  
  615.         }
  616.  
  617.     }
  618.  
  619. }
Add Comment
Please, Sign In to add comment