Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_diag.php - 815 lines - 27241 bytes - Summary - Text - Print

Description: Diagnostics panel.

   1  <?php
   2  
   3  /*
   4   * Textpattern Content Management System
   5   * https://textpattern.com/
   6   *
   7   * Copyright (C) 2020 The Textpattern Development Team
   8   *
   9   * This file is part of Textpattern.
  10   *
  11   * Textpattern is free software; you can redistribute it and/or
  12   * modify it under the terms of the GNU General Public License
  13   * as published by the Free Software Foundation, version 2.
  14   *
  15   * Textpattern is distributed in the hope that it will be useful,
  16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18   * GNU General Public License for more details.
  19   *
  20   * You should have received a copy of the GNU General Public License
  21   * along with Textpattern. If not, see <https://www.gnu.org/licenses/>.
  22   */
  23  
  24  /**
  25   * Diagnostics panel.
  26   *
  27   * @package Admin\Diag
  28   */
  29  
  30  if (!defined('txpinterface')) {
  31      die('txpinterface is undefined.');
  32  }
  33  
  34  /**
  35   * @ignore
  36   */
  37  
  38  define("cs", ': ');
  39  
  40  /**
  41   * @ignore
  42   */
  43  
  44  define("ln", str_repeat('-', 24).n);
  45  
  46  /**
  47   * @ignore
  48   */
  49  
  50  define("priv", '=== ');
  51  
  52  global $files;
  53  
  54  $files = check_file_integrity();
  55  
  56  if (!$files) {
  57      $files = array();
  58  } else {
  59      $files = array_keys($files);
  60  }
  61  
  62  if ($event == 'diag') {
  63      require_privs('diag');
  64  
  65      $step = ($step) ? $step : gps('step');
  66  
  67      $available_steps = array(
  68          'low'     => true,
  69          'high'    => true,
  70          'phpinfo' => true,
  71      );
  72  
  73      if ($step && bouncer($step, $available_steps)) {
  74          if ($step === 'phpinfo') {
  75              phpinfo();
  76              exit;
  77          } else {
  78              doDiagnostics();
  79          }
  80      } else {
  81          doDiagnostics();
  82      }
  83  }
  84  
  85  /**
  86   * Checks if the given Apache module is installed and active.
  87   *
  88   * @param  string $m The module
  89   * @return bool|null TRUE on success, NULL or FALSE on error
  90   */
  91  
  92  function apache_module($m)
  93  {
  94      $modules = @apache_get_modules();
  95  
  96      if (is_array($modules)) {
  97          return in_array($m, $modules);
  98      }
  99  }
 100  
 101  /**
 102   * Lists all database tables used by the Textpattern core.
 103   *
 104   * Returned tables include prefixes.
 105   *
 106   * @return array
 107   */
 108  
 109  function list_txp_tables()
 110  {
 111      $table_names = array(PFX.'textpattern');
 112      $rows = getRows("SHOW TABLES LIKE '".PFX."txp\_%'");
 113  
 114      foreach ($rows as $row) {
 115          $table_names[] = array_shift($row);
 116      }
 117  
 118      return $table_names;
 119  }
 120  
 121  /**
 122   * Checks the status of the given database tables.
 123   *
 124   * @param  array  $tables   The tables to check
 125   * @param  string $type     Check type, either FOR UPGRADE, QUICK, FAST, MEDIUM, EXTENDED, CHANGED
 126   * @param  bool   $warnings If TRUE, displays warnings
 127   * @return array An array of table statuses
 128   * @example
 129   * print_r(
 130   *     check_tables(list_txp_tables())
 131   * );
 132   */
 133  
 134  function check_tables($tables, $type = 'FAST', $warnings = 0)
 135  {
 136      $msgs = array();
 137  
 138      foreach ($tables as $table) {
 139          $rs = getRows("CHECK TABLE `$table` $type");
 140          if ($rs) {
 141              foreach ($rs as $r) {
 142                  if ($r['Msg_type'] != 'status' and ($warnings or $r['Msg_type'] != 'warning')) {
 143                      $msgs[] = $table.cs.$r['Msg_type'].cs.$r['Msg_text'];
 144                  }
 145              }
 146          }
 147      }
 148  
 149      return $msgs;
 150  }
 151  
 152  /**
 153   * Renders a diagnostics message block.
 154   *
 155   * @param  string $msg  The message
 156   * @param  string $type The message type
 157   * @return string HTML
 158   * @access private
 159   */
 160  
 161  function diag_msg_wrap($msg, $type = 'e')
 162  {
 163      $classMap = array(
 164          'e' => 'error',
 165          'i' => 'information',
 166          'w' => 'warning',
 167      );
 168  
 169      $type = isset($classMap[$type]) ? $type : 'e';
 170  
 171      return span($msg, array('class' => $classMap[$type]));
 172  }
 173  
 174  /**
 175   * Outputs a diagnostics report.
 176   *
 177   * This is the main panel.
 178   */
 179  
 180  function doDiagnostics()
 181  {
 182      global $prefs, $files, $txpcfg, $event, $step, $theme, $DB, $txp_is_dev;
 183      extract(get_prefs());
 184  
 185      $urlparts = parse_url(hu);
 186      $mydomain = $urlparts['host'];
 187      $path_to_index = $path_to_site."/index.php";
 188      $is_apache = stristr(serverSet('SERVER_SOFTWARE'), 'Apache') || is_callable('apache_get_version');
 189      $real_doc_root = (isset($_SERVER['DOCUMENT_ROOT'])) ? realpath($_SERVER['DOCUMENT_ROOT']) : '';
 190  
 191      $fail = $notReadable = array();
 192      $now = time();
 193      $heading = gTxt('tab_diagnostics');
 194      $isUpdate = defined('TXP_UPDATE_DONE');
 195  
 196      if (!$txp_is_dev) {
 197          // Check for Textpattern updates, at most once every hour.
 198          $lastCheck = json_decode(get_pref('last_update_check', ''), true);
 199  
 200          if ($now > (@(int)$lastCheck['when'] + (60 * 60))) {
 201              $lastCheck = checkUpdates();
 202          }
 203  
 204          if (isset($lastCheck['response']) && $lastCheck['response'] === false) {
 205              // Problem connecting to update server.
 206              $fail['i'][] = array('update_server_inaccessible', $lastCheck['msg']);
 207          } else {
 208              if (!empty($lastCheck['msg'])) {
 209                  $txpver = empty($lastCheck['msgval']) ? array('{version}' => '') : $lastCheck['msgval'];
 210                  $fail['i'][] = array('textpattern_version_update', $lastCheck['msg'], $txpver);
 211              }
 212  
 213              if (!empty($lastCheck['msg2'])) {
 214                  $txpver = empty($lastCheck['msgval2']) ? array('{version}' => '') : $lastCheck['msgval2'];
 215                  $fail['i'][] = array('textpattern_version_update_beta', $lastCheck['msg2'], $txpver);
 216              }
 217          }
 218      }
 219  
 220      if (!is_callable('version_compare') || version_compare(PHP_VERSION, REQUIRED_PHP_VERSION, '<')) {
 221          $fail['e'][] = array('php_version_required', 'php_version_required', array('{version}' => REQUIRED_PHP_VERSION));
 222      }
 223  
 224      if (@gethostbyname($mydomain) === $mydomain) {
 225          $fail['w'][] = array('dns_lookup_fails', 'dns_lookup_fails', array('{domain}' => $mydomain));
 226      }
 227  
 228      if (!@is_dir($path_to_site)) {
 229          $fail['e'][] = array('path_to_site_inaccessible', 'path_inaccessible', array('{path}' => $path_to_site));
 230      }
 231  
 232      if (rtrim($siteurl, '/') != $siteurl) {
 233          $fail['w'][] = array('site_trailing_slash', 'site_trailing_slash', array('{path}' => $path_to_site));
 234      }
 235  
 236      if (!@is_file($path_to_index) || !@is_readable($path_to_index)) {
 237          $fail['e'][] = array('index_inaccessible', 'path_inaccessible', array('{path}' => $path_to_index));
 238      }
 239  
 240      if (!@is_writable($path_to_site.DS.$img_dir)) {
 241          $notReadable[] = array('{dirtype}' => 'img_dir', '{path}' => $path_to_site.DS.$img_dir);
 242      }
 243  
 244      if (!@is_writable($file_base_path)) {
 245          $notReadable[] = array('{dirtype}' => 'file_base_path', '{path}' => $file_base_path);
 246      }
 247  
 248      if (!@is_writable($path_to_site.DS.$skin_dir)) {
 249          $notReadable[] = array('{dirtype}' => 'skin_dir', '{path}' => $path_to_site.DS.$skin_dir);
 250      }
 251  
 252      if (!@is_writable($tempdir)) {
 253          $notReadable[] = array('{dirtype}' => 'tempdir', '{path}' => $tempdir);
 254      }
 255  
 256      if (!@is_writable(PLUGINPATH)) {
 257          $notReadable[] = array('{dirtype}' => 'plugin_dir', '{path}' => PLUGINPATH);
 258      }
 259  
 260      if ($permlink_mode != 'messy' && $is_apache && !@is_readable($path_to_site.'/.htaccess')) {
 261          $fail['e'][] = array('htaccess_missing');
 262      }
 263  
 264      if ($permlink_mode != 'messy' && is_callable('apache_get_modules') && !apache_module('mod_rewrite')) {
 265          $fail['e'][] = array('mod_rewrite_missing');
 266      }
 267  
 268      if (!ini_get('file_uploads')) {
 269          $fail['i'][] = array('file_uploads_disabled');
 270      }
 271  
 272      if (isset($txpcfg['multisite_root_path'])) {
 273          $basePath = $txpcfg['multisite_root_path'].DS.'admin';
 274  
 275          if (@is_dir($basePath.DS.'setup') && ($txp_is_dev || !Txp::get('\Textpattern\Admin\Tools')->removeFiles($basePath, 'setup'))) {
 276              $fail['w'][] = array('setup_still_exists', 'still_exists', array('{path}' => $basePath.DS.'setup'.DS));
 277          }
 278      } else {
 279          if (@is_dir(txpath.DS.'setup') && ($txp_is_dev || !Txp::get('\Textpattern\Admin\Tools')->removeFiles(txpath, 'setup'))) {
 280              $fail['w'][] = array('setup_still_exists', 'still_exists', array('{path}' => txpath.DS.'setup'.DS));
 281          }
 282      }
 283  
 284      if (empty($tempdir)) {
 285          $fail['w'][] = array('no_temp_dir');
 286      }
 287  
 288      if (is_disabled('mail')) {
 289          $fail['e'][] = array('warn_mail_unavailable');
 290      }
 291  
 292      if ($permlink_mode != 'messy') {
 293          $rs = safe_column("name", 'txp_section', "1 = 1");
 294  
 295          foreach ($rs as $name) {
 296              if ($name && @file_exists($path_to_site.'/'.$name)) {
 297                  $fail['e'][] = array('old_placeholder_exists', 'old_placeholder', array('{path}' => $path_to_site.DS.$name));
 298              }
 299          }
 300      }
 301  
 302      $cs = check_file_integrity(INTEGRITY_REALPATH);
 303  
 304      if (!$cs) {
 305          $cs = array();
 306      }
 307  
 308      // Files that don't match their checksums.
 309      if (!$txp_is_dev && $modified_files = array_keys($cs, INTEGRITY_MODIFIED)) {
 310          $fail['w'][] = array('modified_files', 'modified_files', array('{list}' => n.t.implode(', '.n.t, $modified_files)));
 311      }
 312  
 313      // Running development code in live mode is not recommended.
 314      if (preg_match('/-dev$/', txp_version) && $production_status == 'live') {
 315          $fail['w'][] = array('dev_version_live');
 316      }
 317  
 318      // Missing files.
 319      if ($missing = array_merge(
 320          array_keys($cs, INTEGRITY_MISSING),
 321          array_keys($cs, INTEGRITY_NOT_FILE),
 322          array_keys($cs, INTEGRITY_NOT_READABLE)
 323      )) {
 324          $fail['e'][] = array('missing_files', 'missing_files', array('{list}' => n.t.implode(', '.n.t, $missing)));
 325      }
 326  
 327      // Anything might break if arbitrary functions are disabled.
 328      if (ini_get('disable_functions')) {
 329          $disabled_funcs = do_list_unique(ini_get('disable_functions'));
 330          // Commonly disabled functions that we don't need.
 331          $disabled_funcs = array_filter(array_diff($disabled_funcs, array(
 332              'imagefilltoborder',
 333              'escapeshellarg',
 334              'escapeshellcmd',
 335              'exec',
 336              'passthru',
 337              'proc_close',
 338              'proc_get_status',
 339              'proc_nice',
 340              'proc_open',
 341              'proc_terminate',
 342              'shell_exec',
 343              'system',
 344              'popen',
 345              'dl',
 346              'chown',
 347          )), function($func) {return strpos($func, 'pcntl_') !== 0;});
 348  
 349          if ($disabled_funcs) {
 350              $fail['w'][] = array('some_php_functions_disabled', 'some_php_functions_disabled', array('{list}' => implode(', ', array_filter($disabled_funcs))));
 351          }
 352      }
 353  
 354      // Not sure about this one.
 355  //    if (strncmp(php_sapi_name(), 'cgi', 3) == 0 and ini_get('cgi.rfc2616_headers'))
 356  //    $fail['cgi_header_config'] = gTxt('cgi_header_config');
 357  
 358      $guess_site_url = $_SERVER['HTTP_HOST'].preg_replace('#[/\\\\]$#', '', dirname(dirname($_SERVER['SCRIPT_NAME'])));
 359  
 360      if ($siteurl && strip_prefix($siteurl, 'www.') != strip_prefix($guess_site_url, 'www.')) {
 361          // Skip warning if multi-site setup, as $guess_site_url and $siteurl will mismatch.
 362          if (!isset($txpcfg['multisite_root_path'])) {
 363              $fail['w'][] = array('site_url_mismatch', 'site_url_mismatch', array('{url}' => $guess_site_url));
 364          }
 365      }
 366  
 367      // Test clean URL server vars.
 368      if (hu) {
 369          if (ini_get('allow_url_fopen') && ($permlink_mode != 'messy')) {
 370              $s = md5(uniqid(rand(), true));
 371              ini_set('default_socket_timeout', 10);
 372  
 373              $pretext_data = @file(hu.$s.'/?txpcleantest=1');
 374  
 375              if ($pretext_data) {
 376                  $pretext_req = trim(@$pretext_data[0]);
 377  
 378                  if ($pretext_req != md5('/'.$s.'/?txpcleantest=1')) {
 379                      $fail['w'][] = array('clean_url_data_failed', 'clean_url_data_failed', array('{data}' => txpspecialchars($pretext_req)));
 380                  }
 381              } else {
 382                  $fail['w'][] = array('clean_url_test_failed');
 383              }
 384          }
 385      }
 386  
 387      if ($tables = list_txp_tables()) {
 388          $table_errors = check_tables($tables);
 389  
 390          if ($table_errors) {
 391              $fail['e'][] = array('mysql_table_errors', 'mysql_table_errors', array('{list}' => n.t.implode(', '.n.t, $table_errors)));
 392          }
 393      }
 394  
 395      $active_plugins = array();
 396  
 397      if (!$use_plugins && !$admin_side_plugins) {
 398          $showTypes = '2';
 399      } elseif (!$admin_side_plugins) {
 400          $showTypes = '0, 2, 5';
 401      } elseif (!$use_plugins) {
 402          $showTypes = '1, 2, 3, 4, 5';
 403      } else {
 404          $showTypes = '0, 1, 2, 3, 4, 5';
 405      }
 406  
 407      if ($rows = safe_rows("name, version, code_md5, MD5(code) AS md5", 'txp_plugin', "status > 0 AND type IN (".$showTypes.") ORDER BY name")) {
 408          foreach ($rows as $row) {
 409              $n = $row['name'].'-'.$row['version'];
 410  
 411              if (strtolower($row['md5']) != strtolower($row['code_md5'])) {
 412                  $n .= ' ('.gTxt('diag_modified').')';
 413              }
 414  
 415              $active_plugins[] = $n;
 416          }
 417      }
 418  
 419      $theme_manifest = $theme->manifest();
 420  
 421      // Check GD info.
 422      if (function_exists('gd_info')) {
 423          $gd_info = gd_info();
 424  
 425          $gd_support = array();
 426  
 427          if ($gd_info['GIF Create Support']) {
 428              $gd_support[] = 'GIF';
 429          }
 430  
 431          if ($gd_info['JPEG Support']) {
 432              $gd_support[] = 'JPEG';
 433          }
 434  
 435          if ($gd_info['PNG Support']) {
 436              $gd_support[] = 'PNG';
 437          }
 438  
 439          if (isset($gd_info['WebP Support']) && $gd_info['WebP Support']) {
 440              $gd_support[] = 'WebP';
 441          }
 442  
 443          if ($gd_support) {
 444              $gd_support = implode(', ', $gd_support);
 445          } else {
 446              $gd_support = gTxt('diag_none');
 447          }
 448  
 449          $gd = gTxt('diag_gd_info', array(
 450              '{version}'   => $gd_info['GD Version'],
 451              '{supported}' => $gd_support,
 452          ));
 453      } else {
 454          $gd = gTxt('diag_unavailable');
 455      }
 456  
 457      if (realpath($prefs['tempdir']) === realpath($prefs['plugin_cache_dir'])) {
 458          $fail['e'][] = array('tmp_plugin_paths_match');
 459      }
 460  
 461      // Database server time.
 462      extract(doSpecial(getRow("SELECT @@global.time_zone AS db_global_timezone, @@session.time_zone AS db_session_timezone, NOW() AS db_server_time, UNIX_TIMESTAMP(NOW()) AS db_server_timestamp")));
 463      $db_server_timeoffset = $db_server_timestamp - $now;
 464  
 465      echo pagetop(gTxt('tab_diagnostics'), '');
 466  
 467      echo n.'<div class="txp-layout">'.
 468          n.tag(
 469              hed($heading, 1, array('class' => 'txp-heading')),
 470              'div', array('class' => 'txp-layout-1col')
 471          ).
 472          n.tag_start('div', array(
 473              'class' => 'txp-layout-1col',
 474              'id'    => $event.'_container',
 475          )).
 476          n.tag_start('div', array('id' => 'pre_flight_check')).
 477          hed(gTxt('preflight_check'), 2);
 478  
 479      $thisLang = get_pref('language_ui', TEXTPATTERN_DEFAULT_LANG);
 480      $siteLang = get_pref('language', TEXTPATTERN_DEFAULT_LANG);
 481      $langs = array_unique(array('en', $thisLang));
 482      $pfcStrings = array();
 483      $langCounter = 0;
 484      $txpLang = Txp::get('\Textpattern\L10n\Lang');
 485  
 486      foreach ($langs as $lang) {
 487          // Overwrite the lang strings to English, then revert on second pass.
 488          // This allows the pre-flight check to be displayed in the local
 489          // language above the fold, and in English in the textarea.
 490          $diagPack = $txpLang->getPack($lang, $event);
 491          $diagStrings = array();
 492          $showPophelp = count($langs) === 1 || $langCounter > 0;
 493  
 494          foreach ($diagPack as $key => $packBlock) {
 495              $diagStrings[$key] = $packBlock['data'];
 496          }
 497  
 498          $txpLang->setPack($diagStrings, true);
 499          $not_readable = array();
 500  
 501          foreach ($notReadable as $strings) {
 502              $strings['{dirtype}'] = gTxt($strings['{dirtype}']);
 503              $not_readable[] = diag_msg_wrap(gTxt('dir_not_writable', $strings));
 504          }
 505  
 506          if ($fail || $not_readable) {
 507              foreach ($fail as $type => $content) {
 508                  foreach ($content as $stringInfo) {
 509                      $help = $stringInfo[0];
 510                      $message = isset($stringInfo[1]) ? $stringInfo[1] : $help;
 511                      $args = isset($stringInfo[2]) ? $stringInfo[2] : array();
 512                      $pfcStrings[$lang][] = graf(nl2br(diag_msg_wrap(gTxt($message, $args), $type)).($showPophelp ? popHelp($help) : ''));
 513                  }
 514              }
 515  
 516              if ($not_readable) {
 517                  $pfcStrings[$lang][] = graf(nl2br(join(n, $not_readable).($showPophelp ? popHelp('dir_not_writable') : '')));
 518              }
 519          } else {
 520              $pfcStrings[$lang][] = graf('<span class="ui-icon ui-icon-check"></span>'.sp.gTxt('all_checks_passed'), array('class' => 'success'));
 521          }
 522  
 523          $langCounter++;
 524      }
 525  
 526      // The lang will now be back to the local lingo so we can use $lang
 527      // to display the correct pre-flight check.
 528      echo implode(n, $pfcStrings[$lang]);
 529  
 530      // End of #pre_flight_check.
 531      echo n.tag_end('div');
 532  
 533      $out = array();
 534  
 535      echo n.tag_start('div', array('id' => 'diagnostics')).
 536          hed(gTxt('diagnostic_info'), 2);
 537  
 538      $fmt_date = '%Y-%m-%d %H:%M:%S';
 539      $updateTime = ($dbupdatetime) ? gmstrftime($fmt_date, $dbupdatetime).'/' : '';
 540  
 541      $dets = array(
 542          'low'  => gTxt('low'),
 543          'high' => gTxt('high'),
 544      );
 545  
 546      $out = array(
 547          form(
 548              eInput('diag').
 549              href(gTxt('php_diagnostics').sp.span(gTxt('opens_external_link'), array('class' => 'ui-icon ui-icon-extlink')), array(
 550                  'event'      => 'diag',
 551                  'step'       => 'phpinfo',
 552                  '_txp_token' => form_token(),
 553              ), array(
 554                  'rel'    => 'external noopener',
 555                  'target' => '_blank',
 556              )).
 557              inputLabel(
 558                  'diag_detail_level',
 559                  selectInput('step', $dets, $step, 0, 1, 'diag_detail_level'),
 560                  'detail',
 561                  '',
 562                  array('class' => 'txp-form-field diagnostic-details-level'),
 563                  ''
 564              ).
 565              inputLabel(
 566                  'diag_clear_private',
 567                  checkbox('diag_clear_private', 1, false, 0, 'diag_clear_private'),
 568                  'diag_clear_private', 'diag_clear_private', array('class' => 'txp-form-field'),
 569                  ''
 570              )
 571          ),
 572  
 573          '<textarea class="code" id="diagnostics-detail" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_LARGE.'" dir="ltr" readonly>',
 574          '</textarea>',
 575  
 576          (isset($txpcfg['multisite_root_path']))
 577          ? '<textarea class="code ui-helper-hidden" id="diagnostics-data" cols="'.INPUT_LARGE.'" data-txproot="'.dirname(dirname($txpcfg['multisite_root_path'])).'" dir="ltr" readonly>'
 578          : '<textarea class="code ui-helper-hidden" id="diagnostics-data" cols="'.INPUT_LARGE.'" data-txproot="'.dirname(txpath).'" dir="ltr" readonly>',
 579  
 580          gTxt('diag_txp_version').cs.txp_version.' ('.check_file_integrity(INTEGRITY_DIGEST).')'.n,
 581  
 582          gTxt('diag_last_update').cs.$updateTime.gmstrftime($fmt_date, @filemtime(txpath.'/update/_update.php')).n,
 583  
 584          priv.gTxt('diag_web_domain').cs.$siteurl.n,
 585  
 586          (defined('ahu')) ? priv.gTxt('diag_admin_url').cs.rtrim(preg_replace('|^https?://|', '', ahu), '/').n : '',
 587  
 588          (!empty($txpcfg['cookie_domain'])) ? priv.gTxt('diag_cookie_domain').cs.cookie_domain.n : '',
 589  
 590          priv.gTxt('diag_document_root').cs.@$_SERVER['DOCUMENT_ROOT'].(($real_doc_root != @$_SERVER['DOCUMENT_ROOT']) ? ' ('.$real_doc_root.')' : '').n,
 591  
 592          (isset($txpcfg['multisite_root_path'])) ? gTxt('diag_multisite_root_path').cs.$txpcfg['multisite_root_path'].n : '',
 593  
 594          priv.'$path_to_site'.cs.$path_to_site.n,
 595  
 596          gTxt('diag_txp_path').cs.txpath.n,
 597  
 598          gTxt('diag_permlink_mode').cs.$permlink_mode.n,
 599  
 600          gTxt('diag_production_status').cs.$production_status.n,
 601  
 602          (ini_get('open_basedir')) ? 'open_basedir'.cs.ini_get('open_basedir').n : '',
 603  
 604          (ini_get('upload_tmp_dir')) ? 'upload_tmp_dir'.cs.ini_get('upload_tmp_dir').n : '',
 605  
 606          gTxt('diag_tempdir').cs.$tempdir.n,
 607  
 608          gTxt('diag_php_version').cs.phpversion().n,
 609  
 610          gTxt('diag_gd_library').cs.$gd.n,
 611  
 612          gTxt('diag_server_timezone').cs.Txp::get('\Textpattern\Date\Timezone')->getTimeZone().n,
 613  
 614          gTxt('diag_server_time').cs.strftime('%Y-%m-%d %H:%M:%S').n,
 615  
 616          strip_tags(gTxt('diag_is_dst')).cs.$is_dst.n,
 617  
 618          strip_tags(gTxt('diag_auto_dst')).cs.$auto_dst.n,
 619  
 620          strip_tags(gTxt('diag_gmtoffset')).cs.$timezone_key.sp."($gmtoffset)".n,
 621  
 622          'MySQL'.cs.$DB->version.' ('.getThing('SELECT @@GLOBAL.version_comment').') '.n,
 623  
 624          gTxt('diag_db_server_time').cs.$db_server_time.n,
 625  
 626          gTxt('diag_db_server_timeoffset').cs.$db_server_timeoffset.' s'.n,
 627  
 628          gTxt('diag_db_global_timezone').cs.$db_global_timezone.n,
 629  
 630          gTxt('diag_db_session_timezone').cs.$db_session_timezone.n,
 631  
 632          gTxt('diag_locale').cs.$locale.n,
 633  
 634          gTxt('diag_languages', array('{site_lang}' => $siteLang, '{admin_lang}' => $thisLang)).n,
 635  
 636          (isset($_SERVER['SERVER_SOFTWARE'])) ? gTxt('diag_web_server').cs.$_SERVER['SERVER_SOFTWARE'].n : '',
 637  
 638          (is_callable('apache_get_version')) ? gTxt('diag_apache_version').cs.@apache_get_version().n : '',
 639  
 640          gTxt('diag_php_sapi_mode').cs.PHP_SAPI.n,
 641  
 642          gTxt('diag_rfc2616_headers').cs.ini_get('cgi.rfc2616_headers').n,
 643  
 644          gTxt('diag_server_os_version').cs.php_uname('s').' '.php_uname('r').n,
 645  
 646          gTxt('diag_theme_name').cs.$theme_name.sp.@$theme_manifest['version'].n,
 647  
 648          ($active_plugins ? gTxt('diag_active_plugins').cs.n.t.implode(n.t, $active_plugins).n : ''),
 649  
 650          ($fail || $not_readable)
 651          ? n.gTxt('diag_preflight_check').cs.n.ln.implode(n, doStripTags($pfcStrings['en'])).n.ln
 652          : '',
 653  
 654          ($is_apache && is_readable($path_to_site.'/.htaccess'))
 655          ? n.gTxt('diag_htaccess_contents').cs.n.ln.txpspecialchars(implode('', file($path_to_site.'/.htaccess'))).n.ln
 656          : '',
 657      );
 658  
 659      if ($step == 'high') {
 660          $lastCheck = json_decode(get_pref('last_update_check', ''), true);
 661  
 662          if (!empty($lastCheck['msg']) || !empty($lastCheck['msg2'])) {
 663              $relmain = empty($lastCheck['msgval']) ? array('{version}' => '') : $lastCheck['msgval'];
 664              $relbeta = empty($lastCheck['msgval2']) ? array('{version}' => '') : $lastCheck['msgval2'];
 665              $msgmain = empty($lastCheck['msg']) ? '' : strip_tags(gTxt($lastCheck['msg'], $relmain));
 666              $msgbeta = empty($lastCheck['msg2']) ? '' : strip_tags(gTxt($lastCheck['msg2'], $relbeta));
 667              $out[] = n.gTxt('diag_last_update_check').cs.strftime('%Y-%m-%d %H:%M:%S', $lastCheck['when']).', '.$msgmain.' '.$msgbeta.n;
 668          }
 669  
 670          $out[] = n.gTxt('diag_db_charset').cs.$DB->default_charset.'/'.$DB->charset.n;
 671  
 672          $result = safe_query("SHOW variables WHERE Variable_name LIKE 'character_se%' OR Variable_name LIKE 'collation%'");
 673  
 674          while ($row = mysqli_fetch_row($result)) {
 675              $out[] = $row[0].cs.$row[1].n;
 676  
 677              if ($row[0] == 'character_set_connection') {
 678                  $conn_char = $row[1];
 679              }
 680          }
 681  
 682          $table_names = array(PFX.'textpattern');
 683          $result = safe_query("SHOW TABLES LIKE '".PFX."txp\_%'");
 684  
 685          while ($row = mysqli_fetch_row($result)) {
 686              $table_names[] = $row[0];
 687          }
 688  
 689          $table_msg = array();
 690  
 691          foreach ($table_names as $table) {
 692              $ctr = safe_query("SHOW CREATE TABLE $table");
 693  
 694              if (!$ctr) {
 695                  unset($table_names[$table]);
 696                  continue;
 697              }
 698  
 699              $row = mysqli_fetch_assoc($ctr);
 700              $ctcharset = preg_replace('#^CREATE TABLE.*SET=([^ ]+)[^)]*$#is', '\\1', $row['Create Table']);
 701  
 702              if (isset($conn_char) && !stristr($ctcharset, 'CREATE') && ($conn_char != $ctcharset)) {
 703                  $table_msg[] = "$table is $ctcharset";
 704              }
 705  
 706              $ctr = safe_query("CHECK TABLE $table");
 707              $row = mysqli_fetch_assoc($ctr);
 708  
 709              if (in_array($row['Msg_type'], array('error', 'warning'))) {
 710                  $table_msg[] = $table.cs.$row['Msg_Text'];
 711              }
 712          }
 713  
 714          if ($table_msg == array()) {
 715              $table_msg = (count($table_names) < 17) ?  array('-') : array('OK');
 716          }
 717  
 718          $out[] = count($table_names).sp.gTxt('diag_db_tables').cs.implode(', ', $table_msg).n;
 719  
 720          $cf = preg_grep('/^custom_\d+/', getThings("DESCRIBE `".PFX."textpattern`"));
 721          $out[] = n.get_pref('max_custom_fields', 10).sp.gTxt('diag_custom').cs.
 722                      implode(', ', $cf).sp.'('.count($cf).')'.n;
 723  
 724          $extns = get_loaded_extensions();
 725          $extv = array();
 726  
 727          foreach ($extns as $e) {
 728              $extv[] = $e.(phpversion($e) ? '/'.phpversion($e) : '');
 729          }
 730  
 731          if (is_callable('apache_get_modules')) {
 732              $out[] = n.gTxt('diag_apache_modules').cs.implode(', ', apache_get_modules()).n;
 733          }
 734  
 735          if (@is_array($pretext_data) and count($pretext_data) > 1) {
 736              $out[] = n.gTxt('diag_pretext_data').cs.txpspecialchars(implode('', array_slice($pretext_data, 1, 20))).n;
 737          }
 738  
 739          $out[] = n;
 740  
 741          if ($md5s = check_file_integrity(INTEGRITY_MD5)) {
 742              foreach ($md5s as $f => $checksum) {
 743                  $out[] = $f.cs.n.t.(!$checksum ? gTxt('diag_unknown') : $checksum).n;
 744              }
 745          }
 746  
 747          $out[] = n.ln;
 748      }
 749  
 750      $out[] = callback_event('diag_results', $step).n;
 751      $out[] = '</textarea>';
 752  
 753      echo implode('', $out),
 754          n.tag_end('div'). // End of #diagnostics.
 755          n.tag_end('div'). // End of .txp-layout-1col.
 756          n.'</div>'; // End of .txp-layout.;
 757  }
 758  
 759  /**
 760   * Checks for Textpattern updates.
 761   *
 762   * @return  array|null When updates are found returns an array consisting keys 'version', 'msg'
 763   * @example
 764   * if ($updates = checkUpdates())
 765   * {
 766   *     echo "New version: {$updates['version']}";
 767   * }
 768   */
 769  
 770  function checkUpdates()
 771  {
 772      $endpoint = 'https://textpattern.com/version.json';
 773  
 774      if (function_exists('curl_version')) {
 775          $ch = curl_init($endpoint);
 776          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 777          $contents = curl_exec($ch);
 778      } else {
 779          $contents = file_get_contents($endpoint);
 780      }
 781  
 782      $response = @json_decode($contents, true);
 783      $release = @$response['textpattern-version']['release'];
 784      $prerelease = @$response['textpattern-version']['prerelease'];
 785      $version = get_pref('version');
 786  
 787      $lastCheck = array(
 788          'when'     => time(),
 789          'msg'      => '',
 790          'msg2'     => '',
 791          'msgval'   => array(),
 792          'msgval2'  => array(),
 793          'response' => true,
 794      );
 795  
 796      if (!empty($release)) {
 797          if (version_compare($version, $release) < 0) {
 798              $lastCheck['msg'] = 'textpattern_update_available';
 799              $lastCheck['msgval'] = array('{version}' => $release);
 800          }
 801  
 802          if (version_compare($version, $prerelease) < 0) {
 803              $lastCheck['msg2'] = 'textpattern_update_available_beta';
 804              $lastCheck['msgval2'] = array('{version}' => $prerelease);
 805          }
 806      } else {
 807          $lastCheck['msg'] = 'problem_connecting_update_server';
 808          $lastCheck['msgval'] = array();
 809          $lastCheck['response'] = false;
 810      }
 811  
 812      set_pref('last_update_check', json_encode($lastCheck, TEXTPATTERN_JSON), 'publish', PREF_HIDDEN, 'text_input');
 813  
 814      return $lastCheck;
 815  }

title

Description

title

Description

title

Description

title

title

Body