2017-08-04 16:28:16 +02:00
< ? php
/**
* Classe dedicata alla gestione delle procedure di aggiornamento del database del progetto .
*
* @ since 2.3
*/
class Update
{
2017-08-31 11:32:49 +02:00
/** @var array Elenco degli aggiornamenti da completare */
2017-08-04 16:28:16 +02:00
protected static $updates ;
2017-08-31 11:32:49 +02:00
/**
* Controlla la presenza di aggiornamenti e prepara il database per la procedura .
*/
2017-08-04 16:28:16 +02:00
protected static function prepareToUpdate ()
{
$database = Database :: getConnection ();
$database_ready = $database -> isConnected () && $database -> fetchNum ( " SHOW TABLES LIKE 'updates' " );
// Individuazione di tutti gli aggiornamenti fisicamente presenti
$results = [];
// Aggiornamenti del gestionale
$core = ( array ) glob ( DOCROOT . '/update/*.{php,sql}' , GLOB_BRACE );
foreach ( $core as $value ) {
$infos = pathinfo ( $value );
$value = str_replace ( '_' , '.' , $infos [ 'filename' ]);
if ( self :: isVersion ( $value )) {
$results [] = $value ;
}
}
// Aggiornamenti dei moduli
$modules = ( array ) glob ( DOCROOT . '/modules/*/update/*.{php,sql}' , GLOB_BRACE );
foreach ( $modules as $value ) {
$infos = pathinfo ( $value );
$module = end ( explode ( '/' , dirname ( $infos [ 'dirname' ])));
$value = str_replace ( '_' , '.' , $infos [ 'filename' ]);
if ( self :: isVersion ( $value )) {
$results [] = $module . '_' . $value ;
}
}
$results = array_unique ( $results );
asort ( $results );
// Individuazione di tutti gli aggiornamenti inseriti
$updates = ( $database_ready ) ? $database -> fetchArray ( 'SELECT * FROM `updates`' ) : [];
$versions = array_column ( $updates , 'version' );
$reset = count ( array_intersect ( $results , $versions )) != count ( $results );
2017-08-31 11:32:49 +02:00
// Memorizzazione degli aggiornamenti
2017-08-04 16:28:16 +02:00
if ( $reset && $database -> isConnected ()) {
// Individua le versioni che sono state installate, anche solo parzialmente
$done = ( $database_ready ) ? $database -> fetchArray ( 'SELECT version, done FROM updates WHERE `done` IS NOT NULL' ) : [];
// Reimpostazione della tabella degli aggiornamenti
$create = DOCROOT . '/update/create_updates.sql' ;
if ( file_exists ( $create )) {
$database -> query ( 'DROP TABLE IF EXISTS `updates`' );
$database -> multiQuery ( $create );
}
// Inserimento degli aggiornamenti individuati
foreach ( $results as $result ) {
// Individuazione di script e sql
$temp = explode ( '_' , $result );
2017-08-28 15:29:03 +02:00
$file = DOCROOT . (( str_contains ( $result , '_' )) ? '/modules/' . implode ( '_' , explode ( '_' , $result , - 1 )) : '' ) . '/update/' . str_replace ( '.' , '_' , end ( $temp ));
2017-08-04 16:28:16 +02:00
$sql = file_exists ( $file . '.sql' ) ? 1 : 0 ;
$script = file_exists ( $file . '.php' ) ? 1 : 0 ;
// Reimpostazione degli stati per gli aggiornamenti precedentemente presenti
$pos = array_search ( $result , $versions );
$done = ( $pos !== false ) ? prepare ( $updates [ $pos ][ 'done' ]) : 'NULL' ;
$database -> query ( 'INSERT INTO `updates` (`version`, `sql`, `script`, `done`) VALUES (' . prepare ( $result ) . ', ' . prepare ( $sql ) . ', ' . prepare ( $script ) . ', ' . $done . ')' );
}
// Normalizzazione di charset e collation
self :: normalizeDatabase ( $database -> getDatabaseName ());
}
}
2017-08-31 11:32:49 +02:00
/**
* Restituisce l ' elenco degli aggiornamento incompleti o non ancora effettuati .
*
* @ return array
*/
2017-08-04 16:28:16 +02:00
public static function getTodos ()
{
if ( ! is_array ( self :: $updates )) {
self :: prepareToUpdate ();
$database = Database :: getConnection ();
$updates = $database -> isConnected () ? $database -> fetchArray ( 'SELECT * FROM `updates` WHERE `done` != 1 OR `done` IS NULL ORDER BY `done` DESC, `id` ASC' ) : [];
foreach ( $updates as $key => $value ) {
$updates [ $key ][ 'name' ] = ucwords ( str_replace ( '_' , ' ' , $value [ 'version' ]));
$temp = explode ( '_' , $value [ 'version' ]);
$updates [ $key ][ 'filename' ] = str_replace ( '.' , '_' , end ( $temp ));
2017-08-28 15:29:03 +02:00
$updates [ $key ][ 'directory' ] = (( str_contains ( $value [ 'version' ], '_' )) ? '/modules/' . implode ( '_' , explode ( '_' , $value [ 'version' ], - 1 )) : '' ) . '/update/' ;
2017-08-04 16:28:16 +02:00
}
self :: $updates = $updates ;
}
return self :: $updates ;
}
2017-08-31 11:32:49 +02:00
/**
* Restituisce il primo aggiornamento che deve essere completato .
*
* @ return array
*/
2017-08-04 16:28:16 +02:00
public static function getUpdate ()
{
2017-09-10 18:29:51 +02:00
$todos = self :: getTodos ();
return ! empty ( $todos ) ? $todos ()[ 0 ] : null ;
2017-08-04 16:28:16 +02:00
}
2017-08-31 11:32:49 +02:00
/**
* Controlla che la stringa inserita possieda una struttura corrispondente a quella di una versione .
*
* @ param string $string
*
* @ return bool
*/
2017-08-04 16:28:16 +02:00
public static function isVersion ( $string )
{
return preg_match ( '/^\d+(?:\.\d+)+$/' , $string );
}
2017-08-31 11:32:49 +02:00
/**
* Controlla ci sono aggiornamenti da fare per il database .
*
* @ return bool
*/
2017-08-04 16:28:16 +02:00
public static function isUpdateAvailable ()
{
2017-09-10 18:29:51 +02:00
$todos = self :: getTodos ();
return ! empty ( $todos );
2017-08-04 16:28:16 +02:00
}
2017-08-31 11:32:49 +02:00
/**
* Controlla se la procedura di aggiornamento è conclusa .
*
* @ return bool
*/
2017-08-04 16:28:16 +02:00
public static function isUpdateCompleted ()
{
return ! self :: isUpdateAvailable ();
}
2017-08-31 11:32:49 +02:00
/**
* Controlla se l ' aggiornamento è in esecuzione .
*
* @ return bool
*/
2017-08-04 16:28:16 +02:00
public static function isUpdateLocked ()
{
$todos = array_column ( self :: getTodos (), 'done' );
foreach ( $todos as $todo ) {
if ( $todo !== null && $todo !== 1 ) {
return true ;
}
}
return false ;
}
2017-08-31 11:32:49 +02:00
/**
* Restituisce la versione corrente del software gestita dal database .
*
* @ return string
*/
2017-08-04 16:28:16 +02:00
public static function getDatabaseVersion ()
{
$database = Database :: getConnection ();
$results = $database -> fetchArray ( " SELECT version FROM `updates` WHERE version NOT LIKE '% \ _%' ORDER BY version DESC LIMIT 1 " );
return $results [ 0 ][ 'version' ];
}
2017-08-31 11:32:49 +02:00
/**
* Restituisce la versione corrente del software gestita dal file system ( file VERSION nella root ) .
*
* @ return string
*/
2017-08-04 16:28:16 +02:00
public static function getVersion ()
{
return self :: getFile ( 'VERSION' );
}
2017-08-31 11:32:49 +02:00
/**
* Restituisce la revisione corrente del software gestita dal file system ( file REVISION nella root ) .
*
* @ return string
*/
2017-08-04 16:28:16 +02:00
public static function getRevision ()
{
return self :: getFile ( 'REVISION' );
}
2017-08-31 11:32:49 +02:00
/**
* Ottiene i contenuti di un file .
*
* @ param string $file
*
* @ return string
*/
2017-08-04 16:28:16 +02:00
protected static function getFile ( $file )
{
2017-08-28 15:29:03 +02:00
$file = ( str_contains ( $file , DOCROOT . DIRECTORY_SEPARATOR )) ? $file : DOCROOT . DIRECTORY_SEPARATOR . $file ;
2017-08-04 16:28:16 +02:00
$result = '' ;
$filepath = realpath ( $file );
if ( ! empty ( $filepath )) {
$result = file_get_contents ( $filepath );
$result = str_replace ([ " \r \n " , " \n " ], '' , $result );
}
return trim ( $result );
}
2017-08-31 11:32:49 +02:00
/**
* Effettua una pulizia del database a seguito del completamento dell ' aggiornamento .
*
* @ return bool
*/
2017-08-04 16:28:16 +02:00
public static function updateCleanup ()
{
if ( self :: isUpdateCompleted ()) {
$database = Database :: getConnection ();
// Aggiornamento all'ultima release della versione e compatibilità moduli
$database -> query ( 'UPDATE `zz_modules` SET `compatibility`=' . prepare ( self :: getVersion ()) . ', `version`=' . prepare ( self :: getVersion ()) . ' WHERE `default` = 1' );
// Normalizzazione di charset e collation
self :: normalizeDatabase ( $database -> getDatabaseName ());
return true ;
}
return false ;
}
2017-08-31 11:32:49 +02:00
/**
* Esegue una precisa sezione dell ' aggiornamento fa fare , partendo dalle query e passando poi allo script relativo .
* Prima dell 'esecuzione dello script viene inoltre eseguita un' operazione di normalizzazione dei campi delle tabelle del database finalizzata a generalizzare la gestione delle informazioni per l ' API : vengono quindi aggiunti i campi < b > created_at </ b > e , se permesso dalla versione di MySQL , < b > updated_at </ b > ad ogni tabella registrata del software .
*
* @ param int $rate Numero di singole query da eseguire dell ' aggiornamento corrente
*
* @ return array | bool
*/
2017-08-04 16:28:16 +02:00
public static function doUpdate ( $rate = 20 )
{
global $logger ;
set_time_limit ( 0 );
ignore_user_abort ( true );
if ( ! self :: isUpdateCompleted ()) {
$update = self :: getUpdate ();
$file = DOCROOT . $update [ 'directory' ] . $update [ 'filename' ];
$database = Database :: getConnection ();
try {
2017-08-31 11:32:49 +02:00
// Esecuzione delle query
2017-08-04 16:28:16 +02:00
if ( ! empty ( $update [ 'sql' ]) && ( ! empty ( $update [ 'done' ]) || is_null ( $update [ 'done' ])) && file_exists ( $file . '.sql' )) {
$queries = readSQLFile ( $file . '.sql' , ';' );
$count = count ( $queries );
$start = empty ( $update [ 'done' ]) ? 0 : $update [ 'done' ] - 2 ;
$end = ( $start + $rate + 1 ) > $count ? $count : $start + $rate + 1 ;
if ( $start < $end ) {
for ( $i = $start ; $i < $end ; ++ $i ) {
2017-09-04 12:02:29 +02:00
$database -> query ( $queries [ $i ], tr ( 'Aggiornamento fallito' ) . ': ' . $queries [ $i ]);
2017-08-04 16:28:16 +02:00
$database -> query ( 'UPDATE `updates` SET `done` = ' . prepare ( $i + 3 ) . ' WHERE id = ' . prepare ( $update [ 'id' ]));
}
2017-08-31 11:32:49 +02:00
// Restituisce l'indice della prima e dell'ultima query eseguita, con la differenza relativa per l'avanzamento dell'aggiornamento
2017-08-04 16:28:16 +02:00
return [
$start ,
$end ,
$count ,
];
}
}
2017-08-31 11:32:49 +02:00
// Imposta l'aggiornamento nello stato di esecuzione dello script
2017-08-04 16:28:16 +02:00
$database -> query ( 'UPDATE `updates` SET `done` = 0 WHERE id = ' . prepare ( $update [ 'id' ]));
2017-08-31 10:09:06 +02:00
// Normalizzazione dei campi per l'API
self :: executeScript ( DOCROOT . '/update/api.php' );
2017-08-31 11:32:49 +02:00
// Esecuzione dello script
2017-08-04 16:28:16 +02:00
if ( ! empty ( $update [ 'script' ]) && file_exists ( $file . '.php' )) {
self :: executeScript ( $file . '.php' );
}
2017-08-31 11:32:49 +02:00
// Imposta l'aggiornamento come completato
2017-08-04 16:28:16 +02:00
$database -> query ( 'UPDATE `updates` SET `done` = 1 WHERE id = ' . prepare ( $update [ 'id' ]));
// Normalizzazione di charset e collation
self :: normalizeDatabase ( $database -> getDatabaseName ());
return true ;
} catch ( \Exception $e ) {
$logger -> addRecord ( \Monolog\Logger :: EMERGENCY , $e -> getMessage ());
}
return false ;
}
}
2017-08-31 11:32:49 +02:00
/**
* Normalizza l 'infrastruttura del database indicato, generalizzando charset e collation all' interno del database e delle tabelle ed effettuando una conversione delle tabelle all ' engine InnoDB .
* < b > Attenzione </ b >: se l 'engine InnoDB non è supportato, il server ignorerà la conversione dell' engine e le foreign key del gestionale non funzioneranno adeguatamente .
*
* @ param [ type ] $database_name
*/
2017-08-04 16:28:16 +02:00
protected static function normalizeDatabase ( $database_name )
{
set_time_limit ( 0 );
ignore_user_abort ( true );
$database = Database :: getConnection ();
$mysql_ver = $database -> getMySQLVersion ();
if ( version_compare ( $mysql_ver , '5.5.3' ) >= 0 ) {
$character_set = 'utf8mb4' ;
$collation = 'utf8mb4_general_ci' ;
} else {
$character_set = 'utf8' ;
$collation = 'utf8_general_ci' ;
}
// Normalizzazione del database (le nuove tabelle verranno automaticamente impostate secondo la codifica predefinita)
$default_collation = $database -> fetchArray ( 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ' . prepare ( $database_name ) . ' LIMIT 1' )[ 0 ][ 'DEFAULT_COLLATION_NAME' ];
if ( $default_collation != $collation ) {
$database -> query ( 'ALTER DATABASE `' . $database_name . '` CHARACTER SET ' . $character_set . ' COLLATE ' . $collation );
}
// Normalizzazione delle tabelle
$tables = $database -> fetchArray ( 'SHOW TABLE STATUS IN `' . $database_name . '` WHERE Collation != ' . prepare ( $collation ) . " AND Name != 'updates' " );
if ( ! empty ( $tables )) {
$database -> query ( 'SET foreign_key_checks = 0' );
// Conversione delle tabelle
foreach ( $tables as $table ) {
$database -> query ( 'ALTER TABLE `' . $table [ 'Name' ] . '` CONVERT TO CHARACTER SET ' . $character_set . ' COLLATE ' . $collation );
}
$database -> query ( 'SET foreign_key_checks = 1' );
}
2017-08-30 11:50:46 +02:00
// Normalizzazione dell'engine MySQL
$engines = $database -> fetchArray ( 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ' . prepare ( $database_name ) . " AND ENGINE != 'InnoDB' " );
foreach ( $engines as $engine ) {
$database -> query ( 'ALTER TABLE `' . $engine [ 'TABLE_NAME' ] . '` ENGINE=InnoDB' );
}
2017-08-04 16:28:16 +02:00
}
2017-08-31 11:32:49 +02:00
/**
* Esegue uno script PHP in un ' ambiente il più possibile protetto .
*
* @ param string $script
*/
2017-08-04 16:28:16 +02:00
protected static function executeScript ( $script )
{
2017-08-31 11:32:49 +02:00
$database = Database :: getConnection ();
$dbo = $database ;
2017-08-04 16:28:16 +02:00
// Informazioni relative a MySQL
$mysql_ver = $database -> getMySQLVersion ();
include $script ;
}
}