Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • Differences Between: [Versions 28 and 29] [Versions 28 and 30] [Versions 28 and 31] [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       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  */
    

    Search This Site: