infoutils.php 18 KB

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