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 1539c00e7eSNicouse 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 7939c00e7eSNico * @param \Exception|null $e 8037748cd8SNickeau */ 8139c00e7eSNico 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 12539c00e7eSNico * @param string|null $canonical 12639c00e7eSNico * @param \Exception|null $e 12737748cd8SNickeau */ 12839c00e7eSNico 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"); 155*462bbef8SNico $messageWritten = self::LVL_NAME[$logLevel] . " - $msg - (Page: $id, IP: {$INPUT->server->str('REMOTE_ADDR')})\n"; 15639c00e7eSNico // dokuwiki does not have the warning level 15739c00e7eSNico Logger::error($messageWritten); 15804fd306cSNickeau self::throwErrorIfTest($logLevel, $msg, $e); 159c3437056SNickeau 16037748cd8SNickeau } 16137748cd8SNickeau 16237748cd8SNickeau } 16337748cd8SNickeau 16437748cd8SNickeau /** 16537748cd8SNickeau * @param $message 16637748cd8SNickeau * @param $level 167d61dea15Sgerardnico * @param string $canonical 16839c00e7eSNico * @param bool $publicMessage 16937748cd8SNickeau */ 17039c00e7eSNico public static function log2FrontEnd($message, $level, string $canonical = self::SUPPORT_CANONICAL, bool $publicMessage = false) 17137748cd8SNickeau { 172d61dea15Sgerardnico 173d61dea15Sgerardnico try { 174d61dea15Sgerardnico self::messageNotEmpty($message); 17504fd306cSNickeau } catch (ExceptionCompile $e) { 176d61dea15Sgerardnico $message = $e->getMessage(); 177d61dea15Sgerardnico $level = self::LVL_MSG_ERROR; 178d61dea15Sgerardnico } 179d61dea15Sgerardnico 18037748cd8SNickeau /** 18137748cd8SNickeau * If we are not in the console 18237748cd8SNickeau * and not in test 18337748cd8SNickeau * we test that the message comes in the front end 18437748cd8SNickeau * (example {@link \plugin_combo_frontmatter_test} 18537748cd8SNickeau */ 186c3437056SNickeau $isTerminal = Console::isConsoleRun(); 187c3437056SNickeau if ($isTerminal) { 18837748cd8SNickeau if (!defined('DOKU_UNITTEST')) { 189c3437056SNickeau /** 190c3437056SNickeau * such as {@link cli_plugin_combo} 191c3437056SNickeau */ 192c3437056SNickeau $userAgent = "cli"; 193c3437056SNickeau } else { 194c3437056SNickeau $userAgent = "phpunit"; 19537748cd8SNickeau } 196c3437056SNickeau } else { 197c3437056SNickeau $userAgent = "browser"; 19837748cd8SNickeau } 199c3437056SNickeau 200c3437056SNickeau switch ($userAgent) { 201c3437056SNickeau case "cli": 202c3437056SNickeau echo "$message\n"; 203c3437056SNickeau break; 204c3437056SNickeau case "phpunit": 205c3437056SNickeau case "browser": 206c3437056SNickeau default: 20704fd306cSNickeau if ($canonical !== null) { 20804fd306cSNickeau $label = ucfirst(str_replace(":", " ", $canonical)); 20904fd306cSNickeau $htmlMsg = PluginUtility::getDocumentationHyperLink($canonical, $label, false); 21004fd306cSNickeau } else { 21104fd306cSNickeau $htmlMsg = PluginUtility::getDocumentationHyperLink("", PluginUtility::$PLUGIN_NAME, false); 21237748cd8SNickeau } 21337748cd8SNickeau 21404fd306cSNickeau 21537748cd8SNickeau /** 21637748cd8SNickeau * Adding page - context information 21737748cd8SNickeau * We are not creating the page 21804fd306cSNickeau * direction from {@link MarkupPath::createFromRequestedPage()} 21937748cd8SNickeau * because it throws an error message when the environment 22037748cd8SNickeau * is not good, creating a recursive call. 22137748cd8SNickeau */ 22204fd306cSNickeau global $INPUT; 22304fd306cSNickeau $id = $INPUT->str("id"); 22437748cd8SNickeau if ($id != null) { 225c3437056SNickeau 226c3437056SNickeau /** 227c3437056SNickeau * We don't use any Page object to not 228c3437056SNickeau * create a cycle while building it 229c3437056SNickeau */ 230c3437056SNickeau $url = wl($id, [], true); 231c3437056SNickeau $htmlMsg .= " - <a href=\"$url\">$id</a>"; 232c3437056SNickeau 23337748cd8SNickeau } 23437748cd8SNickeau 23537748cd8SNickeau /** 23637748cd8SNickeau * 23737748cd8SNickeau */ 23837748cd8SNickeau $htmlMsg .= " - " . $message; 23937748cd8SNickeau if ($level > self::LVL_MSG_DEBUG) { 24037748cd8SNickeau $dokuWikiLevel = self::LVL_TO_MSG_LEVEL[$level]; 24104fd306cSNickeau if ($publicMessage) { 24204fd306cSNickeau $allow = MSG_PUBLIC; 24304fd306cSNickeau } else { 24404fd306cSNickeau $allow = MSG_USERS_ONLY; 24504fd306cSNickeau } 24604fd306cSNickeau msg($htmlMsg, $dokuWikiLevel, '', '', $allow); 24737748cd8SNickeau } 24837748cd8SNickeau } 24937748cd8SNickeau } 25037748cd8SNickeau 25137748cd8SNickeau /** 25237748cd8SNickeau * Log a message to the browser console 25337748cd8SNickeau * @param $message 25437748cd8SNickeau */ 25537748cd8SNickeau public static function log2BrowserConsole($message) 25637748cd8SNickeau { 25737748cd8SNickeau // TODO 25837748cd8SNickeau } 259c3437056SNickeau 26004fd306cSNickeau 26104fd306cSNickeau /** 26204fd306cSNickeau * @param $level 26304fd306cSNickeau * @param $message 26404fd306cSNickeau * @param $e - the original exception for chaining 26504fd306cSNickeau * @return void 26604fd306cSNickeau */ 26704fd306cSNickeau private static function throwErrorIfTest($level, $message, \Exception $e = null) 268c3437056SNickeau { 269a535cfe0Sgerardnico if (PluginUtility::isTest() && self::$throwExceptionOnDevTest) { 270a535cfe0Sgerardnico try { 271a535cfe0Sgerardnico $actualLevel = ExecutionContext::getExecutionContext()->getConfig()->getLogExceptionLevel(); 272a535cfe0Sgerardnico } catch (ExceptionNotFound $e) { 273a535cfe0Sgerardnico // In context creation 274a535cfe0Sgerardnico return; 275a535cfe0Sgerardnico } 276a535cfe0Sgerardnico if ($level >= $actualLevel) { 27704fd306cSNickeau throw new LogException($message, $level, $e); 278c3437056SNickeau } 279c3437056SNickeau } 280a535cfe0Sgerardnico } 281d61dea15Sgerardnico 282d61dea15Sgerardnico /** 2834e9ad3c2Sgerardnico * @param string|null $message 28404fd306cSNickeau * @throws ExceptionCompile 285d61dea15Sgerardnico */ 2864e9ad3c2Sgerardnico private static function messageNotEmpty(?string $message) 287d61dea15Sgerardnico { 288d61dea15Sgerardnico $message = trim($message); 2892fea41feSgerardnico if ($message === null || $message === "") { 290d61dea15Sgerardnico $newMessage = "The passed message to the log was empty or null. BackTrace: \n"; 29133a4f219Sgerardnico $newMessage .= LogUtility::getCallStack(); 29204fd306cSNickeau throw new ExceptionCompile($newMessage); 293d61dea15Sgerardnico } 294d61dea15Sgerardnico } 2950e43c1dbSgerardnico 2960e43c1dbSgerardnico public static function disableThrowExceptionOnDevTest() 2970e43c1dbSgerardnico { 2980e43c1dbSgerardnico self::$throwExceptionOnDevTest = false; 2990e43c1dbSgerardnico } 3000e43c1dbSgerardnico 3010e43c1dbSgerardnico public static function enableThrowExceptionOnDevTest() 3020e43c1dbSgerardnico { 3030e43c1dbSgerardnico self::$throwExceptionOnDevTest = true; 3040e43c1dbSgerardnico } 3054cadd4f8SNickeau 3064cadd4f8SNickeau public static function wrapInRedForHtml(string $message): string 3074cadd4f8SNickeau { 30804fd306cSNickeau return "<span class=\"text-danger\">$message</span>"; 3094cadd4f8SNickeau } 31033a4f219Sgerardnico 31133a4f219Sgerardnico /** 31233a4f219Sgerardnico * @return false|string - the actual php call stack (known as backtrace) 31333a4f219Sgerardnico */ 31433a4f219Sgerardnico public static function getCallStack() 31533a4f219Sgerardnico { 31633a4f219Sgerardnico ob_start(); 31733a4f219Sgerardnico $limit = 10; 31833a4f219Sgerardnico /** 31933a4f219Sgerardnico * DEBUG_BACKTRACE_IGNORE_ARGS options to avoid 32033a4f219Sgerardnico * PHP Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1876967424 bytes) 32133a4f219Sgerardnico */ 32233a4f219Sgerardnico debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit); // It prints also the data passed :) 32333a4f219Sgerardnico $trace = ob_get_contents(); 32433a4f219Sgerardnico ob_end_clean(); 32533a4f219Sgerardnico return $trace; 32633a4f219Sgerardnico } 32704fd306cSNickeau 32804fd306cSNickeau /** 32904fd306cSNickeau * @param string $message the message 33004fd306cSNickeau * @param string $canonical the page 33104fd306cSNickeau * @param \Exception|null $e the original exception for trace chaining 33204fd306cSNickeau * @return void 33304fd306cSNickeau */ 33404fd306cSNickeau public static function error(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null) 33504fd306cSNickeau { 33604fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_ERROR, $canonical, $e); 33704fd306cSNickeau } 33804fd306cSNickeau 33939c00e7eSNico public static function warning(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null) 34004fd306cSNickeau { 34104fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_WARNING, $canonical, $e); 34204fd306cSNickeau } 34304fd306cSNickeau 34439c00e7eSNico public static function info(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null) 34504fd306cSNickeau { 34604fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_INFO, $canonical, $e); 34704fd306cSNickeau } 34804fd306cSNickeau 34904fd306cSNickeau /** 35004fd306cSNickeau * @param int $level 35104fd306cSNickeau * @return void 35204fd306cSNickeau * @deprecated use {@link SiteConfig::setLogExceptionLevel()} 35304fd306cSNickeau */ 35404fd306cSNickeau public static function setTestExceptionLevel(int $level) 35504fd306cSNickeau { 35604fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel($level); 35704fd306cSNickeau } 35804fd306cSNickeau 35904fd306cSNickeau public static function setTestExceptionLevelToDefault() 36004fd306cSNickeau { 36104fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel(self::LVL_MSG_WARNING); 36204fd306cSNickeau } 36304fd306cSNickeau 36404fd306cSNickeau public static function errorIfDevOrTest($message, $canonical = "support") 36504fd306cSNickeau { 36604fd306cSNickeau if (PluginUtility::isDevOrTest()) { 36704fd306cSNickeau LogUtility::error($message, $canonical); 36804fd306cSNickeau } 36904fd306cSNickeau } 37004fd306cSNickeau 37104fd306cSNickeau /** 37204fd306cSNickeau * @return void 37304fd306cSNickeau * @deprecated use the config object instead 37404fd306cSNickeau */ 37504fd306cSNickeau public static function setTestExceptionLevelToError() 37604fd306cSNickeau { 37704fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionToError(); 37804fd306cSNickeau } 37904fd306cSNickeau 38004fd306cSNickeau /** 38104fd306cSNickeau * Advertise an error that should not take place if the code was 38204fd306cSNickeau * written properly 38304fd306cSNickeau * @param string $message 38404fd306cSNickeau * @param string $canonical 38504fd306cSNickeau * @param Throwable|null $previous 38604fd306cSNickeau * @return void 38704fd306cSNickeau */ 38804fd306cSNickeau public static function internalError(string $message, string $canonical = "support", Throwable $previous = null) 38904fd306cSNickeau { 39004fd306cSNickeau $internalErrorMessage = "Sorry. An internal error has occurred"; 39104fd306cSNickeau if (PluginUtility::isDevOrTest()) { 39204fd306cSNickeau throw new ExceptionRuntimeInternal("$internalErrorMessage - $message", $canonical, 1, $previous); 39304fd306cSNickeau } else { 39404fd306cSNickeau $errorPreviousMessage = ""; 39504fd306cSNickeau if ($previous !== null) { 39604fd306cSNickeau $errorPreviousMessage = " Error: {$previous->getMessage()}"; 39704fd306cSNickeau } 39804fd306cSNickeau self::error("{$internalErrorMessage}: $message.$errorPreviousMessage", $canonical); 39904fd306cSNickeau } 40004fd306cSNickeau } 40104fd306cSNickeau 40204fd306cSNickeau /** 40304fd306cSNickeau * @param string $message 40404fd306cSNickeau * @param string $canonical 40504fd306cSNickeau * @param $e 40604fd306cSNickeau * @return void 40704fd306cSNickeau * Debug, trace 40804fd306cSNickeau */ 40904fd306cSNickeau public static function debug(string $message, string $canonical = self::SUPPORT_CANONICAL, $e = null) 41004fd306cSNickeau { 41104fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_DEBUG, $canonical, $e); 41204fd306cSNickeau } 41304fd306cSNickeau 41404fd306cSNickeau public static function infoToPublic(string $html, string $canonical) 41504fd306cSNickeau { 41604fd306cSNickeau self::log2FrontEnd($html, LogUtility::LVL_MSG_INFO, $canonical, true); 41704fd306cSNickeau } 41804fd306cSNickeau 41937748cd8SNickeau} 420