1<?php 2/** 3 * Information and debugging functions 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8if(!defined('DOKU_INC')) die('meh.'); 9 10if(!defined('DOKU_MESSAGEURL')){ 11 if(in_array('ssl', stream_get_transports())) { 12 define('DOKU_MESSAGEURL','https://update.dokuwiki.org/check/'); 13 }else{ 14 define('DOKU_MESSAGEURL','http://update.dokuwiki.org/check/'); 15 } 16} 17 18/** 19 * Check for new messages from upstream 20 * 21 * @author Andreas Gohr <andi@splitbrain.org> 22 */ 23function checkUpdateMessages(){ 24 global $conf; 25 global $INFO; 26 global $updateVersion; 27 if(!$conf['updatecheck']) return; 28 if($conf['useacl'] && !$INFO['ismanager']) return; 29 30 $cf = getCacheName($updateVersion, '.updmsg'); 31 $lm = @filemtime($cf); 32 $is_http = substr(DOKU_MESSAGEURL, 0, 5) != 'https'; 33 34 // check if new messages needs to be fetched 35 if($lm < time()-(60*60*24) || $lm < @filemtime(DOKU_INC.DOKU_SCRIPT)){ 36 @touch($cf); 37 dbglog("checkUpdateMessages(): downloading messages to ".$cf.($is_http?' (without SSL)':' (with SSL)')); 38 $http = new DokuHTTPClient(); 39 $http->timeout = 12; 40 $resp = $http->get(DOKU_MESSAGEURL.$updateVersion); 41 if(is_string($resp) && ($resp == "" || substr(trim($resp), -1) == '%')) { 42 // basic sanity check that this is either an empty string response (ie "no messages") 43 // or it looks like one of our messages, not WiFi login or other interposed response 44 io_saveFile($cf,$resp); 45 } else { 46 dbglog("checkUpdateMessages(): unexpected HTTP response received"); 47 } 48 }else{ 49 dbglog("checkUpdateMessages(): messages up to date"); 50 } 51 52 $data = io_readFile($cf); 53 // show messages through the usual message mechanism 54 $msgs = explode("\n%\n",$data); 55 foreach($msgs as $msg){ 56 if($msg) msg($msg,2); 57 } 58} 59 60 61/** 62 * Return DokuWiki's version (split up in date and type) 63 * 64 * @author Andreas Gohr <andi@splitbrain.org> 65 */ 66function getVersionData(){ 67 $version = array(); 68 //import version string 69 if(file_exists(DOKU_INC.'VERSION')){ 70 //official release 71 $version['date'] = trim(io_readFile(DOKU_INC.'VERSION')); 72 $version['type'] = 'Release'; 73 }elseif(is_dir(DOKU_INC.'.git')){ 74 $version['type'] = 'Git'; 75 $version['date'] = 'unknown'; 76 77 if ($date = shell_exec("git log -1 --pretty=format:'%cd' --date=short")) { 78 $version['date'] = hsc($date); 79 } 80 }else{ 81 global $updateVersion; 82 $version['date'] = 'update version '.$updateVersion; 83 $version['type'] = 'snapshot?'; 84 } 85 return $version; 86} 87 88/** 89 * Return DokuWiki's version (as a string) 90 * 91 * @author Anika Henke <anika@selfthinker.org> 92 */ 93function getVersion(){ 94 $version = getVersionData(); 95 return $version['type'].' '.$version['date']; 96} 97 98/** 99 * Run a few sanity checks 100 * 101 * @author Andreas Gohr <andi@splitbrain.org> 102 */ 103function check(){ 104 global $conf; 105 global $INFO; 106 /* @var Input $INPUT */ 107 global $INPUT; 108 109 if ($INFO['isadmin'] || $INFO['ismanager']){ 110 msg('DokuWiki version: '.getVersion(),1); 111 112 if(version_compare(phpversion(),'5.6.0','<')){ 113 msg('Your PHP version is too old ('.phpversion().' vs. 5.6.0+ needed)',-1); 114 }else{ 115 msg('PHP version '.phpversion(),1); 116 } 117 } else { 118 if(version_compare(phpversion(),'5.6.0','<')){ 119 msg('Your PHP version is too old',-1); 120 } 121 } 122 123 $mem = (int) php_to_byte(ini_get('memory_limit')); 124 if($mem){ 125 if($mem < 16777216){ 126 msg('PHP is limited to less than 16MB RAM ('.$mem.' bytes). Increase memory_limit in php.ini',-1); 127 }elseif($mem < 20971520){ 128 msg('PHP is limited to less than 20MB RAM ('.$mem.' bytes), you might encounter problems with bigger pages. Increase memory_limit in php.ini',-1); 129 }elseif($mem < 33554432){ 130 msg('PHP is limited to less than 32MB RAM ('.$mem.' bytes), but that should be enough in most cases. If not, increase memory_limit in php.ini',0); 131 }else{ 132 msg('More than 32MB RAM ('.$mem.' bytes) available.',1); 133 } 134 } 135 136 if(is_writable($conf['changelog'])){ 137 msg('Changelog is writable',1); 138 }else{ 139 if (file_exists($conf['changelog'])) { 140 msg('Changelog is not writable',-1); 141 } 142 } 143 144 if (isset($conf['changelog_old']) && file_exists($conf['changelog_old'])) { 145 msg('Old changelog exists', 0); 146 } 147 148 if (file_exists($conf['changelog'].'_failed')) { 149 msg('Importing old changelog failed', -1); 150 } else if (file_exists($conf['changelog'].'_importing')) { 151 msg('Importing old changelog now.', 0); 152 } else if (file_exists($conf['changelog'].'_import_ok')) { 153 msg('Old changelog imported', 1); 154 if (!plugin_isdisabled('importoldchangelog')) { 155 msg('Importoldchangelog plugin not disabled after import', -1); 156 } 157 } 158 159 if(is_writable(DOKU_CONF)){ 160 msg('conf directory is writable',1); 161 }else{ 162 msg('conf directory is not writable',-1); 163 } 164 165 if($conf['authtype'] == 'plain'){ 166 global $config_cascade; 167 if(is_writable($config_cascade['plainauth.users']['default'])){ 168 msg('conf/users.auth.php is writable',1); 169 }else{ 170 msg('conf/users.auth.php is not writable',0); 171 } 172 } 173 174 if(function_exists('mb_strpos')){ 175 if(defined('UTF8_NOMBSTRING')){ 176 msg('mb_string extension is available but will not be used',0); 177 }else{ 178 msg('mb_string extension is available and will be used',1); 179 if(ini_get('mbstring.func_overload') != 0){ 180 msg('mb_string function overloading is enabled, this will cause problems and should be disabled',-1); 181 } 182 } 183 }else{ 184 msg('mb_string extension not available - PHP only replacements will be used',0); 185 } 186 187 if (!UTF8_PREGSUPPORT) { 188 msg('PHP is missing UTF-8 support in Perl-Compatible Regular Expressions (PCRE)', -1); 189 } 190 if (!UTF8_PROPERTYSUPPORT) { 191 msg('PHP is missing Unicode properties support in Perl-Compatible Regular Expressions (PCRE)', -1); 192 } 193 194 $loc = setlocale(LC_ALL, 0); 195 if(!$loc){ 196 msg('No valid locale is set for your PHP setup. You should fix this',-1); 197 }elseif(stripos($loc,'utf') === false){ 198 msg('Your locale <code>'.hsc($loc).'</code> seems not to be a UTF-8 locale, you should fix this if you encounter problems.',0); 199 }else{ 200 msg('Valid locale '.hsc($loc).' found.', 1); 201 } 202 203 if($conf['allowdebug']){ 204 msg('Debugging support is enabled. If you don\'t need it you should set $conf[\'allowdebug\'] = 0',-1); 205 }else{ 206 msg('Debugging support is disabled',1); 207 } 208 209 if($INFO['userinfo']['name']){ 210 msg('You are currently logged in as '.$INPUT->server->str('REMOTE_USER').' ('.$INFO['userinfo']['name'].')',0); 211 msg('You are part of the groups '.join($INFO['userinfo']['grps'],', '),0); 212 }else{ 213 msg('You are currently not logged in',0); 214 } 215 216 msg('Your current permission for this page is '.$INFO['perm'],0); 217 218 if(is_writable($INFO['filepath'])){ 219 msg('The current page is writable by the webserver',0); 220 }else{ 221 msg('The current page is not writable by the webserver',0); 222 } 223 224 if($INFO['writable']){ 225 msg('The current page is writable by you',0); 226 }else{ 227 msg('The current page is not writable by you',0); 228 } 229 230 // Check for corrupted search index 231 $lengths = idx_listIndexLengths(); 232 $index_corrupted = false; 233 foreach ($lengths as $length) { 234 if (count(idx_getIndex('w', $length)) != count(idx_getIndex('i', $length))) { 235 $index_corrupted = true; 236 break; 237 } 238 } 239 240 foreach (idx_getIndex('metadata', '') as $index) { 241 if (count(idx_getIndex($index.'_w', '')) != count(idx_getIndex($index.'_i', ''))) { 242 $index_corrupted = true; 243 break; 244 } 245 } 246 247 if($index_corrupted) { 248 msg( 249 'The search index is corrupted. It might produce wrong results and most 250 probably needs to be rebuilt. See 251 <a href="http://www.dokuwiki.org/faq:searchindex">faq:searchindex</a> 252 for ways to rebuild the search index.', -1 253 ); 254 } elseif(!empty($lengths)) { 255 msg('The search index seems to be working', 1); 256 } else { 257 msg( 258 'The search index is empty. See 259 <a href="http://www.dokuwiki.org/faq:searchindex">faq:searchindex</a> 260 for help on how to fix the search index. If the default indexer 261 isn\'t used or the wiki is actually empty this is normal.' 262 ); 263 } 264 265 // rough time check 266 $http = new DokuHTTPClient(); 267 $http->max_redirect = 0; 268 $http->timeout = 3; 269 $http->sendRequest('http://www.dokuwiki.org', '', 'HEAD'); 270 $now = time(); 271 if(isset($http->resp_headers['date'])) { 272 $time = strtotime($http->resp_headers['date']); 273 $diff = $time - $now; 274 275 if(abs($diff) < 4) { 276 msg("Server time seems to be okay. Diff: {$diff}s", 1); 277 } else { 278 msg("Your server's clock seems to be out of sync! Consider configuring a sync with a NTP server. Diff: {$diff}s"); 279 } 280 } 281 282} 283 284/** 285 * print a message 286 * 287 * If HTTP headers were not sent yet the message is added 288 * to the global message array else it's printed directly 289 * using html_msgarea() 290 * 291 * 292 * Levels can be: 293 * 294 * -1 error 295 * 0 info 296 * 1 success 297 * 298 * @author Andreas Gohr <andi@splitbrain.org> 299 * @see html_msgarea 300 */ 301 302define('MSG_PUBLIC', 0); 303define('MSG_USERS_ONLY', 1); 304define('MSG_MANAGERS_ONLY',2); 305define('MSG_ADMINS_ONLY',4); 306 307/** 308 * Display a message to the user 309 * 310 * @param string $message 311 * @param int $lvl -1 = error, 0 = info, 1 = success, 2 = notify 312 * @param string $line line number 313 * @param string $file file number 314 * @param int $allow who's allowed to see the message, see MSG_* constants 315 */ 316function msg($message,$lvl=0,$line='',$file='',$allow=MSG_PUBLIC){ 317 global $MSG, $MSG_shown; 318 $errors = array(); 319 $errors[-1] = 'error'; 320 $errors[0] = 'info'; 321 $errors[1] = 'success'; 322 $errors[2] = 'notify'; 323 324 if($line || $file) $message.=' ['.utf8_basename($file).':'.$line.']'; 325 326 if(!isset($MSG)) $MSG = array(); 327 $MSG[]=array('lvl' => $errors[$lvl], 'msg' => $message, 'allow' => $allow); 328 if(isset($MSG_shown) || headers_sent()){ 329 if(function_exists('html_msgarea')){ 330 html_msgarea(); 331 }else{ 332 print "ERROR($lvl) $message"; 333 } 334 unset($GLOBALS['MSG']); 335 } 336} 337/** 338 * Determine whether the current user is allowed to view the message 339 * in the $msg data structure 340 * 341 * @param $msg array dokuwiki msg structure 342 * msg => string, the message 343 * lvl => int, level of the message (see msg() function) 344 * allow => int, flag used to determine who is allowed to see the message 345 * see MSG_* constants 346 * @return bool 347 */ 348function info_msg_allowed($msg){ 349 global $INFO, $auth; 350 351 // is the message public? - everyone and anyone can see it 352 if (empty($msg['allow']) || ($msg['allow'] == MSG_PUBLIC)) return true; 353 354 // restricted msg, but no authentication 355 if (empty($auth)) return false; 356 357 switch ($msg['allow']){ 358 case MSG_USERS_ONLY: 359 return !empty($INFO['userinfo']); 360 361 case MSG_MANAGERS_ONLY: 362 return $INFO['ismanager']; 363 364 case MSG_ADMINS_ONLY: 365 return $INFO['isadmin']; 366 367 default: 368 trigger_error('invalid msg allow restriction. msg="'.$msg['msg'].'" allow='.$msg['allow'].'"', E_USER_WARNING); 369 return $INFO['isadmin']; 370 } 371 372 return false; 373} 374 375/** 376 * print debug messages 377 * 378 * little function to print the content of a var 379 * 380 * @author Andreas Gohr <andi@splitbrain.org> 381 * 382 * @param string $msg 383 * @param bool $hidden 384 */ 385function dbg($msg,$hidden=false){ 386 if($hidden){ 387 echo "<!--\n"; 388 print_r($msg); 389 echo "\n-->"; 390 }else{ 391 echo '<pre class="dbg">'; 392 echo hsc(print_r($msg,true)); 393 echo '</pre>'; 394 } 395} 396 397/** 398 * Print info to a log file 399 * 400 * @author Andreas Gohr <andi@splitbrain.org> 401 * 402 * @param string $msg 403 * @param string $header 404 */ 405function dbglog($msg,$header=''){ 406 global $conf; 407 /* @var Input $INPUT */ 408 global $INPUT; 409 410 // The debug log isn't automatically cleaned thus only write it when 411 // debugging has been enabled by the user. 412 if($conf['allowdebug'] !== 1) return; 413 if(is_object($msg) || is_array($msg)){ 414 $msg = print_r($msg,true); 415 } 416 417 if($header) $msg = "$header\n$msg"; 418 419 $file = $conf['cachedir'].'/debug.log'; 420 $fh = fopen($file,'a'); 421 if($fh){ 422 fwrite($fh,date('H:i:s ').$INPUT->server->str('REMOTE_ADDR').': '.$msg."\n"); 423 fclose($fh); 424 } 425} 426 427/** 428 * Log accesses to deprecated fucntions to the debug log 429 * 430 * @param string $alternative The function or method that should be used instead 431 * @triggers INFO_DEPRECATION_LOG 432 */ 433function dbg_deprecated($alternative = '') { 434 global $conf; 435 global $EVENT_HANDLER; 436 if(!$conf['allowdebug'] && !$EVENT_HANDLER->hasHandlerForEvent('INFO_DEPRECATION_LOG')) { 437 // avoid any work if no one cares 438 return; 439 } 440 441 $backtrace = debug_backtrace(); 442 array_shift($backtrace); 443 $self = $backtrace[0]; 444 $call = $backtrace[1]; 445 446 $data = [ 447 'trace' => $backtrace, 448 'alternative' => $alternative, 449 'called' => trim($self['class'] . '::' . $self['function'] . '()', ':'), 450 'caller' => trim($call['class'] . '::' . $call['function'] . '()', ':'), 451 'file' => $call['file'], 452 'line' => $call['line'], 453 ]; 454 455 $event = new Doku_Event('INFO_DEPRECATION_LOG', $data); 456 if($event->advise_before()) { 457 $msg = $event->data['called'] . ' is deprecated. It was called from '; 458 $msg .= $event->data['caller'] . ' in ' . $event->data['file'] . ':' . $event->data['line']; 459 if($event->data['alternative']) { 460 $msg .= ' ' . $event->data['alternative'] . ' should be used instead!'; 461 } 462 dbglog($msg); 463 } 464 $event->advise_after(); 465} 466 467/** 468 * Print a reversed, prettyprinted backtrace 469 * 470 * @author Gary Owen <gary_owen@bigfoot.com> 471 */ 472function dbg_backtrace(){ 473 // Get backtrace 474 $backtrace = debug_backtrace(); 475 476 // Unset call to debug_print_backtrace 477 array_shift($backtrace); 478 479 // Iterate backtrace 480 $calls = array(); 481 $depth = count($backtrace) - 1; 482 foreach ($backtrace as $i => $call) { 483 $location = $call['file'] . ':' . $call['line']; 484 $function = (isset($call['class'])) ? 485 $call['class'] . $call['type'] . $call['function'] : $call['function']; 486 487 $params = array(); 488 if (isset($call['args'])){ 489 foreach($call['args'] as $arg){ 490 if(is_object($arg)){ 491 $params[] = '[Object '.get_class($arg).']'; 492 }elseif(is_array($arg)){ 493 $params[] = '[Array]'; 494 }elseif(is_null($arg)){ 495 $params[] = '[NULL]'; 496 }else{ 497 $params[] = (string) '"'.$arg.'"'; 498 } 499 } 500 } 501 $params = implode(', ',$params); 502 503 $calls[$depth - $i] = sprintf('%s(%s) called at %s', 504 $function, 505 str_replace("\n", '\n', $params), 506 $location); 507 } 508 ksort($calls); 509 510 return implode("\n", $calls); 511} 512 513/** 514 * Remove all data from an array where the key seems to point to sensitive data 515 * 516 * This is used to remove passwords, mail addresses and similar data from the 517 * debug output 518 * 519 * @author Andreas Gohr <andi@splitbrain.org> 520 * 521 * @param array $data 522 */ 523function debug_guard(&$data){ 524 foreach($data as $key => $value){ 525 if(preg_match('/(notify|pass|auth|secret|ftp|userinfo|token|buid|mail|proxy)/i',$key)){ 526 $data[$key] = '***'; 527 continue; 528 } 529 if(is_array($value)) debug_guard($data[$key]); 530 } 531} 532