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 1504fd306cSNickeauuse Throwable; 1604fd306cSNickeau 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 */ 2704fd306cSNickeau 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"; 6004fd306cSNickeau const SUPPORT_CANONICAL = "support"; 6104fd306cSNickeau 620e43c1dbSgerardnico /** 630e43c1dbSgerardnico * 640e43c1dbSgerardnico * @var bool 650e43c1dbSgerardnico */ 6604fd306cSNickeau private static bool $throwExceptionOnDevTest = true; 6704fd306cSNickeau /** 6804fd306cSNickeau * @var int 6904fd306cSNickeau */ 7004fd306cSNickeau 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 */ 7904fd306cSNickeau 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); 8404fd306cSNickeau } 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) { 10604fd306cSNickeau self::log2file($message, $level, $canonical, $e); 1072f4da794Sgerardnico } 10837748cd8SNickeau 10937748cd8SNickeau /** 11037748cd8SNickeau * If test, we throw an error 11137748cd8SNickeau */ 11204fd306cSNickeau 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 12404fd306cSNickeau * @param \Exception $e 12537748cd8SNickeau */ 12604fd306cSNickeau static function log2file(?string $msg, int $logLevel = self::LVL_MSG_ERROR, $canonical = null, \Exception $e = null) 12737748cd8SNickeau { 12837748cd8SNickeau 129d61dea15Sgerardnico try { 130d61dea15Sgerardnico self::messageNotEmpty($msg); 13104fd306cSNickeau } 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 14904fd306cSNickeau * 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 */ 15304fd306cSNickeau $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 16404fd306cSNickeau 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 */ 17704fd306cSNickeau public static function log2FrontEnd($message, $level, $canonical = "support", bool $publicMessage = false) 17837748cd8SNickeau { 179d61dea15Sgerardnico 180d61dea15Sgerardnico try { 181d61dea15Sgerardnico self::messageNotEmpty($message); 18204fd306cSNickeau } 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: 21404fd306cSNickeau if ($canonical !== null) { 21504fd306cSNickeau $label = ucfirst(str_replace(":", " ", $canonical)); 21604fd306cSNickeau $htmlMsg = PluginUtility::getDocumentationHyperLink($canonical, $label, false); 21704fd306cSNickeau } else { 21804fd306cSNickeau $htmlMsg = PluginUtility::getDocumentationHyperLink("", PluginUtility::$PLUGIN_NAME, false); 21937748cd8SNickeau } 22037748cd8SNickeau 22104fd306cSNickeau 22237748cd8SNickeau /** 22337748cd8SNickeau * Adding page - context information 22437748cd8SNickeau * We are not creating the page 22504fd306cSNickeau * direction from {@link MarkupPath::createFromRequestedPage()} 22637748cd8SNickeau * because it throws an error message when the environment 22737748cd8SNickeau * is not good, creating a recursive call. 22837748cd8SNickeau */ 22904fd306cSNickeau global $INPUT; 23004fd306cSNickeau $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]; 24804fd306cSNickeau if ($publicMessage) { 24904fd306cSNickeau $allow = MSG_PUBLIC; 25004fd306cSNickeau } else { 25104fd306cSNickeau $allow = MSG_USERS_ONLY; 25204fd306cSNickeau } 25304fd306cSNickeau 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 26704fd306cSNickeau 26804fd306cSNickeau /** 26904fd306cSNickeau * @param $level 27004fd306cSNickeau * @param $message 27104fd306cSNickeau * @param $e - the original exception for chaining 27204fd306cSNickeau * @return void 27304fd306cSNickeau */ 27404fd306cSNickeau private static function throwErrorIfTest($level, $message, \Exception $e = null) 275c3437056SNickeau { 276*a535cfe0Sgerardnico if (PluginUtility::isTest() && self::$throwExceptionOnDevTest) { 277*a535cfe0Sgerardnico try { 278*a535cfe0Sgerardnico $actualLevel = ExecutionContext::getExecutionContext()->getConfig()->getLogExceptionLevel(); 279*a535cfe0Sgerardnico } catch (ExceptionNotFound $e) { 280*a535cfe0Sgerardnico // In context creation 281*a535cfe0Sgerardnico return; 282*a535cfe0Sgerardnico } 283*a535cfe0Sgerardnico if ($level >= $actualLevel) { 28404fd306cSNickeau throw new LogException($message, $level, $e); 285c3437056SNickeau } 286c3437056SNickeau } 287*a535cfe0Sgerardnico } 288d61dea15Sgerardnico 289d61dea15Sgerardnico /** 2904e9ad3c2Sgerardnico * @param string|null $message 29104fd306cSNickeau * @throws ExceptionCompile 292d61dea15Sgerardnico */ 2934e9ad3c2Sgerardnico private static function messageNotEmpty(?string $message) 294d61dea15Sgerardnico { 295d61dea15Sgerardnico $message = trim($message); 2962fea41feSgerardnico if ($message === null || $message === "") { 297d61dea15Sgerardnico $newMessage = "The passed message to the log was empty or null. BackTrace: \n"; 29833a4f219Sgerardnico $newMessage .= LogUtility::getCallStack(); 29904fd306cSNickeau throw new ExceptionCompile($newMessage); 300d61dea15Sgerardnico } 301d61dea15Sgerardnico } 3020e43c1dbSgerardnico 3030e43c1dbSgerardnico public static function disableThrowExceptionOnDevTest() 3040e43c1dbSgerardnico { 3050e43c1dbSgerardnico self::$throwExceptionOnDevTest = false; 3060e43c1dbSgerardnico } 3070e43c1dbSgerardnico 3080e43c1dbSgerardnico public static function enableThrowExceptionOnDevTest() 3090e43c1dbSgerardnico { 3100e43c1dbSgerardnico self::$throwExceptionOnDevTest = true; 3110e43c1dbSgerardnico } 3124cadd4f8SNickeau 3134cadd4f8SNickeau public static function wrapInRedForHtml(string $message): string 3144cadd4f8SNickeau { 31504fd306cSNickeau return "<span class=\"text-danger\">$message</span>"; 3164cadd4f8SNickeau } 31733a4f219Sgerardnico 31833a4f219Sgerardnico /** 31933a4f219Sgerardnico * @return false|string - the actual php call stack (known as backtrace) 32033a4f219Sgerardnico */ 32133a4f219Sgerardnico public static function getCallStack() 32233a4f219Sgerardnico { 32333a4f219Sgerardnico ob_start(); 32433a4f219Sgerardnico $limit = 10; 32533a4f219Sgerardnico /** 32633a4f219Sgerardnico * DEBUG_BACKTRACE_IGNORE_ARGS options to avoid 32733a4f219Sgerardnico * PHP Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1876967424 bytes) 32833a4f219Sgerardnico */ 32933a4f219Sgerardnico debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit); // It prints also the data passed :) 33033a4f219Sgerardnico $trace = ob_get_contents(); 33133a4f219Sgerardnico ob_end_clean(); 33233a4f219Sgerardnico return $trace; 33333a4f219Sgerardnico } 33404fd306cSNickeau 33504fd306cSNickeau /** 33604fd306cSNickeau * @param string $message the message 33704fd306cSNickeau * @param string $canonical the page 33804fd306cSNickeau * @param \Exception|null $e the original exception for trace chaining 33904fd306cSNickeau * @return void 34004fd306cSNickeau */ 34104fd306cSNickeau public static function error(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null) 34204fd306cSNickeau { 34304fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_ERROR, $canonical, $e); 34404fd306cSNickeau } 34504fd306cSNickeau 34604fd306cSNickeau public static function warning(string $message, string $canonical = "support", \Exception $e = null) 34704fd306cSNickeau { 34804fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_WARNING, $canonical, $e); 34904fd306cSNickeau } 35004fd306cSNickeau 35104fd306cSNickeau public static function info(string $message, string $canonical = "support", \Exception $e = null) 35204fd306cSNickeau { 35304fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_INFO, $canonical, $e); 35404fd306cSNickeau } 35504fd306cSNickeau 35604fd306cSNickeau /** 35704fd306cSNickeau * @param int $level 35804fd306cSNickeau * @return void 35904fd306cSNickeau * @deprecated use {@link SiteConfig::setLogExceptionLevel()} 36004fd306cSNickeau */ 36104fd306cSNickeau public static function setTestExceptionLevel(int $level) 36204fd306cSNickeau { 36304fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel($level); 36404fd306cSNickeau } 36504fd306cSNickeau 36604fd306cSNickeau public static function setTestExceptionLevelToDefault() 36704fd306cSNickeau { 36804fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel(self::LVL_MSG_WARNING); 36904fd306cSNickeau } 37004fd306cSNickeau 37104fd306cSNickeau public static function errorIfDevOrTest($message, $canonical = "support") 37204fd306cSNickeau { 37304fd306cSNickeau if (PluginUtility::isDevOrTest()) { 37404fd306cSNickeau LogUtility::error($message, $canonical); 37504fd306cSNickeau } 37604fd306cSNickeau } 37704fd306cSNickeau 37804fd306cSNickeau /** 37904fd306cSNickeau * @return void 38004fd306cSNickeau * @deprecated use the config object instead 38104fd306cSNickeau */ 38204fd306cSNickeau public static function setTestExceptionLevelToError() 38304fd306cSNickeau { 38404fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionToError(); 38504fd306cSNickeau } 38604fd306cSNickeau 38704fd306cSNickeau /** 38804fd306cSNickeau * Advertise an error that should not take place if the code was 38904fd306cSNickeau * written properly 39004fd306cSNickeau * @param string $message 39104fd306cSNickeau * @param string $canonical 39204fd306cSNickeau * @param Throwable|null $previous 39304fd306cSNickeau * @return void 39404fd306cSNickeau */ 39504fd306cSNickeau public static function internalError(string $message, string $canonical = "support", Throwable $previous = null) 39604fd306cSNickeau { 39704fd306cSNickeau $internalErrorMessage = "Sorry. An internal error has occurred"; 39804fd306cSNickeau if (PluginUtility::isDevOrTest()) { 39904fd306cSNickeau throw new ExceptionRuntimeInternal("$internalErrorMessage - $message", $canonical, 1, $previous); 40004fd306cSNickeau } else { 40104fd306cSNickeau $errorPreviousMessage = ""; 40204fd306cSNickeau if ($previous !== null) { 40304fd306cSNickeau $errorPreviousMessage = " Error: {$previous->getMessage()}"; 40404fd306cSNickeau } 40504fd306cSNickeau self::error("{$internalErrorMessage}: $message.$errorPreviousMessage", $canonical); 40604fd306cSNickeau } 40704fd306cSNickeau } 40804fd306cSNickeau 40904fd306cSNickeau /** 41004fd306cSNickeau * @param string $message 41104fd306cSNickeau * @param string $canonical 41204fd306cSNickeau * @param $e 41304fd306cSNickeau * @return void 41404fd306cSNickeau * Debug, trace 41504fd306cSNickeau */ 41604fd306cSNickeau public static function debug(string $message, string $canonical = self::SUPPORT_CANONICAL, $e = null) 41704fd306cSNickeau { 41804fd306cSNickeau self::msg($message, LogUtility::LVL_MSG_DEBUG, $canonical, $e); 41904fd306cSNickeau } 42004fd306cSNickeau 42104fd306cSNickeau public static function infoToPublic(string $html, string $canonical) 42204fd306cSNickeau { 42304fd306cSNickeau self::log2FrontEnd($html, LogUtility::LVL_MSG_INFO, $canonical, true); 42404fd306cSNickeau } 42504fd306cSNickeau 42637748cd8SNickeau} 427