MDL-34684: fixed whitespace issues
[moodle.git] / admin / tool / health / index.php
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/>.
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  */
26     ob_start(); //for whitespace test
27     require('../../../config.php');
28     $extraws = ob_get_clean();
30     require_once($CFG->libdir.'/adminlib.php');
31     require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/health/locallib.php');
33     admin_externalpage_setup('toolhealth');
35     define('SEVERITY_NOTICE',      'notice');
36     define('SEVERITY_ANNOYANCE',   'annoyance');
37     define('SEVERITY_SIGNIFICANT', 'significant');
38     define('SEVERITY_CRITICAL',    'critical');
40     $solution = optional_param('solution', 0, PARAM_PLUGIN);
42     require_login();
43     require_capability('moodle/site:config', context_system::instance());
45     $site = get_site();
47     echo $OUTPUT->header();
49     if(strpos($solution, 'problem_') === 0 && class_exists($solution)) {
50         health_print_solution($solution);
51     }
52     else {
53         health_find_problems();
54     }
57     echo $OUTPUT->footer();
60 function health_find_problems() {
61     global $OUTPUT;
63     echo $OUTPUT->heading(get_string('pluginname', 'tool_health'));
65     $issues   = array(
66         SEVERITY_CRITICAL    => array(),
67         SEVERITY_SIGNIFICANT => array(),
68         SEVERITY_ANNOYANCE   => array(),
69         SEVERITY_NOTICE      => array(),
70     );
71     $problems = 0;
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;
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     }
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     }
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     );
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>';
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     }
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;
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     }
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     }
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     }
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     }
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     }
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;
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     }
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     }
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 }*/
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     }
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     }
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     }
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     }
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     }
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;
603         if (is_null($answer)) {
604             $categories = $DB->get_records('question_categories', array(), 'id');
606             // Look for missing parents.
607             $missingparent = tool_health_category_find_missing_parents($categories);
609             // Look for loops.
610             $loops = tool_health_category_find_loops($categories);
612             $answer = array($missingparent, $loops);
613         }
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();
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>';
631         $description .= tool_health_category_list_missing_parents($missingparent);
632         $description .= tool_health_category_list_loops($loops);
634         return $description;
635     }
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();
647         $solution = '<p>Consider executing the following SQL queries. These fix ' .
648                 'the problem by moving some categories to the top level.</p>';
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         }
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         }
662         return $solution;
663     }
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     }
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;
692         if (is_null($answer)) {
693             $categories = $DB->get_records('course_categories', array(), 'id');
695             // Look for missing parents.
696             $missingparent = tool_health_category_find_missing_parents($categories);
698             // Look for loops.
699             $loops = tool_health_category_find_loops($categories);
701             $answer = array($missingparent, $loops);
702         }
704         return $answer;
705     }
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     }
717     /**
718      * Set problem severity.
719      *
720      * @return constant Problem severity.
721      */
722     public function severity() {
723         return SEVERITY_SIGNIFICANT;
724     }
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();
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>';
738         $description .= tool_health_category_list_missing_parents($missingparent);
739         $description .= tool_health_category_list_loops($loops);
741         return $description;
742     }
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();
754         $solution = '<p>Consider executing the following SQL queries. These fix ' .
755                 'the problem by moving some categories to the top level.</p>';
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         }
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         }
769         return $solution;
770     }
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     }
792 /*
794 TODO:
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
799 */