Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   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   * @package    tool_xmldb
  19   * @copyright  2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  20   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21   */
  22  
  23  /**
  24   * This class will look for data in the database that violates the foreign
  25   * key definitions found in the XMLDB definitions.
  26   *
  27   * Note that by default, this check does not complain about foreign key
  28   * violations from, say, a userid column defined as NOT NULL DEFAULT '0'.
  29   * Each 0 in that column will violate the foreign key, but we ignore them.
  30   * If you want a strict check performed, then add &strict=1 to the URL.
  31   *
  32   * @package    tool_xmldb
  33   * @copyright  2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class check_foreign_keys extends XMLDBCheckAction {
  37  
  38      /**
  39       * Init method, every subclass will have its own
  40       */
  41      function init() {
  42          $this->introstr = 'confirmcheckforeignkeys';
  43          parent::init();
  44  
  45          // Set own core attributes
  46  
  47          // Set own custom attributes
  48  
  49          // Get needed strings
  50          $this->loadStrings(array(
  51              'key' => 'tool_xmldb',
  52              'violatedforeignkeys' => 'tool_xmldb',
  53              'noviolatedforeignkeysfound' => 'tool_xmldb',
  54              'violatedforeignkeysfound' => 'tool_xmldb',
  55              'violations' => 'tool_xmldb',
  56              'unknowntable' => 'tool_xmldb',
  57              'unknownfield' => 'tool_xmldb',
  58          ));
  59      }
  60  
  61      protected function check_table(xmldb_table $xmldb_table, array $metacolumns) {
  62          global $DB;
  63          $dbman = $DB->get_manager();
  64  
  65          $strictchecks = optional_param('strict', false, PARAM_BOOL);
  66  
  67          $o = '';
  68          $violatedkeys = array();
  69  
  70          // Keys
  71          if ($xmldb_keys = $xmldb_table->getKeys()) {
  72              $o.='        <ul>';
  73              foreach ($xmldb_keys as $xmldb_key) {
  74                  // We are only interested in foreign keys.
  75                  if (!in_array($xmldb_key->getType(), array(XMLDB_KEY_FOREIGN, XMLDB_KEY_FOREIGN_UNIQUE))) {
  76                      continue;
  77                  }
  78                  $o.='            <li>' . $this->str['key'] . ': ' . $xmldb_key->readableInfo() . ' ';
  79  
  80                  $reftable = $xmldb_key->getRefTable();
  81                  if (!$dbman->table_exists($reftable)) {
  82                      $o.='<font color="red">' . $this->str['unknowntable'] . '</font>';
  83                      // Add the missing index to the list
  84                      $violation = new stdClass();
  85                      $violation->string = 'fkunknowntable';
  86                      $violation->table = $xmldb_table;
  87                      $violation->key = $xmldb_key;
  88                      $violation->reftable = $reftable;
  89                      $violatedkeys[] = $violation;
  90                      continue;
  91                  }
  92  
  93                  // Work out the SQL to find key violations.
  94                  $keyfields = $xmldb_key->getFields();
  95                  $reffields = $xmldb_key->getRefFields();
  96                  $joinconditions = array();
  97                  $nullnessconditions = array();
  98                  $params = array();
  99                  foreach ($keyfields as $i => $field) {
 100                      if (!$dbman->field_exists($reftable, $reffields[$i])) {
 101                          $o.='<font color="red">' . $this->str['unknownfield'] . '</font>';
 102                          // Add the missing index to the list
 103                          $violation = new stdClass();
 104                          $violation->string = 'fkunknownfield';
 105                          $violation->table = $xmldb_table;
 106                          $violation->key = $xmldb_key;
 107                          $violation->reftable = $reftable;
 108                          $violation->reffield = $reffields[$i];
 109                          $violatedkeys[] = $violation;
 110                          continue 2;
 111                      }
 112  
 113                      $joinconditions[] = 't1.' . $field . ' = t2.' . $reffields[$i];
 114                      $xmldb_field = $xmldb_table->getField($field);
 115                      $default = $xmldb_field->getDefault();
 116                      if (!$xmldb_field->getNotNull()) {
 117                          $nullnessconditions[] = 't1.' . $field . ' IS NOT NULL';
 118                      } else if (!$strictchecks && ($default == '0' || !$default)) {
 119                          // We have a default of 0 or '' or something like that.
 120                          // These generate a lot of false-positives, so ignore them
 121                          // for now.
 122                          $nullnessconditions[] = 't1.' . $field . ' <> ?';
 123                          $params[] = $xmldb_field->getDefault();
 124                      }
 125                  }
 126                  $nullnessconditions[] = 't2.id IS NULL';
 127                  $sql = 'SELECT count(1) FROM {' . $xmldb_table->getName() .
 128                          '} t1 LEFT JOIN {' . $reftable . '} t2 ON ' .
 129                          implode(' AND ', $joinconditions) . ' WHERE ' .
 130                          implode(' AND ', $nullnessconditions);
 131  
 132                  // Check there are any problems in the database.
 133                  $violations = $DB->count_records_sql($sql, $params);
 134                  if ($violations == 0) {
 135                      $o.='<font color="green">' . $this->str['ok'] . '</font>';
 136                  } else {
 137                      $o.='<font color="red">' . $this->str['violations'] . '</font>';
 138                      // Add the missing index to the list
 139                      $violation = new stdClass;
 140                      $violation->string = 'fkviolationdetails';
 141                      $violation->table = $xmldb_table;
 142                      $violation->key = $xmldb_key;
 143                      $violation->numviolations = $violations;
 144                      $violation->numrows = $DB->count_records($xmldb_table->getName());
 145                      $violation->sql = str_replace('count(1)', '*', $sql);
 146                      if (!empty($params)) {
 147                          $violation->sqlparams = '(' . implode(', ', $params) . ')';
 148                      } else {
 149                          $violation->sqlparams = '';
 150                      }
 151                      $violatedkeys[] = $violation;
 152                  }
 153                  $o.='</li>';
 154              }
 155              $o.='        </ul>';
 156          }
 157  
 158          return array($o, $violatedkeys);
 159      }
 160  
 161      protected function display_results(array $violatedkeys) {
 162          $r = '<table class="generaltable boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
 163          $r.= '  <tr><td class="generalboxcontent">';
 164          $r.= '    <h2 class="main">' . $this->str['searchresults'] . '</h2>';
 165          $r.= '    <p class="centerpara">' . $this->str['violatedforeignkeys'] . ': ' . count($violatedkeys) . '</p>';
 166          $r.= '  </td></tr>';
 167          $r.= '  <tr><td class="generalboxcontent">';
 168  
 169          // If we have found wrong integers inform about them
 170          if (count($violatedkeys)) {
 171              $r.= '    <p class="centerpara">' . $this->str['violatedforeignkeysfound'] . '</p>';
 172              $r.= '        <ul>';
 173              foreach ($violatedkeys as $violation) {
 174                  $violation->tablename = $violation->table->getName();
 175                  $violation->keyname = $violation->key->getName();
 176  
 177                  $r.= '            <li>' .get_string($violation->string, 'tool_xmldb', $violation);
 178                  if (!empty($violation->sql)) {
 179                      $r.= '<pre>' . s($violation->sql) . '; ' . s($violation->sqlparams) . '</pre>';
 180                  }
 181                  $r.= '</li>';
 182              }
 183              $r.= '        </ul>';
 184          } else {
 185              $r.= '    <p class="centerpara">' . $this->str['noviolatedforeignkeysfound'] . '</p>';
 186          }
 187          $r.= '  </td></tr>';
 188          $r.= '  <tr><td class="generalboxcontent">';
 189          // Add the complete log message
 190          $r.= '    <p class="centerpara">' . $this->str['completelogbelow'] . '</p>';
 191          $r.= '  </td></tr>';
 192          $r.= '</table>';
 193  
 194          return $r;
 195      }
 196  }