Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/admin/tool/health/index.php
Go to the documentation of this file.
00001 <?php
00002 // This file is part of Moodle - http://moodle.org/
00003 //
00004 // Moodle is free software: you can redistribute it and/or modify
00005 // it under the terms of the GNU General Public License as published by
00006 // the Free Software Foundation, either version 3 of the License, or
00007 // (at your option) any later version.
00008 //
00009 // Moodle is distributed in the hope that it will be useful,
00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012 // GNU General Public License for more details.
00013 //
00014 // You should have received a copy of the GNU General Public License
00015 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00016 
00026     ob_start(); //for whitespace test
00027     require('../../../config.php');
00028     $extraws = ob_get_clean();
00029 
00030     require_once($CFG->libdir.'/adminlib.php');
00031 
00032     admin_externalpage_setup('toolhealth');
00033 
00034     define('SEVERITY_NOTICE',      'notice');
00035     define('SEVERITY_ANNOYANCE',   'annoyance');
00036     define('SEVERITY_SIGNIFICANT', 'significant');
00037     define('SEVERITY_CRITICAL',    'critical');
00038 
00039     $solution = optional_param('solution', 0, PARAM_PLUGIN);
00040 
00041     require_login();
00042     require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
00043 
00044     $site = get_site();
00045 
00046     echo $OUTPUT->header();
00047 
00048     if(strpos($solution, 'problem_') === 0 && class_exists($solution)) {
00049         health_print_solution($solution);
00050     }
00051     else {
00052         health_find_problems();
00053     }
00054 
00055 
00056     echo $OUTPUT->footer();
00057 
00058 
00059 function health_find_problems() {
00060     global $OUTPUT;
00061 
00062     echo $OUTPUT->heading(get_string('pluginname', 'tool_health'));
00063 
00064     $issues   = array(
00065         SEVERITY_CRITICAL    => array(),
00066         SEVERITY_SIGNIFICANT => array(),
00067         SEVERITY_ANNOYANCE   => array(),
00068         SEVERITY_NOTICE      => array(),
00069     );
00070     $problems = 0;
00071 
00072     for($i = 1; $i < 1000000; ++$i) {
00073         $classname = sprintf('problem_%06d', $i);
00074         if(!class_exists($classname)) {
00075             continue;
00076         }
00077         $problem = new $classname;
00078 
00079         if($problem->exists()) {
00080             $severity = $problem->severity();
00081             $issues[$severity][$classname] = array(
00082                 'severity'    => $severity,
00083                 'description' => $problem->description(),
00084                 'title'       => $problem->title()
00085             );
00086             ++$problems;
00087         }
00088         unset($problem);
00089     }
00090 
00091     if($problems == 0) {
00092         echo '<div id="healthnoproblemsfound">';
00093         echo get_string('healthnoproblemsfound', 'tool_health');
00094         echo '</div>';
00095     }
00096     else {
00097         echo $OUTPUT->heading(get_string('healthproblemsdetected', 'tool_health'));
00098         $severities = array(SEVERITY_CRITICAL, SEVERITY_SIGNIFICANT, SEVERITY_ANNOYANCE, SEVERITY_NOTICE);
00099         foreach($severities as $severity) {
00100             if(!empty($issues[$severity])) {
00101                 echo '<dl class="healthissues '.$severity.'">';
00102                 foreach($issues[$severity] as $classname => $data) {
00103                     echo '<dt id="'.$classname.'">'.$data['title'].'</dt>';
00104                     echo '<dd>'.$data['description'];
00105                     echo '<form action="index.php#solution" method="get">';
00106                     echo '<input type="hidden" name="solution" value="'.$classname.'" /><input type="submit" value="'.get_string('viewsolution').'" />';
00107                     echo '</form></dd>';
00108                 }
00109                 echo '</dl>';
00110             }
00111         }
00112     }
00113 }
00114 
00115 function health_print_solution($classname) {
00116     global $OUTPUT;
00117     $problem = new $classname;
00118     $data = array(
00119         'title'       => $problem->title(),
00120         'severity'    => $problem->severity(),
00121         'description' => $problem->description(),
00122         'solution'    => $problem->solution()
00123     );
00124 
00125     echo $OUTPUT->heading(get_string('pluginname', 'tool_health'));
00126     echo $OUTPUT->heading(get_string('healthproblemsolution', 'tool_health'));
00127     echo '<dl class="healthissues '.$data['severity'].'">';
00128     echo '<dt>'.$data['title'].'</dt>';
00129     echo '<dd>'.$data['description'].'</dd>';
00130     echo '<dt id="solution" class="solution">'.get_string('healthsolution', 'tool_health').'</dt>';
00131     echo '<dd class="solution">'.$data['solution'].'</dd></dl>';
00132     echo '<form id="healthformreturn" action="index.php#'.$classname.'" method="get">';
00133     echo '<input type="submit" value="'.get_string('healthreturntomain', 'tool_health').'" />';
00134     echo '</form>';
00135 }
00136 
00137 class problem_base {
00138     function exists() {
00139         return false;
00140     }
00141     function title() {
00142         return '???';
00143     }
00144     function severity() {
00145         return SEVERITY_NOTICE;
00146     }
00147     function description() {
00148         return '';
00149     }
00150     function solution() {
00151         return '';
00152     }
00153 }
00154 
00155 class problem_000002 extends problem_base {
00156     function title() {
00157         return 'Extra characters at the end of config.php or other library function';
00158     }
00159     function exists() {
00160         global $extraws;
00161 
00162         if($extraws === '') {
00163             return false;
00164         }
00165         return true;
00166     }
00167     function severity() {
00168         return SEVERITY_SIGNIFICANT;
00169     }
00170     function description() {
00171         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.';
00172     }
00173     function solution() {
00174         global $CFG;
00175         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.';
00176     }
00177 }
00178 
00179 class problem_000003 extends problem_base {
00180     function title() {
00181         return '$CFG->dataroot does not exist or does not have write permissions';
00182     }
00183     function exists() {
00184         global $CFG;
00185         if(!is_dir($CFG->dataroot) || !is_writable($CFG->dataroot)) {
00186             return true;
00187         }
00188         return false;
00189     }
00190     function severity() {
00191         return SEVERITY_SIGNIFICANT;
00192     }
00193     function description() {
00194         global $CFG;
00195         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.';
00196     }
00197     function solution() {
00198         global $CFG;
00199         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.';
00200     }
00201 }
00202 
00203 class problem_000004 extends problem_base {
00204     function title() {
00205         return 'cron.php is not set up to run automatically';
00206     }
00207     function exists() {
00208         global $DB;
00209         $lastcron = $DB->get_field_sql('SELECT max(lastcron) FROM {modules}');
00210         return (time() - $lastcron > 3600 * 24);
00211     }
00212     function severity() {
00213         return SEVERITY_SIGNIFICANT;
00214     }
00215     function description() {
00216         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.';
00217     }
00218     function solution() {
00219         global $CFG;
00220         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.';
00221     }
00222 }
00223 
00224 class problem_000005 extends problem_base {
00225     function title() {
00226         return 'PHP: session.auto_start is enabled';
00227     }
00228     function exists() {
00229         return ini_get_bool('session.auto_start');
00230     }
00231     function severity() {
00232         return SEVERITY_CRITICAL;
00233     }
00234     function description() {
00235         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.';
00236     }
00237     function solution() {
00238         global $CFG;
00239         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>';
00240     }
00241 }
00242 
00243 class problem_000006 extends problem_base {
00244     function title() {
00245         return 'PHP: magic_quotes_runtime is enabled';
00246     }
00247     function exists() {
00248         return (ini_get_bool('magic_quotes_runtime'));
00249     }
00250     function severity() {
00251         return SEVERITY_SIGNIFICANT;
00252     }
00253     function description() {
00254         return 'Your PHP configuration includes an enabled setting, magic_quotes_runtime, that <strong>must be disabled</strong> in order for Moodle to work correctly. Notable symptoms arising from this misconfiguration include strange display errors whenever a text field that includes single or double quotes is processed.';
00255     }
00256     function solution() {
00257         global $CFG;
00258         return '<p>There are two ways you can solve this problem:</p><ol><li>If you have access to your main <strong>php.ini</strong> file, then find the line that looks like this: <pre>magic_quotes_runtime = On</pre> and change it to <pre>magic_quotes_runtime = Off</pre> and then restart your web server. Be warned that this, as any other PHP setting change, might affect other web applications running on the server.</li><li>Finally, you may be able to change this setting just for your site by creating or editing the file <strong>'.$CFG->dirroot.'/.htaccess</strong> to contain this line: <pre>php_value magic_quotes_runtime "Off"</pre></li></ol>';
00259     }
00260 }
00261 
00262 class problem_000007 extends problem_base {
00263     function title() {
00264         return 'PHP: file_uploads is disabled';
00265     }
00266     function exists() {
00267         return !ini_get_bool('file_uploads');
00268     }
00269     function severity() {
00270         return SEVERITY_SIGNIFICANT;
00271     }
00272     function description() {
00273         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.';
00274     }
00275     function solution() {
00276         global $CFG;
00277         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>';
00278     }
00279 }
00280 
00281 class problem_000008 extends problem_base {
00282     function title() {
00283         return 'PHP: memory_limit cannot be controlled by Moodle';
00284     }
00285     function exists() {
00286         global $CFG;
00287 
00288         $oldmemlimit = @ini_get('memory_limit');
00289         if (empty($oldmemlimit)) {
00290             // PHP not compiled with memory limits, this means that it's
00291             // probably limited to 8M or in case of Windows not at all.
00292             // We can ignore it for now - there is not much to test anyway
00293             // TODO: add manual test that fills memory??
00294             return false;
00295         }
00296         $oldmemlimit = get_real_size($oldmemlimit);
00297         //now lets change the memory limit to something higher
00298         $newmemlimit = ($oldmemlimit + 1024*1024*5);
00299         raise_memory_limit($newmemlimit);
00300         $testmemlimit = get_real_size(@ini_get('memory_limit'));
00301         //verify the change had any effect at all
00302         if ($oldmemlimit == $testmemlimit) {
00303             //memory limit can not be changed - is it big enough then?
00304             if ($oldmemlimit < get_real_size('128M')) {
00305                 return true;
00306             } else {
00307                 return false;
00308             }
00309         }
00310         reduce_memory_limit($oldmemlimit);
00311         return false;
00312     }
00313     function severity() {
00314         return SEVERITY_NOTICE;
00315     }
00316     function description() {
00317         return 'The settings for PHP on your server do not allow a script to request more memory during its execution. '.
00318                'This means that there is a hard limit of '.@ini_get('memory_limit').' for each script. '.
00319                'It is possible that certain operations within Moodle will require more than this amount in order '.
00320                'to complete successfully, especially if there are lots of data to be processed.';
00321     }
00322     function solution() {
00323         return 'It is recommended that you contact your web server administrator to address this issue.';
00324     }
00325 }
00326 
00327 class problem_000009 extends problem_base {
00328     function title() {
00329         return 'SQL: using account without password';
00330     }
00331     function exists() {
00332         global $CFG;
00333         return empty($CFG->dbpass);
00334     }
00335     function severity() {
00336         return SEVERITY_CRITICAL;
00337     }
00338     function description() {
00339         global $CFG;
00340         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>');
00341     }
00342     function solution() {
00343         global $CFG;
00344         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.');
00345     }
00346 }
00347 /* // not implemented in 2.0 yet
00348 class problem_000010 extends problem_base {
00349     function title() {
00350         return 'Uploaded files: slasharguments disabled or not working';
00351     }
00352     function exists() {
00353         if (!$this->is_enabled()) {
00354             return true;
00355         }
00356         if ($this->status() < 1) {
00357             return true;
00358         }
00359         return false;
00360     }
00361     function severity() {
00362         if ($this->is_enabled() and $this->status() == 0) {
00363             return SEVERITY_SIGNIFICANT;
00364         } else {
00365             return SEVERITY_ANNOYANCE;
00366         }
00367     }
00368     function description() {
00369         global $CFG;
00370         $desc = 'Slasharguments are needed for relative linking in uploaded resources:<ul>';
00371         if (!$this->is_enabled()) {
00372             $desc .= '<li>slasharguments are <strong>disabled</strong> in Moodle configuration</li>';
00373         } else {
00374             $desc .= '<li>slasharguments are enabled in Moodle configuration</li>';
00375         }
00376         if ($this->status() == -1) {
00377             $desc .= '<li>can not run automatic test, you can verify it <a href="'.$CFG->wwwroot.'/file.php/testslasharguments" target="_blank">here</a> manually</li>';
00378         } else if ($this->status() == 0) {
00379             $desc .= '<li>slashargument test <strong>failed</strong>, please check server configuration</li>';
00380         } else {
00381             $desc .= '<li>slashargument test passed</li>';
00382         }
00383         $desc .= '</ul>';
00384         return $desc;
00385     }
00386     function solution() {
00387         global $CFG;
00388         $enabled = $this->is_enabled();
00389         $status = $this->status();
00390         $solution = '';
00391         if ($enabled and ($status == 0)) {
00392             $solution .= 'Slasharguments are enabled, but the test failed. Please disable slasharguments in Moodle configuration or fix the server configuration.<hr />';
00393         } else if ((!$enabled) and ($status == 0)) {
00394             $solution .= 'Slasharguments are disabled and the test failed. You may try to fix the server configuration.<hr />';
00395         } else if ($enabled and ($status == -1)) {
00396             $solution .= 'Slasharguments are enabled, <a href="'.$CFG->wwwroot.'/file.php/testslasharguments">automatic testing</a> not possible.<hr />';
00397         } else if ((!$enabled) and ($status == -1)) {
00398             $solution .= 'Slasharguments are disabled, <a href="'.$CFG->wwwroot.'/file.php/testslasharguments">automatic testing</a> not possible.<hr />';
00399         } else if ((!$enabled) and ($status > 0)) {
00400             $solution .= 'Slasharguments are disabled though the iternal test is OK. You should enable slasharguments in Moodle configuration.';
00401         } else if ($enabled and ($status > 0)) {
00402             $solution .= 'Congratulations - everything seems OK now :-D';
00403         }
00404         if ($status < 1) {
00405             $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>';
00406             $solution .= '<p>Apache 1:<ul><li>try to add <code>cgi.fix_pathinfo=1</code> to php.ini</li></ul></p>';
00407             $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>';
00408         }
00409         return $solution;
00410     }
00411     function is_enabled() {
00412         global $CFG;
00413         return !empty($CFG->slasharguments);
00414     }
00415     function status() {
00416         global $CFG;
00417         $handle = @fopen($CFG->wwwroot.'/file.php?file=/testslasharguments', "r");
00418         $contents = @trim(fread($handle, 10));
00419         @fclose($handle);
00420         if ($contents != 'test -1') {
00421             return -1;
00422         }
00423         $handle = @fopen($CFG->wwwroot.'/file.php/testslasharguments', "r");
00424         $contents = trim(@fread($handle, 10));
00425         @fclose($handle);
00426         switch ($contents) {
00427             case 'test 1': return 1;
00428             case 'test 2': return 2;
00429             default:  return 0;
00430         }
00431     }
00432 }*/
00433 
00434 class problem_000012 extends problem_base {
00435     function title() {
00436         return 'Random questions data consistency';
00437     }
00438     function exists() {
00439         global $DB;
00440         return $DB->record_exists_select('question', "qtype = 'random' AND parent <> id", array());
00441     }
00442     function severity() {
00443         return SEVERITY_ANNOYANCE;
00444     }
00445     function description() {
00446         return '<p>For random questions, question.parent should equal question.id. ' .
00447         'There are some questions in your database for which this is not true. ' .
00448         'One way that this could have happened is for random questions restored from backup before ' .
00449         '<a href="http://tracker.moodle.org/browse/MDL-5482">MDL-5482</a> was fixed.</p>';
00450     }
00451     function solution() {
00452         global $CFG;
00453         return '<p>Upgrade to Moodle 1.9.1 or later, or manually execute the SQL</p>' .
00454         '<pre>UPDATE ' . $CFG->prefix . 'question SET parent = id WHERE qtype = \'random\' and parent &lt;> id;</pre>';
00455     }
00456 }
00457 
00458 class problem_000013 extends problem_base {
00459     function title() {
00460         return 'Multi-answer questions data consistency';
00461     }
00462     function exists() {
00463         global $DB;
00464         $positionexpr = $DB->sql_position($DB->sql_concat("','", "q.id", "','"),
00465                 $DB->sql_concat("','", "qma.sequence", "','"));
00466         return $DB->record_exists_sql("
00467                 SELECT * FROM {question} q
00468                     JOIN {question_multianswer} qma ON $positionexpr > 0
00469                 WHERE qma.question <> q.parent") ||
00470             $DB->record_exists_sql("
00471                 SELECT * FROM {question} q
00472                     JOIN {question} parent_q ON parent_q.id = q.parent
00473                 WHERE q.category <> parent_q.category");
00474     }
00475     function severity() {
00476         return SEVERITY_ANNOYANCE;
00477     }
00478     function description() {
00479         return '<p>For each sub-question whose id is listed in ' .
00480         'question_multianswer.sequence, its question.parent field should equal ' .
00481         'question_multianswer.question; and each sub-question should be in the same ' .
00482         'category as its parent. There are questions in your database for ' .
00483         'which this is not the case. One way that this could have happened is ' .
00484         'for multi-answer questions restored from backup before ' .
00485         '<a href="http://tracker.moodle.org/browse/MDL-14750">MDL-14750</a> was fixed.</p>';
00486     }
00487     function solution() {
00488         return '<p>Upgrade to Moodle 1.9.1 or later, or manually execute the ' .
00489         'code in question_multianswer_fix_subquestion_parents_and_categories in ' .
00490         '<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' .
00491         'from the 1.9 stable branch</a>.</p>';
00492     }
00493 }
00494 
00495 class problem_000014 extends problem_base {
00496     function title() {
00497         return 'Only multianswer and random questions should be the parent of another question';
00498     }
00499     function exists() {
00500         global $DB;
00501         return $DB->record_exists_sql("
00502                 SELECT * FROM {question} q
00503                     JOIN {question} parent_q ON parent_q.id = q.parent
00504                 WHERE parent_q.qtype NOT IN ('random', 'multianswer')");
00505     }
00506     function severity() {
00507         return SEVERITY_ANNOYANCE;
00508     }
00509     function description() {
00510         return '<p>You have questions that violate this in your databse. ' .
00511         'You will need to investigate to determine how this happened.</p>';
00512     }
00513     function solution() {
00514         return '<p>It is impossible to give a solution without knowing more about ' .
00515         ' how the problem was caused. You may be able to get help from the ' .
00516         '<a href="http://moodle.org/mod/forum/view.php?f=121">Quiz forum</a>.</p>';
00517     }
00518 }
00519 
00520 class problem_000015 extends problem_base {
00521     function title() {
00522         return 'Question categories should belong to a valid context';
00523     }
00524     function exists() {
00525         global $DB;
00526         return $DB->record_exists_sql("
00527             SELECT qc.*, (SELECT COUNT(1) FROM {question} q WHERE q.category = qc.id) AS numquestions
00528             FROM {question_categories} qc
00529                 LEFT JOIN {context} con ON qc.contextid = con.id
00530             WHERE con.id IS NULL");
00531     }
00532     function severity() {
00533         return SEVERITY_ANNOYANCE;
00534     }
00535     function description() {
00536         global $DB;
00537         $problemcategories = $DB->get_records_sql("
00538             SELECT qc.id, qc.name, qc.contextid, (SELECT COUNT(1) FROM {question} q WHERE q.category = qc.id) AS numquestions
00539             FROM {question_categories} qc
00540                 LEFT JOIN {context} con ON qc.contextid = con.id
00541             WHERE con.id IS NULL
00542             ORDER BY numquestions DESC, qc.name");
00543         $table = '<table><thead><tr><th>Cat id</th><th>Category name</th>' .
00544         "<th>Context id</th><th>Num Questions</th></tr></thead><tbody>\n";
00545         foreach ($problemcategories as $cat) {
00546             $table .= "<tr><td>$cat->id</td><td>" . s($cat->name) . "</td><td>" .
00547             $cat->contextid ."</td><td>$cat->numquestions</td></tr>\n";
00548         }
00549         $table .= '</tbody></table>';
00550         return '<p>All question categories are linked to a context id, and, ' .
00551         'the context they are linked to must exist. The following categories ' .
00552         'belong to a non-existant category:</p>' . $table . '<p>Any of these ' .
00553         'categories that contain no questions can just be deleted form the database. ' .
00554         'Other categories will require more thought.</p>';
00555     }
00556     function solution() {
00557         global $CFG;
00558         return '<p>You can delete the empty categories by executing the following SQL:</p><pre>
00559 DELETE FROM ' . $CFG->prefix . 'question_categories
00560 WHERE
00561     NOT EXISTS (SELECT * FROM ' . $CFG->prefix . 'question q WHERE q.category = ' . $CFG->prefix . 'question_categories.id)
00562 AND NOT EXISTS (SELECT * FROM ' . $CFG->prefix . 'context con WHERE contextid = con.id)
00563         </pre><p>Any remaining categories that contain questions will require more thought. ' .
00564         'People in the <a href="http://moodle.org/mod/forum/view.php?f=121">Quiz forum</a> may be able to help.</p>';
00565     }
00566 }
00567 
00568 class problem_000016 extends problem_base {
00569     function title() {
00570         return 'Question categories should belong to the same context as their parent';
00571     }
00572     function exists() {
00573         global $DB;
00574         return $DB->record_exists_sql("
00575             SELECT parent_qc.id AS parent, child_qc.id AS child, child_qc.contextid
00576             FROM {question_categories} child_qc
00577                 JOIN {question_categories} parent_qc ON child_qc.parent = parent_qc.id
00578             WHERE child_qc.contextid <> parent_qc.contextid");
00579     }
00580     function severity() {
00581         return SEVERITY_ANNOYANCE;
00582     }
00583     function description() {
00584         global $DB;
00585         $problemcategories = $DB->get_records_sql("
00586             SELECT
00587                 parent_qc.id AS parentid, parent_qc.name AS parentname, parent_qc.contextid AS parentcon,
00588                 child_qc.id AS childid, child_qc.name AS childname, child_qc.contextid AS childcon
00589             FROM {question_categories} child_qc
00590                 JOIN {question_categories} parent_qc ON child_qc.parent = parent_qc.id
00591             WHERE child_qc.contextid <> parent_qc.contextid");
00592         $table = '<table><thead><tr><th colspan="3">Child category</th><th colspan="3">Parent category</th></tr><tr>' .
00593         '<th>Id</th><th>Name</th><th>Context id</th>' .
00594         '<th>Id</th><th>Name</th><th>Context id</th>' .
00595         "</tr></thead><tbody>\n";
00596         foreach ($problemcategories as $cat) {
00597             $table .= "<tr><td>$cat->childid</td><td>" . s($cat->childname) .
00598             "</td><td>$cat->childcon</td><td>$cat->parentid</td><td>" . s($cat->parentname) .
00599             "</td><td>$cat->parentcon</td></tr>\n";
00600         }
00601         $table .= '</tbody></table>';
00602         return '<p>When one question category is the parent of another, then they ' .
00603         'should both belong to the same context. This is not true for the following categories:</p>' .
00604         $table;
00605     }
00606     function solution() {
00607         return '<p>An automated solution is difficult. It depends whether the ' .
00608         'parent or child category is in the wrong pace.' .
00609         'People in the <a href="http://moodle.org/mod/forum/view.php?f=121">Quiz forum</a> may be able to help.</p>';
00610     }
00611 }
00612 
00613 class problem_000017 extends problem_base {
00614     function title() {
00615         return 'Question categories tree structure';
00616     }
00617     function find_problems() {
00618         global $DB;
00619         static $answer = null;
00620 
00621         if (is_null($answer)) {
00622             $categories = $DB->get_records('question_categories', array(), 'id');
00623 
00624             // Look for missing parents.
00625             $missingparent = array();
00626             foreach ($categories as $category) {
00627                 if ($category->parent != 0 && !array_key_exists($category->parent, $categories)) {
00628                     $missingparent[$category->id] = $category;
00629                 }
00630             }
00631 
00632             // Look for loops.
00633             $loops = array();
00634             while (!empty($categories)) {
00635                 $current = array_pop($categories);
00636                 $thisloop = array($current->id => $current);
00637                 while (true) {
00638                     if (isset($thisloop[$current->parent])) {
00639                         // Loop detected
00640                         $loops[$current->id] = $thisloop;
00641                         break;
00642                     } else if (!isset($categories[$current->parent])) {
00643                         // Got to the top level, or a category we already know is OK.
00644                         break;
00645                     } else {
00646                         // Continue following the path.
00647                         $current = $categories[$current->parent];
00648                         $thisloop[$current->id] = $current;
00649                         unset($categories[$current->id]);
00650                     }
00651                 }
00652             }
00653 
00654             $answer = array($missingparent, $loops);
00655         }
00656 
00657         return $answer;
00658     }
00659     function exists() {
00660         list($missingparent, $loops) = $this->find_problems();
00661         return !empty($missingparent) || !empty($loops);
00662     }
00663     function severity() {
00664         return SEVERITY_ANNOYANCE;
00665     }
00666     function description() {
00667         list($missingparent, $loops) = $this->find_problems();
00668 
00669         $description = '<p>The question categories should be arranged into tree ' .
00670                 ' structures by the question_categories.parent field. Sometimes ' .
00671                 ' this tree structure gets messed up.</p>';
00672 
00673         if (!empty($missingparent)) {
00674             $description .= '<p>The following categories are missing their parents:</p><ul>';
00675             foreach ($missingparent as $cat) {
00676                 $description .= "<li>Category $cat->id: " . s($cat->name) . "</li>\n";
00677             }
00678             $description .= "</ul>\n";
00679         }
00680 
00681         if (!empty($loops)) {
00682             $description .= '<p>The following categories form a loop of parents:</p><ul>';
00683             foreach ($loops as $loop) {
00684                 $description .= "<li><ul>\n";
00685                 foreach ($loop as $cat) {
00686                     $description .= "<li>Category $cat->id: " . s($cat->name) . " has parent $cat->parent</li>\n";
00687                 }
00688                 $description .= "</ul></li>\n";
00689             }
00690             $description .= "</ul>\n";
00691         }
00692 
00693         return $description;
00694     }
00695     function solution() {
00696         global $CFG;
00697         list($missingparent, $loops) = $this->find_problems();
00698 
00699         $solution = '<p>Consider executing the following SQL queries. These fix ' .
00700                 'the problem by moving some categories to the top level.</p>';
00701 
00702         if (!empty($missingparent)) {
00703             $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
00704                     "        SET parent = 0\n" .
00705                     "        WHERE id IN (" . implode(',', array_keys($missingparent)) . ");</pre>\n";
00706         }
00707 
00708         if (!empty($loops)) {
00709             $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
00710                     "        SET parent = 0\n" .
00711                     "        WHERE id IN (" . implode(',', array_keys($loops)) . ");</pre>\n";
00712         }
00713 
00714         return $solution;
00715     }
00716 }
00717 
00718 class problem_00000x extends problem_base {
00719     function title() {
00720         return '';
00721     }
00722     function exists() {
00723         return false;
00724     }
00725     function severity() {
00726         return SEVERITY_SIGNIFICANT;
00727     }
00728     function description() {
00729         return '';
00730     }
00731     function solution() {
00732         global $CFG;
00733         return '';
00734     }
00735 }
00736 
00737 /*
00738 
00739 TODO:
00740 
00741     session.save_path -- it doesn't really matter because we are already IN a session, right?
00742     detect unsupported characters in $CFG->wwwroot - see bug Bug #6091 - relative vs absolute path during backup/restore process
00743 
00744 */
 All Data Structures Namespaces Files Functions Variables Enumerations