137748cd8SNickeau<?php 237748cd8SNickeau/** 337748cd8SNickeau * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved. 437748cd8SNickeau * 537748cd8SNickeau * This source code is licensed under the GPL license found in the 637748cd8SNickeau * COPYING file in the root directory of this source tree. 737748cd8SNickeau * 837748cd8SNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 937748cd8SNickeau * @author ComboStrap <support@combostrap.com> 1037748cd8SNickeau * 1137748cd8SNickeau */ 1237748cd8SNickeau 1337748cd8SNickeaunamespace ComboStrap; 1437748cd8SNickeau 15*04fd306cSNickeauuse Throwable; 16*04fd306cSNickeau 1737748cd8SNickeaurequire_once(__DIR__ . '/PluginUtility.php'); 1837748cd8SNickeau 1937748cd8SNickeauclass LogUtility 2037748cd8SNickeau{ 2137748cd8SNickeau 2237748cd8SNickeau /** 2337748cd8SNickeau * Constant for the function {@link msg()} 2437748cd8SNickeau * -1 = error, 0 = info, 1 = success, 2 = notify 2537748cd8SNickeau * (Not even in order of importance) 2637748cd8SNickeau */ 27*04fd306cSNickeau const LVL_MSG_ABOVE_ERROR = 5; // a level to disable the error to thrown in test 2837748cd8SNickeau const LVL_MSG_ERROR = 4; //-1; 2937748cd8SNickeau const LVL_MSG_WARNING = 3; //2; 3037748cd8SNickeau const LVL_MSG_SUCCESS = 2; //1; 3137748cd8SNickeau const LVL_MSG_INFO = 1; //0; 3237748cd8SNickeau const LVL_MSG_DEBUG = 0; //3; 3337748cd8SNickeau 3437748cd8SNickeau 3537748cd8SNickeau /** 3637748cd8SNickeau * Id level to name 3737748cd8SNickeau */ 3837748cd8SNickeau const LVL_NAME = array( 3937748cd8SNickeau 0 => "debug", 4037748cd8SNickeau 1 => "info", 4137748cd8SNickeau 3 => "warning", 4237748cd8SNickeau 2 => "success", 4337748cd8SNickeau 4 => "error" 4437748cd8SNickeau ); 4537748cd8SNickeau 4637748cd8SNickeau /** 4737748cd8SNickeau * Id level to name 4837748cd8SNickeau * {@link msg()} constant 4937748cd8SNickeau */ 5037748cd8SNickeau const LVL_TO_MSG_LEVEL = array( 5137748cd8SNickeau 0 => 3, 5237748cd8SNickeau 1 => 0, 5337748cd8SNickeau 2 => 1, 5437748cd8SNickeau 3 => 2, 5537748cd8SNickeau 4 => -1 5637748cd8SNickeau ); 5737748cd8SNickeau 5837748cd8SNickeau 5937748cd8SNickeau const LOGLEVEL_URI_QUERY_PROPERTY = "loglevel"; 60*04fd306cSNickeau const SUPPORT_CANONICAL = "support"; 61*04fd306cSNickeau 620e43c1dbSgerardnico /** 630e43c1dbSgerardnico * 640e43c1dbSgerardnico * @var bool 650e43c1dbSgerardnico */ 66*04fd306cSNickeau private static bool $throwExceptionOnDevTest = true; 67*04fd306cSNickeau /** 68*04fd306cSNickeau * @var int 69*04fd306cSNickeau */ 70*04fd306cSNickeau const DEFAULT_THROW_LEVEL = self::LVL_MSG_WARNING; 7137748cd8SNickeau 7237748cd8SNickeau /** 7337748cd8SNickeau * Send a message to a manager and log it 7437748cd8SNickeau * Fail if in test 7537748cd8SNickeau * @param string $message 7637748cd8SNickeau * @param int $level - the level see LVL constant 7737748cd8SNickeau * @param string $canonical - the canonical 7837748cd8SNickeau */ 79*04fd306cSNickeau public static function msg(string $message, int $level = self::LVL_MSG_ERROR, string $canonical = "support", \Exception $e = null) 8037748cd8SNickeau { 8137748cd8SNickeau 82d61dea15Sgerardnico try { 83d61dea15Sgerardnico self::messageNotEmpty($message); 84*04fd306cSNickeau } catch (ExceptionCompile $e) { 85d61dea15Sgerardnico self::log2file($e->getMessage(), LogUtility::LVL_MSG_ERROR, $canonical); 86e7bbc4b8Sgerardnico } 87e7bbc4b8Sgerardnico 8837748cd8SNickeau /** 8937748cd8SNickeau * Log to frontend 9037748cd8SNickeau */ 91c3437056SNickeau self::log2FrontEnd($message, $level, $canonical); 9237748cd8SNickeau 9337748cd8SNickeau /** 9437748cd8SNickeau * Log level passed for a page (only for file used) 9537748cd8SNickeau * to not allow an attacker to see all errors in frontend 9637748cd8SNickeau */ 9737748cd8SNickeau global $INPUT; 9837748cd8SNickeau $loglevelProp = $INPUT->str(self::LOGLEVEL_URI_QUERY_PROPERTY, null); 9937748cd8SNickeau if (!empty($loglevelProp)) { 10037748cd8SNickeau $level = $loglevelProp; 10137748cd8SNickeau } 1022f4da794Sgerardnico /** 1032f4da794Sgerardnico * TODO: Make it a configuration ? 1042f4da794Sgerardnico */ 1052f4da794Sgerardnico if ($level >= self::LVL_MSG_WARNING) { 106*04fd306cSNickeau self::log2file($message, $level, $canonical, $e); 1072f4da794Sgerardnico } 10837748cd8SNickeau 10937748cd8SNickeau /** 11037748cd8SNickeau * If test, we throw an error 11137748cd8SNickeau */ 112*04fd306cSNickeau self::throwErrorIfTest($level, $message, $e); 11337748cd8SNickeau } 11437748cd8SNickeau 11537748cd8SNickeau /** 11637748cd8SNickeau * Print log to a file 11737748cd8SNickeau * 11837748cd8SNickeau * Adapted from {@link dbglog} 11937748cd8SNickeau * Note: {@link dbg()} dbg print to the web page 12037748cd8SNickeau * 121e96d9362Sgerardnico * @param null|string $msg - may be null always this is the default if a variable is not initialized. 12237748cd8SNickeau * @param int $logLevel 12337748cd8SNickeau * @param null $canonical 124*04fd306cSNickeau * @param \Exception $e 12537748cd8SNickeau */ 126*04fd306cSNickeau static function log2file(?string $msg, int $logLevel = self::LVL_MSG_ERROR, $canonical = null, \Exception $e = null) 12737748cd8SNickeau { 12837748cd8SNickeau 129d61dea15Sgerardnico try { 130d61dea15Sgerardnico self::messageNotEmpty($msg); 131*04fd306cSNickeau } catch (ExceptionCompile $e) { 132d61dea15Sgerardnico $msg = $e->getMessage(); 133d61dea15Sgerardnico $logLevel = self::LVL_MSG_ERROR; 134d61dea15Sgerardnico } 135d61dea15Sgerardnico 136c3437056SNickeau if (PluginUtility::isTest() || $logLevel >= self::LVL_MSG_WARNING) { 13737748cd8SNickeau 13837748cd8SNickeau $prefix = PluginUtility::$PLUGIN_NAME; 13937748cd8SNickeau if (!empty($canonical)) { 14037748cd8SNickeau $prefix .= ' - ' . $canonical; 14137748cd8SNickeau } 14237748cd8SNickeau $msg = $prefix . ' - ' . $msg; 14337748cd8SNickeau 14437748cd8SNickeau global $INPUT; 14537748cd8SNickeau global $conf; 14637748cd8SNickeau 147c3437056SNickeau /** 148c3437056SNickeau * Adding page - context information 149*04fd306cSNickeau * We are not using {@link MarkupPath::createFromRequestedPage()} 150c3437056SNickeau * because it throws an error message when the environment 151c3437056SNickeau * is not good, creating a recursive call. 152c3437056SNickeau */ 153*04fd306cSNickeau $id = $INPUT->str("id"); 15437748cd8SNickeau 15537748cd8SNickeau $file = $conf['cachedir'] . '/debug.log'; 15637748cd8SNickeau $fh = fopen($file, 'a'); 15737748cd8SNickeau if ($fh) { 15837748cd8SNickeau $sep = " - "; 15937748cd8SNickeau fwrite($fh, date('c') . $sep . self::LVL_NAME[$logLevel] . $sep . $msg . $sep . $INPUT->server->str('REMOTE_ADDR') . $sep . $id . "\n"); 16037748cd8SNickeau fclose($fh); 16137748cd8SNickeau } 162c3437056SNickeau 163c3437056SNickeau 164*04fd306cSNickeau self::throwErrorIfTest($logLevel, $msg, $e); 165c3437056SNickeau 166c3437056SNickeau 16737748cd8SNickeau } 16837748cd8SNickeau 16937748cd8SNickeau } 17037748cd8SNickeau 17137748cd8SNickeau /** 17237748cd8SNickeau * @param $message 17337748cd8SNickeau * @param $level 174d61dea15Sgerardnico * @param string $canonical 17537748cd8SNickeau * @param bool $withIconURL 17637748cd8SNickeau */ 177*04fd306cSNickeau public static function log2FrontEnd($message, $level, $canonical = "support", bool $publicMessage = false) 17837748cd8SNickeau { 179d61dea15Sgerardnico 180d61dea15Sgerardnico try { 181d61dea15Sgerardnico self::messageNotEmpty($message); 182*04fd306cSNickeau } catch (ExceptionCompile $e) { 183d61dea15Sgerardnico $message = $e->getMessage(); 184d61dea15Sgerardnico $level = self::LVL_MSG_ERROR; 185d61dea15Sgerardnico } 186d61dea15Sgerardnico 18737748cd8SNickeau /** 18837748cd8SNickeau * If we are not in the console 18937748cd8SNickeau * and not in test 19037748cd8SNickeau * we test that the message comes in the front end 19137748cd8SNickeau * (example {@link \plugin_combo_frontmatter_test} 19237748cd8SNickeau */ 193c3437056SNickeau $isTerminal = Console::isConsoleRun(); 194c3437056SNickeau if ($isTerminal) { 19537748cd8SNickeau if (!defined('DOKU_UNITTEST')) { 196c3437056SNickeau /** 197c3437056SNickeau * such as {@link cli_plugin_combo} 198c3437056SNickeau */ 199c3437056SNickeau $userAgent = "cli"; 200c3437056SNickeau } else { 201c3437056SNickeau $userAgent = "phpunit"; 20237748cd8SNickeau } 203c3437056SNickeau } else { 204c3437056SNickeau $userAgent = "browser"; 20537748cd8SNickeau } 206c3437056SNickeau 207c3437056SNickeau switch ($userAgent) { 208c3437056SNickeau case "cli": 209c3437056SNickeau echo "$message\n"; 210c3437056SNickeau break; 211c3437056SNickeau case "phpunit": 212c3437056SNickeau case "browser": 213c3437056SNickeau default: 214*04fd306cSNickeau if ($canonical !== null) { 215*04fd306cSNickeau $label = ucfirst(str_replace(":", " ", $canonical)); 216*04fd306cSNickeau $htmlMsg = PluginUtility::getDocumentationHyperLink($canonical, $label, false); 217*04fd306cSNickeau } else { 218*04fd306cSNickeau $htmlMsg = PluginUtility::getDocumentationHyperLink("", PluginUtility::$PLUGIN_NAME, false); 21937748cd8SNickeau } 22037748cd8SNickeau 221*04fd306cSNickeau 22237748cd8SNickeau /** 22337748cd8SNickeau * Adding page - context information 22437748cd8SNickeau * We are not creating the page 225*04fd306cSNickeau * direction from {@link MarkupPath::createFromRequestedPage()} 22637748cd8SNickeau * because it throws an error message when the environment 22737748cd8SNickeau * is not good, creating a recursive call. 22837748cd8SNickeau */ 229*04fd306cSNickeau global $INPUT; 230*04fd306cSNickeau $id = $INPUT->str("id"); 23137748cd8SNickeau if ($id != null) { 232c3437056SNickeau 233c3437056SNickeau /** 234c3437056SNickeau * We don't use any Page object to not 235c3437056SNickeau * create a cycle while building it 236c3437056SNickeau */ 237c3437056SNickeau $url = wl($id, [], true); 238c3437056SNickeau $htmlMsg .= " - <a href=\"$url\">$id</a>"; 239c3437056SNickeau 24037748cd8SNickeau } 24137748cd8SNickeau 24237748cd8SNickeau /** 24337748cd8SNickeau * 24437748cd8SNickeau */ 24537748cd8SNickeau $htmlMsg .= " - " . $message; 24637748cd8SNickeau if ($level > self::LVL_MSG_DEBUG) { 24737748cd8SNickeau $dokuWikiLevel = self::LVL_TO_MSG_LEVEL[$level]; 248*04fd306cSNickeau if ($publicMessage) { 249*04fd306cSNickeau $allow = MSG_PUBLIC; 250*04fd306cSNickeau } else { 251*04fd306cSNickeau $allow = MSG_USERS_ONLY; 252*04fd306cSNickeau } 253*04fd306cSNickeau msg($htmlMsg, $dokuWikiLevel, '', '',$allow); 25437748cd8SNickeau } 25537748cd8SNickeau } 25637748cd8SNickeau } 25737748cd8SNickeau 25837748cd8SNickeau /** 25937748cd8SNickeau * Log a message to the browser console 26037748cd8SNickeau * @param $message 26137748cd8SNickeau */ 26237748cd8SNickeau public static function log2BrowserConsole($message) 26337748cd8SNickeau { 26437748cd8SNickeau // TODO 26537748cd8SNickeau } 266c3437056SNickeau 267*04fd306cSNickeau 268*04fd306cSNickeau /** 269*04fd306cSNickeau * @param $level 270*04fd306cSNickeau * @param $message 271*04fd306cSNickeau * @param $e - the original exception for chaining 272*04fd306cSNickeau * @return void 273*04fd306cSNickeau */ 274*04fd306cSNickeau private static function throwErrorIfTest($level, $message, \Exception $e = null) 275c3437056SNickeau { 276*04fd306cSNickeau $actualLevel = ExecutionContext::getActualOrCreateFromEnv()->getConfig() 277*04fd306cSNickeau ->getLogExceptionLevel(); 278c3437056SNickeau if (PluginUtility::isTest() 279*04fd306cSNickeau && ($level >= $actualLevel) 2800e43c1dbSgerardnico && self::$throwExceptionOnDevTest 281c3437056SNickeau ) { 282*04fd306cSNickeau throw new LogException($message, $level, $e); 283c3437056SNickeau } 284c3437056SNickeau } 285d61dea15Sgerardnico 286d61dea15Sgerardnico /** 2874e9ad3c2Sgerardnico * @param string|null $message 288*04fd306cSNickeau * @throws ExceptionCompile 289d61dea15Sgerardnico */ 2904e9ad3c2Sgerardnico private static function messageNotEmpty(?string $message) 291d61dea15Sgerardnico { 292d61dea15Sgerardnico $message = trim($message); 2932fea41feSgerardnico if ($message === null || $message === "") { 294d61dea15Sgerardnico $newMessage = "The passed message to the log was empty or null. BackTrace: \n"; 29533a4f219Sgerardnico $newMessage .= LogUtility::getCallStack(); 296*04fd306cSNickeau throw new ExceptionCompile($newMessage); 297d61dea15Sgerardnico } 298d61dea15Sgerardnico } 2990e43c1dbSgerardnico 3000e43c1dbSgerardnico public static function disableThrowExceptionOnDevTest() 3010e43c1dbSgerardnico { 3020e43c1dbSgerardnico self::$throwExceptionOnDevTest = false; 3030e43c1dbSgerardnico } 3040e43c1dbSgerardnico 3050e43c1dbSgerardnico public static function enableThrowExceptionOnDevTest() 3060e43c1dbSgerardnico { 3070e43c1dbSgerardnico self::$throwExceptionOnDevTest = true; 3080e43c1dbSgerardnico } 3094cadd4f8SNickeau 3104cadd4f8SNickeau public static function wrapInRedForHtml(string $message): string 3114cadd4f8SNickeau { 312*04fd306cSNickeau return "<span class=\"text-danger\">$message</span>"; 3134cadd4f8SNickeau } 31433a4f219Sgerardnico 31533a4f219Sgerardnico /** 31633a4f219Sgerardnico * @return false|string - the actual php call stack (known as backtrace) 31733a4f219Sgerardnico */ 31833a4f219Sgerardnico public static function getCallStack() 31933a4f219Sgerardnico { 32033a4f219Sgerardnico ob_start(); 32133a4f219Sgerardnico $limit = 10; 32233a4f219Sgerardnico /** 32333a4f219Sgerardnico * DEBUG_BACKTRACE_IGNORE_ARGS options to avoid 32433a4f219Sgerardnico * PHP Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1876967424 bytes) 32533a4f219Sgerardnico */ 32633a4f219Sgerardnico debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit); // It prints also the data passed :) 32733a4f219Sgerardnico $trace = ob_get_contents(); 32833a4f219Sgerardnico ob_end_clean(); 32933a4f219Sgerardnico return $trace; 33033a4f219Sgerardnico } 331*04fd306cSNickeau 332*04fd306cSNickeau /** 333*04fd306cSNickeau * @param string $message the message 334*04fd306cSNickeau * @param string $canonical the page 335*04fd306cSNickeau * @param \Exception|null $e the original exception for trace chaining 336*04fd306cSNickeau * @return void 337*04fd306cSNickeau */ 338*04fd306cSNickeau public static function error(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null) 339*04fd306cSNickeau { 340*04fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_ERROR, $canonical, $e); 341*04fd306cSNickeau } 342*04fd306cSNickeau 343*04fd306cSNickeau public static function warning(string $message, string $canonical = "support", \Exception $e = null) 344*04fd306cSNickeau { 345*04fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_WARNING, $canonical, $e); 346*04fd306cSNickeau } 347*04fd306cSNickeau 348*04fd306cSNickeau public static function info(string $message, string $canonical = "support", \Exception $e = null) 349*04fd306cSNickeau { 350*04fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_INFO, $canonical, $e); 351*04fd306cSNickeau } 352*04fd306cSNickeau 353*04fd306cSNickeau /** 354*04fd306cSNickeau * @param int $level 355*04fd306cSNickeau * @return void 356*04fd306cSNickeau * @deprecated use {@link SiteConfig::setLogExceptionLevel()} 357*04fd306cSNickeau */ 358*04fd306cSNickeau public static function setTestExceptionLevel(int $level) 359*04fd306cSNickeau { 360*04fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel($level); 361*04fd306cSNickeau } 362*04fd306cSNickeau 363*04fd306cSNickeau public static function setTestExceptionLevelToDefault() 364*04fd306cSNickeau { 365*04fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel(self::LVL_MSG_WARNING); 366*04fd306cSNickeau } 367*04fd306cSNickeau 368*04fd306cSNickeau public static function errorIfDevOrTest($message, $canonical = "support") 369*04fd306cSNickeau { 370*04fd306cSNickeau if (PluginUtility::isDevOrTest()) { 371*04fd306cSNickeau LogUtility::error($message, $canonical); 372*04fd306cSNickeau } 373*04fd306cSNickeau } 374*04fd306cSNickeau 375*04fd306cSNickeau /** 376*04fd306cSNickeau * @return void 377*04fd306cSNickeau * @deprecated use the config object instead 378*04fd306cSNickeau */ 379*04fd306cSNickeau public static function setTestExceptionLevelToError() 380*04fd306cSNickeau { 381*04fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionToError(); 382*04fd306cSNickeau } 383*04fd306cSNickeau 384*04fd306cSNickeau /** 385*04fd306cSNickeau * Advertise an error that should not take place if the code was 386*04fd306cSNickeau * written properly 387*04fd306cSNickeau * @param string $message 388*04fd306cSNickeau * @param string $canonical 389*04fd306cSNickeau * @param Throwable|null $previous 390*04fd306cSNickeau * @return void 391*04fd306cSNickeau */ 392*04fd306cSNickeau public static function internalError(string $message, string $canonical = "support", Throwable $previous = null) 393*04fd306cSNickeau { 394*04fd306cSNickeau $internalErrorMessage = "Sorry. An internal error has occurred"; 395*04fd306cSNickeau if (PluginUtility::isDevOrTest()) { 396*04fd306cSNickeau throw new ExceptionRuntimeInternal("$internalErrorMessage - $message", $canonical, 1, $previous); 397*04fd306cSNickeau } else { 398*04fd306cSNickeau $errorPreviousMessage = ""; 399*04fd306cSNickeau if ($previous !== null) { 400*04fd306cSNickeau $errorPreviousMessage = " Error: {$previous->getMessage()}"; 401*04fd306cSNickeau } 402*04fd306cSNickeau self::error("{$internalErrorMessage}: $message.$errorPreviousMessage", $canonical); 403*04fd306cSNickeau } 404*04fd306cSNickeau } 405*04fd306cSNickeau 406*04fd306cSNickeau /** 407*04fd306cSNickeau * @param string $message 408*04fd306cSNickeau * @param string $canonical 409*04fd306cSNickeau * @param $e 410*04fd306cSNickeau * @return void 411*04fd306cSNickeau * Debug, trace 412*04fd306cSNickeau */ 413*04fd306cSNickeau public static function debug(string $message, string $canonical = self::SUPPORT_CANONICAL, $e = null) 414*04fd306cSNickeau { 415*04fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_DEBUG, $canonical, $e); 416*04fd306cSNickeau } 417*04fd306cSNickeau 418*04fd306cSNickeau public static function infoToPublic(string $html, string $canonical) 419*04fd306cSNickeau { 420*04fd306cSNickeau self::log2FrontEnd($html, LogUtility::LVL_MSG_INFO, $canonical, true); 421*04fd306cSNickeau } 422*04fd306cSNickeau 42337748cd8SNickeau} 424