PluginProbe
DecaLog / 4.4.0
DecaLog v4.4.0
3.0.2 3.1.0 3.10.0 3.2.0 3.3.0 3.4.0 3.4.1 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.6.3 3.7.0 3.7.1 3.8.0 3.9.0 3.9.1 4.0.0 4.1.0 4.2.0 4.3.0 4.3.1 4.4.0 4.5.0 All 75 releases
decalog / includes / handlers / class-browserconsolehandler.php

class-browserconsolehandler.php in DecaLog 4.4.0, at includes/handlers/class-browserconsolehandler.php

198 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * BrowserConsole handler for Monolog
4 *
5 * Handles all features of BrowserConsole handler for Monolog.
6 *
7 * @package Handlers
8 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
9 * @author Olivier Poitrey <rs@dailymotion.com>.
10 * @since 3.1.0
11 */
12
13 namespace Decalog\Handler;
14
15 use DLMonolog\Formatter\LineFormatter;
16 use DLMonolog\Formatter\FormatterInterface;
17 use DLMonolog\Utils;
18 use DLMonolog\Handler\AbstractProcessingHandler;
19 use Decalog\Plugin\Feature\EventTypes;
20
21 /**
22 * Define the Monolog BrowserConsole handler.
23 *
24 * Handles all features of BrowserConsole handler for Monolog.
25 *
26 * @package Handlers
27 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
28 * @author Olivier Poitrey <rs@dailymotion.com>.
29 * @since 3.1.0
30 */
31 class BrowserConsoleHandler extends AbstractProcessingHandler {
32
33 protected static $initialized = false;
34 protected static $records = [];
35
36 /**
37 * {@inheritDoc}
38 *
39 * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format.
40 *
41 * Example of formatted string:
42 *
43 * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white}
44 */
45 protected function getDefaultFormatter(): FormatterInterface {
46 return new LineFormatter( '[[%level_name%]]{font-weight: bold} %message%' );
47 }
48
49 /**
50 * {@inheritDoc}
51 */
52 protected function write( array $record ): void {
53 static::$records[] = $record;
54 if ( ! static::$initialized ) {
55 static::$initialized = true;
56 $this->registerShutdownFunction();
57 }
58 }
59
60 /**
61 * Convert records to javascript console commands and send it to the browser.
62 * This method is automatically called on PHP shutdown if output is HTML or Javascript.
63 */
64 public static function send(): void {
65 $format = static::getResponseFormat();
66 if ( $format === 'unknown' ) {
67 return;
68 }
69 if ( count( static::$records ) ) {
70 if ( $format === 'html' ) {
71 static::writeOutput( '<script>' . static::generateScript() . '</script>' );
72 } elseif ( $format === 'js' ) {
73 static::writeOutput( static::generateScript() );
74 }
75 }
76 }
77
78 /**
79 * Wrapper for register_shutdown_function to allow overriding
80 */
81 protected function registerShutdownFunction(): void {
82 if ( PHP_SAPI !== 'cli' ) {
83 register_shutdown_function( [ 'Decalog\Handler\BrowserConsoleHandler', 'send' ] );
84 }
85 }
86
87 /**
88 * Wrapper for echo to allow overriding
89 */
90 protected static function writeOutput( string $str ): void {
91 echo $str;
92 }
93
94 /**
95 * Checks the format of the response
96 *
97 * If Content-Type is set to application/javascript or text/javascript -> js
98 * If Content-Type is set to text/html, or is unset -> html
99 * If Content-Type is anything else -> unknown
100 *
101 * @return string One of 'js', 'html' or 'unknown'
102 */
103 protected static function getResponseFormat(): string {
104 foreach ( headers_list() as $header ) {
105 if ( stripos( $header, 'content-type:' ) === 0 ) {
106 if ( stripos( $header, 'application/javascript' ) !== false || stripos( $header, 'text/javascript' ) !== false ) {
107 return 'js';
108 }
109 if ( stripos( $header, 'text/html' ) === false ) {
110 return 'unknown';
111 }
112 break;
113 }
114 }
115 return 'html';
116 }
117
118 private static function generateScript(): string {
119 $script = [];
120 foreach ( static::$records as $record ) {
121 $context = static::dump( 'Context', $record['context'] );
122 $extra = static::dump( 'Extra', $record['extra'] );
123 if ( empty( $context ) && empty( $extra ) ) {
124 $script[] = static::call_array( 'log', static::handleStyles( $record['formatted'] ) );
125 } else {
126 $script = array_merge(
127 $script,
128 [ static::call_array( 'groupCollapsed', static::handleStyles( $record['formatted'] ) ) ],
129 $context,
130 $extra,
131 [ static::call( 'groupEnd' ) ]
132 );
133 }
134 }
135 return "(function (c) {if (c && c.groupCollapsed) {\n" . implode( "\n", $script ) . "\n}})(console);";
136 }
137
138 private static function handleStyles( string $formatted ): array {
139 $args = [];
140 $format = '%c' . $formatted;
141 preg_match_all( '/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER );
142 foreach ( array_reverse( $matches ) as $match ) {
143 $args[] = '"font-weight: normal"';
144 $args[] = static::quote( static::handleCustomStyles( $formatted ) );
145 $pos = $match[0][1];
146 $format = Utils::substr( $format, 0, $pos ) . '%c' . $match[1][0] . '%c' . Utils::substr( $format, $pos + strlen( $match[0][0] ) );
147 }
148 $args[] = static::quote( 'font-weight: normal' );
149 $args[] = static::quote( $format );
150 return array_reverse( $args );
151 }
152
153 private static function handleCustomStyles( string $style ): string {
154 return preg_replace_callback(
155 '/^\[\[(.*)\]\]/',
156 function ( array $m ) {
157 $level = strtolower( $m[1] );
158 if ( ! array_key_exists( $level, EventTypes::$levels_colors ) ) {
159 $level = 'unknown';
160 }
161 return 'background-color:' . EventTypes::$levels_colors[ $level ][1] . ';color:' . EventTypes::$levels_colors[ $level ][0] . ';border-radius: 3px;padding:1px 6px;';
162 },
163 $style
164 );
165 }
166
167 private static function dump( string $title, array $dict ): array {
168 $script = [];
169 $dict = array_filter( $dict );
170 if ( empty( $dict ) ) {
171 return $script;
172 }
173 $script[] = static::call( 'log', static::quote( '%c%s' ), static::quote( 'font-weight: bold' ), static::quote( $title ) );
174 foreach ( $dict as $key => $value ) {
175 $value = wp_json_encode( $value );
176 if ( empty( $value ) ) {
177 $value = static::quote( '' );
178 }
179 $script[] = static::call( 'log', static::quote( '%s: %o' ), static::quote( (string) $key ), $value );
180 }
181
182 return $script;
183 }
184
185 private static function quote( string $arg ): string {
186 return '"' . addcslashes( $arg, "\"\n\\" ) . '"';
187 }
188
189 private static function call( ...$args ): string {
190 $method = array_shift( $args );
191 return static::call_array( $method, $args );
192 }
193
194 private static function call_array( string $method, array $args ): string {
195 return 'c.' . $method . '(' . implode( ', ', $args ) . ');';
196 }
197 }
198