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*39c00e7eSNicouse dokuwiki\Logger; 1604fd306cSNickeauuse Throwable; 1704fd306cSNickeau 1837748cd8SNickeaurequire_once(__DIR__ . '/PluginUtility.php'); 1937748cd8SNickeau 2037748cd8SNickeauclass LogUtility 2137748cd8SNickeau{ 2237748cd8SNickeau 2337748cd8SNickeau /** 2437748cd8SNickeau * Constant for the function {@link msg()} 2537748cd8SNickeau * -1 = error, 0 = info, 1 = success, 2 = notify 2637748cd8SNickeau * (Not even in order of importance) 2737748cd8SNickeau */ 2804fd306cSNickeau const LVL_MSG_ABOVE_ERROR = 5; // a level to disable the error to thrown in test 2937748cd8SNickeau const LVL_MSG_ERROR = 4; //-1; 3037748cd8SNickeau const LVL_MSG_WARNING = 3; //2; 3137748cd8SNickeau const LVL_MSG_SUCCESS = 2; //1; 3237748cd8SNickeau const LVL_MSG_INFO = 1; //0; 3337748cd8SNickeau const LVL_MSG_DEBUG = 0; //3; 3437748cd8SNickeau 3537748cd8SNickeau 3637748cd8SNickeau /** 3737748cd8SNickeau * Id level to name 3837748cd8SNickeau */ 3937748cd8SNickeau const LVL_NAME = array( 4037748cd8SNickeau 0 => "debug", 4137748cd8SNickeau 1 => "info", 4237748cd8SNickeau 3 => "warning", 4337748cd8SNickeau 2 => "success", 4437748cd8SNickeau 4 => "error" 4537748cd8SNickeau ); 4637748cd8SNickeau 4737748cd8SNickeau /** 4837748cd8SNickeau * Id level to name 4937748cd8SNickeau * {@link msg()} constant 5037748cd8SNickeau */ 5137748cd8SNickeau const LVL_TO_MSG_LEVEL = array( 5237748cd8SNickeau 0 => 3, 5337748cd8SNickeau 1 => 0, 5437748cd8SNickeau 2 => 1, 5537748cd8SNickeau 3 => 2, 5637748cd8SNickeau 4 => -1 5737748cd8SNickeau ); 5837748cd8SNickeau 5937748cd8SNickeau 6037748cd8SNickeau const LOGLEVEL_URI_QUERY_PROPERTY = "loglevel"; 6104fd306cSNickeau const SUPPORT_CANONICAL = "support"; 6204fd306cSNickeau 630e43c1dbSgerardnico /** 640e43c1dbSgerardnico * 650e43c1dbSgerardnico * @var bool 660e43c1dbSgerardnico */ 6704fd306cSNickeau private static bool $throwExceptionOnDevTest = true; 6804fd306cSNickeau /** 6904fd306cSNickeau * @var int 7004fd306cSNickeau */ 7104fd306cSNickeau const DEFAULT_THROW_LEVEL = self::LVL_MSG_WARNING; 7237748cd8SNickeau 7337748cd8SNickeau /** 7437748cd8SNickeau * Send a message to a manager and log it 7537748cd8SNickeau * Fail if in test 7637748cd8SNickeau * @param string $message 7737748cd8SNickeau * @param int $level - the level see LVL constant 7837748cd8SNickeau * @param string $canonical - the canonical 79*39c00e7eSNico * @param \Exception|null $e 8037748cd8SNickeau */ 81*39c00e7eSNico public static function msg(string $message, int $level = self::LVL_MSG_ERROR, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null) 8237748cd8SNickeau { 8337748cd8SNickeau 84d61dea15Sgerardnico try { 85d61dea15Sgerardnico self::messageNotEmpty($message); 8604fd306cSNickeau } catch (ExceptionCompile $e) { 87d61dea15Sgerardnico self::log2file($e->getMessage(), LogUtility::LVL_MSG_ERROR, $canonical); 88e7bbc4b8Sgerardnico } 89e7bbc4b8Sgerardnico 9037748cd8SNickeau /** 9137748cd8SNickeau * Log to frontend 9237748cd8SNickeau */ 93c3437056SNickeau self::log2FrontEnd($message, $level, $canonical); 9437748cd8SNickeau 9537748cd8SNickeau /** 9637748cd8SNickeau * Log level passed for a page (only for file used) 9737748cd8SNickeau * to not allow an attacker to see all errors in frontend 9837748cd8SNickeau */ 9937748cd8SNickeau global $INPUT; 10037748cd8SNickeau $loglevelProp = $INPUT->str(self::LOGLEVEL_URI_QUERY_PROPERTY, null); 10137748cd8SNickeau if (!empty($loglevelProp)) { 10237748cd8SNickeau $level = $loglevelProp; 10337748cd8SNickeau } 1042f4da794Sgerardnico /** 1052f4da794Sgerardnico * TODO: Make it a configuration ? 1062f4da794Sgerardnico */ 1072f4da794Sgerardnico if ($level >= self::LVL_MSG_WARNING) { 10804fd306cSNickeau self::log2file($message, $level, $canonical, $e); 1092f4da794Sgerardnico } 11037748cd8SNickeau 11137748cd8SNickeau /** 11237748cd8SNickeau * If test, we throw an error 11337748cd8SNickeau */ 11404fd306cSNickeau self::throwErrorIfTest($level, $message, $e); 11537748cd8SNickeau } 11637748cd8SNickeau 11737748cd8SNickeau /** 11837748cd8SNickeau * Print log to a file 11937748cd8SNickeau * 12037748cd8SNickeau * Adapted from {@link dbglog} 12137748cd8SNickeau * Note: {@link dbg()} dbg print to the web page 12237748cd8SNickeau * 123e96d9362Sgerardnico * @param null|string $msg - may be null always this is the default if a variable is not initialized. 12437748cd8SNickeau * @param int $logLevel 125*39c00e7eSNico * @param string|null $canonical 126*39c00e7eSNico * @param \Exception|null $e 12737748cd8SNickeau */ 128*39c00e7eSNico static function log2file(?string $msg, int $logLevel = self::LVL_MSG_ERROR, ?string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null) 12937748cd8SNickeau { 13037748cd8SNickeau 131d61dea15Sgerardnico try { 132d61dea15Sgerardnico self::messageNotEmpty($msg); 13304fd306cSNickeau } catch (ExceptionCompile $e) { 134d61dea15Sgerardnico $msg = $e->getMessage(); 135d61dea15Sgerardnico $logLevel = self::LVL_MSG_ERROR; 136d61dea15Sgerardnico } 137d61dea15Sgerardnico 138c3437056SNickeau if (PluginUtility::isTest() || $logLevel >= self::LVL_MSG_WARNING) { 13937748cd8SNickeau 14037748cd8SNickeau $prefix = PluginUtility::$PLUGIN_NAME; 14137748cd8SNickeau if (!empty($canonical)) { 14237748cd8SNickeau $prefix .= ' - ' . $canonical; 14337748cd8SNickeau } 14437748cd8SNickeau $msg = $prefix . ' - ' . $msg; 14537748cd8SNickeau 14637748cd8SNickeau global $INPUT; 14737748cd8SNickeau 148c3437056SNickeau /** 149c3437056SNickeau * Adding page - context information 15004fd306cSNickeau * We are not using {@link MarkupPath::createFromRequestedPage()} 151c3437056SNickeau * because it throws an error message when the environment 152c3437056SNickeau * is not good, creating a recursive call. 153c3437056SNickeau */ 15404fd306cSNickeau $id = $INPUT->str("id"); 15537748cd8SNickeau $sep = " - "; 156*39c00e7eSNico $messageWritten = date('c') . $sep . self::LVL_NAME[$logLevel] . $sep . $msg . $sep . $INPUT->server->str('REMOTE_ADDR') . $sep . $id . "\n"; 157*39c00e7eSNico // dokuwiki does not have the warning level 158*39c00e7eSNico Logger::error($messageWritten); 15904fd306cSNickeau self::throwErrorIfTest($logLevel, $msg, $e); 160c3437056SNickeau 161c3437056SNickeau 16237748cd8SNickeau } 16337748cd8SNickeau 16437748cd8SNickeau } 16537748cd8SNickeau 16637748cd8SNickeau /** 16737748cd8SNickeau * @param $message 16837748cd8SNickeau * @param $level 169d61dea15Sgerardnico * @param string $canonical 170*39c00e7eSNico * @param bool $publicMessage 17137748cd8SNickeau */ 172*39c00e7eSNico public static function log2FrontEnd($message, $level, string $canonical = self::SUPPORT_CANONICAL, bool $publicMessage = false) 17337748cd8SNickeau { 174d61dea15Sgerardnico 175d61dea15Sgerardnico try { 176d61dea15Sgerardnico self::messageNotEmpty($message); 17704fd306cSNickeau } catch (ExceptionCompile $e) { 178d61dea15Sgerardnico $message = $e->getMessage(); 179d61dea15Sgerardnico $level = self::LVL_MSG_ERROR; 180d61dea15Sgerardnico } 181d61dea15Sgerardnico 18237748cd8SNickeau /** 18337748cd8SNickeau * If we are not in the console 18437748cd8SNickeau * and not in test 18537748cd8SNickeau * we test that the message comes in the front end 18637748cd8SNickeau * (example {@link \plugin_combo_frontmatter_test} 18737748cd8SNickeau */ 188c3437056SNickeau $isTerminal = Console::isConsoleRun(); 189c3437056SNickeau if ($isTerminal) { 19037748cd8SNickeau if (!defined('DOKU_UNITTEST')) { 191c3437056SNickeau /** 192c3437056SNickeau * such as {@link cli_plugin_combo} 193c3437056SNickeau */ 194c3437056SNickeau $userAgent = "cli"; 195c3437056SNickeau } else { 196c3437056SNickeau $userAgent = "phpunit"; 19737748cd8SNickeau } 198c3437056SNickeau } else { 199c3437056SNickeau $userAgent = "browser"; 20037748cd8SNickeau } 201c3437056SNickeau 202c3437056SNickeau switch ($userAgent) { 203c3437056SNickeau case "cli": 204c3437056SNickeau echo "$message\n"; 205c3437056SNickeau break; 206c3437056SNickeau case "phpunit": 207c3437056SNickeau case "browser": 208c3437056SNickeau default: 20904fd306cSNickeau if ($canonical !== null) { 21004fd306cSNickeau $label = ucfirst(str_replace(":", " ", $canonical)); 21104fd306cSNickeau $htmlMsg = PluginUtility::getDocumentationHyperLink($canonical, $label, false); 21204fd306cSNickeau } else { 21304fd306cSNickeau $htmlMsg = PluginUtility::getDocumentationHyperLink("", PluginUtility::$PLUGIN_NAME, false); 21437748cd8SNickeau } 21537748cd8SNickeau 21604fd306cSNickeau 21737748cd8SNickeau /** 21837748cd8SNickeau * Adding page - context information 21937748cd8SNickeau * We are not creating the page 22004fd306cSNickeau * direction from {@link MarkupPath::createFromRequestedPage()} 22137748cd8SNickeau * because it throws an error message when the environment 22237748cd8SNickeau * is not good, creating a recursive call. 22337748cd8SNickeau */ 22404fd306cSNickeau global $INPUT; 22504fd306cSNickeau $id = $INPUT->str("id"); 22637748cd8SNickeau if ($id != null) { 227c3437056SNickeau 228c3437056SNickeau /** 229c3437056SNickeau * We don't use any Page object to not 230c3437056SNickeau * create a cycle while building it 231c3437056SNickeau */ 232c3437056SNickeau $url = wl($id, [], true); 233c3437056SNickeau $htmlMsg .= " - <a href=\"$url\">$id</a>"; 234c3437056SNickeau 23537748cd8SNickeau } 23637748cd8SNickeau 23737748cd8SNickeau /** 23837748cd8SNickeau * 23937748cd8SNickeau */ 24037748cd8SNickeau $htmlMsg .= " - " . $message; 24137748cd8SNickeau if ($level > self::LVL_MSG_DEBUG) { 24237748cd8SNickeau $dokuWikiLevel = self::LVL_TO_MSG_LEVEL[$level]; 24304fd306cSNickeau if ($publicMessage) { 24404fd306cSNickeau $allow = MSG_PUBLIC; 24504fd306cSNickeau } else { 24604fd306cSNickeau $allow = MSG_USERS_ONLY; 24704fd306cSNickeau } 24804fd306cSNickeau msg($htmlMsg, $dokuWikiLevel, '', '', $allow); 24937748cd8SNickeau } 25037748cd8SNickeau } 25137748cd8SNickeau } 25237748cd8SNickeau 25337748cd8SNickeau /** 25437748cd8SNickeau * Log a message to the browser console 25537748cd8SNickeau * @param $message 25637748cd8SNickeau */ 25737748cd8SNickeau public static function log2BrowserConsole($message) 25837748cd8SNickeau { 25937748cd8SNickeau // TODO 26037748cd8SNickeau } 261c3437056SNickeau 26204fd306cSNickeau 26304fd306cSNickeau /** 26404fd306cSNickeau * @param $level 26504fd306cSNickeau * @param $message 26604fd306cSNickeau * @param $e - the original exception for chaining 26704fd306cSNickeau * @return void 26804fd306cSNickeau */ 26904fd306cSNickeau private static function throwErrorIfTest($level, $message, \Exception $e = null) 270c3437056SNickeau { 271a535cfe0Sgerardnico if (PluginUtility::isTest() && self::$throwExceptionOnDevTest) { 272a535cfe0Sgerardnico try { 273a535cfe0Sgerardnico $actualLevel = ExecutionContext::getExecutionContext()->getConfig()->getLogExceptionLevel(); 274a535cfe0Sgerardnico } catch (ExceptionNotFound $e) { 275a535cfe0Sgerardnico // In context creation 276a535cfe0Sgerardnico return; 277a535cfe0Sgerardnico } 278a535cfe0Sgerardnico if ($level >= $actualLevel) { 27904fd306cSNickeau throw new LogException($message, $level, $e); 280c3437056SNickeau } 281c3437056SNickeau } 282a535cfe0Sgerardnico } 283d61dea15Sgerardnico 284d61dea15Sgerardnico /** 2854e9ad3c2Sgerardnico * @param string|null $message 28604fd306cSNickeau * @throws ExceptionCompile 287d61dea15Sgerardnico */ 2884e9ad3c2Sgerardnico private static function messageNotEmpty(?string $message) 289d61dea15Sgerardnico { 290d61dea15Sgerardnico $message = trim($message); 2912fea41feSgerardnico if ($message === null || $message === "") { 292d61dea15Sgerardnico $newMessage = "The passed message to the log was empty or null. BackTrace: \n"; 29333a4f219Sgerardnico $newMessage .= LogUtility::getCallStack(); 29404fd306cSNickeau throw new ExceptionCompile($newMessage); 295d61dea15Sgerardnico } 296d61dea15Sgerardnico } 2970e43c1dbSgerardnico 2980e43c1dbSgerardnico public static function disableThrowExceptionOnDevTest() 2990e43c1dbSgerardnico { 3000e43c1dbSgerardnico self::$throwExceptionOnDevTest = false; 3010e43c1dbSgerardnico } 3020e43c1dbSgerardnico 3030e43c1dbSgerardnico public static function enableThrowExceptionOnDevTest() 3040e43c1dbSgerardnico { 3050e43c1dbSgerardnico self::$throwExceptionOnDevTest = true; 3060e43c1dbSgerardnico } 3074cadd4f8SNickeau 3084cadd4f8SNickeau public static function wrapInRedForHtml(string $message): string 3094cadd4f8SNickeau { 31004fd306cSNickeau return "<span class=\"text-danger\">$message</span>"; 3114cadd4f8SNickeau } 31233a4f219Sgerardnico 31333a4f219Sgerardnico /** 31433a4f219Sgerardnico * @return false|string - the actual php call stack (known as backtrace) 31533a4f219Sgerardnico */ 31633a4f219Sgerardnico public static function getCallStack() 31733a4f219Sgerardnico { 31833a4f219Sgerardnico ob_start(); 31933a4f219Sgerardnico $limit = 10; 32033a4f219Sgerardnico /** 32133a4f219Sgerardnico * DEBUG_BACKTRACE_IGNORE_ARGS options to avoid 32233a4f219Sgerardnico * PHP Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1876967424 bytes) 32333a4f219Sgerardnico */ 32433a4f219Sgerardnico debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit); // It prints also the data passed :) 32533a4f219Sgerardnico $trace = ob_get_contents(); 32633a4f219Sgerardnico ob_end_clean(); 32733a4f219Sgerardnico return $trace; 32833a4f219Sgerardnico } 32904fd306cSNickeau 33004fd306cSNickeau /** 33104fd306cSNickeau * @param string $message the message 33204fd306cSNickeau * @param string $canonical the page 33304fd306cSNickeau * @param \Exception|null $e the original exception for trace chaining 33404fd306cSNickeau * @return void 33504fd306cSNickeau */ 33604fd306cSNickeau public static function error(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null) 33704fd306cSNickeau { 33804fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_ERROR, $canonical, $e); 33904fd306cSNickeau } 34004fd306cSNickeau 341*39c00e7eSNico public static function warning(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null) 34204fd306cSNickeau { 34304fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_WARNING, $canonical, $e); 34404fd306cSNickeau } 34504fd306cSNickeau 346*39c00e7eSNico public static function info(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null) 34704fd306cSNickeau { 34804fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_INFO, $canonical, $e); 34904fd306cSNickeau } 35004fd306cSNickeau 35104fd306cSNickeau /** 35204fd306cSNickeau * @param int $level 35304fd306cSNickeau * @return void 35404fd306cSNickeau * @deprecated use {@link SiteConfig::setLogExceptionLevel()} 35504fd306cSNickeau */ 35604fd306cSNickeau public static function setTestExceptionLevel(int $level) 35704fd306cSNickeau { 35804fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel($level); 35904fd306cSNickeau } 36004fd306cSNickeau 36104fd306cSNickeau public static function setTestExceptionLevelToDefault() 36204fd306cSNickeau { 36304fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel(self::LVL_MSG_WARNING); 36404fd306cSNickeau } 36504fd306cSNickeau 36604fd306cSNickeau public static function errorIfDevOrTest($message, $canonical = "support") 36704fd306cSNickeau { 36804fd306cSNickeau if (PluginUtility::isDevOrTest()) { 36904fd306cSNickeau LogUtility::error($message, $canonical); 37004fd306cSNickeau } 37104fd306cSNickeau } 37204fd306cSNickeau 37304fd306cSNickeau /** 37404fd306cSNickeau * @return void 37504fd306cSNickeau * @deprecated use the config object instead 37604fd306cSNickeau */ 37704fd306cSNickeau public static function setTestExceptionLevelToError() 37804fd306cSNickeau { 37904fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionToError(); 38004fd306cSNickeau } 38104fd306cSNickeau 38204fd306cSNickeau /** 38304fd306cSNickeau * Advertise an error that should not take place if the code was 38404fd306cSNickeau * written properly 38504fd306cSNickeau * @param string $message 38604fd306cSNickeau * @param string $canonical 38704fd306cSNickeau * @param Throwable|null $previous 38804fd306cSNickeau * @return void 38904fd306cSNickeau */ 39004fd306cSNickeau public static function internalError(string $message, string $canonical = "support", Throwable $previous = null) 39104fd306cSNickeau { 39204fd306cSNickeau $internalErrorMessage = "Sorry. An internal error has occurred"; 39304fd306cSNickeau if (PluginUtility::isDevOrTest()) { 39404fd306cSNickeau throw new ExceptionRuntimeInternal("$internalErrorMessage - $message", $canonical, 1, $previous); 39504fd306cSNickeau } else { 39604fd306cSNickeau $errorPreviousMessage = ""; 39704fd306cSNickeau if ($previous !== null) { 39804fd306cSNickeau $errorPreviousMessage = " Error: {$previous->getMessage()}"; 39904fd306cSNickeau } 40004fd306cSNickeau self::error("{$internalErrorMessage}: $message.$errorPreviousMessage", $canonical); 40104fd306cSNickeau } 40204fd306cSNickeau } 40304fd306cSNickeau 40404fd306cSNickeau /** 40504fd306cSNickeau * @param string $message 40604fd306cSNickeau * @param string $canonical 40704fd306cSNickeau * @param $e 40804fd306cSNickeau * @return void 40904fd306cSNickeau * Debug, trace 41004fd306cSNickeau */ 41104fd306cSNickeau public static function debug(string $message, string $canonical = self::SUPPORT_CANONICAL, $e = null) 41204fd306cSNickeau { 41304fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_DEBUG, $canonical, $e); 41404fd306cSNickeau } 41504fd306cSNickeau 41604fd306cSNickeau public static function infoToPublic(string $html, string $canonical) 41704fd306cSNickeau { 41804fd306cSNickeau self::log2FrontEnd($html, LogUtility::LVL_MSG_INFO, $canonical, true); 41904fd306cSNickeau } 42004fd306cSNickeau 42137748cd8SNickeau} 422