Complex classes like DoliDBSqlite3 often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DoliDBSqlite3, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 33 | class DoliDBSqlite3 extends DoliDB |
||
| 34 | { |
||
| 35 | //! Database type |
||
| 36 | public $type='sqlite3'; |
||
| 37 | //! Database label |
||
| 38 | const LABEL='Sqlite3'; |
||
| 39 | //! Version min database |
||
| 40 | const VERSIONMIN='3.0.0'; |
||
| 41 | /** @var SQLite3Result Resultset of last query */ |
||
| 42 | private $_results; |
||
| 43 | |||
| 44 | const WEEK_MONDAY_FIRST=1; |
||
| 45 | const WEEK_YEAR = 2; |
||
| 46 | const WEEK_FIRST_WEEKDAY=4; |
||
| 47 | |||
| 48 | |||
| 49 | /** |
||
| 50 | * Constructor. |
||
| 51 | * This create an opened connexion to a database server and eventually to a database |
||
| 52 | * |
||
| 53 | * @param string $type Type of database (mysql, pgsql...) |
||
| 54 | * @param string $host Address of database server |
||
| 55 | * @param string $user Nom de l'utilisateur autorise |
||
| 56 | * @param string $pass Mot de passe |
||
| 57 | * @param string $name Nom de la database |
||
| 58 | * @param int $port Port of database server |
||
| 59 | */ |
||
| 60 | public function __construct($type, $host, $user, $pass, $name = '', $port = 0) |
||
| 61 | { |
||
| 62 | global $conf; |
||
| 63 | |||
| 64 | // Note that having "static" property for "$forcecharset" and "$forcecollate" will make error here in strict mode, so they are not static |
||
| 65 | if (! empty($conf->db->character_set)) $this->forcecharset=$conf->db->character_set; |
||
| 66 | if (! empty($conf->db->dolibarr_main_db_collation)) $this->forcecollate=$conf->db->dolibarr_main_db_collation; |
||
| 67 | |||
| 68 | $this->database_user=$user; |
||
| 69 | $this->database_host=$host; |
||
| 70 | $this->database_port=$port; |
||
| 71 | |||
| 72 | $this->transaction_opened=0; |
||
| 73 | |||
| 74 | //print "Name DB: $host,$user,$pass,$name<br>"; |
||
| 75 | |||
| 76 | /*if (! function_exists("sqlite_query")) |
||
| 77 | { |
||
| 78 | $this->connected = false; |
||
| 79 | $this->ok = false; |
||
| 80 | $this->error="Sqlite PHP functions for using Sqlite driver are not available in this version of PHP. Try to use another driver."; |
||
| 81 | dol_syslog(get_class($this)."::DoliDBSqlite3 : Sqlite PHP functions for using Sqlite driver are not available in this version of PHP. Try to use another driver.",LOG_ERR); |
||
| 82 | return $this->ok; |
||
| 83 | }*/ |
||
| 84 | |||
| 85 | /*if (! $host) |
||
| 86 | { |
||
| 87 | $this->connected = false; |
||
| 88 | $this->ok = false; |
||
| 89 | $this->error=$langs->trans("ErrorWrongHostParameter"); |
||
| 90 | dol_syslog(get_class($this)."::DoliDBSqlite3 : Erreur Connect, wrong host parameters",LOG_ERR); |
||
| 91 | return $this->ok; |
||
| 92 | }*/ |
||
| 93 | |||
| 94 | // Essai connexion serveur |
||
| 95 | // We do not try to connect to database, only to server. Connect to database is done later in constrcutor |
||
| 96 | $this->db = $this->connect($host, $user, $pass, $name, $port); |
||
|
|
|||
| 97 | |||
| 98 | if ($this->db) |
||
| 99 | { |
||
| 100 | $this->connected = true; |
||
| 101 | $this->ok = true; |
||
| 102 | $this->database_selected = true; |
||
| 103 | $this->database_name = $name; |
||
| 104 | |||
| 105 | $this->addCustomFunction('IF'); |
||
| 106 | $this->addCustomFunction('MONTH'); |
||
| 107 | $this->addCustomFunction('CURTIME'); |
||
| 108 | $this->addCustomFunction('CURDATE'); |
||
| 109 | $this->addCustomFunction('WEEK', 1); |
||
| 110 | $this->addCustomFunction('WEEK', 2); |
||
| 111 | $this->addCustomFunction('WEEKDAY'); |
||
| 112 | $this->addCustomFunction('date_format'); |
||
| 113 | //$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); |
||
| 114 | } |
||
| 115 | else |
||
| 116 | { |
||
| 117 | // host, login ou password incorrect |
||
| 118 | $this->connected = false; |
||
| 119 | $this->ok = false; |
||
| 120 | $this->database_selected = false; |
||
| 121 | $this->database_name = ''; |
||
| 122 | //$this->error=sqlite_connect_error(); |
||
| 123 | dol_syslog(get_class($this)."::DoliDBSqlite3 : Error Connect ".$this->error, LOG_ERR); |
||
| 124 | } |
||
| 125 | |||
| 126 | return $this->ok; |
||
| 127 | } |
||
| 128 | |||
| 129 | |||
| 130 | /** |
||
| 131 | * Convert a SQL request in Mysql syntax to native syntax |
||
| 132 | * |
||
| 133 | * @param string $line SQL request line to convert |
||
| 134 | * @param string $type Type of SQL order ('ddl' for insert, update, select, delete or 'dml' for create, alter...) |
||
| 135 | * @return string SQL request line converted |
||
| 136 | */ |
||
| 137 | public static function convertSQLFromMysql($line, $type = 'ddl') |
||
| 138 | { |
||
| 139 | // Removed empty line if this is a comment line for SVN tagging |
||
| 140 | if (preg_match('/^--\s\$Id/i', $line)) { |
||
| 141 | return ''; |
||
| 142 | } |
||
| 143 | // Return line if this is a comment |
||
| 144 | if (preg_match('/^#/i', $line) || preg_match('/^$/i', $line) || preg_match('/^--/i', $line)) |
||
| 145 | { |
||
| 146 | return $line; |
||
| 147 | } |
||
| 148 | if ($line != "") |
||
| 149 | { |
||
| 150 | if ($type == 'auto') |
||
| 151 | { |
||
| 152 | if (preg_match('/ALTER TABLE/i', $line)) $type='dml'; |
||
| 153 | elseif (preg_match('/CREATE TABLE/i', $line)) $type='dml'; |
||
| 154 | elseif (preg_match('/DROP TABLE/i', $line)) $type='dml'; |
||
| 155 | } |
||
| 156 | |||
| 157 | if ($type == 'dml') |
||
| 158 | { |
||
| 159 | $line=preg_replace('/\s/', ' ', $line); // Replace tabulation with space |
||
| 160 | |||
| 161 | // we are inside create table statement so lets process datatypes |
||
| 162 | if (preg_match('/(ISAM|innodb)/i', $line)) { // end of create table sequence |
||
| 163 | $line=preg_replace('/\)[\s\t]*type[\s\t]*=[\s\t]*(MyISAM|innodb);/i', ');', $line); |
||
| 164 | $line=preg_replace('/\)[\s\t]*engine[\s\t]*=[\s\t]*(MyISAM|innodb);/i', ');', $line); |
||
| 165 | $line=preg_replace('/,$/', '', $line); |
||
| 166 | } |
||
| 167 | |||
| 168 | // Process case: "CREATE TABLE llx_mytable(rowid integer NOT NULL AUTO_INCREMENT PRIMARY KEY,code..." |
||
| 169 | if (preg_match('/[\s\t\(]*(\w*)[\s\t]+int.*auto_increment/i', $line, $reg)) { |
||
| 170 | $newline=preg_replace('/([\s\t\(]*)([a-zA-Z_0-9]*)[\s\t]+int.*auto_increment[^,]*/i', '\\1 \\2 integer PRIMARY KEY AUTOINCREMENT', $line); |
||
| 171 | //$line = "-- ".$line." replaced by --\n".$newline; |
||
| 172 | $line=$newline; |
||
| 173 | } |
||
| 174 | |||
| 175 | // tinyint type conversion |
||
| 176 | $line=str_replace('tinyint', 'smallint', $line); |
||
| 177 | |||
| 178 | // nuke unsigned |
||
| 179 | $line=preg_replace('/(int\w+|smallint)\s+unsigned/i', '\\1', $line); |
||
| 180 | |||
| 181 | // blob -> text |
||
| 182 | $line=preg_replace('/\w*blob/i', 'text', $line); |
||
| 183 | |||
| 184 | // tinytext/mediumtext -> text |
||
| 185 | $line=preg_replace('/tinytext/i', 'text', $line); |
||
| 186 | $line=preg_replace('/mediumtext/i', 'text', $line); |
||
| 187 | |||
| 188 | // change not null datetime field to null valid ones |
||
| 189 | // (to support remapping of "zero time" to null |
||
| 190 | $line=preg_replace('/datetime not null/i', 'datetime', $line); |
||
| 191 | $line=preg_replace('/datetime/i', 'timestamp', $line); |
||
| 192 | |||
| 193 | // double -> numeric |
||
| 194 | $line=preg_replace('/^double/i', 'numeric', $line); |
||
| 195 | $line=preg_replace('/(\s*)double/i', '\\1numeric', $line); |
||
| 196 | // float -> numeric |
||
| 197 | $line=preg_replace('/^float/i', 'numeric', $line); |
||
| 198 | $line=preg_replace('/(\s*)float/i', '\\1numeric', $line); |
||
| 199 | |||
| 200 | // unique index(field1,field2) |
||
| 201 | if (preg_match('/unique index\s*\((\w+\s*,\s*\w+)\)/i', $line)) |
||
| 202 | { |
||
| 203 | $line=preg_replace('/unique index\s*\((\w+\s*,\s*\w+)\)/i', 'UNIQUE\(\\1\)', $line); |
||
| 204 | } |
||
| 205 | |||
| 206 | // We remove end of requests "AFTER fieldxxx" |
||
| 207 | $line=preg_replace('/AFTER [a-z0-9_]+/i', '', $line); |
||
| 208 | |||
| 209 | // We remove start of requests "ALTER TABLE tablexxx" if this is a DROP INDEX |
||
| 210 | $line=preg_replace('/ALTER TABLE [a-z0-9_]+ DROP INDEX/i', 'DROP INDEX', $line); |
||
| 211 | |||
| 212 | // Translate order to rename fields |
||
| 213 | if (preg_match('/ALTER TABLE ([a-z0-9_]+) CHANGE(?: COLUMN)? ([a-z0-9_]+) ([a-z0-9_]+)(.*)$/i', $line, $reg)) |
||
| 214 | { |
||
| 215 | $line = "-- ".$line." replaced by --\n"; |
||
| 216 | $line.= "ALTER TABLE ".$reg[1]." RENAME COLUMN ".$reg[2]." TO ".$reg[3]; |
||
| 217 | } |
||
| 218 | |||
| 219 | // Translate order to modify field format |
||
| 220 | if (preg_match('/ALTER TABLE ([a-z0-9_]+) MODIFY(?: COLUMN)? ([a-z0-9_]+) (.*)$/i', $line, $reg)) |
||
| 221 | { |
||
| 222 | $line = "-- ".$line." replaced by --\n"; |
||
| 223 | $newreg3=$reg[3]; |
||
| 224 | $newreg3=preg_replace('/ DEFAULT NULL/i', '', $newreg3); |
||
| 225 | $newreg3=preg_replace('/ NOT NULL/i', '', $newreg3); |
||
| 226 | $newreg3=preg_replace('/ NULL/i', '', $newreg3); |
||
| 227 | $newreg3=preg_replace('/ DEFAULT 0/i', '', $newreg3); |
||
| 228 | $newreg3=preg_replace('/ DEFAULT \'[0-9a-zA-Z_@]*\'/i', '', $newreg3); |
||
| 229 | $line.= "ALTER TABLE ".$reg[1]." ALTER COLUMN ".$reg[2]." TYPE ".$newreg3; |
||
| 230 | // TODO Add alter to set default value or null/not null if there is this in $reg[3] |
||
| 231 | } |
||
| 232 | |||
| 233 | // alter table add primary key (field1, field2 ...) -> We create a unique index instead as dynamic creation of primary key is not supported |
||
| 234 | // ALTER TABLE llx_dolibarr_modules ADD PRIMARY KEY pk_dolibarr_modules (numero, entity); |
||
| 235 | if (preg_match('/ALTER\s+TABLE\s*(.*)\s*ADD\s+PRIMARY\s+KEY\s*(.*)\s*\((.*)$/i', $line, $reg)) |
||
| 236 | { |
||
| 237 | $line = "-- ".$line." replaced by --\n"; |
||
| 238 | $line.= "CREATE UNIQUE INDEX ".$reg[2]." ON ".$reg[1]."(".$reg[3]; |
||
| 239 | } |
||
| 240 | |||
| 241 | // Translate order to drop foreign keys |
||
| 242 | // ALTER TABLE llx_dolibarr_modules DROP FOREIGN KEY fk_xxx; |
||
| 243 | if (preg_match('/ALTER\s+TABLE\s*(.*)\s*DROP\s+FOREIGN\s+KEY\s*(.*)$/i', $line, $reg)) |
||
| 244 | { |
||
| 245 | $line = "-- ".$line." replaced by --\n"; |
||
| 246 | $line.= "ALTER TABLE ".$reg[1]." DROP CONSTRAINT ".$reg[2]; |
||
| 247 | } |
||
| 248 | |||
| 249 | // alter table add [unique] [index] (field1, field2 ...) |
||
| 250 | // ALTER TABLE llx_accountingaccount ADD INDEX idx_accountingaccount_fk_pcg_version (fk_pcg_version) |
||
| 251 | if (preg_match('/ALTER\s+TABLE\s*(.*)\s*ADD\s+(UNIQUE INDEX|INDEX|UNIQUE)\s+(.*)\s*\(([\w,\s]+)\)/i', $line, $reg)) |
||
| 252 | { |
||
| 253 | $fieldlist=$reg[4]; |
||
| 254 | $idxname=$reg[3]; |
||
| 255 | $tablename=$reg[1]; |
||
| 256 | $line = "-- ".$line." replaced by --\n"; |
||
| 257 | $line.= "CREATE ".(preg_match('/UNIQUE/', $reg[2])?'UNIQUE ':'')."INDEX ".$idxname." ON ".$tablename." (".$fieldlist.")"; |
||
| 258 | } |
||
| 259 | if (preg_match('/ALTER\s+TABLE\s*(.*)\s*ADD\s+CONSTRAINT\s+(.*)\s*FOREIGN\s+KEY\s*\(([\w,\s]+)\)\s*REFERENCES\s+(\w+)\s*\(([\w,\s]+)\)/i', $line, $reg)) { |
||
| 260 | // Pour l'instant les contraintes ne sont pas créées |
||
| 261 | dol_syslog(get_class().'::query line emptied'); |
||
| 262 | $line = 'SELECT 0;'; |
||
| 263 | } |
||
| 264 | |||
| 265 | //if (preg_match('/rowid\s+.*\s+PRIMARY\s+KEY,/i', $line)) { |
||
| 266 | //preg_replace('/(rowid\s+.*\s+PRIMARY\s+KEY\s*,)/i', '/* \\1 */', $line); |
||
| 267 | //} |
||
| 268 | } |
||
| 269 | |||
| 270 | // Delete using criteria on other table must not declare twice the deleted table |
||
| 271 | // DELETE FROM tabletodelete USING tabletodelete, othertable -> DELETE FROM tabletodelete USING othertable |
||
| 272 | if (preg_match('/DELETE FROM ([a-z_]+) USING ([a-z_]+), ([a-z_]+)/i', $line, $reg)) |
||
| 273 | { |
||
| 274 | if ($reg[1] == $reg[2]) // If same table, we remove second one |
||
| 275 | { |
||
| 276 | $line=preg_replace('/DELETE FROM ([a-z_]+) USING ([a-z_]+), ([a-z_]+)/i', 'DELETE FROM \\1 USING \\3', $line); |
||
| 277 | } |
||
| 278 | } |
||
| 279 | |||
| 280 | // Remove () in the tables in FROM if one table |
||
| 281 | $line=preg_replace('/FROM\s*\((([a-z_]+)\s+as\s+([a-z_]+)\s*)\)/i', 'FROM \\1', $line); |
||
| 282 | //print $line."\n"; |
||
| 283 | |||
| 284 | // Remove () in the tables in FROM if two table |
||
| 285 | $line=preg_replace('/FROM\s*\(([a-z_]+\s+as\s+[a-z_]+)\s*,\s*([a-z_]+\s+as\s+[a-z_]+\s*)\)/i', 'FROM \\1, \\2', $line); |
||
| 286 | //print $line."\n"; |
||
| 287 | |||
| 288 | // Remove () in the tables in FROM if two table |
||
| 289 | $line=preg_replace('/FROM\s*\(([a-z_]+\s+as\s+[a-z_]+)\s*,\s*([a-z_]+\s+as\s+[a-z_]+\s*),\s*([a-z_]+\s+as\s+[a-z_]+\s*)\)/i', 'FROM \\1, \\2, \\3', $line); |
||
| 290 | //print $line."\n"; |
||
| 291 | |||
| 292 | //print "type=".$type." newline=".$line."<br>\n"; |
||
| 293 | } |
||
| 294 | |||
| 295 | return $line; |
||
| 296 | } |
||
| 297 | |||
| 298 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 299 | /** |
||
| 300 | * Select a database |
||
| 301 | * |
||
| 302 | * @param string $database Name of database |
||
| 303 | * @return boolean true if OK, false if KO |
||
| 304 | */ |
||
| 305 | public function select_db($database) |
||
| 306 | { |
||
| 307 | // phpcs:enable |
||
| 308 | dol_syslog(get_class($this)."::select_db database=".$database, LOG_DEBUG); |
||
| 309 | // sqlite_select_db() does not exist |
||
| 310 | //return sqlite_select_db($this->db,$database); |
||
| 311 | return true; |
||
| 312 | } |
||
| 313 | |||
| 314 | |||
| 315 | /** |
||
| 316 | * Connexion to server |
||
| 317 | * |
||
| 318 | * @param string $host database server host |
||
| 319 | * @param string $login login |
||
| 320 | * @param string $passwd password |
||
| 321 | * @param string $name name of database (not used for mysql, used for pgsql) |
||
| 322 | * @param integer $port Port of database server |
||
| 323 | * @return SQLite3 Database access handler |
||
| 324 | * @see close() |
||
| 325 | */ |
||
| 326 | public function connect($host, $login, $passwd, $name, $port = 0) |
||
| 327 | { |
||
| 328 | global $main_data_dir; |
||
| 329 | |||
| 330 | dol_syslog(get_class($this)."::connect name=".$name, LOG_DEBUG); |
||
| 331 | |||
| 332 | $dir=$main_data_dir; |
||
| 333 | if (empty($dir)) $dir=DOL_DATA_ROOT; |
||
| 334 | // With sqlite, port must be in connect parameters |
||
| 335 | //if (! $newport) $newport=3306; |
||
| 336 | $database_name = $dir.'/database_'.$name.'.sdb'; |
||
| 337 | try { |
||
| 338 | /*** connect to SQLite database ***/ |
||
| 339 | //$this->db = new PDO("sqlite:".$dir.'/database_'.$name.'.sdb'); |
||
| 340 | $this->db = new SQLite3($database_name); |
||
| 341 | //$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); |
||
| 342 | } |
||
| 343 | catch(Exception $e) |
||
| 344 | { |
||
| 345 | $this->error= self::LABEL.' '.$e->getMessage().' current dir='.$database_name; |
||
| 346 | return ''; |
||
| 347 | } |
||
| 348 | |||
| 349 | //print "Resultat fonction connect: ".$this->db; |
||
| 350 | return $this->db; |
||
| 351 | } |
||
| 352 | |||
| 353 | |||
| 354 | /** |
||
| 355 | * Return version of database server |
||
| 356 | * |
||
| 357 | * @return string Version string |
||
| 358 | */ |
||
| 359 | public function getVersion() |
||
| 360 | { |
||
| 361 | $tmp=$this->db->version(); |
||
| 362 | return $tmp['versionString']; |
||
| 363 | } |
||
| 364 | |||
| 365 | /** |
||
| 366 | * Return version of database client driver |
||
| 367 | * |
||
| 368 | * @return string Version string |
||
| 369 | */ |
||
| 370 | public function getDriverInfo() |
||
| 371 | { |
||
| 372 | return 'sqlite3 php driver'; |
||
| 373 | } |
||
| 374 | |||
| 375 | |||
| 376 | /** |
||
| 377 | * Close database connexion |
||
| 378 | * |
||
| 379 | * @return bool True if disconnect successfull, false otherwise |
||
| 380 | * @see connect() |
||
| 381 | */ |
||
| 382 | public function close() |
||
| 383 | { |
||
| 384 | if ($this->db) |
||
| 385 | { |
||
| 386 | if ($this->transaction_opened > 0) dol_syslog(get_class($this)."::close Closing a connection with an opened transaction depth=".$this->transaction_opened, LOG_ERR); |
||
| 387 | $this->connected=false; |
||
| 388 | $this->db->close(); |
||
| 389 | unset($this->db); // Clean this->db |
||
| 390 | return true; |
||
| 391 | } |
||
| 392 | return false; |
||
| 393 | } |
||
| 394 | |||
| 395 | /** |
||
| 396 | * Execute a SQL request and return the resultset |
||
| 397 | * |
||
| 398 | * @param string $query SQL query string |
||
| 399 | * @param int $usesavepoint 0=Default mode, 1=Run a savepoint before and a rollbock to savepoint if error (this allow to have some request with errors inside global transactions). |
||
| 400 | * Note that with Mysql, this parameter is not used as Myssql can already commit a transaction even if one request is in error, without using savepoints. |
||
| 401 | * @param string $type Type of SQL order ('ddl' for insert, update, select, delete or 'dml' for create, alter...) |
||
| 402 | * @return SQLite3Result Resultset of answer |
||
| 403 | */ |
||
| 404 | public function query($query, $usesavepoint = 0, $type = 'auto') |
||
| 405 | { |
||
| 406 | global $conf; |
||
| 407 | |||
| 408 | $ret=null; |
||
| 409 | |||
| 410 | $query = trim($query); |
||
| 411 | |||
| 412 | $this->error = ''; |
||
| 413 | |||
| 414 | // Convert MySQL syntax to SQLite syntax |
||
| 415 | if (preg_match('/ALTER\s+TABLE\s*(.*)\s*ADD\s+CONSTRAINT\s+(.*)\s*FOREIGN\s+KEY\s*\(([\w,\s]+)\)\s*REFERENCES\s+(\w+)\s*\(([\w,\s]+)\)/i', $query, $reg)) { |
||
| 416 | // Ajout d'une clef étrangère à la table |
||
| 417 | // procédure de remplacement de la table pour ajouter la contrainte |
||
| 418 | // Exemple : ALTER TABLE llx_adherent ADD CONSTRAINT adherent_fk_soc FOREIGN KEY (fk_soc) REFERENCES llx_societe (rowid) |
||
| 419 | // -> CREATE TABLE ( ... ,CONSTRAINT adherent_fk_soc FOREIGN KEY (fk_soc) REFERENCES llx_societe (rowid)) |
||
| 420 | $foreignFields = $reg[5]; |
||
| 421 | $foreignTable = $reg[4]; |
||
| 422 | $localfields = $reg[3]; |
||
| 423 | $constraintname=trim($reg[2]); |
||
| 424 | $tablename=trim($reg[1]); |
||
| 425 | |||
| 426 | $descTable = $this->db->querySingle("SELECT sql FROM sqlite_master WHERE name='" . $tablename . "'"); |
||
| 427 | |||
| 428 | // 1- Renommer la table avec un nom temporaire |
||
| 429 | $this->query('ALTER TABLE ' . $tablename . ' RENAME TO tmp_' . $tablename); |
||
| 430 | |||
| 431 | // 2- Recréer la table avec la contrainte ajoutée |
||
| 432 | |||
| 433 | // on bricole la requete pour ajouter la contrainte |
||
| 434 | $descTable = substr($descTable, 0, strlen($descTable) - 1); |
||
| 435 | $descTable .= ", CONSTRAINT " . $constraintname . " FOREIGN KEY (" . $localfields . ") REFERENCES " .$foreignTable . "(" . $foreignFields . ")"; |
||
| 436 | |||
| 437 | // fermeture de l'instruction |
||
| 438 | $descTable .= ')'; |
||
| 439 | |||
| 440 | // Création proprement dite de la table |
||
| 441 | $this->query($descTable); |
||
| 442 | |||
| 443 | // 3- Transférer les données |
||
| 444 | $this->query('INSERT INTO ' . $tablename . ' SELECT * FROM tmp_' . $tablename); |
||
| 445 | |||
| 446 | // 4- Supprimer la table temporaire |
||
| 447 | $this->query('DROP TABLE tmp_' . $tablename); |
||
| 448 | |||
| 449 | // dummy statement |
||
| 450 | $query="SELECT 0"; |
||
| 451 | } else { |
||
| 452 | $query=$this->convertSQLFromMysql($query, $type); |
||
| 453 | } |
||
| 454 | //print "After convertSQLFromMysql:\n".$query."<br>\n"; |
||
| 455 | |||
| 456 | if (! in_array($query, array('BEGIN','COMMIT','ROLLBACK'))) dol_syslog('sql='.$query, LOG_DEBUG); |
||
| 457 | if (empty($query)) return false; // Return false = error if empty request |
||
| 458 | |||
| 459 | // Ordre SQL ne necessitant pas de connexion a une base (exemple: CREATE DATABASE) |
||
| 460 | try { |
||
| 461 | //$ret = $this->db->exec($query); |
||
| 462 | $ret = $this->db->query($query); // $ret is a Sqlite3Result |
||
| 463 | if ($ret) { |
||
| 464 | $ret->queryString = $query; |
||
| 465 | } |
||
| 466 | } |
||
| 467 | catch(Exception $e) |
||
| 468 | { |
||
| 469 | $this->error=$this->db->lastErrorMsg(); |
||
| 470 | } |
||
| 471 | |||
| 472 | if (! preg_match("/^COMMIT/i", $query) && ! preg_match("/^ROLLBACK/i", $query)) |
||
| 473 | { |
||
| 474 | // Si requete utilisateur, on la sauvegarde ainsi que son resultset |
||
| 475 | if (! is_object($ret) || $this->error) |
||
| 476 | { |
||
| 477 | $this->lastqueryerror = $query; |
||
| 478 | $this->lasterror = $this->error(); |
||
| 479 | $this->lasterrno = $this->errno(); |
||
| 480 | |||
| 481 | dol_syslog(get_class($this)."::query SQL Error query: ".$query, LOG_ERR); |
||
| 482 | |||
| 483 | $errormsg = get_class($this)."::query SQL Error message: ".$this->lasterror; |
||
| 484 | |||
| 485 | if (preg_match('/[0-9]/', $this->lasterrno)) { |
||
| 486 | $errormsg .= ' ('.$this->lasterrno.')'; |
||
| 487 | } |
||
| 488 | |||
| 489 | if ($conf->global->SYSLOG_LEVEL < LOG_DEBUG) dol_syslog(get_class($this)."::query SQL Error query: ".$query, LOG_ERR); // Log of request was not yet done previously |
||
| 490 | dol_syslog(get_class($this)."::query SQL Error message: ".$errormsg, LOG_ERR); |
||
| 491 | } |
||
| 492 | $this->lastquery=$query; |
||
| 493 | $this->_results = $ret; |
||
| 494 | } |
||
| 495 | |||
| 496 | return $ret; |
||
| 497 | } |
||
| 498 | |||
| 499 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 500 | /** |
||
| 501 | * Renvoie la ligne courante (comme un objet) pour le curseur resultset |
||
| 502 | * |
||
| 503 | * @param SQLite3Result $resultset Curseur de la requete voulue |
||
| 504 | * @return false|object Object result line or false if KO or end of cursor |
||
| 505 | */ |
||
| 506 | public function fetch_object($resultset) |
||
| 507 | { |
||
| 508 | // phpcs:enable |
||
| 509 | // Si le resultset n'est pas fourni, on prend le dernier utilise sur cette connexion |
||
| 510 | if (! is_object($resultset)) { $resultset=$this->_results; } |
||
| 511 | //return $resultset->fetch(PDO::FETCH_OBJ); |
||
| 512 | $ret = $resultset->fetchArray(SQLITE3_ASSOC); |
||
| 513 | if ($ret) { |
||
| 514 | return (object) $ret; |
||
| 515 | } |
||
| 516 | return false; |
||
| 517 | } |
||
| 518 | |||
| 519 | |||
| 520 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 521 | /** |
||
| 522 | * Return datas as an array |
||
| 523 | * |
||
| 524 | * @param SQLite3Result $resultset Resultset of request |
||
| 525 | * @return false|array Array or false if KO or end of cursor |
||
| 526 | */ |
||
| 527 | public function fetch_array($resultset) |
||
| 528 | { |
||
| 529 | // phpcs:enable |
||
| 530 | // If resultset not provided, we take the last used by connexion |
||
| 531 | if (! is_object($resultset)) { $resultset=$this->_results; } |
||
| 532 | //return $resultset->fetch(PDO::FETCH_ASSOC); |
||
| 533 | $ret = $resultset->fetchArray(SQLITE3_ASSOC); |
||
| 534 | return $ret; |
||
| 535 | } |
||
| 536 | |||
| 537 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 538 | /** |
||
| 539 | * Return datas as an array |
||
| 540 | * |
||
| 541 | * @param SQLite3Result $resultset Resultset of request |
||
| 542 | * @return false|array Array or false if KO or end of cursor |
||
| 543 | */ |
||
| 544 | public function fetch_row($resultset) |
||
| 545 | { |
||
| 546 | // phpcs:enable |
||
| 547 | // If resultset not provided, we take the last used by connexion |
||
| 548 | if (! is_bool($resultset)) |
||
| 549 | { |
||
| 550 | if (! is_object($resultset)) { $resultset=$this->_results; } |
||
| 551 | return $resultset->fetchArray(SQLITE3_NUM); |
||
| 552 | } |
||
| 553 | else |
||
| 554 | { |
||
| 555 | // si le curseur est un booleen on retourne la valeur 0 |
||
| 556 | return false; |
||
| 557 | } |
||
| 558 | } |
||
| 559 | |||
| 560 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 561 | /** |
||
| 562 | * Return number of lines for result of a SELECT |
||
| 563 | * |
||
| 564 | * @param SQLite3Result $resultset Resulset of requests |
||
| 565 | * @return int Nb of lines |
||
| 566 | * @see affected_rows() |
||
| 567 | */ |
||
| 568 | public function num_rows($resultset) |
||
| 569 | { |
||
| 570 | // phpcs:enable |
||
| 571 | // FIXME: SQLite3Result does not have a queryString member |
||
| 572 | |||
| 573 | // If resultset not provided, we take the last used by connexion |
||
| 574 | if (! is_object($resultset)) { $resultset=$this->_results; } |
||
| 575 | if (preg_match("/^SELECT/i", $resultset->queryString)) { |
||
| 576 | return $this->db->querySingle("SELECT count(*) FROM (" . $resultset->queryString . ") q"); |
||
| 577 | } |
||
| 578 | return 0; |
||
| 579 | } |
||
| 580 | |||
| 581 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 582 | /** |
||
| 583 | * Return number of lines for result of a SELECT |
||
| 584 | * |
||
| 585 | * @param SQLite3Result $resultset Resulset of requests |
||
| 586 | * @return int Nb of lines |
||
| 587 | * @see affected_rows() |
||
| 588 | */ |
||
| 589 | public function affected_rows($resultset) |
||
| 602 | } |
||
| 603 | |||
| 604 | |||
| 605 | /** |
||
| 606 | * Free last resultset used. |
||
| 607 | * |
||
| 608 | * @param SQLite3Result $resultset Curseur de la requete voulue |
||
| 609 | * @return void |
||
| 610 | */ |
||
| 611 | public function free($resultset = null) |
||
| 612 | { |
||
| 613 | // If resultset not provided, we take the last used by connexion |
||
| 614 | if (! is_object($resultset)) { $resultset=$this->_results; } |
||
| 615 | // Si resultset en est un, on libere la memoire |
||
| 616 | if ($resultset && is_object($resultset)) $resultset->finalize(); |
||
| 617 | } |
||
| 618 | |||
| 619 | /** |
||
| 620 | * Escape a string to insert data |
||
| 621 | * |
||
| 622 | * @param string $stringtoencode String to escape |
||
| 623 | * @return string String escaped |
||
| 624 | */ |
||
| 625 | public function escape($stringtoencode) |
||
| 626 | { |
||
| 627 | return Sqlite3::escapeString($stringtoencode); |
||
| 628 | } |
||
| 629 | |||
| 630 | /** |
||
| 631 | * Renvoie le code erreur generique de l'operation precedente. |
||
| 632 | * |
||
| 633 | * @return string Error code (Exemples: DB_ERROR_TABLE_ALREADY_EXISTS, DB_ERROR_RECORD_ALREADY_EXISTS...) |
||
| 634 | */ |
||
| 635 | public function errno() |
||
| 636 | { |
||
| 637 | if (! $this->connected) { |
||
| 638 | // Si il y a eu echec de connexion, $this->db n'est pas valide. |
||
| 639 | return 'DB_ERROR_FAILED_TO_CONNECT'; |
||
| 640 | } |
||
| 641 | else { |
||
| 642 | // Constants to convert error code to a generic Dolibarr error code |
||
| 643 | /*$errorcode_map = array( |
||
| 644 | 1004 => 'DB_ERROR_CANNOT_CREATE', |
||
| 645 | 1005 => 'DB_ERROR_CANNOT_CREATE', |
||
| 646 | 1006 => 'DB_ERROR_CANNOT_CREATE', |
||
| 647 | 1007 => 'DB_ERROR_ALREADY_EXISTS', |
||
| 648 | 1008 => 'DB_ERROR_CANNOT_DROP', |
||
| 649 | 1025 => 'DB_ERROR_NO_FOREIGN_KEY_TO_DROP', |
||
| 650 | 1044 => 'DB_ERROR_ACCESSDENIED', |
||
| 651 | 1046 => 'DB_ERROR_NODBSELECTED', |
||
| 652 | 1048 => 'DB_ERROR_CONSTRAINT', |
||
| 653 | 'HY000' => 'DB_ERROR_TABLE_ALREADY_EXISTS', |
||
| 654 | 1051 => 'DB_ERROR_NOSUCHTABLE', |
||
| 655 | 1054 => 'DB_ERROR_NOSUCHFIELD', |
||
| 656 | 1060 => 'DB_ERROR_COLUMN_ALREADY_EXISTS', |
||
| 657 | 1061 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS', |
||
| 658 | 1062 => 'DB_ERROR_RECORD_ALREADY_EXISTS', |
||
| 659 | 1064 => 'DB_ERROR_SYNTAX', |
||
| 660 | 1068 => 'DB_ERROR_PRIMARY_KEY_ALREADY_EXISTS', |
||
| 661 | 1075 => 'DB_ERROR_CANT_DROP_PRIMARY_KEY', |
||
| 662 | 1091 => 'DB_ERROR_NOSUCHFIELD', |
||
| 663 | 1100 => 'DB_ERROR_NOT_LOCKED', |
||
| 664 | 1136 => 'DB_ERROR_VALUE_COUNT_ON_ROW', |
||
| 665 | 1146 => 'DB_ERROR_NOSUCHTABLE', |
||
| 666 | 1216 => 'DB_ERROR_NO_PARENT', |
||
| 667 | 1217 => 'DB_ERROR_CHILD_EXISTS', |
||
| 668 | 1451 => 'DB_ERROR_CHILD_EXISTS' |
||
| 669 | ); |
||
| 670 | |||
| 671 | if (isset($errorcode_map[$this->db->errorCode()])) |
||
| 672 | { |
||
| 673 | return $errorcode_map[$this->db->errorCode()]; |
||
| 674 | }*/ |
||
| 675 | $errno=$this->db->lastErrorCode(); |
||
| 676 | if ($errno=='HY000' || $errno == 0) |
||
| 677 | { |
||
| 678 | if (preg_match('/table.*already exists/i', $this->error)) return 'DB_ERROR_TABLE_ALREADY_EXISTS'; |
||
| 679 | elseif (preg_match('/index.*already exists/i', $this->error)) return 'DB_ERROR_KEY_NAME_ALREADY_EXISTS'; |
||
| 680 | elseif (preg_match('/syntax error/i', $this->error)) return 'DB_ERROR_SYNTAX'; |
||
| 681 | } |
||
| 682 | if ($errno=='23000') |
||
| 683 | { |
||
| 684 | if (preg_match('/column.* not unique/i', $this->error)) return 'DB_ERROR_RECORD_ALREADY_EXISTS'; |
||
| 685 | elseif (preg_match('/PRIMARY KEY must be unique/i', $this->error)) return 'DB_ERROR_RECORD_ALREADY_EXISTS'; |
||
| 686 | } |
||
| 687 | if ($errno > 1) { |
||
| 688 | // TODO Voir la liste des messages d'erreur |
||
| 689 | } |
||
| 690 | |||
| 691 | return ($errno?'DB_ERROR_'.$errno:'0'); |
||
| 692 | } |
||
| 693 | } |
||
| 694 | |||
| 695 | /** |
||
| 696 | * Renvoie le texte de l'erreur mysql de l'operation precedente. |
||
| 697 | * |
||
| 698 | * @return string Error text |
||
| 699 | */ |
||
| 700 | public function error() |
||
| 701 | { |
||
| 702 | if (! $this->connected) { |
||
| 703 | // Si il y a eu echec de connexion, $this->db n'est pas valide pour sqlite_error. |
||
| 704 | return 'Not connected. Check setup parameters in conf/conf.php file and your sqlite version'; |
||
| 705 | } |
||
| 706 | else { |
||
| 707 | return $this->error; |
||
| 708 | } |
||
| 709 | } |
||
| 710 | |||
| 711 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 712 | /** |
||
| 713 | * Get last ID after an insert INSERT |
||
| 714 | * |
||
| 715 | * @param string $tab Table name concerned by insert. Ne sert pas sous MySql mais requis pour compatibilite avec Postgresql |
||
| 716 | * @param string $fieldid Field name |
||
| 717 | * @return int Id of row |
||
| 718 | */ |
||
| 719 | public function last_insert_id($tab, $fieldid = 'rowid') |
||
| 720 | { |
||
| 721 | // phpcs:enable |
||
| 722 | return $this->db->lastInsertRowId(); |
||
| 723 | } |
||
| 724 | |||
| 725 | /** |
||
| 726 | * Encrypt sensitive data in database |
||
| 727 | * Warning: This function includes the escape, so it must use direct value |
||
| 728 | * |
||
| 729 | * @param string $fieldorvalue Field name or value to encrypt |
||
| 730 | * @param int $withQuotes Return string with quotes |
||
| 731 | * @return string XXX(field) or XXX('value') or field or 'value' |
||
| 732 | */ |
||
| 733 | public function encrypt($fieldorvalue, $withQuotes = 0) |
||
| 734 | { |
||
| 735 | global $conf; |
||
| 736 | |||
| 737 | // Type of encryption (2: AES (recommended), 1: DES , 0: no encryption) |
||
| 738 | $cryptType = ($conf->db->dolibarr_main_db_encryption?$conf->db->dolibarr_main_db_encryption:0); |
||
| 739 | |||
| 740 | //Encryption key |
||
| 741 | $cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey)?$conf->db->dolibarr_main_db_cryptkey:''); |
||
| 742 | |||
| 743 | $return = ($withQuotes?"'":"").$this->escape($fieldorvalue).($withQuotes?"'":""); |
||
| 744 | |||
| 745 | if ($cryptType && !empty($cryptKey)) |
||
| 746 | { |
||
| 747 | if ($cryptType == 2) |
||
| 748 | { |
||
| 749 | $return = 'AES_ENCRYPT('.$return.',\''.$cryptKey.'\')'; |
||
| 750 | } |
||
| 751 | elseif ($cryptType == 1) |
||
| 752 | { |
||
| 753 | $return = 'DES_ENCRYPT('.$return.',\''.$cryptKey.'\')'; |
||
| 754 | } |
||
| 755 | } |
||
| 756 | |||
| 757 | return $return; |
||
| 758 | } |
||
| 759 | |||
| 760 | /** |
||
| 761 | * Decrypt sensitive data in database |
||
| 762 | * |
||
| 763 | * @param string $value Value to decrypt |
||
| 764 | * @return string Decrypted value if used |
||
| 765 | */ |
||
| 766 | public function decrypt($value) |
||
| 767 | { |
||
| 768 | global $conf; |
||
| 769 | |||
| 770 | // Type of encryption (2: AES (recommended), 1: DES , 0: no encryption) |
||
| 771 | $cryptType = ($conf->db->dolibarr_main_db_encryption?$conf->db->dolibarr_main_db_encryption:0); |
||
| 772 | |||
| 773 | //Encryption key |
||
| 774 | $cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey)?$conf->db->dolibarr_main_db_cryptkey:''); |
||
| 775 | |||
| 776 | $return = $value; |
||
| 777 | |||
| 778 | if ($cryptType && !empty($cryptKey)) |
||
| 779 | { |
||
| 780 | if ($cryptType == 2) |
||
| 781 | { |
||
| 782 | $return = 'AES_DECRYPT('.$value.',\''.$cryptKey.'\')'; |
||
| 783 | } |
||
| 784 | elseif ($cryptType == 1) |
||
| 785 | { |
||
| 786 | $return = 'DES_DECRYPT('.$value.',\''.$cryptKey.'\')'; |
||
| 787 | } |
||
| 788 | } |
||
| 789 | |||
| 790 | return $return; |
||
| 791 | } |
||
| 792 | |||
| 793 | |||
| 794 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 795 | /** |
||
| 796 | * Return connexion ID |
||
| 797 | * |
||
| 798 | * @return string Id connexion |
||
| 799 | */ |
||
| 800 | public function DDLGetConnectId() |
||
| 801 | { |
||
| 802 | // phpcs:enable |
||
| 803 | return '?'; |
||
| 804 | } |
||
| 805 | |||
| 806 | |||
| 807 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 808 | /** |
||
| 809 | * Create a new database |
||
| 810 | * Do not use function xxx_create_db (xxx=mysql, ...) as they are deprecated |
||
| 811 | * We force to create database with charset this->forcecharset and collate this->forcecollate |
||
| 812 | * |
||
| 813 | * @param string $database Database name to create |
||
| 814 | * @param string $charset Charset used to store data |
||
| 815 | * @param string $collation Charset used to sort data |
||
| 816 | * @param string $owner Username of database owner |
||
| 817 | * @return SQLite3Result resource defined if OK, null if KO |
||
| 818 | */ |
||
| 819 | public function DDLCreateDb($database, $charset = '', $collation = '', $owner = '') |
||
| 820 | { |
||
| 821 | // phpcs:enable |
||
| 822 | if (empty($charset)) $charset=$this->forcecharset; |
||
| 823 | if (empty($collation)) $collation=$this->forcecollate; |
||
| 824 | |||
| 825 | // ALTER DATABASE dolibarr_db DEFAULT CHARACTER SET latin DEFAULT COLLATE latin1_swedish_ci |
||
| 826 | $sql = 'CREATE DATABASE '.$database; |
||
| 827 | $sql.= ' DEFAULT CHARACTER SET '.$charset.' DEFAULT COLLATE '.$collation; |
||
| 828 | |||
| 829 | dol_syslog($sql, LOG_DEBUG); |
||
| 830 | $ret=$this->query($sql); |
||
| 831 | if (! $ret) |
||
| 832 | { |
||
| 833 | // We try again for compatibility with Mysql < 4.1.1 |
||
| 834 | $sql = 'CREATE DATABASE '.$database; |
||
| 835 | $ret=$this->query($sql); |
||
| 836 | dol_syslog($sql, LOG_DEBUG); |
||
| 837 | } |
||
| 838 | return $ret; |
||
| 839 | } |
||
| 840 | |||
| 841 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 842 | /** |
||
| 843 | * List tables into a database |
||
| 844 | * |
||
| 845 | * @param string $database Name of database |
||
| 846 | * @param string $table Name of table filter ('xxx%') |
||
| 847 | * @return array List of tables in an array |
||
| 848 | */ |
||
| 849 | public function DDLListTables($database, $table = '') |
||
| 850 | { |
||
| 851 | // phpcs:enable |
||
| 852 | $listtables=array(); |
||
| 853 | |||
| 854 | $like = ''; |
||
| 855 | if ($table) $like = "LIKE '".$table."'"; |
||
| 856 | $sql="SHOW TABLES FROM ".$database." ".$like.";"; |
||
| 857 | //print $sql; |
||
| 858 | $result = $this->query($sql); |
||
| 859 | if ($result) |
||
| 860 | { |
||
| 861 | while($row = $this->fetch_row($result)) |
||
| 862 | { |
||
| 863 | $listtables[] = $row[0]; |
||
| 864 | } |
||
| 865 | } |
||
| 866 | return $listtables; |
||
| 867 | } |
||
| 868 | |||
| 869 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 870 | /** |
||
| 871 | * List information of columns into a table. |
||
| 872 | * |
||
| 873 | * @param string $table Name of table |
||
| 874 | * @return array Tableau des informations des champs de la table |
||
| 875 | * TODO modify for sqlite |
||
| 876 | */ |
||
| 877 | public function DDLInfoTable($table) |
||
| 878 | { |
||
| 879 | // phpcs:enable |
||
| 880 | $infotables=array(); |
||
| 881 | |||
| 882 | $sql="SHOW FULL COLUMNS FROM ".$table.";"; |
||
| 883 | |||
| 884 | dol_syslog($sql, LOG_DEBUG); |
||
| 885 | $result = $this->query($sql); |
||
| 886 | if ($result) |
||
| 887 | { |
||
| 888 | while($row = $this->fetch_row($result)) |
||
| 889 | { |
||
| 890 | $infotables[] = $row; |
||
| 891 | } |
||
| 892 | } |
||
| 893 | return $infotables; |
||
| 894 | } |
||
| 895 | |||
| 896 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 897 | /** |
||
| 898 | * Create a table into database |
||
| 899 | * |
||
| 900 | * @param string $table Nom de la table |
||
| 901 | * @param array $fields Tableau associatif [nom champ][tableau des descriptions] |
||
| 902 | * @param string $primary_key Nom du champ qui sera la clef primaire |
||
| 903 | * @param string $type Type de la table |
||
| 904 | * @param array $unique_keys Tableau associatifs Nom de champs qui seront clef unique => valeur |
||
| 905 | * @param array $fulltext_keys Tableau des Nom de champs qui seront indexes en fulltext |
||
| 906 | * @param array $keys Tableau des champs cles noms => valeur |
||
| 907 | * @return int <0 if KO, >=0 if OK |
||
| 908 | */ |
||
| 909 | public function DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys = null, $fulltext_keys = null, $keys = null) |
||
| 910 | { |
||
| 911 | // phpcs:enable |
||
| 912 | // FIXME: $fulltext_keys parameter is unused |
||
| 913 | |||
| 914 | // cles recherchees dans le tableau des descriptions (fields) : type,value,attribute,null,default,extra |
||
| 915 | // ex. : $fields['rowid'] = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment'); |
||
| 916 | $sql = "create table ".$table."("; |
||
| 917 | $i=0; |
||
| 918 | foreach($fields as $field_name => $field_desc) |
||
| 919 | { |
||
| 920 | $sqlfields[$i] = $field_name." "; |
||
| 921 | $sqlfields[$i] .= $field_desc['type']; |
||
| 922 | if( preg_match("/^[^\s]/i", $field_desc['value'])) |
||
| 923 | $sqlfields[$i] .= "(".$field_desc['value'].")"; |
||
| 924 | elseif( preg_match("/^[^\s]/i", $field_desc['attribute'])) |
||
| 925 | $sqlfields[$i] .= " ".$field_desc['attribute']; |
||
| 926 | elseif( preg_match("/^[^\s]/i", $field_desc['default'])) |
||
| 927 | { |
||
| 928 | if(preg_match("/null/i", $field_desc['default'])) |
||
| 929 | $sqlfields[$i] .= " default ".$field_desc['default']; |
||
| 930 | else |
||
| 931 | $sqlfields[$i] .= " default '".$field_desc['default']."'"; |
||
| 932 | } |
||
| 933 | elseif( preg_match("/^[^\s]/i", $field_desc['null'])) |
||
| 934 | $sqlfields[$i] .= " ".$field_desc['null']; |
||
| 935 | |||
| 936 | elseif( preg_match("/^[^\s]/i", $field_desc['extra'])) |
||
| 937 | $sqlfields[$i] .= " ".$field_desc['extra']; |
||
| 938 | $i++; |
||
| 939 | } |
||
| 940 | if($primary_key != "") |
||
| 941 | $pk = "primary key(".$primary_key.")"; |
||
| 942 | |||
| 943 | if(is_array($unique_keys)) |
||
| 944 | { |
||
| 945 | $i = 0; |
||
| 946 | foreach($unique_keys as $key => $value) |
||
| 947 | { |
||
| 948 | $sqluq[$i] = "UNIQUE KEY '".$key."' ('".$value."')"; |
||
| 949 | $i++; |
||
| 950 | } |
||
| 951 | } |
||
| 952 | if(is_array($keys)) |
||
| 953 | { |
||
| 954 | $i = 0; |
||
| 955 | foreach($keys as $key => $value) |
||
| 956 | { |
||
| 957 | $sqlk[$i] = "KEY ".$key." (".$value.")"; |
||
| 958 | $i++; |
||
| 959 | } |
||
| 960 | } |
||
| 961 | $sql .= implode(',', $sqlfields); |
||
| 962 | if($primary_key != "") |
||
| 963 | $sql .= ",".$pk; |
||
| 964 | if(is_array($unique_keys)) |
||
| 965 | $sql .= ",".implode(',', $sqluq); |
||
| 966 | if(is_array($keys)) |
||
| 967 | $sql .= ",".implode(',', $sqlk); |
||
| 968 | $sql .=") type=".$type; |
||
| 969 | |||
| 970 | dol_syslog($sql, LOG_DEBUG); |
||
| 971 | if(! $this -> query($sql)) |
||
| 972 | return -1; |
||
| 973 | return 1; |
||
| 974 | } |
||
| 975 | |||
| 976 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 977 | /** |
||
| 978 | * Drop a table into database |
||
| 979 | * |
||
| 980 | * @param string $table Name of table |
||
| 981 | * @return int <0 if KO, >=0 if OK |
||
| 982 | */ |
||
| 983 | public function DDLDropTable($table) |
||
| 984 | { |
||
| 985 | // phpcs:enable |
||
| 986 | $sql = "DROP TABLE ".$table; |
||
| 987 | |||
| 988 | if (! $this->query($sql)) |
||
| 989 | return -1; |
||
| 990 | else |
||
| 991 | return 1; |
||
| 992 | } |
||
| 993 | |||
| 994 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 995 | /** |
||
| 996 | * Return a pointer of line with description of a table or field |
||
| 997 | * |
||
| 998 | * @param string $table Name of table |
||
| 999 | * @param string $field Optionnel : Name of field if we want description of field |
||
| 1000 | * @return SQLite3Result Resource |
||
| 1001 | */ |
||
| 1002 | public function DDLDescTable($table, $field = "") |
||
| 1003 | { |
||
| 1004 | // phpcs:enable |
||
| 1005 | $sql="DESC ".$table." ".$field; |
||
| 1006 | |||
| 1007 | dol_syslog(get_class($this)."::DDLDescTable ".$sql, LOG_DEBUG); |
||
| 1008 | $this->_results = $this->query($sql); |
||
| 1009 | return $this->_results; |
||
| 1010 | } |
||
| 1011 | |||
| 1012 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 1013 | /** |
||
| 1014 | * Create a new field into table |
||
| 1015 | * |
||
| 1016 | * @param string $table Name of table |
||
| 1017 | * @param string $field_name Name of field to add |
||
| 1018 | * @param string $field_desc Tableau associatif de description du champ a inserer[nom du parametre][valeur du parametre] |
||
| 1019 | * @param string $field_position Optionnel ex.: "after champtruc" |
||
| 1020 | * @return int <0 if KO, >0 if OK |
||
| 1021 | */ |
||
| 1022 | public function DDLAddField($table, $field_name, $field_desc, $field_position = "") |
||
| 1023 | { |
||
| 1024 | // phpcs:enable |
||
| 1025 | // cles recherchees dans le tableau des descriptions (field_desc) : type,value,attribute,null,default,extra |
||
| 1026 | // ex. : $field_desc = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment'); |
||
| 1027 | $sql= "ALTER TABLE ".$table." ADD ".$field_name." "; |
||
| 1028 | $sql.= $field_desc['type']; |
||
| 1029 | if(preg_match("/^[^\s]/i", $field_desc['value'])) |
||
| 1030 | if (! in_array($field_desc['type'], array('date','datetime'))) |
||
| 1031 | { |
||
| 1032 | $sql.= "(".$field_desc['value'].")"; |
||
| 1033 | } |
||
| 1034 | if(preg_match("/^[^\s]/i", $field_desc['attribute'])) |
||
| 1035 | $sql.= " ".$field_desc['attribute']; |
||
| 1036 | if(preg_match("/^[^\s]/i", $field_desc['null'])) |
||
| 1037 | $sql.= " ".$field_desc['null']; |
||
| 1038 | if(preg_match("/^[^\s]/i", $field_desc['default'])) |
||
| 1039 | { |
||
| 1040 | if(preg_match("/null/i", $field_desc['default'])) |
||
| 1041 | $sql.= " default ".$field_desc['default']; |
||
| 1042 | else |
||
| 1043 | $sql.= " default '".$field_desc['default']."'"; |
||
| 1044 | } |
||
| 1045 | if(preg_match("/^[^\s]/i", $field_desc['extra'])) |
||
| 1046 | $sql.= " ".$field_desc['extra']; |
||
| 1047 | $sql.= " ".$field_position; |
||
| 1048 | |||
| 1049 | dol_syslog(get_class($this)."::DDLAddField ".$sql, LOG_DEBUG); |
||
| 1050 | if(! $this->query($sql)) |
||
| 1051 | { |
||
| 1052 | return -1; |
||
| 1053 | } |
||
| 1054 | return 1; |
||
| 1055 | } |
||
| 1056 | |||
| 1057 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 1058 | /** |
||
| 1059 | * Update format of a field into a table |
||
| 1060 | * |
||
| 1061 | * @param string $table Name of table |
||
| 1062 | * @param string $field_name Name of field to modify |
||
| 1063 | * @param string $field_desc Array with description of field format |
||
| 1064 | * @return int <0 if KO, >0 if OK |
||
| 1065 | */ |
||
| 1066 | public function DDLUpdateField($table, $field_name, $field_desc) |
||
| 1067 | { |
||
| 1068 | // phpcs:enable |
||
| 1069 | $sql = "ALTER TABLE ".$table; |
||
| 1070 | $sql .= " MODIFY COLUMN ".$field_name." ".$field_desc['type']; |
||
| 1071 | if ($field_desc['type'] == 'tinyint' || $field_desc['type'] == 'int' || $field_desc['type'] == 'varchar') { |
||
| 1072 | $sql.="(".$field_desc['value'].")"; |
||
| 1073 | } |
||
| 1074 | |||
| 1075 | dol_syslog(get_class($this)."::DDLUpdateField ".$sql, LOG_DEBUG); |
||
| 1076 | if (! $this->query($sql)) |
||
| 1077 | return -1; |
||
| 1078 | return 1; |
||
| 1079 | } |
||
| 1080 | |||
| 1081 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 1082 | /** |
||
| 1083 | * Drop a field from table |
||
| 1084 | * |
||
| 1085 | * @param string $table Name of table |
||
| 1086 | * @param string $field_name Name of field to drop |
||
| 1087 | * @return int <0 if KO, >0 if OK |
||
| 1088 | */ |
||
| 1089 | public function DDLDropField($table, $field_name) |
||
| 1090 | { |
||
| 1091 | // phpcs:enable |
||
| 1092 | $sql= "ALTER TABLE ".$table." DROP COLUMN `".$field_name."`"; |
||
| 1093 | dol_syslog(get_class($this)."::DDLDropField ".$sql, LOG_DEBUG); |
||
| 1094 | if (! $this->query($sql)) |
||
| 1095 | { |
||
| 1096 | $this->error=$this->lasterror(); |
||
| 1097 | return -1; |
||
| 1098 | } |
||
| 1099 | return 1; |
||
| 1100 | } |
||
| 1101 | |||
| 1102 | |||
| 1103 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 1104 | /** |
||
| 1105 | * Create a user and privileges to connect to database (even if database does not exists yet) |
||
| 1106 | * |
||
| 1107 | * @param string $dolibarr_main_db_host Ip serveur |
||
| 1108 | * @param string $dolibarr_main_db_user Nom user a creer |
||
| 1109 | * @param string $dolibarr_main_db_pass Mot de passe user a creer |
||
| 1110 | * @param string $dolibarr_main_db_name Database name where user must be granted |
||
| 1111 | * @return int <0 if KO, >=0 if OK |
||
| 1112 | */ |
||
| 1113 | public function DDLCreateUser($dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_pass, $dolibarr_main_db_name) |
||
| 1114 | { |
||
| 1115 | // phpcs:enable |
||
| 1116 | $sql = "INSERT INTO user "; |
||
| 1117 | $sql.= "(Host,User,password,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Index_Priv,Alter_priv,Lock_tables_priv)"; |
||
| 1118 | $sql.= " VALUES ('".$this->escape($dolibarr_main_db_host)."','".$this->escape($dolibarr_main_db_user)."',password('".addslashes($dolibarr_main_db_pass)."')"; |
||
| 1119 | $sql.= ",'Y','Y','Y','Y','Y','Y','Y','Y','Y')"; |
||
| 1120 | |||
| 1121 | dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log |
||
| 1122 | $resql=$this->query($sql); |
||
| 1123 | if (! $resql) |
||
| 1124 | { |
||
| 1125 | return -1; |
||
| 1126 | } |
||
| 1127 | |||
| 1128 | $sql = "INSERT INTO db "; |
||
| 1129 | $sql.= "(Host,Db,User,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Index_Priv,Alter_priv,Lock_tables_priv)"; |
||
| 1130 | $sql.= " VALUES ('".$this->escape($dolibarr_main_db_host)."','".$this->escape($dolibarr_main_db_name)."','".addslashes($dolibarr_main_db_user)."'"; |
||
| 1131 | $sql.= ",'Y','Y','Y','Y','Y','Y','Y','Y','Y')"; |
||
| 1132 | |||
| 1133 | dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); |
||
| 1134 | $resql=$this->query($sql); |
||
| 1135 | if (! $resql) |
||
| 1136 | { |
||
| 1137 | return -1; |
||
| 1138 | } |
||
| 1139 | |||
| 1140 | $sql="FLUSH Privileges"; |
||
| 1141 | |||
| 1142 | dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); |
||
| 1143 | $resql=$this->query($sql); |
||
| 1144 | if (! $resql) |
||
| 1145 | { |
||
| 1146 | return -1; |
||
| 1147 | } |
||
| 1148 | return 1; |
||
| 1149 | } |
||
| 1150 | |||
| 1151 | /** |
||
| 1152 | * Return charset used to store data in database |
||
| 1153 | * |
||
| 1154 | * @return string Charset |
||
| 1155 | */ |
||
| 1156 | public function getDefaultCharacterSetDatabase() |
||
| 1157 | { |
||
| 1158 | return 'UTF-8'; |
||
| 1159 | } |
||
| 1160 | |||
| 1161 | /** |
||
| 1162 | * Return list of available charset that can be used to store data in database |
||
| 1163 | * |
||
| 1164 | * @return array List of Charset |
||
| 1165 | */ |
||
| 1166 | public function getListOfCharacterSet() |
||
| 1167 | { |
||
| 1168 | $liste = array(); |
||
| 1169 | $i=0; |
||
| 1170 | $liste[$i]['charset'] = 'UTF-8'; |
||
| 1171 | $liste[$i]['description'] = 'UTF-8'; |
||
| 1172 | return $liste; |
||
| 1173 | } |
||
| 1174 | |||
| 1175 | /** |
||
| 1176 | * Return collation used in database |
||
| 1177 | * |
||
| 1178 | * @return string Collation value |
||
| 1179 | */ |
||
| 1180 | public function getDefaultCollationDatabase() |
||
| 1181 | { |
||
| 1182 | return 'UTF-8'; |
||
| 1183 | } |
||
| 1184 | |||
| 1185 | /** |
||
| 1186 | * Return list of available collation that can be used for database |
||
| 1187 | * |
||
| 1188 | * @return array List of Collation |
||
| 1189 | */ |
||
| 1190 | public function getListOfCollation() |
||
| 1191 | { |
||
| 1192 | $liste = array(); |
||
| 1193 | $i=0; |
||
| 1194 | $liste[$i]['charset'] = 'UTF-8'; |
||
| 1195 | $liste[$i]['description'] = 'UTF-8'; |
||
| 1196 | return $liste; |
||
| 1197 | } |
||
| 1198 | |||
| 1199 | /** |
||
| 1200 | * Return full path of dump program |
||
| 1201 | * |
||
| 1202 | * @return string Full path of dump program |
||
| 1203 | */ |
||
| 1204 | public function getPathOfDump() |
||
| 1205 | { |
||
| 1206 | // FIXME: not for SQLite |
||
| 1207 | $fullpathofdump='/pathtomysqldump/mysqldump'; |
||
| 1208 | |||
| 1209 | $resql=$this->query('SHOW VARIABLES LIKE \'basedir\''); |
||
| 1210 | if ($resql) |
||
| 1211 | { |
||
| 1212 | $liste=$this->fetch_array($resql); |
||
| 1213 | $basedir=$liste['Value']; |
||
| 1214 | $fullpathofdump=$basedir.(preg_match('/\/$/', $basedir)?'':'/').'bin/mysqldump'; |
||
| 1215 | } |
||
| 1216 | return $fullpathofdump; |
||
| 1217 | } |
||
| 1218 | |||
| 1219 | /** |
||
| 1220 | * Return full path of restore program |
||
| 1221 | * |
||
| 1222 | * @return string Full path of restore program |
||
| 1223 | */ |
||
| 1224 | public function getPathOfRestore() |
||
| 1237 | } |
||
| 1238 | |||
| 1239 | /** |
||
| 1240 | * Return value of server parameters |
||
| 1241 | * |
||
| 1242 | * @param string $filter Filter list on a particular value |
||
| 1243 | * @return array Array of key-values (key=>value) |
||
| 1244 | */ |
||
| 1245 | public function getServerParametersValues($filter = '') |
||
| 1246 | { |
||
| 1247 | $result=array(); |
||
| 1248 | static $pragmas; |
||
| 1249 | if (! isset($pragmas)) { |
||
| 1250 | // Définition de la liste des pragmas utilisés qui ne retournent qu'une seule valeur |
||
| 1251 | // indépendante de la base de données. |
||
| 1252 | // cf. http://www.sqlite.org/pragma.html |
||
| 1253 | $pragmas = array( |
||
| 1254 | 'application_id', 'auto_vacuum', 'automatic_index', 'busy_timeout', 'cache_size', |
||
| 1255 | 'cache_spill', 'case_sensitive_like', 'checkpoint_fullsync', 'collation_list', |
||
| 1256 | 'compile_options', 'data_version', /*'database_list',*/ |
||
| 1257 | 'defer_foreign_keys', 'encoding', 'foreign_key_check', 'freelist_count', |
||
| 1258 | 'full_column_names', 'fullsync', 'ingore_check_constraints', 'integrity_check', |
||
| 1259 | 'journal_mode', 'journal_size_limit', 'legacy_file_format', 'locking_mode', |
||
| 1260 | 'max_page_count', 'page_count', 'page_size', 'parser_trace', |
||
| 1261 | 'query_only', 'quick_check', 'read_uncommitted', 'recursive_triggers', |
||
| 1262 | 'reverse_unordered_selects', 'schema_version', 'user_version', |
||
| 1263 | 'secure_delete', 'short_column_names', 'shrink_memory', 'soft_heap_limit', |
||
| 1264 | 'synchronous', 'temp_store', /*'temp_store_directory',*/ 'threads', |
||
| 1265 | 'vdbe_addoptrace', 'vdbe_debug', 'vdbe_listing', 'vdbe_trace', |
||
| 1266 | 'wal_autocheckpoint', |
||
| 1267 | ); |
||
| 1268 | } |
||
| 1269 | |||
| 1270 | // TODO prendre en compte le filtre |
||
| 1271 | foreach($pragmas as $var) { |
||
| 1272 | $sql = "PRAGMA $var"; |
||
| 1273 | $resql=$this->query($sql); |
||
| 1274 | if ($resql) |
||
| 1275 | { |
||
| 1276 | $obj = $this->fetch_row($resql); |
||
| 1277 | //dol_syslog(get_class($this)."::select_db getServerParametersValues $var=". print_r($obj, true), LOG_DEBUG); |
||
| 1278 | $result[$var] = $obj[0]; |
||
| 1279 | } |
||
| 1280 | else { |
||
| 1281 | // TODO Récupérer le message |
||
| 1282 | $result[$var] = 'FAIL'; |
||
| 1283 | } |
||
| 1284 | } |
||
| 1285 | return $result; |
||
| 1286 | } |
||
| 1287 | |||
| 1288 | /** |
||
| 1289 | * Return value of server status |
||
| 1290 | * |
||
| 1291 | * @param string $filter Filter list on a particular value |
||
| 1292 | * @return array Array of key-values (key=>value) |
||
| 1293 | */ |
||
| 1294 | public function getServerStatusValues($filter = '') |
||
| 1295 | { |
||
| 1296 | $result=array(); |
||
| 1297 | /* |
||
| 1298 | $sql='SHOW STATUS'; |
||
| 1299 | if ($filter) $sql.=" LIKE '".$this->escape($filter)."'"; |
||
| 1300 | $resql=$this->query($sql); |
||
| 1301 | if ($resql) |
||
| 1302 | { |
||
| 1303 | while ($obj=$this->fetch_object($resql)) $result[$obj->Variable_name]=$obj->Value; |
||
| 1304 | } |
||
| 1305 | */ |
||
| 1306 | |||
| 1307 | return $result; |
||
| 1308 | } |
||
| 1309 | |||
| 1310 | /** |
||
| 1311 | * Permet le chargement d'une fonction personnalisee dans le moteur de base de donnees. |
||
| 1312 | * Note: le nom de la fonction personnalisee est prefixee par 'db'. La fonction doit être |
||
| 1313 | * statique et publique. Le nombre de parametres est determine automatiquement. |
||
| 1314 | * |
||
| 1315 | * @param string $name Le nom de la fonction a definir dans Sqlite |
||
| 1316 | * @param int $arg_count Arg count |
||
| 1317 | * @return void |
||
| 1318 | */ |
||
| 1319 | private function addCustomFunction($name, $arg_count = -1) |
||
| 1320 | { |
||
| 1321 | if ($this->db) |
||
| 1322 | { |
||
| 1323 | $newname=preg_replace('/_/', '', $name); |
||
| 1324 | $localname = __CLASS__ . '::' . 'db' . $newname; |
||
| 1325 | $reflectClass = new ReflectionClass(__CLASS__); |
||
| 1326 | $reflectFunction = $reflectClass->getMethod('db' . $newname); |
||
| 1327 | if ($arg_count < 0) { |
||
| 1328 | $arg_count = $reflectFunction->getNumberOfParameters(); |
||
| 1329 | } |
||
| 1330 | if (!$this->db->createFunction($name, $localname, $arg_count)) |
||
| 1331 | { |
||
| 1332 | $this->error = "unable to create custom function '$name'"; |
||
| 1333 | } |
||
| 1334 | } |
||
| 1335 | } |
||
| 1336 | |||
| 1337 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 1338 | /** |
||
| 1339 | * calc_daynr |
||
| 1340 | * |
||
| 1341 | * @param int $year Year |
||
| 1342 | * @param int $month Month |
||
| 1343 | * @param int $day Day |
||
| 1344 | * @return int Formatted date |
||
| 1345 | */ |
||
| 1346 | private static function calc_daynr($year, $month, $day) |
||
| 1347 | { |
||
| 1348 | // phpcs:enable |
||
| 1349 | $y = $year; |
||
| 1350 | if ($y == 0 && $month == 0) return 0; |
||
| 1351 | $num = (365* $y + 31 * ($month - 1) + $day); |
||
| 1352 | if ($month <= 2) { |
||
| 1353 | $y--; } |
||
| 1354 | else { |
||
| 1355 | $num -= floor(($month * 4 + 23) / 10); |
||
| 1356 | } |
||
| 1357 | $temp = floor(($y / 100 + 1) * 3 / 4); |
||
| 1358 | return $num + floor($y / 4) - $temp; |
||
| 1359 | } |
||
| 1360 | |||
| 1361 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 1362 | /** |
||
| 1363 | * calc_weekday |
||
| 1364 | * |
||
| 1365 | * @param int $daynr ??? |
||
| 1366 | * @param bool $sunday_first_day_of_week ??? |
||
| 1367 | * @return int |
||
| 1368 | */ |
||
| 1369 | private static function calc_weekday($daynr, $sunday_first_day_of_week) |
||
| 1370 | { |
||
| 1371 | // phpcs:enable |
||
| 1372 | $ret = floor(($daynr + 5 + ($sunday_first_day_of_week ? 1 : 0)) % 7); |
||
| 1373 | return $ret; |
||
| 1374 | } |
||
| 1375 | |||
| 1376 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 1377 | /** |
||
| 1378 | * calc_days_in_year |
||
| 1379 | * |
||
| 1380 | * @param string $year Year |
||
| 1381 | * @return int Nb of days in year |
||
| 1382 | */ |
||
| 1383 | private static function calc_days_in_year($year) |
||
| 1384 | { |
||
| 1385 | // phpcs:enable |
||
| 1386 | return (($year & 3) == 0 && ($year%100 || ($year%400 == 0 && $year)) ? 366 : 365); |
||
| 1387 | } |
||
| 1388 | |||
| 1389 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
| 1390 | /** |
||
| 1391 | * calc_week |
||
| 1392 | * |
||
| 1393 | * @param string $year Year |
||
| 1394 | * @param string $month Month |
||
| 1395 | * @param string $day Day |
||
| 1396 | * @param string $week_behaviour Week behaviour |
||
| 1397 | * @param string $calc_year ??? |
||
| 1398 | * @return string ??? |
||
| 1399 | */ |
||
| 1400 | private static function calc_week($year, $month, $day, $week_behaviour, &$calc_year) |
||
| 1435 | } |
||
| 1436 | } |
||
| 1437 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..