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 ;
2018-09-18 17:35:04 +02:00
/** @var array Percorsi da controllare per gli aggiornamenti */
protected static $directories = [
'modules' ,
2018-09-19 09:57:30 +02:00
'plugins' ,
2018-09-18 17:35:04 +02:00
];
2017-08-04 16:28:16 +02:00
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 ()
{
2018-09-20 12:05:22 +02:00
$database = database ();
2017-08-04 16:28:16 +02:00
2018-07-03 15:05:27 +02:00
$database_ready = $database -> isConnected () && $database -> tableExists ( 'updates' );
2017-08-04 16:28:16 +02:00
2018-09-18 17:35:04 +02:00
// Individuazione di tutti gli aggiornamenti presenti
2017-08-04 16:28:16 +02:00
// Aggiornamenti del gestionale
2018-01-17 15:47:00 +01:00
$core = self :: getCoreUpdates ();
2018-09-18 17:35:04 +02:00
// Aggiornamenti supportati
$modules = self :: getCustomUpdates ();
2017-08-04 16:28:16 +02:00
2018-01-17 15:47:00 +01:00
$results = array_merge ( $core , $modules );
2018-09-18 17:35:04 +02:00
$paths = array_column ( $results , 'path' );
2017-08-04 16:28:16 +02:00
2018-09-18 17:35:04 +02:00
// Individuazione di tutti gli aggiornamenti inseriti nel database
2017-08-04 16:28:16 +02:00
$updates = ( $database_ready ) ? $database -> fetchArray ( 'SELECT * FROM `updates`' ) : [];
2018-09-18 17:35:04 +02:00
$versions = [];
foreach ( $updates as $update ) {
$versions [] = self :: findUpdatePath ( $update );
}
2017-08-04 16:28:16 +02:00
2018-09-18 17:35:04 +02:00
$reset = count ( array_intersect ( $paths , $versions )) != count ( $results );
2017-08-04 16:28:16 +02:00
2017-08-31 11:32:49 +02:00
// Memorizzazione degli aggiornamenti
2017-08-04 16:28:16 +02:00
if ( $reset && $database -> isConnected ()) {
// 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
2018-09-18 17:35:04 +02:00
$sql = file_exists ( $result [ 'path' ] . '.sql' ) ? 1 : 0 ;
$script = file_exists ( $result [ 'path' ] . '.php' ) ? 1 : 0 ;
2017-08-04 16:28:16 +02:00
// Reimpostazione degli stati per gli aggiornamenti precedentemente presenti
2018-09-18 17:35:04 +02:00
$pos = array_search ( $result [ 'path' ], $versions );
$done = ( $pos !== false ) ? $updates [ $pos ][ 'done' ] : null ;
$directory = explode ( 'update/' , $result [ 'path' ])[ 0 ];
$database -> insert ( 'updates' , [
'directory' => rtrim ( $directory , '/' ),
'version' => $result [ 'version' ],
'sql' => $sql ,
'script' => $script ,
'done' => $done ,
]);
2017-08-04 16:28:16 +02:00
}
// Normalizzazione di charset e collation
self :: normalizeDatabase ( $database -> getDatabaseName ());
}
}
2018-01-17 15:47:00 +01:00
/**
* Restituisce l ' elenco degli aggiornamento del gestionale presenti nella cartella < b > update < b >.
*
* @ return array
*/
protected static function getCoreUpdates ()
{
2018-09-18 17:35:04 +02:00
return self :: getUpdates ( DOCROOT . '/update' );
}
2018-01-17 15:47:00 +01:00
2018-09-18 17:35:04 +02:00
/**
* Restituisce l ' elenco degli aggiornamento nel percorso indicato .
*
* @ param string $directory
*
* @ return array
*/
protected static function getUpdates ( $directory )
{
$results = [];
$previous = [];
$files = glob ( $directory . '/*.{php,sql}' , GLOB_BRACE );
foreach ( $files as $file ) {
$infos = pathinfo ( $file );
$version = str_replace ( '_' , '.' , $infos [ 'filename' ]);
if ( array_search ( $version , $previous ) === false && self :: isVersion ( $version )) {
$path = ltrim ( $infos [ 'dirname' ] . '/' . $infos [ 'filename' ], DOCROOT );
$path = ltrim ( $path , '/' );
$results [] = [
'path' => $path ,
'version' => $version ,
];
$previous [] = $version ;
2018-01-17 15:47:00 +01:00
}
}
asort ( $results );
return $results ;
}
/**
2018-09-18 17:35:04 +02:00
* Restituisce l ' elenco degli aggiornamento delle strutture supportate , presenti nella cartella < b > update < b >.
2018-01-17 15:47:00 +01:00
*
* @ return array
*/
2018-09-18 17:35:04 +02:00
protected static function getCustomUpdates ()
2018-01-17 15:47:00 +01:00
{
$results = [];
2018-09-18 17:35:04 +02:00
foreach ( self :: $directories as $dir ) {
$folders = glob ( DOCROOT . '/' . $dir . '/*/update' , GLOB_ONLYDIR );
2018-01-17 15:47:00 +01:00
2018-09-18 17:35:04 +02:00
foreach ( $folders as $folder ) {
$results = array_merge ( $results , self :: getUpdates ( $folder ));
}
}
2018-01-17 15:47:00 +01:00
2018-09-18 17:35:04 +02:00
return $results ;
}
2018-01-17 15:47:00 +01:00
2018-09-18 17:35:04 +02:00
protected static function findUpdatePath ( $update )
{
$version = str_replace ( '.' , '_' , $update [ 'version' ]);
$old_standard = str_contains ( $update [ 'version' ], '_' );
if ( empty ( $update [ 'directory' ]) && ! $old_standard ) {
return 'update/' . $version ;
2018-01-17 15:47:00 +01:00
}
2018-09-18 17:35:04 +02:00
if ( $old_standard ) {
$module = implode ( '_' , explode ( '_' , $update [ 'version' ], - 1 ));
$version = explode ( '_' , $update [ 'version' ]);
$version = end ( $version );
2018-01-17 15:47:00 +01:00
2018-09-18 17:35:04 +02:00
$version = str_replace ( '.' , '_' , $version );
return 'modules/' . $module . '/update/' . $version ;
}
return $update [ 'directory' ] . '/update/' . $version ;
2018-01-17 15:47:00 +01:00
}
2017-08-31 11:32:49 +02:00
/**
* Restituisce l ' elenco degli aggiornamento incompleti o non ancora effettuati .
*
* @ return array
*/
2018-09-18 17:35:04 +02:00
public static function getTodoUpdates ()
2017-08-04 16:28:16 +02:00
{
if ( ! is_array ( self :: $updates )) {
self :: prepareToUpdate ();
2018-09-20 12:05:22 +02:00
$database = database ();
2017-08-04 16:28:16 +02:00
$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 ) {
2018-09-18 17:35:04 +02:00
$name = explode ( '/' , $value [ 'directory' ]);
$updates [ $key ][ 'name' ] = ucwords ( end ( $name )) . ' ' . $value [ 'version' ];
2017-08-04 16:28:16 +02:00
2018-09-18 17:35:04 +02:00
$updates [ $key ][ 'filename' ] = str_replace ( '.' , '_' , $value [ 'version' ]);
2017-08-04 16:28:16 +02:00
2018-09-18 17:35:04 +02:00
$updates [ $key ][ 'directory' ] = $value [ 'directory' ] . '/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
*/
2018-09-18 17:35:04 +02:00
public static function getCurrentUpdate ()
2017-08-04 16:28:16 +02:00
{
2018-09-18 17:35:04 +02:00
$todos = self :: getTodoUpdates ();
2017-09-10 18:29:51 +02:00
2017-09-12 09:57:02 +02:00
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 )
{
2018-06-26 16:33:15 +02:00
return preg_match ( '/^\d+(?:\.\d+)+$/' , $string ) === 1 ;
2017-08-04 16:28:16 +02:00
}
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 ()
{
2018-09-18 17:35:04 +02:00
$todos = self :: getTodoUpdates ();
2017-09-10 18:29:51 +02:00
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 ()
{
2018-09-18 17:35:04 +02:00
$todos = array_column ( self :: getTodoUpdates (), 'done' );
2017-08-04 16:28:16 +02:00
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 ()
{
2018-09-20 12:05:22 +02:00
$database = database ();
2017-08-04 16:28:16 +02:00
$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
/**
2018-09-04 12:30:22 +02:00
* Restituisce la versione corrente del software ( file VERSION nella root e versione a database ) .
2017-08-31 11:32:49 +02:00
*
* @ return string
*/
2017-08-04 16:28:16 +02:00
public static function getVersion ()
{
2017-12-30 12:13:13 +01:00
$result = self :: getFile ( 'VERSION' );
if ( empty ( $result )) {
2018-09-20 12:05:22 +02:00
$database = database ();
2018-01-17 15:47:00 +01:00
if ( $database -> isInstalled ()) {
$result = self :: getDatabaseVersion ();
} else {
2018-01-26 20:25:16 +01:00
$updatelist = self :: getCoreUpdates ();
2018-09-19 15:12:20 +02:00
$result = end ( $updatelist )[ 'version' ];
2018-01-17 15:47:00 +01:00
}
2017-12-30 12:13:13 +01:00
}
return $result ;
2017-08-04 16:28:16 +02:00
}
2018-09-04 12:30:22 +02:00
/**
* Controlla se la versione corrente del software è una beta ( versione instabile ) .
*
* @ return bool
*/
public static function isBeta ()
{
$version = self :: getVersion ();
return str_contains ( $version , 'beta' );
}
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 ()) {
2018-09-20 12:05:22 +02:00
$database = database ();
2017-08-04 16:28:16 +02:00
// 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 )
{
set_time_limit ( 0 );
ignore_user_abort ( true );
if ( ! self :: isUpdateCompleted ()) {
2018-09-18 17:35:04 +02:00
$update = self :: getCurrentUpdate ();
2017-08-04 16:28:16 +02:00
2018-09-18 18:13:06 +02:00
$file = DOCROOT . '/' . $update [ 'directory' ] . $update [ 'filename' ];
2017-08-04 16:28:16 +02:00
2018-09-20 12:05:22 +02:00
$database = database ();
2017-08-04 16:28:16 +02:00
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 ) {
2018-07-17 12:52:07 +02:00
$database -> query ( $queries [ $i ], [], tr ( 'Aggiornamento fallito' ) . ': ' . $queries [ $i ]);
2017-08-04 16:28:16 +02:00
2018-07-17 12:52:07 +02:00
$database -> query ( 'UPDATE `updates` SET `done` = :done WHERE id = :id' , [
':done' => $i + 3 ,
':id' => $update [ 'id' ],
]);
2017-08-04 16:28:16 +02:00
}
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
2018-07-17 12:52:07 +02:00
$database -> query ( 'UPDATE `updates` SET `done` = :done WHERE id = :id' , [
':done' => 0 ,
':id' => $update [ 'id' ],
]);
2017-08-04 16:28:16 +02:00
2018-07-14 13:34:00 +02:00
// Permessi di default delle viste
2018-07-14 13:39:04 +02:00
if ( $database -> tableExists ( 'zz_views' )) {
$gruppi = $database -> fetchArray ( 'SELECT `id` FROM `zz_groups`' );
$viste = $database -> fetchArray ( 'SELECT `id` FROM `zz_views` WHERE `id` NOT IN (SELECT `id_vista` FROM `zz_group_view`)' );
$array = [];
foreach ( $viste as $vista ) {
foreach ( $gruppi as $gruppo ) {
$array [] = [
'id_gruppo' => $gruppo [ 'id' ],
'id_vista' => $vista [ 'id' ],
];
}
}
if ( ! empty ( $array )) {
$database -> insert ( 'zz_group_view' , $array );
2018-07-14 13:34:00 +02:00
}
}
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
2018-07-17 12:52:07 +02:00
$database -> query ( 'UPDATE `updates` SET `done` = :done WHERE id = :id' , [
':done' => 1 ,
':id' => $update [ 'id' ],
]);
2017-08-04 16:28:16 +02:00
// Normalizzazione di charset e collation
self :: normalizeDatabase ( $database -> getDatabaseName ());
return true ;
} catch ( \Exception $e ) {
2018-07-19 14:34:52 +02:00
$logger = logger ();
2017-08-04 16:28:16 +02:00
$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 .
*
2018-06-26 16:33:15 +02:00
* @ param string $database_name
2017-08-31 11:32:49 +02:00
*/
2017-08-04 16:28:16 +02:00
protected static function normalizeDatabase ( $database_name )
{
set_time_limit ( 0 );
ignore_user_abort ( true );
2018-09-20 12:05:22 +02:00
$database = database ();
2017-08-04 16:28:16 +02:00
2018-08-11 15:37:38 +02:00
$database -> getPDO () -> setAttribute ( PDO :: ATTR_EMULATE_PREPARES , true );
2017-08-04 16:28:16 +02:00
$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' );
}
2018-08-11 15:37:38 +02:00
$database -> getPDO () -> setAttribute ( PDO :: ATTR_EMULATE_PREPARES , false );
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 )
{
2018-09-20 12:05:22 +02:00
$dbo = $database = database ();
2017-08-04 16:28:16 +02:00
// Informazioni relative a MySQL
$mysql_ver = $database -> getMySQLVersion ();
include $script ;
}
}