'DEBUG', self::INFO => 'INFO', self::WARNING => 'WARNING', self::ERROR => 'ERROR', ); /** * @var string The file to which messages should be written. */ public $file; /** * @var int Only write messages with this level or higher */ public $level; /** * @var resource */ protected $stream; /** * MC4WP_Debug_Log constructor. * * @param string $file * @param mixed $level; */ public function __construct( $file, $level = self::DEBUG ) { $this->file = $file; $this->level = self::to_level( $level ); } /** * @param mixed $level * @param string $message * @return boolean */ public function log( $level, $message ) { $level = self::to_level( $level ); // only log if message level is higher than log level if( $level < $this->level ) { return false; } // obfuscate email addresses in log message since log might be public. $message = mc4wp_obfuscate_email_addresses( (string) $message ); // first, get rid of everything between "invisible" tags $message = preg_replace( '/<(?:style|script|head)>.+?<\/(?:style|script|head)>/is', '', $message ); // then, strip tags (while retaining content of these tags) $message = strip_tags( $message ); $message = trim( $message ); // generate line $level_name = self::get_level_name( $level ); $datetime = date( 'Y-m-d H:i:s', ( time() - date('Z') ) + ( get_option( 'gmt_offset', 0 ) * 3600 ) ); $message = sprintf( '[%s] %s: %s', $datetime, $level_name, $message ) . PHP_EOL; // did we open stream yet? if( ! is_resource( $this->stream ) ) { // open stream $this->stream = @fopen( $this->file, 'c+' ); // if this failed, bail.. if ( ! is_resource( $this->stream ) ) { return false; } // make sure first line of log file is a PHP tag + exit statement (to prevent direct file access) $line = fgets( $this->stream ); $php_exit_string = ''; if( strpos( $line, $php_exit_string ) !== 0 ) { rewind( $this->stream ); fwrite( $this->stream, $php_exit_string . PHP_EOL . $line ); } // place pointer at end of file fseek( $this->stream, 0, SEEK_END ); } // lock file while we write, ignore errors (not much we can do) flock( $this->stream, LOCK_EX ); // write the message to the file fwrite( $this->stream, $message ); // unlock file again, but don't close it for remainder of this request flock( $this->stream, LOCK_UN ); return true; } /** * @param string $message * @return boolean */ public function warning( $message ) { return $this->log( self::WARNING, $message ); } /** * @param string $message * @return boolean */ public function info( $message ) { return $this->log( self::INFO, $message ); } /** * @param string $message * @return boolean */ public function error( $message ) { return $this->log( self::ERROR, $message ); } /** * @param string $message * @return boolean */ public function debug( $message ) { return $this->log( self::DEBUG, $message ); } /** * Converts PSR-3 levels to local ones if necessary * * @param string|int Level number or name (PSR-3) * @return int */ public static function to_level( $level ) { if ( is_string( $level ) ) { $level = strtoupper( $level ); if( defined( __CLASS__ . '::' . $level ) ) { return constant( __CLASS__ . '::' . $level ); } throw new InvalidArgumentException( 'Level "' . $level . '" is not defined, use one of: ' . implode( ', ', array_keys( self::$levels ) ) ); } return $level; } /** * Gets the name of the logging level. * * @param int $level * @return string */ public static function get_level_name( $level ) { if ( ! isset( self::$levels[ $level ] ) ) { throw new InvalidArgumentException( 'Level "' . $level . '" is not defined, use one of: ' . implode( ', ', array_keys( self::$levels ) ) ); } return self::$levels[ $level ]; } /** * Tests if the log file is writable * * @return bool */ public function test() { $handle = @fopen( $this->file, 'a' ); $writable = false; if( is_resource( $handle ) ) { $writable = true; fclose( $handle ); } return $writable; } }