[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/admin/ -> health.php (source)

   1  <?php  // $Id: health.php,v 1.17.4.4 2008/09/01 05:29:32 tjhunt Exp $
   2  
   3      ob_start(); //for whitespace test
   4      require_once ('../config.php');
   5  
   6      // extra whitespace test - intentionally breaks cookieless mode
   7      $extraws = '';
   8      while (true) {
   9          $extraws .= ob_get_contents();
  10          if (!@ob_end_clean()) {
  11              break;
  12          }
  13      }
  14  
  15      require_once($CFG->libdir.'/adminlib.php');
  16  
  17      admin_externalpage_setup('healthcenter');
  18  
  19      define('SEVERITY_NOTICE',      'notice');
  20      define('SEVERITY_ANNOYANCE',   'annoyance');
  21      define('SEVERITY_SIGNIFICANT', 'significant');
  22      define('SEVERITY_CRITICAL',    'critical');
  23  
  24      $solution = optional_param('solution', 0, PARAM_SAFEDIR); //in fact it is class name alhanumeric and _
  25  
  26      require_login();
  27      require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
  28  
  29      $site = get_site();
  30  
  31      admin_externalpage_print_header();
  32  
  33  echo <<<STYLES
  34  <style type="text/css">
  35  div#healthnoproblemsfound {
  36      width: 60%;
  37      margin: auto;
  38      padding: 1em;
  39      border: 1px black solid;
  40      -moz-border-radius: 6px;
  41  }
  42  dl.healthissues {
  43      width: 60%;
  44      margin: auto;
  45  }
  46  dl.critical dt, dl.critical dd {
  47      background-color: #a71501;
  48  }
  49  dl.significant dt, dl.significant dd {
  50      background-color: #d36707;
  51  }
  52  dl.annoyance dt, dl.annoyance dd {
  53      background-color: #dba707;
  54  }
  55  dl.notice dt, dl.notice dd {
  56      background-color: #e5db36;
  57  }
  58  dt.solution, dd.solution, div#healthnoproblemsfound {
  59      background-color: #5BB83E !important;
  60  }
  61  dl.healthissues dt, dl.healthissues dd {
  62      margin: 0px;
  63      padding: 1em;
  64      border: 1px black solid;
  65  }
  66  dl.healthissues dt {
  67      font-weight: bold;
  68      border-bottom: none;
  69      padding-bottom: 0.5em;
  70  }
  71  dl.healthissues dd {
  72      border-top: none;
  73      padding-top: 0.5em;
  74      margin-bottom: 10px;
  75  }
  76  dl.healthissues dd form {
  77      margin-top: 0.5em;
  78      text-align: right;
  79  }
  80  form#healthformreturn {
  81      text-align: center;
  82      margin: 2em;
  83  }
  84  dd.solution p {
  85      padding: 0px;
  86      margin: 1em 0px;
  87  }
  88  dd.solution li {
  89      margin-top: 1em;
  90  }
  91  
  92  </style>
  93  STYLES;
  94  
  95      if(strpos($solution, 'problem_') === 0 && class_exists($solution)) {
  96          health_print_solution($solution);
  97      }
  98      else {
  99          health_find_problems();
 100      }
 101  
 102  
 103      admin_externalpage_print_footer();
 104  
 105  
 106  function health_find_problems() {
 107  
 108      print_heading(get_string('healthcenter'));
 109  
 110      $issues   = array(
 111          SEVERITY_CRITICAL    => array(),
 112          SEVERITY_SIGNIFICANT => array(),
 113          SEVERITY_ANNOYANCE   => array(),
 114          SEVERITY_NOTICE      => array(),
 115      );
 116      $problems = 0;
 117  
 118      for($i = 1; $i < 1000000; ++$i) {
 119          $classname = sprintf('problem_%06d', $i);
 120          if(!class_exists($classname)) {
 121              break;
 122          }
 123          $problem = new $classname;
 124          if($problem->exists()) {
 125              $severity = $problem->severity();
 126              $issues[$severity][$classname] = array(
 127                  'severity'    => $severity,
 128                  'description' => $problem->description(),
 129                  'title'       => $problem->title()
 130              );
 131              ++$problems;
 132          }
 133          unset($problem);
 134      }
 135  
 136      if($problems == 0) {
 137          echo '<div id="healthnoproblemsfound">';
 138          echo get_string('healthnoproblemsfound');
 139          echo '</div>';
 140      }
 141      else {
 142          print_heading(get_string('healthproblemsdetected'));
 143          $severities = array(SEVERITY_CRITICAL, SEVERITY_SIGNIFICANT, SEVERITY_ANNOYANCE, SEVERITY_NOTICE);
 144          foreach($severities as $severity) {
 145              if(!empty($issues[$severity])) {
 146                  echo '<dl class="healthissues '.$severity.'">';
 147                  foreach($issues[$severity] as $classname => $data) {
 148                      echo '<dt id="'.$classname.'">'.$data['title'].'</dt>';
 149                      echo '<dd>'.$data['description'];
 150                      echo '<form action="health.php#solution" method="get">';
 151                      echo '<input type="hidden" name="solution" value="'.$classname.'" /><input type="submit" value="'.get_string('viewsolution').'" />';
 152                      echo '</form></dd>';
 153                  }
 154                  echo '</dl>';
 155              }
 156          }
 157      }
 158  }
 159  
 160  function health_print_solution($classname) {
 161      $problem = new $classname;
 162      $data = array(
 163          'title'       => $problem->title(),
 164          'severity'    => $problem->severity(),
 165          'description' => $problem->description(),
 166          'solution'    => $problem->solution()
 167      );
 168  
 169      print_heading(get_string('healthcenter'));
 170      print_heading(get_string('healthproblemsolution'));
 171      echo '<dl class="healthissues '.$data['severity'].'">';
 172      echo '<dt>'.$data['title'].'</dt>';
 173      echo '<dd>'.$data['description'].'</dd>';
 174      echo '<dt id="solution" class="solution">'.get_string('healthsolution').'</dt>';
 175      echo '<dd class="solution">'.$data['solution'].'</dd></dl>';
 176      echo '<form id="healthformreturn" action="health.php#'.$classname.'" method="get">';
 177      echo '<input type="submit" value="'.get_string('healthreturntomain').'" />';
 178      echo '</form>';
 179  }
 180  
 181  class problem_base {
 182      function exists() {
 183          return false;
 184      }
 185      function title() {
 186          return '???';
 187      }
 188      function severity() {
 189          return SEVERITY_NOTICE;
 190      }
 191      function description() {
 192          return '';
 193      }
 194      function solution() {
 195          return '';
 196      }
 197  }
 198  
 199  class problem_000001 extends problem_base {
 200      function title() {
 201          return 'Invalid value for $CFG->dirroot';
 202      }
 203      function exists() {
 204          global $CFG;
 205          $dirroot = dirname(realpath('../index.php'));
 206          if (!empty($dirroot) && $dirroot != $CFG->dirroot) {
 207              return true;
 208          }
 209          return false;
 210      }
 211      function severity() {
 212          return SEVERITY_CRITICAL;
 213      }
 214      function description() {
 215          global $CFG;
 216          return 'Your <strong>config.php</strong> file contains the setting <strong>$CFG-&gt;dirroot = "'.$CFG->dirroot.'"</strong>, which is incorrect. Unless you correct this problem, Moodle will not function correctly, if at all.';
 217      }
 218      function solution() {
 219          global $CFG;
 220          $dirroot = dirname(realpath('../index.php'));
 221          return 'You need to edit your <strong>config.php</strong> file. Find the line which reads <pre>$CFG->dirroot = \''.$CFG->dirroot.'\';</pre> and change it to read <pre>$CFG->dirroot = \''.$dirroot.'\'</pre>';
 222      }
 223  }
 224  
 225  class problem_000002 extends problem_base {
 226      function title() {
 227          return 'Extra characters at the end of config.php or other library function';
 228      }
 229      function exists() {
 230          global $extraws;
 231  
 232          if($extraws === '') {
 233              return false;
 234          }
 235          return true;
 236      }
 237      function severity() {
 238          return SEVERITY_SIGNIFICANT;
 239      }
 240      function description() {
 241          return 'Your Moodle configuration file config.php or another library file, contains some characters after the closing PHP tag (?>). This causes Moodle to exhibit several kinds of problems (such as broken downloaded files) and must be fixed.';
 242      }
 243      function solution() {
 244          global $CFG;
 245          return 'You need to edit <strong>'.$CFG->dirroot.'/config.php</strong> and remove all characters (including spaces and returns) after the ending ?> tag. These two characters should be the very last in that file. The extra trailing whitespace may be also present in other PHP files that are included from lib/setup.php.';
 246      }
 247  }
 248  
 249  class problem_000003 extends problem_base {
 250      function title() {
 251          return '$CFG->dataroot does not exist or does not have write permissions';
 252      }
 253      function exists() {
 254          global $CFG;
 255          if(!is_dir($CFG->dataroot) || !is_writable($CFG->dataroot)) {
 256              return true;
 257          }
 258          return false;
 259      }
 260      function severity() {
 261          return SEVERITY_SIGNIFICANT;
 262      }
 263      function description() {
 264          global $CFG;
 265          return 'Your <strong>config.php</strong> says that your "data root" directory is <strong>'.$CFG->dataroot.'</strong>. However, this directory either does not exist or cannot be written to by Moodle. This means that a variety of problems will be present, such as users not being able to log in and not being able to upload any files. It is imperative that you address this problem for Moodle to work correctly.';
 266      }
 267      function solution() {
 268          global $CFG;
 269          return 'First of all, make sure that the directory <strong>'.$CFG->dataroot.'</strong> exists. If the directory does exist, then you must make sure that Moodle is able to write to it. Contact your web server administrator and request that he gives write permissions for that directory to the user that the web server process is running as.';
 270      }
 271  }
 272  
 273  class problem_000004 extends problem_base {
 274      function title() {
 275          return 'cron.php is not set up to run automatically';
 276      }
 277      function exists() {
 278          global $CFG;
 279          $lastcron = get_field_sql('SELECT max(lastcron) FROM '.$CFG->prefix.'modules');
 280          return (time() - $lastcron > 3600 * 24);
 281      }
 282      function severity() {
 283          return SEVERITY_SIGNIFICANT;
 284      }
 285      function description() {
 286          return 'The cron.php mainenance script has not been run in the past 24 hours. This probably means that your server is not configured to automatically run this script in regular time intervals. If this is the case, then Moodle will mostly work as it should but some operations (notably sending email to users) will not be carried out at all.';
 287      }
 288      function solution() {
 289          global $CFG;
 290          return 'For detailed instructions on how to enable cron, see <a href="'.$CFG->wwwroot.'/doc/?file=install.html#cron">this section</a> of the installation manual.';
 291      }
 292  }
 293  
 294  class problem_000005 extends problem_base {
 295      function title() {
 296          return 'PHP: session.auto_start is enabled';
 297      }
 298      function exists() {
 299          return ini_get_bool('session.auto_start');
 300      }
 301      function severity() {
 302          return SEVERITY_CRITICAL;
 303      }
 304      function description() {
 305          return 'Your PHP configuration includes an enabled setting, session.auto_start, that <strong>must be disabled</strong> in order for Moodle to work correctly. Notable symptoms arising from this misconfiguration include fatal errors and/or blank pages when trying to log in.';
 306      }
 307      function solution() {
 308          global $CFG;
 309          return '<p>There are two ways you can solve this problem:</p><ol><li>If you have access to your main <strong>php.ini</strong> file, then find the line that looks like this: <pre>session.auto_start = 1</pre> and change it to <pre>session.auto_start = 0</pre> and then restart your web server. Be warned that this, as any other PHP setting change, might affect other web applications running on the server.</li><li>Finally, you may be able to change this setting just for your site by creating or editing the file <strong>'.$CFG->dirroot.'/.htaccess</strong> to contain this line: <pre>php_value session.auto_start "0"</pre></li></ol>';
 310      }
 311  }
 312  
 313  class problem_000006 extends problem_base {
 314      function title() {
 315          return 'PHP: magic_quotes_runtime is enabled';
 316      }
 317      function exists() {
 318          return (ini_get_bool('magic_quotes_runtime'));
 319      }
 320      function severity() {
 321          return SEVERITY_SIGNIFICANT;
 322      }
 323      function description() {
 324          return 'Your PHP configuration includes an enabled setting, magic_quotes_runtime, that <strong>must be disabled</strong> in order for Moodle to work correctly. Notable symptoms arising from this misconfiguration include strange display errors whenever a text field that includes single or double quotes is processed.';
 325      }
 326      function solution() {
 327          global $CFG;
 328          return '<p>There are two ways you can solve this problem:</p><ol><li>If you have access to your main <strong>php.ini</strong> file, then find the line that looks like this: <pre>magic_quotes_runtime = On</pre> and change it to <pre>magic_quotes_runtime = Off</pre> and then restart your web server. Be warned that this, as any other PHP setting change, might affect other web applications running on the server.</li><li>Finally, you may be able to change this setting just for your site by creating or editing the file <strong>'.$CFG->dirroot.'/.htaccess</strong> to contain this line: <pre>php_value magic_quotes_runtime "Off"</pre></li></ol>';
 329      }
 330  }
 331  
 332  class problem_000007 extends problem_base {
 333      function title() {
 334          return 'PHP: file_uploads is disabled';
 335      }
 336      function exists() {
 337          return !ini_get_bool('file_uploads');
 338      }
 339      function severity() {
 340          return SEVERITY_SIGNIFICANT;
 341      }
 342      function description() {
 343          return 'Your PHP configuration includes a disabled setting, file_uploads, that <strong>must be enabled</strong> to let Moodle offer its full functionality. Until this setting is enabled, it will not be possible to upload any files into Moodle. This includes, for example, course content and user pictures.';
 344      }
 345      function solution() {
 346          global $CFG;
 347          return '<p>There are two ways you can solve this problem:</p><ol><li>If you have access to your main <strong>php.ini</strong> file, then find the line that looks like this: <pre>file_uploads = Off</pre> and change it to <pre>file_uploads = On</pre> and then restart your web server. Be warned that this, as any other PHP setting change, might affect other web applications running on the server.</li><li>Finally, you may be able to change this setting just for your site by creating or editing the file <strong>'.$CFG->dirroot.'/.htaccess</strong> to contain this line: <pre>php_value file_uploads "On"</pre></li></ol>';
 348      }
 349  }
 350  
 351  class problem_000008 extends problem_base {
 352      function title() {
 353          return 'PHP: memory_limit cannot be controlled by Moodle';
 354      }
 355      function exists() {
 356          $oldmemlimit = @ini_get('memory_limit');
 357          if(empty($oldmemlimit)) {
 358              // PHP not compiled with memory limits, this means that it's
 359              // probably limited to 8M or in case of Windows not at all.
 360              // We can ignore it for now - there is not much to test anyway
 361              // TODO: add manual test that fills memory??
 362              return false;
 363          }
 364          $oldmemlimit = get_real_size($oldmemlimit);
 365          //now lets change the memory limit to something unique below 128M==134217728
 366          @ini_set('memory_limit', 134217720);
 367          $testmemlimit = get_real_size(@ini_get('memory_limit'));
 368          //verify the change had any effect at all
 369          if ($oldmemlimit == $testmemlimit) {
 370              //memory limit can not be changed - is it big enough then?
 371              if ($oldmemlimit < get_real_size('128M')) {
 372                  return true;
 373              } else {
 374                  return false;
 375              }
 376          }
 377          @ini_set('memory_limit', $oldmemlimit);
 378          return false;
 379      }
 380      function severity() {
 381          return SEVERITY_NOTICE;
 382      }
 383      function description() {
 384          return 'The settings for PHP on your server do not allow a script to request more memory during its execution. '.
 385                 'This means that there is a hard limit of '.@ini_get('memory_limit').' for each script. '.
 386                 'It is possible that certain operations within Moodle will require more than this amount in order '.
 387                 'to complete successfully, especially if there are lots of data to be processed.';
 388      }
 389      function solution() {
 390          return 'It is recommended that you contact your web server administrator to address this issue.';
 391      }
 392  }
 393  
 394  class problem_000009 extends problem_base {
 395      function title() {
 396          return 'SQL: using account without password';
 397      }
 398      function exists() {
 399          global $CFG;
 400          return empty($CFG->dbpass);
 401      }
 402      function severity() {
 403          return SEVERITY_CRITICAL;
 404      }
 405      function description() {
 406          global $CFG;
 407          return 'The user account your are connecting to the database server with is set up without a password. This is a very big security risk and is only somewhat lessened if your database is configured to not accept connections from any hosts other than the server Moodle is running on. Unless you use a strong password to connect to the database, you risk unauthorized access to and manipulation of your data.'.($CFG->dbuser != 'root'?'':' <strong>This is especially alarming because such access to the database would be as the superuser (root)!</strong>');
 408      }
 409      function solution() {
 410          global $CFG;
 411          return 'You should change the password of the user <strong>'.$CFG->dbuser.'</strong> both in your database and in your Moodle <strong>config.php</strong> immediately!'.($CFG->dbuser != 'root'?'':' It would also be a good idea to change the user account from root to something else, because this would lessen the impact in the event that your database is compromised anyway.');
 412      }
 413  }
 414  
 415  class problem_000010 extends problem_base {
 416      function title() {
 417          return 'Uploaded files: slasharguments disabled or not working';
 418      }
 419      function exists() {
 420          if (!$this->is_enabled()) {
 421              return true;
 422          }
 423          if ($this->status() < 1) {
 424              return true;
 425          }
 426          return false;
 427      }
 428      function severity() {
 429          if ($this->is_enabled() and $this->status() == 0) {
 430              return SEVERITY_SIGNIFICANT;
 431          } else {
 432              return SEVERITY_ANNOYANCE;
 433          }
 434      }
 435      function description() {
 436          global $CFG;
 437          $desc = 'Slasharguments are needed for relative linking in uploaded resources:<ul>';
 438          if (!$this->is_enabled()) {
 439              $desc .= '<li>slasharguments are <strong>disabled</strong> in Moodle configuration</li>';
 440          } else {
 441              $desc .= '<li>slasharguments are enabled in Moodle configuration</li>';
 442          }
 443          if ($this->status() == -1) {
 444              $desc .= '<li>can not run automatic test, you can verify it <a href="'.$CFG->wwwroot.'/file.php/testslasharguments" target="_blank">here</a> manually</li>';
 445          } else if ($this->status() == 0) {
 446              $desc .= '<li>slashargument test <strong>failed</strong>, please check server configuration</li>';
 447          } else {
 448              $desc .= '<li>slashargument test passed</li>';
 449          }
 450          $desc .= '</ul>';
 451          return $desc;
 452      }
 453      function solution() {
 454          global $CFG;
 455          $enabled = $this->is_enabled();
 456          $status = $this->status();
 457          $solution = '';
 458          if ($enabled and ($status == 0)) {
 459              $solution .= 'Slasharguments are enabled, but the test failed. Please disable slasharguments in Moodle configuration or fix the server configuration.<hr />';
 460          } else if ((!$enabled) and ($status == 0)) {
 461              $solution .= 'Slasharguments are disabled and the test failed. You may try to fix the server configuration.<hr />';
 462          } else if ($enabled and ($status == -1)) {
 463              $solution .= 'Slasharguments are enabled, <a href="'.$CFG->wwwroot.'/file.php/testslasharguments">automatic testing</a> not possible.<hr />';
 464          } else if ((!$enabled) and ($status == -1)) {
 465              $solution .= 'Slasharguments are disabled, <a href="'.$CFG->wwwroot.'/file.php/testslasharguments">automatic testing</a> not possible.<hr />';
 466          } else if ((!$enabled) and ($status > 0)) {
 467              $solution .= 'Slasharguments are disabled though the iternal test is OK. You should enable slasharguments in Moodle configuration.';
 468          } else if ($enabled and ($status > 0)) {
 469              $solution .= 'Congratulations - everything seems OK now :-D';
 470          }
 471          if ($status < 1) {
 472              $solution .= '<p>IIS:<ul><li>try to add <code>cgi.fix_pathinfo=1</code> to php.ini</li><li>do NOT enable AllowPathInfoForScriptMappings !!!</li><li>slasharguments may not work when using ISAPI and PHP 4.3.10 and older</li></ul></p>';
 473              $solution .= '<p>Apache 1:<ul><li>try to add <code>cgi.fix_pathinfo=1</code> to php.ini</li></ul></p>';
 474              $solution .= '<p>Apache 2:<ul><li>you must add <code>AcceptPathInfo on</code> to php.ini or .htaccess</li><li>try to add <code>cgi.fix_pathinfo=1</code> to php.ini</li></ul></p>';
 475          }
 476          return $solution;
 477      }
 478      function is_enabled() {
 479          global $CFG;
 480          return !empty($CFG->slasharguments);
 481      }
 482      function status() {
 483          global $CFG;
 484          $handle = @fopen($CFG->wwwroot.'/file.php?file=/testslasharguments', "r");
 485          $contents = @trim(fread($handle, 10));
 486          @fclose($handle);
 487          if ($contents != 'test -1') {
 488              return -1;
 489          }
 490          $handle = @fopen($CFG->wwwroot.'/file.php/testslasharguments', "r");
 491          $contents = trim(@fread($handle, 10));
 492          @fclose($handle);
 493          switch ($contents) {
 494              case 'test 1': return 1;
 495              case 'test 2': return 2;
 496              default:  return 0;
 497          }
 498      }
 499  }
 500  
 501  class problem_000011 extends problem_base {
 502      function title() {
 503          return 'Session errors detected';
 504      }
 505      function exists() {
 506          global $CFG;
 507          return isset($CFG->session_error_counter);
 508      }
 509      function severity() {
 510          return SEVERITY_ANNOYANCE;
 511      }
 512      function description() {
 513          global $CFG;
 514          if (isset($CFG->session_error_counter)) {
 515              return 'Session problems were detected. Total count: '.$CFG->session_error_counter;
 516          } else {
 517              return 'No session errors detected.';
 518          }
 519      }
 520      function solution() {
 521          global $CFG;
 522          if (optional_param('resetsesserrorcounter', 0, PARAM_BOOL)) {
 523              if (get_field('config', 'name', 'name', 'session_error_counter')) {
 524                  delete_records('config', 'name', 'session_error_counter');
 525              }
 526              return 'Error counter was cleared.';
 527          } else {
 528              return '<p>Session errors can be caused by:</p><ul>' .
 529              '<li>unresolved problem in server software (aka random switching of users),</li>' .
 530              '<li>blocked or modified cookies,</li>' .
 531              '<li>deleting of active session files.</li>' .
 532              '</ul><p><a href="'.me().'&amp;resetsesserrorcounter=1">Reset counter</a></p>';
 533          }
 534      }
 535  }
 536  
 537  class problem_000012 extends problem_base {
 538      function title() {
 539          return 'Random questions data consistency';
 540      }
 541      function exists() {
 542          return record_exists_select('question', "qtype = 'random' AND parent <> id");
 543      }
 544      function severity() {
 545          return SEVERITY_ANNOYANCE;
 546      }
 547      function description() {
 548          return '<p>For random questions, question.parent should equal question.id. ' .
 549          'There are some questions in your database for which this is not true. ' .
 550          'One way that this could have happened is for random questions restored from backup before ' .
 551          '<a href="http://tracker.moodle.org/browse/MDL-5482">MDL-5482</a> was fixed.</p>';
 552      }
 553      function solution() {
 554          global $CFG;
 555          return '<p>Upgrade to Moodle 1.9.1 or later, or manually execute the SQL</p>' .
 556          '<pre>UPDATE ' . $CFG->prefix . 'question SET parent = id WHERE qtype = \'random\' and parent &lt;> id;</pre>';
 557      }
 558  }
 559  
 560  class problem_000013 extends problem_base {
 561      function title() {
 562          return 'Multi-answer questions data consistency';
 563      }
 564      function exists() {
 565          global $CFG;
 566          $positionexpr = sql_position(sql_concat("','", "q.id", "','"), 
 567                  sql_concat("','", "qma.sequence", "','"));
 568          return record_exists_sql("
 569                  SELECT * FROM {$CFG->prefix}question q
 570                      JOIN {$CFG->prefix}question_multianswer qma ON $positionexpr > 0
 571                  WHERE qma.question <> q.parent") ||
 572              record_exists_sql("
 573                  SELECT * FROM {$CFG->prefix}question q
 574                      JOIN {$CFG->prefix}question parent_q ON parent_q.id = q.parent
 575                  WHERE q.category <> parent_q.category");
 576      }
 577      function severity() {
 578          return SEVERITY_ANNOYANCE;
 579      }
 580      function description() {
 581          return '<p>For each sub-question whose id is listed in ' .
 582          'question_multianswer.sequence, its question.parent field should equal ' .
 583          'question_multianswer.question; and each sub-question should be in the same ' .
 584          'category as its parent. There are questions in your database for ' .
 585          'which this is not the case. One way that this could have happened is ' .
 586          'for multi-answer questions restored from backup before ' .
 587          '<a href="http://tracker.moodle.org/browse/MDL-14750">MDL-14750</a> was fixed.</p>';
 588      }
 589      function solution() {
 590          return '<p>Upgrade to Moodle 1.9.1 or later, or manually execute the ' .
 591          'code in question_multianswer_fix_subquestion_parents_and_categories in ' .
 592          '<a href="http://cvs.moodle.org/moodle/question/type/multianswer/db/upgrade.php?revision=1.1.10.2&amp;view=markup">/question/type/multianswer/db/upgrade.php' .
 593          'from the 1.9 stable branch</a>.</p>';
 594      }
 595  }
 596  
 597  class problem_000014 extends problem_base {
 598      function title() {
 599          return 'Only multianswer and random questions should be the parent of another question';
 600      }
 601      function exists() {
 602          global $CFG;
 603          return record_exists_sql("
 604                  SELECT * FROM {$CFG->prefix}question q
 605                      JOIN {$CFG->prefix}question parent_q ON parent_q.id = q.parent
 606                  WHERE parent_q.qtype NOT IN ('random', 'multianswer')");
 607      }
 608      function severity() {
 609          return SEVERITY_ANNOYANCE;
 610      }
 611      function description() {
 612          return '<p>You have questions that violate this in your databse. ' .
 613          'You will need to investigate to determine how this happened.</p>';
 614      }
 615      function solution() {
 616          return '<p>It is impossible to give a solution without knowing more about ' .
 617          ' how the problem was caused. You may be able to get help from the ' .
 618          '<a href="http://moodle.org/mod/forum/view.php?f=121">Quiz forum</a>.</p>';
 619      }
 620  }
 621  
 622  class problem_000015 extends problem_base {
 623      function title() {
 624          return 'Question categories should belong to a valid context';
 625      }
 626      function exists() {
 627          global $CFG;
 628          return record_exists_sql("
 629              SELECT qc.*, (SELECT COUNT(1) FROM {$CFG->prefix}question q WHERE q.category = qc.id) AS numquestions
 630              FROM {$CFG->prefix}question_categories qc
 631                  LEFT JOIN {$CFG->prefix}context con ON qc.contextid = con.id
 632              WHERE con.id IS NULL");
 633      }
 634      function severity() {
 635          return SEVERITY_ANNOYANCE;
 636      }
 637      function description() {
 638          global $CFG;
 639          $problemcategories = get_records_sql("
 640              SELECT qc.id, qc.name, qc.contextid, (SELECT COUNT(1) FROM {$CFG->prefix}question q WHERE q.category = qc.id) AS numquestions
 641              FROM {$CFG->prefix}question_categories qc
 642                  LEFT JOIN {$CFG->prefix}context con ON qc.contextid = con.id
 643              WHERE con.id IS NULL
 644              ORDER BY numquestions DESC, qc.name");
 645          $table = '<table><thead><tr><th>Cat id</th><th>Category name</th>' .
 646          "<th>Context id</th><th>Num Questions</th></tr></thead><tbody>\n";
 647          if ($problemcategories) {
 648              foreach ($problemcategories as $cat) {
 649                  $table .= "<tr><td>$cat->id</td><td>" . s($cat->name) . "</td><td>" .
 650                  $cat->contextid ."</td><td>$cat->numquestions</td></tr>\n";
 651              }
 652          }
 653          $table .= '</tbody></table>';
 654          return '<p>All question categories are linked to a context id, and, ' .
 655          'the context they are linked to must exist. The following categories ' .
 656          'belong to a non-existant category:</p>' . $table . '<p>Any of these ' .
 657          'categories that contain no questions can just be deleted form the database. ' .
 658          'Other categories will require more thought.</p>';
 659      }
 660      function solution() {
 661          global $CFG;
 662          return '<p>You can delete the empty categories by executing the following SQL:</p><pre>
 663  DELETE FROM ' . $CFG->prefix . 'question_categories qc
 664  WHERE
 665      NOT EXISTS (SELECT * FROM ' . $CFG->prefix . 'question q WHERE q.category = qc.id)
 666  AND NOT EXISTS (SELECT * FROM ' . $CFG->prefix . 'context con WHERE qc.contextid = con.id)
 667          </pre><p>Any remaining categories that contain questions will require more thought. ' .
 668          'People in the <a href="http://moodle.org/mod/forum/view.php?f=121">Quiz forum</a> may be able to help.</p>';
 669      }
 670  }
 671  
 672  class problem_000016 extends problem_base {
 673      function title() {
 674          return 'Question categories should belong to the same context as their parent';
 675      }
 676      function exists() {
 677          global $CFG;
 678          return record_exists_sql("
 679              SELECT parent_qc.id AS parent, child_qc.id AS child, child_qc.contextid
 680              FROM {$CFG->prefix}question_categories child_qc
 681                  JOIN {$CFG->prefix}question_categories parent_qc ON child_qc.parent = parent_qc.id
 682              WHERE child_qc.contextid <> parent_qc.contextid");
 683      }
 684      function severity() {
 685          return SEVERITY_ANNOYANCE;
 686      }
 687      function description() {
 688          global $CFG;
 689          $problemcategories = get_records_sql("
 690              SELECT
 691                  parent_qc.id AS parentid, parent_qc.name AS parentname, parent_qc.contextid AS parentcon,
 692                  child_qc.id AS childid, child_qc.name AS childname, child_qc.contextid AS childcon
 693              FROM {$CFG->prefix}question_categories child_qc
 694                  JOIN {$CFG->prefix}question_categories parent_qc ON child_qc.parent = parent_qc.id
 695              WHERE child_qc.contextid <> parent_qc.contextid");
 696          $table = '<table><thead><tr><th colspan="3">Child category</th><th colspan="3">Parent category</th></tr><tr>' .
 697          '<th>Id</th><th>Name</th><th>Context id</th>' .
 698          '<th>Id</th><th>Name</th><th>Context id</th>' .
 699          "</tr></thead><tbody>\n";
 700          if ($problemcategories) {
 701              foreach ($problemcategories as $cat) {
 702                  $table .= "<tr><td>$cat->childid</td><td>" . s($cat->childname) .
 703                  "</td><td>$cat->childcon</td><td>$cat->parentid</td><td>" . s($cat->parentname) .
 704                  "</td><td>$cat->parentcon</td></tr>\n";
 705              }
 706          }
 707          $table .= '</tbody></table>';
 708          return '<p>When one question category is the parent of another, then they ' .
 709          'should both belong to the same context. This is not true for the following categories:</p>' .
 710          $table;
 711      }
 712      function solution() {
 713          return '<p>An automated solution is difficult. It depends whether the ' .
 714          'parent or child category is in the wrong pace.' .
 715          'People in the <a href="http://moodle.org/mod/forum/view.php?f=121">Quiz forum</a> may be able to help.</p>';
 716      }
 717  }
 718  
 719  class problem_000017 extends problem_base {
 720      function title() {
 721          return 'Question categories tree structure';
 722      }
 723      function find_problems() {
 724          static $answer = null;
 725  
 726          if (is_null($answer)) {
 727              $categories = get_records('question_categories', '', '', 'id');
 728  
 729              // Look for missing parents.
 730              $missingparent = array();
 731              foreach ($categories as $category) {
 732                  if ($category->parent != 0 && !array_key_exists($category->parent, $categories)) {
 733                      $missingparent[$category->id] = $category;
 734                  }
 735              }
 736  
 737              // Look for loops.
 738              $loops = array();
 739              while (!empty($categories)) {
 740                  $current = array_pop($categories);
 741                  $thisloop = array($current->id => $current);
 742                  while (true) {
 743                      if (isset($thisloop[$current->parent])) {
 744                          // Loop detected
 745                          $loops[$current->id] = $thisloop;
 746                          break;
 747                      } else if (!isset($categories[$current->parent])) {
 748                          // Got to the top level, or a category we already know is OK.
 749                          break;
 750                      } else {
 751                          // Continue following the path.
 752                          $current = $categories[$current->parent];
 753                          $thisloop[$current->id] = $current;
 754                          unset($categories[$current->id]);
 755                      }
 756                  }
 757              }
 758  
 759              $answer = array($missingparent, $loops);
 760          }
 761  
 762          return $answer;
 763      }
 764      function exists() {
 765          list($missingparent, $loops) = $this->find_problems();
 766          return !empty($missingparent) || !empty($loops);
 767      }
 768      function severity() {
 769          return SEVERITY_ANNOYANCE;
 770      }
 771      function description() {
 772          list($missingparent, $loops) = $this->find_problems();
 773  
 774          $description = '<p>The question categories should be arranged into tree ' .
 775                  ' structures by the question_categories.parent field. Sometimes ' .
 776                  ' this tree structure gets messed up.</p>';
 777  
 778          if (!empty($missingparent)) {
 779              $description .= '<p>The following categories are missing their parents:</p><ul>';
 780              foreach ($missingparent as $cat) {
 781                  $description .= "<li>Category $cat->id: " . s($cat->name) . "</li>\n";
 782              }
 783              $description .= "</ul>\n";
 784          }
 785  
 786          if (!empty($loops)) {
 787              $description .= '<p>The following categories form a loop of parents:</p><ul>';
 788              foreach ($loops as $loop) {
 789                  $description .= "<li><ul>\n";
 790                  foreach ($loop as $cat) {
 791                      $description .= "<li>Category $cat->id: " . s($cat->name) . " has parent $cat->parent</li>\n";
 792                  }
 793                  $description .= "</ul></li>\n";
 794              }
 795              $description .= "</ul>\n";
 796          }
 797  
 798          return $description;
 799      }
 800      function solution() {
 801          global $CFG;
 802          list($missingparent, $loops) = $this->find_problems();
 803  
 804          $solution = '<p>Consider executing the following SQL queries. These fix ' .
 805                  'the problem by moving some categories to the top level.</p>';
 806  
 807          if (!empty($missingparent)) {
 808              $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
 809                      "        SET parent = 0\n" .
 810                      "        WHERE id IN (" . implode(',', array_keys($missingparent)) . ");</pre>\n";
 811          }
 812  
 813          if (!empty($loops)) {
 814              $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
 815                      "        SET parent = 0\n" .
 816                      "        WHERE id IN (" . implode(',', array_keys($loops)) . ");</pre>\n";
 817          }
 818  
 819          return $solution;
 820      }
 821  }
 822  
 823  class problem_00000x extends problem_base {
 824      function title() {
 825          return '';
 826      }
 827      function exists() {
 828          return false;
 829      }
 830      function severity() {
 831          return SEVERITY_SIGNIFICANT;
 832      }
 833      function description() {
 834          return '';
 835      }
 836      function solution() {
 837          global $CFG;
 838          return '';
 839      }
 840  }
 841  
 842  /*
 843  
 844  TODO:
 845  
 846      session.save_path -- it doesn't really matter because we are already IN a session, right?
 847      detect unsupported characters in $CFG->wwwroot - see bug Bug #6091 - relative vs absolute path during backup/restore process
 848  
 849  */
 850  
 851  ?>


Generated: Wed Jan 14 11:33:29 2009 Cross-referenced by PHPXref 0.7