[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/admin/tool/health/ -> index.php (source)

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Strings for component 'tool_health', language 'en', branch 'MOODLE_22_STABLE'
  19   *
  20   * @package    tool
  21   * @subpackage health
  22   * @copyright  1999 onwards Martin Dougiamas (http://dougiamas.com)
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26      ob_start(); //for whitespace test
  27      require('../../../config.php');
  28      $extraws = ob_get_clean();
  29  
  30      require_once($CFG->libdir.'/adminlib.php');
  31  
  32      admin_externalpage_setup('toolhealth');
  33  
  34      define('SEVERITY_NOTICE',      'notice');
  35      define('SEVERITY_ANNOYANCE',   'annoyance');
  36      define('SEVERITY_SIGNIFICANT', 'significant');
  37      define('SEVERITY_CRITICAL',    'critical');
  38  
  39      $solution = optional_param('solution', 0, PARAM_PLUGIN);
  40  
  41      require_login();
  42      require_capability('moodle/site:config', context_system::instance());
  43  
  44      $site = get_site();
  45  
  46      echo $OUTPUT->header();
  47  
  48      if(strpos($solution, 'problem_') === 0 && class_exists($solution)) {
  49          health_print_solution($solution);
  50      }
  51      else {
  52          health_find_problems();
  53      }
  54  
  55  
  56      echo $OUTPUT->footer();
  57  
  58  
  59  function health_find_problems() {
  60      global $OUTPUT;
  61  
  62      echo $OUTPUT->heading(get_string('pluginname', 'tool_health'));
  63  
  64      $issues   = array(
  65          SEVERITY_CRITICAL    => array(),
  66          SEVERITY_SIGNIFICANT => array(),
  67          SEVERITY_ANNOYANCE   => array(),
  68          SEVERITY_NOTICE      => array(),
  69      );
  70      $problems = 0;
  71  
  72      for($i = 1; $i < 1000000; ++$i) {
  73          $classname = sprintf('problem_%06d', $i);
  74          if(!class_exists($classname)) {
  75              continue;
  76          }
  77          $problem = new $classname;
  78  
  79          if($problem->exists()) {
  80              $severity = $problem->severity();
  81              $issues[$severity][$classname] = array(
  82                  'severity'    => $severity,
  83                  'description' => $problem->description(),
  84                  'title'       => $problem->title()
  85              );
  86              ++$problems;
  87          }
  88          unset($problem);
  89      }
  90  
  91      if($problems == 0) {
  92          echo '<div id="healthnoproblemsfound">';
  93          echo get_string('healthnoproblemsfound', 'tool_health');
  94          echo '</div>';
  95      }
  96      else {
  97          echo $OUTPUT->heading(get_string('healthproblemsdetected', 'tool_health'));
  98          $severities = array(SEVERITY_CRITICAL, SEVERITY_SIGNIFICANT, SEVERITY_ANNOYANCE, SEVERITY_NOTICE);
  99          foreach($severities as $severity) {
 100              if(!empty($issues[$severity])) {
 101                  echo '<dl class="healthissues '.$severity.'">';
 102                  foreach($issues[$severity] as $classname => $data) {
 103                      echo '<dt id="'.$classname.'">'.$data['title'].'</dt>';
 104                      echo '<dd>'.$data['description'];
 105                      echo '<form action="index.php#solution" method="get">';
 106                      echo '<input type="hidden" name="solution" value="'.$classname.'" /><input type="submit" value="'.get_string('viewsolution').'" />';
 107                      echo '</form></dd>';
 108                  }
 109                  echo '</dl>';
 110              }
 111          }
 112      }
 113  }
 114  
 115  function health_print_solution($classname) {
 116      global $OUTPUT;
 117      $problem = new $classname;
 118      $data = array(
 119          'title'       => $problem->title(),
 120          'severity'    => $problem->severity(),
 121          'description' => $problem->description(),
 122          'solution'    => $problem->solution()
 123      );
 124  
 125      echo $OUTPUT->heading(get_string('pluginname', 'tool_health'));
 126      echo $OUTPUT->heading(get_string('healthproblemsolution', 'tool_health'));
 127      echo '<dl class="healthissues '.$data['severity'].'">';
 128      echo '<dt>'.$data['title'].'</dt>';
 129      echo '<dd>'.$data['description'].'</dd>';
 130      echo '<dt id="solution" class="solution">'.get_string('healthsolution', 'tool_health').'</dt>';
 131      echo '<dd class="solution">'.$data['solution'].'</dd></dl>';
 132      echo '<form id="healthformreturn" action="index.php#'.$classname.'" method="get">';
 133      echo '<input type="submit" value="'.get_string('healthreturntomain', 'tool_health').'" />';
 134      echo '</form>';
 135  }
 136  
 137  class problem_base {
 138      function exists() {
 139          return false;
 140      }
 141      function title() {
 142          return '???';
 143      }
 144      function severity() {
 145          return SEVERITY_NOTICE;
 146      }
 147      function description() {
 148          return '';
 149      }
 150      function solution() {
 151          return '';
 152      }
 153  }
 154  
 155  class problem_000002 extends problem_base {
 156      function title() {
 157          return 'Extra characters at the end of config.php or other library function';
 158      }
 159      function exists() {
 160          global $extraws;
 161  
 162          if($extraws === '') {
 163              return false;
 164          }
 165          return true;
 166      }
 167      function severity() {
 168          return SEVERITY_SIGNIFICANT;
 169      }
 170      function description() {
 171          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.';
 172      }
 173      function solution() {
 174          global $CFG;
 175          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.';
 176      }
 177  }
 178  
 179  class problem_000003 extends problem_base {
 180      function title() {
 181          return '$CFG->dataroot does not exist or does not have write permissions';
 182      }
 183      function exists() {
 184          global $CFG;
 185          if(!is_dir($CFG->dataroot) || !is_writable($CFG->dataroot)) {
 186              return true;
 187          }
 188          return false;
 189      }
 190      function severity() {
 191          return SEVERITY_SIGNIFICANT;
 192      }
 193      function description() {
 194          global $CFG;
 195          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.';
 196      }
 197      function solution() {
 198          global $CFG;
 199          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.';
 200      }
 201  }
 202  
 203  class problem_000004 extends problem_base {
 204      function title() {
 205          return 'cron.php is not set up to run automatically';
 206      }
 207      function exists() {
 208          global $DB;
 209          $lastcron = $DB->get_field_sql('SELECT max(lastcron) FROM {modules}');
 210          return (time() - $lastcron > 3600 * 24);
 211      }
 212      function severity() {
 213          return SEVERITY_SIGNIFICANT;
 214      }
 215      function description() {
 216          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.';
 217      }
 218      function solution() {
 219          global $CFG;
 220          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.';
 221      }
 222  }
 223  
 224  class problem_000005 extends problem_base {
 225      function title() {
 226          return 'PHP: session.auto_start is enabled';
 227      }
 228      function exists() {
 229          return ini_get_bool('session.auto_start');
 230      }
 231      function severity() {
 232          return SEVERITY_CRITICAL;
 233      }
 234      function description() {
 235          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.';
 236      }
 237      function solution() {
 238          global $CFG;
 239          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>';
 240      }
 241  }
 242  
 243  class problem_000007 extends problem_base {
 244      function title() {
 245          return 'PHP: file_uploads is disabled';
 246      }
 247      function exists() {
 248          return !ini_get_bool('file_uploads');
 249      }
 250      function severity() {
 251          return SEVERITY_SIGNIFICANT;
 252      }
 253      function description() {
 254          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.';
 255      }
 256      function solution() {
 257          global $CFG;
 258          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>';
 259      }
 260  }
 261  
 262  class problem_000008 extends problem_base {
 263      function title() {
 264          return 'PHP: memory_limit cannot be controlled by Moodle';
 265      }
 266      function exists() {
 267          global $CFG;
 268  
 269          $oldmemlimit = @ini_get('memory_limit');
 270          if (empty($oldmemlimit)) {
 271              // PHP not compiled with memory limits, this means that it's
 272              // probably limited to 8M or in case of Windows not at all.
 273              // We can ignore it for now - there is not much to test anyway
 274              // TODO: add manual test that fills memory??
 275              return false;
 276          }
 277          $oldmemlimit = get_real_size($oldmemlimit);
 278          //now lets change the memory limit to something higher
 279          $newmemlimit = ($oldmemlimit + 1024*1024*5);
 280          raise_memory_limit($newmemlimit);
 281          $testmemlimit = get_real_size(@ini_get('memory_limit'));
 282          //verify the change had any effect at all
 283          if ($oldmemlimit == $testmemlimit) {
 284              //memory limit can not be changed - is it big enough then?
 285              if ($oldmemlimit < get_real_size('128M')) {
 286                  return true;
 287              } else {
 288                  return false;
 289              }
 290          }
 291          reduce_memory_limit($oldmemlimit);
 292          return false;
 293      }
 294      function severity() {
 295          return SEVERITY_NOTICE;
 296      }
 297      function description() {
 298          return 'The settings for PHP on your server do not allow a script to request more memory during its execution. '.
 299                 'This means that there is a hard limit of '.@ini_get('memory_limit').' for each script. '.
 300                 'It is possible that certain operations within Moodle will require more than this amount in order '.
 301                 'to complete successfully, especially if there are lots of data to be processed.';
 302      }
 303      function solution() {
 304          return 'It is recommended that you contact your web server administrator to address this issue.';
 305      }
 306  }
 307  
 308  class problem_000009 extends problem_base {
 309      function title() {
 310          return 'SQL: using account without password';
 311      }
 312      function exists() {
 313          global $CFG;
 314          return empty($CFG->dbpass);
 315      }
 316      function severity() {
 317          return SEVERITY_CRITICAL;
 318      }
 319      function description() {
 320          global $CFG;
 321          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>');
 322      }
 323      function solution() {
 324          global $CFG;
 325          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.');
 326      }
 327  }
 328  /* // not implemented in 2.0 yet
 329  class problem_000010 extends problem_base {
 330      function title() {
 331          return 'Uploaded files: slasharguments disabled or not working';
 332      }
 333      function exists() {
 334          if (!$this->is_enabled()) {
 335              return true;
 336          }
 337          if ($this->status() < 1) {
 338              return true;
 339          }
 340          return false;
 341      }
 342      function severity() {
 343          if ($this->is_enabled() and $this->status() == 0) {
 344              return SEVERITY_SIGNIFICANT;
 345          } else {
 346              return SEVERITY_ANNOYANCE;
 347          }
 348      }
 349      function description() {
 350          global $CFG;
 351          $desc = 'Slasharguments are needed for relative linking in uploaded resources:<ul>';
 352          if (!$this->is_enabled()) {
 353              $desc .= '<li>slasharguments are <strong>disabled</strong> in Moodle configuration</li>';
 354          } else {
 355              $desc .= '<li>slasharguments are enabled in Moodle configuration</li>';
 356          }
 357          if ($this->status() == -1) {
 358              $desc .= '<li>can not run automatic test, you can verify it <a href="'.$CFG->wwwroot.'/file.php/testslasharguments" target="_blank">here</a> manually</li>';
 359          } else if ($this->status() == 0) {
 360              $desc .= '<li>slashargument test <strong>failed</strong>, please check server configuration</li>';
 361          } else {
 362              $desc .= '<li>slashargument test passed</li>';
 363          }
 364          $desc .= '</ul>';
 365          return $desc;
 366      }
 367      function solution() {
 368          global $CFG;
 369          $enabled = $this->is_enabled();
 370          $status = $this->status();
 371          $solution = '';
 372          if ($enabled and ($status == 0)) {
 373              $solution .= 'Slasharguments are enabled, but the test failed. Please disable slasharguments in Moodle configuration or fix the server configuration.<hr />';
 374          } else if ((!$enabled) and ($status == 0)) {
 375              $solution .= 'Slasharguments are disabled and the test failed. You may try to fix the server configuration.<hr />';
 376          } else if ($enabled and ($status == -1)) {
 377              $solution .= 'Slasharguments are enabled, <a href="'.$CFG->wwwroot.'/file.php/testslasharguments">automatic testing</a> not possible.<hr />';
 378          } else if ((!$enabled) and ($status == -1)) {
 379              $solution .= 'Slasharguments are disabled, <a href="'.$CFG->wwwroot.'/file.php/testslasharguments">automatic testing</a> not possible.<hr />';
 380          } else if ((!$enabled) and ($status > 0)) {
 381              $solution .= 'Slasharguments are disabled though the iternal test is OK. You should enable slasharguments in Moodle configuration.';
 382          } else if ($enabled and ($status > 0)) {
 383              $solution .= 'Congratulations - everything seems OK now :-D';
 384          }
 385          if ($status < 1) {
 386              $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>';
 387              $solution .= '<p>Apache 1:<ul><li>try to add <code>cgi.fix_pathinfo=1</code> to php.ini</li></ul></p>';
 388              $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>';
 389          }
 390          return $solution;
 391      }
 392      function is_enabled() {
 393          global $CFG;
 394          return !empty($CFG->slasharguments);
 395      }
 396      function status() {
 397          global $CFG;
 398          $handle = @fopen($CFG->wwwroot.'/file.php?file=/testslasharguments', "r");
 399          $contents = @trim(fread($handle, 10));
 400          @fclose($handle);
 401          if ($contents != 'test -1') {
 402              return -1;
 403          }
 404          $handle = @fopen($CFG->wwwroot.'/file.php/testslasharguments', "r");
 405          $contents = trim(@fread($handle, 10));
 406          @fclose($handle);
 407          switch ($contents) {
 408              case 'test 1': return 1;
 409              case 'test 2': return 2;
 410              default:  return 0;
 411          }
 412      }
 413  }*/
 414  
 415  class problem_000012 extends problem_base {
 416      function title() {
 417          return 'Random questions data consistency';
 418      }
 419      function exists() {
 420          global $DB;
 421          return $DB->record_exists_select('question', "qtype = 'random' AND parent <> id", array());
 422      }
 423      function severity() {
 424          return SEVERITY_ANNOYANCE;
 425      }
 426      function description() {
 427          return '<p>For random questions, question.parent should equal question.id. ' .
 428          'There are some questions in your database for which this is not true. ' .
 429          'One way that this could have happened is for random questions restored from backup before ' .
 430          '<a href="http://tracker.moodle.org/browse/MDL-5482">MDL-5482</a> was fixed.</p>';
 431      }
 432      function solution() {
 433          global $CFG;
 434          return '<p>Upgrade to Moodle 1.9.1 or later, or manually execute the SQL</p>' .
 435          '<pre>UPDATE ' . $CFG->prefix . 'question SET parent = id WHERE qtype = \'random\' and parent &lt;> id;</pre>';
 436      }
 437  }
 438  
 439  class problem_000013 extends problem_base {
 440      function title() {
 441          return 'Multi-answer questions data consistency';
 442      }
 443      function exists() {
 444          global $DB;
 445          $positionexpr = $DB->sql_position($DB->sql_concat("','", "q.id", "','"),
 446                  $DB->sql_concat("','", "qma.sequence", "','"));
 447          return $DB->record_exists_sql("
 448                  SELECT * FROM {question} q
 449                      JOIN {question_multianswer} qma ON $positionexpr > 0
 450                  WHERE qma.question <> q.parent") ||
 451              $DB->record_exists_sql("
 452                  SELECT * FROM {question} q
 453                      JOIN {question} parent_q ON parent_q.id = q.parent
 454                  WHERE q.category <> parent_q.category");
 455      }
 456      function severity() {
 457          return SEVERITY_ANNOYANCE;
 458      }
 459      function description() {
 460          return '<p>For each sub-question whose id is listed in ' .
 461          'question_multianswer.sequence, its question.parent field should equal ' .
 462          'question_multianswer.question; and each sub-question should be in the same ' .
 463          'category as its parent. There are questions in your database for ' .
 464          'which this is not the case. One way that this could have happened is ' .
 465          'for multi-answer questions restored from backup before ' .
 466          '<a href="http://tracker.moodle.org/browse/MDL-14750">MDL-14750</a> was fixed.</p>';
 467      }
 468      function solution() {
 469          return '<p>Upgrade to Moodle 1.9.1 or later, or manually execute the ' .
 470          'code in question_multianswer_fix_subquestion_parents_and_categories in ' .
 471          '<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' .
 472          'from the 1.9 stable branch</a>.</p>';
 473      }
 474  }
 475  
 476  class problem_000014 extends problem_base {
 477      function title() {
 478          return 'Only multianswer and random questions should be the parent of another question';
 479      }
 480      function exists() {
 481          global $DB;
 482          return $DB->record_exists_sql("
 483                  SELECT * FROM {question} q
 484                      JOIN {question} parent_q ON parent_q.id = q.parent
 485                  WHERE parent_q.qtype NOT IN ('random', 'multianswer')");
 486      }
 487      function severity() {
 488          return SEVERITY_ANNOYANCE;
 489      }
 490      function description() {
 491          return '<p>You have questions that violate this in your databse. ' .
 492          'You will need to investigate to determine how this happened.</p>';
 493      }
 494      function solution() {
 495          return '<p>It is impossible to give a solution without knowing more about ' .
 496          ' how the problem was caused. You may be able to get help from the ' .
 497          '<a href="http://moodle.org/mod/forum/view.php?f=121">Quiz forum</a>.</p>';
 498      }
 499  }
 500  
 501  class problem_000015 extends problem_base {
 502      function title() {
 503          return 'Question categories should belong to a valid context';
 504      }
 505      function exists() {
 506          global $DB;
 507          return $DB->record_exists_sql("
 508              SELECT qc.*, (SELECT COUNT(1) FROM {question} q WHERE q.category = qc.id) AS numquestions
 509              FROM {question_categories} qc
 510                  LEFT JOIN {context} con ON qc.contextid = con.id
 511              WHERE con.id IS NULL");
 512      }
 513      function severity() {
 514          return SEVERITY_ANNOYANCE;
 515      }
 516      function description() {
 517          global $DB;
 518          $problemcategories = $DB->get_records_sql("
 519              SELECT qc.id, qc.name, qc.contextid, (SELECT COUNT(1) FROM {question} q WHERE q.category = qc.id) AS numquestions
 520              FROM {question_categories} qc
 521                  LEFT JOIN {context} con ON qc.contextid = con.id
 522              WHERE con.id IS NULL
 523              ORDER BY numquestions DESC, qc.name");
 524          $table = '<table><thead><tr><th>Cat id</th><th>Category name</th>' .
 525          "<th>Context id</th><th>Num Questions</th></tr></thead><tbody>\n";
 526          foreach ($problemcategories as $cat) {
 527              $table .= "<tr><td>$cat->id</td><td>" . s($cat->name) . "</td><td>" .
 528              $cat->contextid ."</td><td>$cat->numquestions</td></tr>\n";
 529          }
 530          $table .= '</tbody></table>';
 531          return '<p>All question categories are linked to a context id, and, ' .
 532          'the context they are linked to must exist. The following categories ' .
 533          'belong to a non-existant category:</p>' . $table . '<p>Any of these ' .
 534          'categories that contain no questions can just be deleted form the database. ' .
 535          'Other categories will require more thought.</p>';
 536      }
 537      function solution() {
 538          global $CFG;
 539          return '<p>You can delete the empty categories by executing the following SQL:</p><pre>
 540  DELETE FROM ' . $CFG->prefix . 'question_categories
 541  WHERE
 542      NOT EXISTS (SELECT * FROM ' . $CFG->prefix . 'question q WHERE q.category = ' . $CFG->prefix . 'question_categories.id)
 543  AND NOT EXISTS (SELECT * FROM ' . $CFG->prefix . 'context con WHERE contextid = con.id)
 544          </pre><p>Any remaining categories that contain questions will require more thought. ' .
 545          'People in the <a href="http://moodle.org/mod/forum/view.php?f=121">Quiz forum</a> may be able to help.</p>';
 546      }
 547  }
 548  
 549  class problem_000016 extends problem_base {
 550      function title() {
 551          return 'Question categories should belong to the same context as their parent';
 552      }
 553      function exists() {
 554          global $DB;
 555          return $DB->record_exists_sql("
 556              SELECT parent_qc.id AS parent, child_qc.id AS child, child_qc.contextid
 557              FROM {question_categories} child_qc
 558                  JOIN {question_categories} parent_qc ON child_qc.parent = parent_qc.id
 559              WHERE child_qc.contextid <> parent_qc.contextid");
 560      }
 561      function severity() {
 562          return SEVERITY_ANNOYANCE;
 563      }
 564      function description() {
 565          global $DB;
 566          $problemcategories = $DB->get_records_sql("
 567              SELECT
 568                  parent_qc.id AS parentid, parent_qc.name AS parentname, parent_qc.contextid AS parentcon,
 569                  child_qc.id AS childid, child_qc.name AS childname, child_qc.contextid AS childcon
 570              FROM {question_categories} child_qc
 571                  JOIN {question_categories} parent_qc ON child_qc.parent = parent_qc.id
 572              WHERE child_qc.contextid <> parent_qc.contextid");
 573          $table = '<table><thead><tr><th colspan="3">Child category</th><th colspan="3">Parent category</th></tr><tr>' .
 574          '<th>Id</th><th>Name</th><th>Context id</th>' .
 575          '<th>Id</th><th>Name</th><th>Context id</th>' .
 576          "</tr></thead><tbody>\n";
 577          foreach ($problemcategories as $cat) {
 578              $table .= "<tr><td>$cat->childid</td><td>" . s($cat->childname) .
 579              "</td><td>$cat->childcon</td><td>$cat->parentid</td><td>" . s($cat->parentname) .
 580              "</td><td>$cat->parentcon</td></tr>\n";
 581          }
 582          $table .= '</tbody></table>';
 583          return '<p>When one question category is the parent of another, then they ' .
 584          'should both belong to the same context. This is not true for the following categories:</p>' .
 585          $table;
 586      }
 587      function solution() {
 588          return '<p>An automated solution is difficult. It depends whether the ' .
 589          'parent or child category is in the wrong pace.' .
 590          'People in the <a href="http://moodle.org/mod/forum/view.php?f=121">Quiz forum</a> may be able to help.</p>';
 591      }
 592  }
 593  
 594  class problem_000017 extends problem_base {
 595      function title() {
 596          return 'Question categories tree structure';
 597      }
 598      function find_problems() {
 599          global $DB;
 600          static $answer = null;
 601  
 602          if (is_null($answer)) {
 603              $categories = $DB->get_records('question_categories', array(), 'id');
 604  
 605              // Look for missing parents.
 606              $missingparent = array();
 607              foreach ($categories as $category) {
 608                  if ($category->parent != 0 && !array_key_exists($category->parent, $categories)) {
 609                      $missingparent[$category->id] = $category;
 610                  }
 611              }
 612  
 613              // Look for loops.
 614              $loops = array();
 615              while (!empty($categories)) {
 616                  $current = array_pop($categories);
 617                  $thisloop = array($current->id => $current);
 618                  while (true) {
 619                      if (isset($thisloop[$current->parent])) {
 620                          // Loop detected
 621                          $loops[$current->id] = $thisloop;
 622                          break;
 623                      } else if (!isset($categories[$current->parent])) {
 624                          // Got to the top level, or a category we already know is OK.
 625                          break;
 626                      } else {
 627                          // Continue following the path.
 628                          $current = $categories[$current->parent];
 629                          $thisloop[$current->id] = $current;
 630                          unset($categories[$current->id]);
 631                      }
 632                  }
 633              }
 634  
 635              $answer = array($missingparent, $loops);
 636          }
 637  
 638          return $answer;
 639      }
 640      function exists() {
 641          list($missingparent, $loops) = $this->find_problems();
 642          return !empty($missingparent) || !empty($loops);
 643      }
 644      function severity() {
 645          return SEVERITY_ANNOYANCE;
 646      }
 647      function description() {
 648          list($missingparent, $loops) = $this->find_problems();
 649  
 650          $description = '<p>The question categories should be arranged into tree ' .
 651                  ' structures by the question_categories.parent field. Sometimes ' .
 652                  ' this tree structure gets messed up.</p>';
 653  
 654          if (!empty($missingparent)) {
 655              $description .= '<p>The following categories are missing their parents:</p><ul>';
 656              foreach ($missingparent as $cat) {
 657                  $description .= "<li>Category $cat->id: " . s($cat->name) . "</li>\n";
 658              }
 659              $description .= "</ul>\n";
 660          }
 661  
 662          if (!empty($loops)) {
 663              $description .= '<p>The following categories form a loop of parents:</p><ul>';
 664              foreach ($loops as $loop) {
 665                  $description .= "<li><ul>\n";
 666                  foreach ($loop as $cat) {
 667                      $description .= "<li>Category $cat->id: " . s($cat->name) . " has parent $cat->parent</li>\n";
 668                  }
 669                  $description .= "</ul></li>\n";
 670              }
 671              $description .= "</ul>\n";
 672          }
 673  
 674          return $description;
 675      }
 676      function solution() {
 677          global $CFG;
 678          list($missingparent, $loops) = $this->find_problems();
 679  
 680          $solution = '<p>Consider executing the following SQL queries. These fix ' .
 681                  'the problem by moving some categories to the top level.</p>';
 682  
 683          if (!empty($missingparent)) {
 684              $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
 685                      "        SET parent = 0\n" .
 686                      "        WHERE id IN (" . implode(',', array_keys($missingparent)) . ");</pre>\n";
 687          }
 688  
 689          if (!empty($loops)) {
 690              $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
 691                      "        SET parent = 0\n" .
 692                      "        WHERE id IN (" . implode(',', array_keys($loops)) . ");</pre>\n";
 693          }
 694  
 695          return $solution;
 696      }
 697  }
 698  
 699  class problem_00000x extends problem_base {
 700      function title() {
 701          return '';
 702      }
 703      function exists() {
 704          return false;
 705      }
 706      function severity() {
 707          return SEVERITY_SIGNIFICANT;
 708      }
 709      function description() {
 710          return '';
 711      }
 712      function solution() {
 713          global $CFG;
 714          return '';
 715      }
 716  }
 717  
 718  /*
 719  
 720  TODO:
 721  
 722      session.save_path -- it doesn't really matter because we are already IN a session, right?
 723      detect unsupported characters in $CFG->wwwroot - see bug Bug #6091 - relative vs absolute path during backup/restore process
 724  
 725  */


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1