ShortPixelLogger.php
10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
<?php
namespace EnableMediaReplace\ShortPixelLogger;
/*** Logger class
*
* Class uses the debug data model for keeping log entries.
* Logger should not be called before init hook!
*/
class ShortPixelLogger
{
static protected $instance = null;
protected $start_time;
protected $is_active = false;
protected $is_manual_request = false;
protected $show_debug_view = false;
protected $items = array();
protected $logPath = false;
protected $logMode = FILE_APPEND;
protected $logLevel;
protected $format = "[ %%time%% ] %%color%% %%level%% %%color_end%% \t %%message%% \t %%caller%% ( %%time_passed%% )";
protected $format_data = "\t %%data%% ";
protected $hooks = array();
private $logFile; // pointer resource to the logFile.
/* protected $hooks = array(
'shortpixel_image_exists' => array('numargs' => 3),
'shortpixel_webp_image_base' => array('numargs' => 2),
'shortpixel_image_urls' => array('numargs' => 2),
); // @todo monitor hooks, but this should be more dynamic. Do when moving to module via config.
*/
// utility
private $namespace;
private $view;
protected $template = 'view-debug-box';
/** Debugger constructor
* Two ways to activate the debugger. 1) Define SHORTPIXEL_DEBUG in wp-config.php. Either must be true or a number corresponding to required LogLevel
* 2) Put SHORTPIXEL_DEBUG in the request. Either true or number.
*/
public function __construct()
{
$this->start_time = microtime(true);
$this->logLevel = DebugItem::LEVEL_WARN;
$ns = __NAMESPACE__;
$this->namespace = substr($ns, 0, strpos($ns, '\\')); // try to get first part of namespace
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is not a form
if (isset($_REQUEST['SHORTPIXEL_DEBUG'])) // manual takes precedence over constants
{
$this->is_manual_request = true;
$this->is_active = true;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is not a form
if ($_REQUEST['SHORTPIXEL_DEBUG'] === 'true')
{
$this->logLevel = DebugItem::LEVEL_INFO;
}
else {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is not a form
$this->logLevel = intval($_REQUEST['SHORTPIXEL_DEBUG']);
}
}
else if ( (defined('SHORTPIXEL_DEBUG') && SHORTPIXEL_DEBUG > 0) )
{
$this->is_active = true;
if (SHORTPIXEL_DEBUG === true)
$this->logLevel = DebugItem::LEVEL_INFO;
else {
$this->logLevel = intval(SHORTPIXEL_DEBUG);
}
}
if (defined('SHORTPIXEL_DEBUG_TARGET') && SHORTPIXEL_DEBUG_TARGET || $this->is_manual_request)
{
if (defined('SHORTPIXEL_LOG_OVERWRITE')) // if overwrite, do this on init once.
file_put_contents($this->logPath,'-- Log Reset -- ' .PHP_EOL);
}
if ($this->is_active)
{
/* On Early init, this function might not exist, then queue it when needed */
if (! function_exists('wp_get_current_user'))
add_action('init', array($this, 'initView'));
else
$this->initView();
}
if ($this->is_active && count($this->hooks) > 0)
$this->monitorHooks();
}
/** Init the view when needed. Private function ( public because of WP_HOOK )
* Never call directly */
public function initView()
{
$user_is_administrator = (current_user_can('manage_options')) ? true : false;
if ($this->is_active && $this->is_manual_request && $user_is_administrator )
{
$logPath = $this->logPath;
$uploads = wp_get_upload_dir();
if ( 0 === strpos( $logPath, $uploads['basedir'] ) ) { // Simple as it should, filepath and basedir share.
// Replace file location with url location.
$logLink = str_replace( $uploads['basedir'], $uploads['baseurl'], $logPath );
}
$this->view = new \stdClass;
$this->view->logLink = 'view-source:' . esc_url($logLink);
add_action('admin_footer', array($this, 'loadView'));
}
}
public static function getInstance()
{
if ( self::$instance === null)
{
self::$instance = new ShortPixelLogger();
}
return self::$instance;
}
public function setLogPath($logPath)
{
$this->logPath = $logPath;
$this->getWriteFile(true); // reset the writeFile here.
}
protected function addLog($message, $level, $data = array())
{
// $log = self::getInstance();
// don't log anything too low or when not active.
if ($this->logLevel < $level || ! $this->is_active)
{
return;
}
// Force administrator on manuals.
if ( $this->is_manual_request )
{
if (! function_exists('wp_get_current_user')) // not loaded yet
return false;
$user_is_administrator = (current_user_can('manage_options')) ? true : false;
if (! $user_is_administrator)
return false;
}
// Check where to log to.
if ($this->logPath === false)
{
$upload_dir = wp_upload_dir(null,false,false);
$this->logPath = $this->setLogPath($upload_dir['basedir'] . '/' . $this->namespace . ".log");
}
$arg = array();
$args['level'] = $level;
$args['data'] = $data;
$newItem = new DebugItem($message, $args);
$this->items[] = $newItem;
if ($this->is_active)
{
$this->write($newItem);
}
}
/** Writes to log File. */
protected function write($debugItem, $mode = 'file')
{
$items = $debugItem->getForFormat();
$items['time_passed'] = round ( ($items['time'] - $this->start_time), 5);
$items['time'] = date('Y-m-d H:i:s', (int) $items['time'] );
if ( ($items['caller']) && is_array($items['caller']) && count($items['caller']) > 0)
{
$caller = $items['caller'];
$items['caller'] = $caller['file'] . ' in ' . $caller['function'] . '(' . $caller['line'] . ')';
}
$line = $this->formatLine($items);
$file = $this->getWriteFile();
// try to write to file. Don't write if directory doesn't exists (leads to notices)
if ($file )
{
fwrite($file, $line);
// file_put_contents($this->logPath,$line, FILE_APPEND);
}
else {
// error_log($line);
}
}
protected function getWriteFile($reset = false)
{
if (! is_null($this->logFile) && $reset === false)
{
return $this->logFile;
}
elseif(is_object($this->logFile))
{
fclose($this->logFile);
}
$logDir = dirname($this->logPath);
if (! is_dir($logDir) || ! is_writable($logDir))
{
error_log('ShortpixelLogger: Log Directory is not writable');
$this->logFile = false;
return false;
}
$file = fopen($this->logPath, 'a');
if ($file === false)
{
error_log('ShortpixelLogger: File could not be opened / created: ' . $this->logPath);
$this->logFile = false;
return $file;
}
$this->logFile = $file;
return $file;
}
protected function formatLine($args = array() )
{
$line= $this->format;
foreach($args as $key => $value)
{
if (! is_array($value) && ! is_object($value))
$line = str_replace('%%' . $key . '%%', $value, $line);
}
$line .= PHP_EOL;
if (isset($args['data']))
{
$data = array_filter($args['data']);
if (count($data) > 0)
{
foreach($data as $item)
{
$line .= $item . PHP_EOL;
}
}
}
return $line;
}
protected function setLogLevel($level)
{
$this->logLevel = $level;
}
protected function getEnv($name)
{
if (isset($this->{$name}))
{
return $this->{$name};
}
else {
return false;
}
}
public static function addError($message, $args = array())
{
$level = DebugItem::LEVEL_ERROR;
$log = self::getInstance();
$log->addLog($message, $level, $args);
}
public static function addWarn($message, $args = array())
{
$level = DebugItem::LEVEL_WARN;
$log = self::getInstance();
$log->addLog($message, $level, $args);
}
// Alias, since it goes wrong so often.
public static function addWarning($message, $args = array())
{
self::addWarn($message, $args);
}
public static function addInfo($message, $args = array())
{
$level = DebugItem::LEVEL_INFO;
$log = self::getInstance();
$log->addLog($message, $level, $args);
}
public static function addDebug($message, $args = array())
{
$level = DebugItem::LEVEL_DEBUG;
$log = self::getInstance();
$log->addLog($message, $level, $args);
}
/** These should be removed every release. They are temporary only for d'bugging the current release */
public static function addTemp($message, $args = array())
{
self::addDebug($message, $args);
}
public static function logLevel($level)
{
$log = self::getInstance();
static::addInfo('Changing Log level' . $level);
$log->setLogLevel($level);
}
public static function getLogLevel()
{
$log = self::getInstance();
return $log->getEnv('logLevel');
}
public static function isManualDebug()
{
$log = self::getInstance();
return $log->getEnv('is_manual_request');
}
public static function getLogPath()
{
$log = self::getInstance();
return $log->getEnv('logPath');
}
/** Function to test if the debugger is active
* @return boolean true when active.
*/
public static function debugIsActive()
{
$log = self::getInstance();
return $log->getEnv('is_active');
}
protected function monitorHooks()
{
foreach($this->hooks as $hook => $data)
{
$numargs = isset($data['numargs']) ? $data['numargs'] : 1;
$prio = isset($data['priority']) ? $data['priority'] : 10;
add_filter($hook, function($value) use ($hook) {
$args = func_get_args();
return $this->logHook($hook, $value, $args); }, $prio, $numargs);
}
}
public function logHook($hook, $value, $args)
{
array_shift($args);
self::addInfo('[Hook] - ' . $hook . ' with ' . var_export($value,true), $args);
return $value;
}
public function loadView()
{
// load either param or class template.
$template = $this->template;
$view = $this->view;
$view->namespace = $this->namespace;
$controller = $this;
$template_path = __DIR__ . '/' . $this->template . '.php';
if (file_exists($template_path))
{
include($template_path);
}
else {
self::addError("View $template for ShortPixelLogger could not be found in " . $template_path,
array('class' => get_class($this)));
}
}
} // class debugController