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