migration.class.php
Go to the documentation of this file.
00001 <?php
00002 /*
00003  * @version $Id: migration.class.php 20154 2013-02-09 11:04:49Z yllen $
00004  -------------------------------------------------------------------------
00005  GLPI - Gestionnaire Libre de Parc Informatique
00006  Copyright (C) 2003-2013 by the INDEPNET Development Team.
00007 
00008  http://indepnet.net/   http://glpi-project.org
00009  -------------------------------------------------------------------------
00010 
00011  LICENSE
00012 
00013  This file is part of GLPI.
00014 
00015  GLPI is free software; you can redistribute it and/or modify
00016  it under the terms of the GNU General Public License as published by
00017  the Free Software Foundation; either version 2 of the License, or
00018  (at your option) any later version.
00019 
00020  GLPI is distributed in the hope that it will be useful,
00021  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  GNU General Public License for more details.
00024 
00025  You should have received a copy of the GNU General Public License
00026  along with GLPI. If not, see <http://www.gnu.org/licenses/>.
00027  --------------------------------------------------------------------------
00028  */
00029 
00030 /** @file
00031 * @brief
00032 */
00033 
00034 if (!defined('GLPI_ROOT')) {
00035    die("Sorry. You can't access directly to this file");
00036 }
00037 
00038 // class Central
00039 class Migration {
00040 
00041    private   $change     = array();
00042    protected $version;
00043    private   $deb;
00044    private   $lastMessage;
00045    private   $log_errors = 0;
00046    private   $current_message_area_id;
00047 
00048 
00049    /**
00050     * @param $ver    number of new version of GLPI
00051    **/
00052    function __construct($ver) {
00053 
00054       $this->deb = time();
00055       $this->setVersion($ver);
00056    }
00057 
00058 
00059    /**
00060     * @since version 0.84
00061     *
00062     * @param $ver    number of new version
00063    **/
00064    function setVersion($ver) {
00065 
00066       $this->flushLogDisplayMessage();
00067       $this->version = $ver;
00068       $this->addNewMessageArea("migration_message_$ver");
00069    }
00070 
00071 
00072    /**
00073     * @since version 0.84
00074     *
00075     * @param $id
00076    **/
00077    function addNewMessageArea($id) {
00078 
00079       $this->current_message_area_id = $id;
00080       echo "<div id='".$this->current_message_area_id."'>
00081             <p class='center'>".__('Work in progress...')."</p></div>";
00082 
00083       $this->flushLogDisplayMessage();
00084    }
00085 
00086 
00087    /**
00088     * Flush previous display message in log file
00089     *
00090     * @since version 0.84
00091    **/
00092    function flushLogDisplayMessage() {
00093 
00094       if (isset($this->lastMessage)) {
00095          $tps = Html::timestampToString(time() - $this->lastMessage['time']);
00096          $this->log($tps . ' for "' . $this->lastMessage['msg'] . '"', false);
00097          unset($this->lastMessage);
00098       }
00099    }
00100 
00101 
00102    /**
00103     * Additional message in global message
00104     *
00105     * @param $msg    text  to display
00106    **/
00107    function displayMessage($msg) {
00108 
00109       $now = time();
00110       $tps = Html::timestampToString($now-$this->deb);
00111       echo "<script type='text/javascript'>document.getElementById('".
00112              $this->current_message_area_id."').innerHTML=\"<p class='center'>".addslashes($msg).
00113              " ($tps)</p>\";".
00114            "</script>\n";
00115 
00116       $this->flushLogDisplayMessage();
00117       $this->lastMessage = array('time' => time(),
00118                                  'msg'  => $msg);
00119 
00120       Html::glpi_flush();
00121    }
00122 
00123 
00124    /**
00125     * log message for this migration
00126     *
00127     * @since version 0.84
00128     *
00129     * @param $message
00130     * @param $warning
00131    **/
00132    function log($message, $warning) {
00133 
00134       if ($warning) {
00135          $log_file_name = 'warning_during_migration_to_'.$this->version;
00136       } else {
00137          $log_file_name = 'migration_to_'.$this->version;
00138       }
00139 
00140      // Do not log if more than 3 log error
00141      if ($this->log_errors < 3
00142          && !Toolbox::logInFile($log_file_name, $message . ' @ ', true)) {
00143          $this->log_errors++;
00144      }
00145    }
00146 
00147 
00148    /**
00149     * Display a title
00150     *
00151     * @param $title string
00152    **/
00153    function displayTitle($title) {
00154       echo "<h3>".Html::entities_deep($title)."</h3>";
00155    }
00156 
00157 
00158    /**
00159     * Display a Warning
00160     *
00161     * @param $msg    string
00162     * @param $red    boolean (false by default)
00163    **/
00164    function displayWarning($msg, $red=false) {
00165 
00166       echo ($red ? "<div class='red'><p>" : "<p><span class='b'>") .
00167             Html::entities_deep($msg) . ($red ? "</p></div>" : "</span></p>");
00168 
00169       $this->log($msg, true);
00170    }
00171 
00172 
00173    /**
00174     * Define field's format
00175     *
00176     * @param $type            string   can be bool, string, integer, date, datatime, text, longtext,
00177     *                                         autoincrement, char
00178     * @param $default_value   string   new field's default value,
00179     *                                  if a specific default value needs to be used
00180     * @param $nodefault       bolean   (false by default)
00181    **/
00182    private function fieldFormat($type, $default_value, $nodefault=false) {
00183 
00184       $format = '';
00185       switch ($type) {
00186          case 'bool' :
00187             $format = "TINYINT(1) NOT NULL";
00188             if (!$nodefault) {
00189                if (is_null($default_value)) {
00190                   $format .= " DEFAULT '0'";
00191                } else if (in_array($default_value, array('0', '1'))) {
00192                   $format .= " DEFAULT '$default_value'";
00193                } else {
00194                   trigger_error(__('default_value must be 0 or 1'), E_USER_ERROR);
00195                }
00196             }
00197             break;
00198 
00199          case 'char' :
00200             $format = "CHAR(1)";
00201             if (!$nodefault) {
00202                if (is_null($default_value)) {
00203                   $format .= " DEFAULT NULL";
00204                } else {
00205                   $format .= " NOT NULL DEFAULT '$default_value'";
00206                }
00207             }
00208             break;
00209 
00210          case 'string' :
00211             $format = "VARCHAR(255) COLLATE utf8_unicode_ci";
00212             if (!$nodefault) {
00213                if (is_null($default_value)) {
00214                   $format .= " DEFAULT NULL";
00215                } else {
00216                   $format .= " NOT NULL DEFAULT '$default_value'";
00217                }
00218             }
00219             break;
00220 
00221          case 'integer' :
00222             $format = "INT(11) NOT NULL";
00223             if (!$nodefault) {
00224                if (is_null($default_value)) {
00225                   $format .= " DEFAULT '0'";
00226                } else if (is_numeric($default_value)) {
00227                   $format .= " DEFAULT '$default_value'";
00228                } else {
00229                   trigger_error(__('default_value must be numeric'), E_USER_ERROR);
00230                }
00231             }
00232             break;
00233 
00234          case 'date' :
00235             $format = "DATE";
00236             if (!$nodefault) {
00237                if (is_null($default_value)) {
00238                   $format.= " DEFAULT NULL";
00239                } else {
00240                   $format.= " DEFAULT '$default_value'";
00241                }
00242             }
00243             break;
00244 
00245          case 'datetime' :
00246             $format = "DATETIME";
00247             if (!$nodefault) {
00248                if (is_null($default_value)) {
00249                   $format.= " DEFAULT NULL";
00250                } else {
00251                   $format.= " DEFAULT '$default_value'";
00252                }
00253             }
00254             break;
00255 
00256          case 'text' :
00257             $format = "TEXT COLLATE utf8_unicode_ci";
00258             if (!$nodefault) {
00259                if (is_null($default_value)) {
00260                   $format.= " DEFAULT NULL";
00261                } else {
00262                   $format.= " NOT NULL DEFAULT '$default_value'";
00263                }
00264             }
00265             break;
00266 
00267          case 'longtext' :
00268             $format = "LONGTEXT COLLATE utf8_unicode_ci";
00269             if (!$nodefault) {
00270                if (is_null($default_value)) {
00271                   $format .= " DEFAULT NULL";
00272                } else {
00273                   $format .= " NOT NULL DEFAULT '$default_value'";
00274                }
00275             }
00276             break;
00277 
00278          // for plugins
00279          case 'autoincrement' :
00280             $format = "INT(11) NOT NULL AUTO_INCREMENT";
00281             break;
00282 
00283          default :
00284             // for compatibility with old 0.80 migrations
00285             $format = $type;
00286             break;
00287       }
00288       return $format;
00289    }
00290 
00291 
00292    /**
00293     * Add a new GLPI normalized field
00294     *
00295     * @param $table     string
00296     * @param $field     string   to add
00297     * @param $type      string   (see fieldFormat)
00298     * @param $options   array
00299     *    - update    : if not empty = value of $field (must be protected)
00300     *    - condition : if needed
00301     *    - value     : default_value new field's default value, if a specific default value needs to be used
00302     *    - nodefault : do not define default value (default false)
00303     *    - comment   : comment to be added during field creation
00304     *    - after     : where adding the new field
00305    **/
00306    function addField($table, $field, $type, $options=array()) {
00307       global $DB;
00308 
00309       $params['update']    = '';
00310       $params['condition'] = '';
00311       $params['value']     = NULL;
00312       $params['nodefault'] = false;
00313       $params['comment']   = '';
00314       $params['after']     = '';
00315 
00316       if (is_array($options) && count($options)) {
00317          foreach ($options as $key => $val) {
00318             $params[$key] = $val;
00319          }
00320       }
00321 
00322       $format = $this->fieldFormat($type, $params['value'], $params['nodefault']);
00323 
00324       if ($params['comment']) {
00325          $params['comment'] = " COMMENT '".addslashes($params['comment'])."'";
00326       }
00327 
00328       if ($params['after']) {
00329          $params['after'] = " AFTER `".$params['after']."`";
00330       }
00331 
00332       if ($format) {
00333          if (!FieldExists($table, $field, false)) {
00334             $this->change[$table][] = "ADD `$field` $format ".$params['comment'] ." ".
00335                                            $params['after']."";
00336 
00337             if (isset($params['update']) && strlen($params['update'])) {
00338                $this->migrationOneTable($table);
00339                $query = "UPDATE `$table`
00340                          SET `$field` = ".$params['update']." ".
00341                          $params['condition']."";
00342                $DB->queryOrDie($query, $this->version." set $field in $table");
00343             }
00344             return true;
00345          }
00346          return false;
00347       }
00348    }
00349 
00350 
00351    /**
00352     * Modify field for migration
00353     *
00354     * @param $table        string
00355     * @param $oldfield     string   old name of the field
00356     * @param $newfield     string   new name of the field
00357     * @param $type         string   (see fieldFormat)
00358     * @param $options      array
00359     *    - default_value new field's default value, if a specific default value needs to be used
00360     *    - comment comment to be added during field creation
00361     *    - nodefault : do not define default value (default false)
00362    **/
00363    function changeField($table, $oldfield, $newfield, $type, $options=array()) {
00364 
00365       $params['value']     = NULL;
00366       $params['nodefault'] = false;
00367       $params['comment']   = '';
00368 
00369       if (is_array($options) && count($options)) {
00370          foreach ($options as $key => $val) {
00371             $params[$key] = $val;
00372          }
00373       }
00374 
00375       $format = $this->fieldFormat($type, $params['value'], $params['nodefault']);
00376 
00377       if ($params['comment']) {
00378          $params['comment'] = " COMMENT '".addslashes($params['comment'])."'";
00379       }
00380 
00381 
00382       if (FieldExists($table, $oldfield, false)) {
00383          // in order the function to be replayed
00384          // Drop new field if name changed
00385          if (($oldfield != $newfield)
00386              && FieldExists($table, $newfield)) {
00387             $this->change[$table][] = "DROP `$newfield` ";
00388          }
00389 
00390          if ($format) {
00391             $this->change[$table][] = "CHANGE `$oldfield` `$newfield` $format ".$params['comment']."";
00392          }
00393          return true;
00394       }
00395 
00396       return false;
00397    }
00398 
00399 
00400    /**
00401     * Drop field for migration
00402     *
00403     * @param $table  string
00404     * @param $field  string   field to drop
00405    **/
00406    function dropField($table, $field) {
00407 
00408       if (FieldExists($table, $field, false)) {
00409          $this->change[$table][] = "DROP `$field`";
00410       }
00411    }
00412 
00413 
00414    /**
00415     * Drop immediatly a table if it exists
00416     *
00417     * @param table   string
00418    **/
00419    function dropTable($table) {
00420       global $DB;
00421 
00422       if (TableExists($table)) {
00423          $DB->query("DROP TABLE `$table`");
00424       }
00425    }
00426 
00427 
00428    /**
00429     * Add index for migration
00430     *
00431     * @param $table        string
00432     * @param $fields       string or array
00433     * @param $indexname    string            if empty =$fields (default '')
00434     * @param $type         string            index or unique (default 'INDEX')
00435     * @param $len          integer           for field length (default 0)
00436    **/
00437    function addKey($table, $fields, $indexname='', $type='INDEX', $len=0) {
00438 
00439       // si pas de nom d'index, on prend celui du ou des champs
00440       if (!$indexname) {
00441          if (is_array($fields)) {
00442             $indexname = implode($fields, "_");
00443          } else {
00444             $indexname = $fields;
00445          }
00446       }
00447 
00448       if (!isIndex($table,$indexname)) {
00449          if (is_array($fields)) {
00450             if ($len) {
00451                $fields = "`".implode($fields, "`($len), `")."`($len)";
00452             } else {
00453                $fields = "`".implode($fields, "`, `")."`";
00454             }
00455          } else if ($len) {
00456             $fields = "`$fields`($len)";
00457          } else {
00458             $fields = "`$fields`";
00459          }
00460 
00461          $this->change[$table][] = "ADD $type `$indexname` ($fields)";
00462       }
00463    }
00464 
00465 
00466    /**
00467     * Drop index for migration
00468     *
00469     * @param $table     string
00470     * @param $indexname string
00471    **/
00472    function dropKey($table, $indexname) {
00473 
00474       if (isIndex($table,$indexname)) {
00475          $this->change[$table][] = "DROP INDEX `$indexname`";
00476       }
00477    }
00478 
00479 
00480    /**
00481     * Rename table for migration
00482     *
00483     * @param $oldtable  string
00484     * @param $newtable  string
00485    **/
00486    function renameTable($oldtable, $newtable) {
00487       global $DB;
00488 
00489       if (!TableExists("$newtable") && TableExists("$oldtable")) {
00490          $query = "RENAME TABLE `$oldtable` TO `$newtable`";
00491          $DB->queryOrDie($query, $this->version." rename $oldtable");
00492       }
00493    }
00494 
00495 
00496    /**
00497     * Copy table for migration
00498     *
00499     * @since version 0.84
00500     *
00501     * @param $oldtable  string   The name of the table already inside the database
00502     * @param $newtable  string   The copy of the old table
00503    **/
00504    function copyTable($oldtable, $newtable) {
00505       global $DB;
00506 
00507       if (!TableExists($newtable)
00508           && TableExists($oldtable)) {
00509 
00510 //          // Try to do a flush tables if RELOAD privileges available
00511 //          $query = "FLUSH TABLES `$oldtable`, `$newtable`";
00512 //          $DB->query($query);
00513 
00514          $query = "CREATE TABLE `$newtable` LIKE `$oldtable`";
00515          $DB->queryOrDie($query, $this->version." create $newtable");
00516 
00517          $query = "INSERT INTO `$newtable`
00518                           (SELECT *
00519                            FROM `$oldtable`)";
00520          $DB->queryOrDie($query, $this->version." copy from $oldtable to $newtable");
00521       }
00522    }
00523 
00524 
00525    /**
00526     * Insert an entry inside a table
00527     *
00528     * @since version 0.84
00529     *
00530     * @param $table  string   The table to alter
00531     * @param $input  array    The elements to add inside the table
00532     *
00533     * @return id of the last item inserted by mysql
00534    **/
00535    function insertInTable($table, array $input) {
00536       global $DB;
00537 
00538       if (TableExists("$table")
00539           && is_array($input) && (count($input) > 0)) {
00540 
00541          $fields = array();
00542          $values = array();
00543          foreach ($input as $field => $value) {
00544             if (FieldExists($table, $field)) {
00545                $fields[] = "`$field`";
00546                $values[] = "'$value'";
00547             }
00548          }
00549          $query = "INSERT INTO `$table`
00550                           (" . implode(', ', $fields) . ")
00551                    VALUES (" .implode(', ', $values) . ")";
00552          $DB->queryOrDie($query, $this->version." insert in $table");
00553 
00554          return $DB->insert_id();
00555       }
00556    }
00557 
00558 
00559    /**
00560     * Execute migration for only one table
00561     *
00562     * @param $table  string
00563    **/
00564    function migrationOneTable($table) {
00565       global $DB;
00566 
00567       if (isset($this->change[$table])) {
00568          $query = "ALTER TABLE `$table` ".implode($this->change[$table], " ,\n")." ";
00569          $this->displayMessage( sprintf(__('Change of the database layout - %s'), $table));
00570          $DB->queryOrDie($query, $this->version." multiple alter in $table");
00571 
00572          unset($this->change[$table]);
00573       }
00574    }
00575 
00576 
00577    /**
00578     * Execute global migration
00579    **/
00580    function executeMigration() {
00581 
00582       foreach ($this->change as $table => $tab) {
00583          $this->migrationOneTable($table);
00584       }
00585 
00586       // end of global message
00587       $this->displayMessage(__('Task completed.'));
00588    }
00589 
00590 
00591    /**
00592     * Register a new rule
00593     *
00594     * @param $rule      Array of fields of glpi_rules
00595     * @param $criteria  Array of Array of fields of glpi_rulecriterias
00596     * @param $actions   Array of Array of fields of glpi_ruleactions
00597     *
00598     * @since version 0.84
00599     *
00600     * @return integer : new rule id
00601    **/
00602    function createRule(Array $rule, Array $criteria, Array $actions) {
00603       global $DB;
00604 
00605       // Avoid duplicate - Need to be improved using a rule uuid of other
00606       if (countElementsInTable('glpi_rules', "`name`='".$DB->escape($rule['name'])."'")) {
00607          return 0;
00608       }
00609       $rule['comment']     = sprintf(__('Automatically generated by GLPI %s'), $this->version);
00610       $rule['description'] = '';
00611 
00612       // Compute ranking
00613       $sql = "SELECT MAX(`ranking`) AS rank
00614               FROM `glpi_rules`
00615               WHERE `sub_type` = '".$rule['sub_type']."'";
00616       $result = $DB->query($sql);
00617 
00618       $ranking = 1;
00619       if ($DB->numrows($result) > 0) {
00620          $datas = $DB->fetch_assoc($result);
00621          $ranking = $datas["rank"] + 1;
00622       }
00623 
00624       // The rule itself
00625       $fields = "`ranking`";
00626       $values = "'$ranking'";
00627       foreach ($rule as $field => $value) {
00628          $fields .= ", `$field`";
00629          $values .= ", '".$DB->escape($value)."'";
00630       }
00631       $sql = "INSERT INTO `glpi_rules`
00632                      ($fields)
00633               VALUES ($values)";
00634       $DB->queryOrDie($sql);
00635       $rid = $DB->insert_id();
00636 
00637       // The rule criteria
00638       foreach ($criteria as $criterion) {
00639          $fields = "`rules_id`";
00640          $values = "'$rid'";
00641          foreach ($criterion as $field => $value) {
00642             $fields .= ", `$field`";
00643             $values .= ", '".$DB->escape($value)."'";
00644          }
00645          $sql = "INSERT INTO `glpi_rulecriterias`
00646                         ($fields)
00647                  VALUES ($values)";
00648          $DB->queryOrDie($sql);
00649       }
00650 
00651       // The rule criteria actions
00652       foreach ($actions as $action) {
00653          $fields = "`rules_id`";
00654          $values = "'$rid'";
00655          foreach ($action as $field => $value) {
00656             $fields .= ", `$field`";
00657             $values .= ", '".$DB->escape($value)."'";
00658          }
00659          $sql = "INSERT INTO `glpi_ruleactions`
00660                         ($fields)
00661                  VALUES ($values)";
00662          $DB->queryOrDie($sql);
00663       }
00664    }
00665 }
00666 ?>