1: <?php
2: /**
3: * Xoops Logger handlers - component main class file
4: *
5: * You may not change or alter any portion of this comment or credits
6: * of supporting developers from this source code or any supporting source code
7: * which is considered copyrighted (c) material of the original comment or credit authors.
8: * This program is distributed in the hope that it will be useful,
9: * but WITHOUT ANY WARRANTY; without even the implied warranty of
10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11: *
12: * @copyright (c) 2000-2020 XOOPS Project (https://xoops.org)
13: * @license GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14: * @package kernel
15: * @subpackage logger
16: * @since 2.3.0
17: * @author Kazumi Ono <onokazu@xoops.org>
18: * @author Skalpa Keo <skalpa@xoops.org>
19: * @author Taiwen Jiang <phppp@users.sourceforge.net>
20: *
21: * @todo Not well written, just keep as it is. Refactored in 3.0
22: */
23: defined('XOOPS_ROOT_PATH') || exit('Restricted access');
24:
25: /**
26: * Collects information for a page request
27: *
28: * Records information about database queries, blocks, and execution time
29: * and can display it as HTML. It also catches php runtime errors.
30: *
31: * @package kernel
32: */
33: class XoopsLogger
34: {
35: /**
36: * *#@+
37: *
38: * @var array
39: */
40: public $queries = array();
41: public $blocks = array();
42: public $extra = array();
43: public $logstart = array();
44: public $logend = array();
45: public $errors = array();
46: public $deprecated = array();
47: /**
48: * *#@-
49: */
50:
51: public $usePopup = false;
52: public $activated = true;
53:
54: /**
55: * *@access protected
56: */
57: public $renderingEnabled = false;
58:
59: /**
60: * XoopsLogger::__construct()
61: */
62: public function __construct()
63: {
64: }
65:
66: /**
67: * Deprecated, use getInstance() instead
68: */
69: public function instance()
70: {
71: return XoopsLogger::getInstance();
72: }
73:
74: /**
75: * Get a reference to the only instance of this class
76: *
77: * @return object XoopsLogger reference to the only instance
78: */
79: public static function getInstance()
80: {
81: static $instance;
82: if (!isset($instance)) {
83: $instance = new XoopsLogger();
84: // Always catch errors, for security reasons
85: set_error_handler('XoopsErrorHandler_HandleError');
86: // grab any uncaught exception
87: set_exception_handler(array($instance, 'handleException'));
88: }
89:
90: return $instance;
91: }
92:
93: /**
94: * Enable logger output rendering
95: * When output rendering is enabled, the logger will insert its output within the page content.
96: * If the string <!--{xo-logger-output}--> is found in the page content, the logger output will
97: * replace it, otherwise it will be inserted after all the page output.
98: */
99: public function enableRendering()
100: {
101: if (!$this->renderingEnabled) {
102: ob_start(array(&$this, 'render'));
103: $this->renderingEnabled = true;
104: }
105: }
106:
107: /**
108: * Returns the current microtime in seconds.
109: *
110: * @return float
111: */
112: public function microtime()
113: {
114: /** @var array $now */
115: $now = explode(' ', microtime());
116:
117: return (float)$now[0] + (float)$now[1];
118: }
119:
120: /**
121: * Start a timer
122: *
123: * @param string $name name of the timer
124: */
125: public function startTime($name = 'XOOPS')
126: {
127: if ($this->activated) {
128: $this->logstart[$name] = $this->microtime();
129: }
130: }
131:
132: /**
133: * Stop a timer
134: *
135: * @param string $name name of the timer
136: */
137: public function stopTime($name = 'XOOPS')
138: {
139: if ($this->activated) {
140: $this->logend[$name] = $this->microtime();
141: }
142: }
143:
144: /**
145: * Log a database query
146: *
147: * @param string $sql SQL string
148: * @param string $error error message (if any)
149: * @param int $errno error number (if any)
150: * @param null $query_time
151: */
152: public function addQuery($sql, $error = null, $errno = null, $query_time = null)
153: {
154: if ($this->activated) {
155: $this->queries[] = array('sql' => $sql, 'error' => $error, 'errno' => $errno, 'query_time' => $query_time);
156: }
157: }
158:
159: /**
160: * Log display of a block
161: *
162: * @param string $name name of the block
163: * @param bool $cached was the block cached?
164: * @param int $cachetime cachetime of the block
165: */
166: public function addBlock($name, $cached = false, $cachetime = 0)
167: {
168: if ($this->activated) {
169: $this->blocks[] = array('name' => $name, 'cached' => $cached, 'cachetime' => $cachetime);
170: }
171: }
172:
173: /**
174: * Log extra information
175: *
176: * @param string $name name for the entry
177: * @param int $msg text message for the entry
178: */
179: public function addExtra($name, $msg)
180: {
181: if ($this->activated) {
182: $this->extra[] = array('name' => $name, 'msg' => $msg);
183: }
184: }
185:
186: /**
187: * Log messages for deprecated functions
188: *
189: * this was deprecated, but is still in broad use?
190: *
191: * @param string $msg text message for the entry
192: *
193: */
194: public function addDeprecated($msg)
195: {
196: if ($this->activated) {
197: $backTrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
198: $miniTrace = ' trace: ';
199: foreach ($backTrace as $i => $trace) {
200: $miniTrace .= $trace['file'] . ':' . $trace['line'] . ' ';
201: }
202: $miniTrace = str_replace(XOOPS_VAR_PATH, '', $miniTrace);
203: $miniTrace = str_replace(XOOPS_PATH, '', $miniTrace);
204: $miniTrace = str_replace(XOOPS_ROOT_PATH, '', $miniTrace);
205:
206: $this->deprecated[] = $msg . $miniTrace;
207: }
208: }
209:
210: /**
211: * Error handling callback (called by the zend engine)
212: *
213: * @param integer $errno
214: * @param string $errstr
215: * @param string $errfile
216: * @param string $errline
217: * @param array|null $trace
218: */
219: public function handleError($errno, $errstr, $errfile, $errline,$trace=null)
220: {
221: if ($this->activated && ($errno & error_reporting())) {
222: // NOTE: we only store relative pathnames
223: $this->errors[] = compact('errno', 'errstr', 'errfile', 'errline');
224: }
225: if ($errno == E_USER_ERROR) {
226: $includeTrace = true;
227: if (substr($errstr, 0, '8') === 'notrace:') {
228: $includeTrace = false;
229: $errstr = substr($errstr, 8);
230: }
231: echo sprintf(_XOOPS_FATAL_MESSAGE, $errstr);
232: if ($includeTrace) {
233: echo "<div style='color:#f0f0f0;background-color:#f0f0f0;'>" . _XOOPS_FATAL_BACKTRACE . ':<br>';
234: if ($trace === null && function_exists('debug_backtrace')) {
235: $trace = \debug_backtrace();
236: array_shift($trace); // Remove the first element, which is this function itself
237: }
238: foreach ($trace as $step) {
239: if (isset($step['file'])) {
240: echo $this->sanitizePath($step['file']);
241: echo ' (' . $step['line'] . ")\n<br>";
242: }
243: }
244: echo '</div>';
245: }
246: exit();
247: }
248: }
249:
250: /**
251: * Exception handling callback.
252: *
253: * @param \Exception|\Throwable $e uncaught Exception or Error
254: *
255: * @return void
256: */
257: public function handleException($e)
258: {
259: if ($this->isThrowable($e)) {
260: $msg = get_class($e) . ': ' . $this->sanitizePath($this->sanitizeDbMessage($e->getMessage()));
261: $this->handleError(E_USER_ERROR, $msg, $e->getFile(), $e->getLine(), $e->getTrace());
262: }
263: }
264:
265: /**
266: * Determine if an object implements Throwable (or is an Exception that would under PHP 7.)
267: *
268: * @param mixed $e Expected to be an object related to Exception or Throwable
269: *
270: * @return bool true if related to Throwable or Exception, otherwise false
271: */
272: protected function isThrowable($e)
273: {
274: $type = interface_exists('\Throwable', false) ? '\Throwable' : '\Exception';
275: return $e instanceof $type;
276: }
277:
278: /**
279: *
280: * @access protected
281: *
282: * @param string $path
283: *
284: * @return mixed|string
285: */
286: public function sanitizePath($path)
287: {
288: $path = str_replace(array('\\', XOOPS_ROOT_PATH, str_replace('\\', '/', realpath(XOOPS_ROOT_PATH))), array('/', '', ''), $path);
289:
290: return $path;
291: }
292:
293: /**
294: * sanitizeDbMessage
295: * @access protected
296: *
297: * @param string $message
298: *
299: * @return string
300: */
301: protected function sanitizeDbMessage($message)
302: {
303: // XOOPS_DB_PREFIX XOOPS_DB_NAME
304: $message = str_replace(XOOPS_DB_PREFIX.'_', '', $message);
305: $message = str_replace(XOOPS_DB_NAME.'.', '', $message);
306:
307: return $message;
308: }
309:
310: /**
311: * Output buffering callback inserting logger dump in page output
312: * @param $output
313: * @return string
314: */
315: public function render($output)
316: {
317: global $xoopsUser;
318: if (!$this->activated) {
319: return $output;
320: }
321:
322: $log = $this->dump($this->usePopup ? 'popup' : '');
323: $this->renderingEnabled = $this->activated = false;
324: $pattern = '<!--{xo-logger-output}-->';
325: $pos = strpos($output, $pattern);
326: if ($pos !== false) {
327: return substr($output, 0, $pos) . $log . substr($output, $pos + strlen($pattern));
328: } else {
329: return $output . $log;
330: }
331: }
332:
333: /**
334: * *#@+
335: *
336: * @protected
337: * @param string $mode
338: * @return
339: */
340: public function dump($mode = '')
341: {
342: include XOOPS_ROOT_PATH . '/class/logger/render.php';
343:
344: return $ret;
345: }
346:
347: /**
348: * get the current execution time of a timer
349: *
350: * @param string $name name of the counter
351: * @param bool $unset removes counter from global log
352: * @return float current execution time of the counter
353: */
354: public function dumpTime($name = 'XOOPS', $unset = false)
355: {
356: if (!$this->activated) {
357: return null;
358: }
359:
360: if (!isset($this->logstart[$name])) {
361: return 0;
362: }
363: $stop = isset($this->logend[$name]) ? $this->logend[$name] : $this->microtime();
364: $start = $this->logstart[$name];
365:
366: if ($unset) {
367: unset($this->logstart[$name]);
368: }
369:
370: return $stop - $start;
371: }
372:
373: /**
374: * XoopsLogger::triggerError()
375: *
376: * @deprecated
377: * @param int $errkey
378: * @param string $errStr
379: * @param string $errFile
380: * @param string $errLine
381: * @param int $errNo
382: * @return void
383: */
384: public function triggerError($errkey = 0, $errStr = '', $errFile = '', $errLine = '', $errNo = 0)
385: {
386: $GLOBALS['xoopsLogger']->addDeprecated(__METHOD__ . ' is deprecated since XOOPS 2.5.4');
387:
388: if (!empty($errStr)) {
389: $errStr = sprintf($errStr, $errkey);
390: }
391: $errFile = $this->sanitizePath($errFile);
392: $this->handleError($errNo, $errStr, $errFile, $errLine);
393: }
394:
395: /**
396: * *#@+
397: *
398: * @deprecated
399: */
400: public function dumpAll()
401: {
402: $GLOBALS['xoopsLogger']->addDeprecated(__METHOD__ . ' is deprecated since XOOPS 2.5.4, please use \'$xoopsLogger->dump(\'\');\' instead.');
403:
404: return $this->dump('');
405: }
406:
407: /**
408: * dumpBlocks @deprecated
409: *
410: * @return dump
411: */
412: public function dumpBlocks()
413: {
414: $GLOBALS['xoopsLogger']->addDeprecated(__METHOD__ . ' is deprecated since XOOPS 2.5.4, please use \'$xoopsLogger->dump(\'blocks\');\' instead.');
415:
416: return $this->dump('blocks');
417: }
418:
419: /**
420: * dumpExtra @deprecated
421: *
422: * @return dump
423: */
424: public function dumpExtra()
425: {
426: $GLOBALS['xoopsLogger']->addDeprecated(__METHOD__ . ' is deprecated since XOOPS 2.5.4, please use \'$xoopsLogger->dump(\'extra\');\' instead.');
427:
428: return $this->dump('extra');
429: }
430:
431: /**
432: * dump Queries @deprecated
433: *
434: * @return mixed
435: */
436: public function dumpQueries()
437: {
438: $GLOBALS['xoopsLogger']->addDeprecated(__METHOD__ . ' is deprecated since XOOPS 2.5.4, please use \'$xoopsLogger->dump(\'queries\');\' instead.');
439:
440: return $this->dump('queries');
441: }
442: /**
443: * *#@-
444: */
445: }
446:
447: /**
448: * PHP Error handler
449: *
450: * NB: You're not supposed to call this function directly, if you don't understand why, then
451: * you'd better spend some time reading your PHP manual before you hurt somebody
452: *
453: * @internal Using a function and not calling the handler method directly because of old PHP versions
454: * set_error_handler() have problems with the array( obj,methodname ) syntax
455: * @param $errNo
456: * @param $errStr
457: * @param $errFile
458: * @param $errLine
459: * @param null $errContext
460: * @return bool
461: */
462: function XoopsErrorHandler_HandleError($errNo, $errStr, $errFile, $errLine, $errContext = null)
463: {
464: /*
465: // We don't want every error to come through this will help speed things up'
466: if ($errNo == '2048') {
467: return true;
468: }
469: // XOOPS should always be STRICT compliant thus the above lines makes no sense and will be removed! -- Added by Taiwen Jiang
470: */
471: $logger = XoopsLogger::getInstance();
472: $logger->handleError($errNo, $errStr, $errFile, $errLine, $errContext);
473: return null;
474: }
475: