source: moodle/trunk/fuentes/admin/tool/health/index.php @ 136

Last change on this file since 136 was 136, checked in by mabarracus, 4 years ago

Ported code to xenial

File size: 30.5 KB
Line 
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
59function 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
115function 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
137class 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
155class 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
179class 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
203class 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
224class 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
243class 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
262class 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
308class 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
329class 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
415class 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
439class 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
476class 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
501class 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>
540DELETE FROM ' . $CFG->prefix . 'question_categories
541WHERE
542    NOT EXISTS (SELECT * FROM ' . $CFG->prefix . 'question q WHERE q.category = ' . $CFG->prefix . 'question_categories.id)
543AND 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
549class 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
594class 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
699class 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
720TODO:
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*/
Note: See TracBrowser for help on using the repository browser.