[ Index ]

PHP Cross Reference of Unnamed Project

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1