2018-10-06 00:53:13 +02:00
< ? php
namespace Friendica\Core ;
2018-10-29 10:16:07 +01:00
use Friendica\Database\DBA ;
2018-10-06 00:53:13 +02:00
use Friendica\Database\DBStructure ;
2018-11-08 16:26:49 +01:00
use Friendica\Util\Strings ;
2018-10-06 00:53:13 +02:00
class Update
{
2018-10-14 13:19:37 +02:00
const SUCCESS = 0 ;
const FAILED = 1 ;
2018-10-14 13:26:53 +02:00
/**
* @ brief Function to check if the Database structure needs an update .
*
2019-02-03 22:46:50 +01:00
* @ param string $basePath The base path of this application
2018-10-14 13:26:53 +02:00
* @ param boolean $via_worker boolean Is the check run via the worker ?
2019-01-06 22:06:53 +01:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-14 13:26:53 +02:00
*/
2019-02-03 22:46:50 +01:00
public static function check ( $basePath , $via_worker )
2018-10-14 13:26:53 +02:00
{
2018-12-03 02:57:41 +01:00
if ( ! DBA :: connected ()) {
return ;
}
2018-10-14 13:26:53 +02:00
$build = Config :: get ( 'system' , 'build' );
if ( empty ( $build )) {
Config :: set ( 'system' , 'build' , DB_UPDATE_VERSION - 1 );
$build = DB_UPDATE_VERSION - 1 ;
}
// We don't support upgrading from very old versions anymore
if ( $build < NEW_UPDATE_ROUTINE_VERSION ) {
die ( 'You try to update from a version prior to database version 1170. The direct upgrade path is not supported. Please update to version 3.5.4 before updating to this version.' );
}
2019-02-24 11:52:40 +01:00
if ( $build < DB_UPDATE_VERSION ) {
if ( $via_worker ) {
// Calling the database update directly via the worker enables us to perform database changes to the workerqueue table itself.
// This is a fallback, since normally the database update will be performed by a worker job.
// This worker job doesn't work for changes to the "workerqueue" table itself.
self :: run ( $basePath );
} else {
Worker :: add ( PRIORITY_CRITICAL , 'DBUpdate' );
}
2018-10-14 13:26:53 +02:00
}
}
2018-10-06 00:53:13 +02:00
/**
* Automatic database updates
2018-10-29 10:16:07 +01:00
*
2019-02-03 22:46:50 +01:00
* @ param string $basePath The base path of this application
2019-02-24 12:24:09 +01:00
* @ param bool $force Force the Update - Check even if the database version doesn ' t match
* @ param bool $override Overrides any running / stuck updates
2019-02-03 22:46:50 +01:00
* @ param bool $verbose Run the Update - Check verbose
* @ param bool $sendMail Sends a Mail to the administrator in case of success / failure
2018-10-29 10:16:07 +01:00
*
* @ return string Empty string if the update is successful , error messages otherwise
2019-01-06 22:06:53 +01:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-06 00:53:13 +02:00
*/
2019-02-24 12:24:09 +01:00
public static function run ( $basePath , $force = false , $override = false , $verbose = false , $sendMail = true )
2018-10-06 00:53:13 +02:00
{
2018-10-29 10:16:07 +01:00
// In force mode, we release the dbupdate lock first
// Necessary in case of an stuck update
2019-02-24 12:24:09 +01:00
if ( $override ) {
2019-02-24 10:08:28 +01:00
Lock :: release ( 'dbupdate' , true );
2018-10-29 10:16:07 +01:00
}
2018-10-06 00:53:13 +02:00
$build = Config :: get ( 'system' , 'build' );
if ( empty ( $build ) || ( $build > DB_UPDATE_VERSION )) {
$build = DB_UPDATE_VERSION - 1 ;
Config :: set ( 'system' , 'build' , $build );
}
2019-02-24 12:24:09 +01:00
if ( $build != DB_UPDATE_VERSION || $force ) {
2018-10-06 00:53:13 +02:00
require_once 'update.php' ;
$stored = intval ( $build );
$current = intval ( DB_UPDATE_VERSION );
2019-02-24 12:24:09 +01:00
if ( $stored < $current || $force ) {
2018-10-06 00:53:13 +02:00
Config :: load ( 'database' );
2018-10-31 15:22:44 +01:00
Logger :: log ( 'Update from \'' . $stored . '\' to \'' . $current . '\' - starting' , Logger :: DEBUG );
2018-10-06 00:53:13 +02:00
// Compare the current structure with the defined structure
2018-10-29 10:16:07 +01:00
// If the Lock is acquired, never release it automatically to avoid double updates
2018-10-29 10:21:10 +01:00
if ( Lock :: acquire ( 'dbupdate' , 120 , Cache :: INFINITE )) {
2018-10-06 20:38:35 +02:00
// run the pre_update_nnnn functions in update.php
for ( $x = $stored + 1 ; $x <= $current ; $x ++ ) {
$r = self :: runUpdateFunction ( $x , 'pre_update' );
if ( ! $r ) {
break ;
}
2018-10-06 00:53:13 +02:00
}
2018-10-06 20:38:35 +02:00
// update the structure in one call
2019-02-03 22:46:50 +01:00
$retval = DBStructure :: update ( $basePath , $verbose , true );
2019-02-23 10:16:07 +01:00
if ( ! empty ( $retval )) {
2018-10-29 10:16:07 +01:00
if ( $sendMail ) {
self :: updateFailed (
DB_UPDATE_VERSION ,
$retval
);
}
2018-10-31 15:22:44 +01:00
Logger :: log ( 'ERROR: Update from \'' . $stored . '\' to \'' . $current . '\' - failed: ' - $retval , Logger :: ALL );
2018-10-06 20:38:35 +02:00
Lock :: release ( 'dbupdate' );
2018-10-29 10:16:07 +01:00
return $retval ;
2018-10-06 20:38:35 +02:00
} else {
2018-10-07 10:42:14 +02:00
Config :: set ( 'database' , 'last_successful_update' , $current );
Config :: set ( 'database' , 'last_successful_update_time' , time ());
2018-10-31 15:22:44 +01:00
Logger :: log ( 'Update from \'' . $stored . '\' to \'' . $current . '\' - finished' , Logger :: DEBUG );
2018-10-06 20:38:35 +02:00
}
2018-10-06 00:53:13 +02:00
2018-10-06 20:38:35 +02:00
// run the update_nnnn functions in update.php
for ( $x = $stored + 1 ; $x <= $current ; $x ++ ) {
$r = self :: runUpdateFunction ( $x , 'update' );
if ( ! $r ) {
break ;
}
2018-10-06 00:53:13 +02:00
}
2018-10-06 20:38:35 +02:00
2018-10-31 15:22:44 +01:00
Logger :: log ( 'Update from \'' . $stored . '\' to \'' . $current . '\' - successful' , Logger :: DEBUG );
2018-10-29 10:16:07 +01:00
if ( $sendMail ) {
self :: updateSuccessfull ( $stored , $current );
}
2018-10-06 20:38:35 +02:00
Lock :: release ( 'dbupdate' );
2018-10-06 00:53:13 +02:00
}
}
}
2018-10-29 10:16:07 +01:00
return '' ;
2018-10-06 00:53:13 +02:00
}
/**
* Executes a specific update function
*
2019-01-06 22:06:53 +01:00
* @ param int $x the DB version number of the function
2018-10-06 00:53:13 +02:00
* @ param string $prefix the prefix of the function ( update , pre_update )
*
* @ return bool true , if the update function worked
2019-01-06 22:06:53 +01:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-06 00:53:13 +02:00
*/
public static function runUpdateFunction ( $x , $prefix )
{
$funcname = $prefix . '_' . $x ;
2018-10-31 15:22:44 +01:00
Logger :: log ( 'Update function \'' . $funcname . '\' - start' , Logger :: DEBUG );
2018-10-06 00:53:13 +02:00
if ( function_exists ( $funcname )) {
// There could be a lot of processes running or about to run.
// We want exactly one process to run the update command.
// So store the fact that we're taking responsibility
// after first checking to see if somebody else already has.
// If the update fails or times-out completely you may need to
// delete the config entry to try again.
2018-10-29 10:21:10 +01:00
if ( Lock :: acquire ( 'dbupdate_function' , 120 , Cache :: INFINITE )) {
2018-10-06 20:38:35 +02:00
// call the specific update
$retval = $funcname ();
2018-10-06 00:53:13 +02:00
2018-10-06 20:38:35 +02:00
if ( $retval ) {
//send the administrator an e-mail
2018-10-29 10:16:07 +01:00
self :: updateFailed (
2018-10-06 20:38:35 +02:00
$x ,
L10n :: t ( 'Update %s failed. See error logs.' , $x )
);
2018-10-31 15:22:44 +01:00
Logger :: log ( 'ERROR: Update function \'' . $funcname . '\' - failed: ' . $retval , Logger :: ALL );
2018-10-06 20:38:35 +02:00
Lock :: release ( 'dbupdate_function' );
return false ;
} else {
Config :: set ( 'database' , 'last_successful_update_function' , $funcname );
Config :: set ( 'database' , 'last_successful_update_function_time' , time ());
if ( $prefix == 'update' ) {
Config :: set ( 'system' , 'build' , $x );
}
Lock :: release ( 'dbupdate_function' );
2018-10-31 15:22:44 +01:00
Logger :: log ( 'Update function \'' . $funcname . '\' - finished' , Logger :: DEBUG );
2018-10-06 20:38:35 +02:00
return true ;
}
2018-10-06 00:53:13 +02:00
}
} else {
2018-10-31 14:48:19 +01:00
Logger :: log ( 'Skipping \'' . $funcname . '\' without executing' , Logger :: DEBUG );
2018-10-06 20:38:35 +02:00
Config :: set ( 'database' , 'last_successful_update_function' , $funcname );
Config :: set ( 'database' , 'last_successful_update_function_time' , time ());
2018-10-06 00:53:13 +02:00
if ( $prefix == 'update' ) {
Config :: set ( 'system' , 'build' , $x );
}
return true ;
}
}
2018-10-29 10:16:07 +01:00
/**
* send the email and do what is needed to do on update fails
*
2019-01-06 22:06:53 +01:00
* @ param int $update_id number of failed update
* @ param string $error_message error message
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-29 10:16:07 +01:00
*/
private static function updateFailed ( $update_id , $error_message ) {
//send the administrators an e-mail
2019-03-02 13:57:47 +01:00
$condition = [ 'email' => explode ( " , " , str_replace ( " " , " " , Config :: get ( 'config' , 'admin_email' ))), 'parent-uid' => 0 ];
$adminlist = DBA :: select ( 'user' , [ 'uid' , 'language' , 'email' ], $condition , [ 'order' => [ 'uid' ]]);
2018-10-29 10:16:07 +01:00
// No valid result?
if ( ! DBA :: isResult ( $adminlist )) {
2018-10-31 14:48:19 +01:00
Logger :: log ( sprintf ( 'Cannot notify administrators about update_id=%d, error_message=%s' , $update_id , $error_message ), Logger :: INFO );
2018-10-29 10:16:07 +01:00
// Don't continue
return ;
}
2019-03-02 13:57:47 +01:00
$sent = [];
2018-10-29 10:16:07 +01:00
// every admin could had different language
2019-03-02 14:07:29 +01:00
while ( $admin = DBA :: fetch ( $adminlist )) {
2019-03-02 13:57:47 +01:00
if ( in_array ( $admin [ 'email' ], $sent )) {
continue ;
}
$sent [] = $admin [ 'email' ];
2018-10-29 10:16:07 +01:00
$lang = (( $admin [ 'language' ]) ? $admin [ 'language' ] : 'en' );
L10n :: pushLang ( $lang );
2018-11-08 16:26:49 +01:00
$preamble = Strings :: deindent ( L10n :: t ( "
2018-10-29 10:16:07 +01:00
The friendica developers released update % s recently ,
but when I tried to install it , something went terribly wrong .
This needs to be fixed soon and I can ' t do it alone . Please contact a
friendica developer if you can not help me on your own . My database might be invalid . " ,
$update_id ));
$body = L10n :: t ( " The error message is \n [pre]%s[/pre] " , $error_message );
notification ([
'uid' => $admin [ 'uid' ],
'type' => SYSTEM_EMAIL ,
'to_email' => $admin [ 'email' ],
'preamble' => $preamble ,
'body' => $body ,
'language' => $lang ]
);
L10n :: popLang ();
}
//try the logger
2018-10-31 14:48:19 +01:00
Logger :: log ( " CRITICAL: Database structure update failed: " . $error_message );
2018-10-29 10:16:07 +01:00
}
private static function updateSuccessfull ( $from_build , $to_build )
{
//send the administrators an e-mail
2019-03-02 13:57:47 +01:00
$condition = [ 'email' => explode ( " , " , str_replace ( " " , " " , Config :: get ( 'config' , 'admin_email' ))), 'parent-uid' => 0 ];
$adminlist = DBA :: select ( 'user' , [ 'uid' , 'language' , 'email' ], $condition , [ 'order' => [ 'uid' ]]);
2018-10-29 10:16:07 +01:00
if ( DBA :: isResult ( $adminlist )) {
2019-03-02 13:57:47 +01:00
$sent = [];
2018-10-29 10:16:07 +01:00
// every admin could had different language
2019-03-02 14:07:29 +01:00
while ( $admin = DBA :: fetch ( $adminlist )) {
2019-03-02 13:57:47 +01:00
if ( in_array ( $admin [ 'email' ], $sent )) {
continue ;
}
$sent [] = $admin [ 'email' ];
2018-10-29 10:16:07 +01:00
$lang = (( $admin [ 'language' ]) ? $admin [ 'language' ] : 'en' );
L10n :: pushLang ( $lang );
2018-11-08 16:26:49 +01:00
$preamble = Strings :: deindent ( L10n :: t ( "
2018-10-29 10:21:10 +01:00
The friendica database was successfully updated from % s to % s . " ,
2018-10-29 10:16:07 +01:00
$from_build , $to_build ));
notification ([
'uid' => $admin [ 'uid' ],
'type' => SYSTEM_EMAIL ,
'to_email' => $admin [ 'email' ],
'preamble' => $preamble ,
'body' => $preamble ,
'language' => $lang ]
);
L10n :: popLang ();
}
}
//try the logger
2018-10-31 14:48:19 +01:00
Logger :: log ( " Database structure update successful. " , Logger :: TRACE );
2018-10-29 10:16:07 +01:00
}
2018-10-06 20:38:35 +02:00
}