2018-10-05 22:53:13 +00:00
< ? php
2020-02-09 14:45:36 +00:00
/**
2023-01-01 14:36:24 +00:00
* @ copyright Copyright ( C ) 2010 - 2023 , the Friendica project
2020-02-09 14:45:36 +00:00
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*
*/
2018-10-05 22:53:13 +00:00
namespace Friendica\Core ;
2019-03-24 21:51:30 +00:00
use Friendica\App ;
2021-01-17 20:32:13 +00:00
use Friendica\App\Mode ;
2018-10-29 09:16:07 +00:00
use Friendica\Database\DBA ;
2018-10-05 22:53:13 +00:00
use Friendica\Database\DBStructure ;
2020-01-06 23:20:31 +00:00
use Friendica\DI ;
2022-11-12 17:01:22 +00:00
use Friendica\Model\User ;
2021-01-17 20:32:13 +00:00
use Friendica\Network\HTTPException\InternalServerErrorException ;
2021-01-30 07:50:20 +00:00
use Friendica\Util\DateTimeFormat ;
2018-11-08 15:26:49 +00:00
use Friendica\Util\Strings ;
2018-10-05 22:53:13 +00:00
class Update
{
2018-10-14 11:19:37 +00:00
const SUCCESS = 0 ;
const FAILED = 1 ;
2022-10-17 09:24:32 +00:00
const NEW_TABLE_STRUCTURE_VERSION = 1288 ;
2018-10-14 11:26:53 +00:00
/**
2020-01-19 06:05:23 +00:00
* Function to check if the Database structure needs an update .
2018-10-14 11:26:53 +00:00
*
2019-03-30 17:54:22 +00:00
* @ param string $basePath The base path of this application
* @ param boolean $via_worker Is the check run via the worker ?
2019-03-30 18:08:47 +00:00
* @ param App\Mode $mode The current app mode
2022-06-27 10:09:31 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-14 11:26:53 +00:00
*/
2023-01-06 11:50:14 +00:00
public static function check ( string $basePath , bool $via_worker )
2018-10-14 11:26:53 +00:00
{
2018-12-03 01:57:41 +00:00
if ( ! DBA :: connected ()) {
return ;
}
2019-03-24 21:51:30 +00:00
// Don't check the status if the last update was failed
2022-12-28 01:07:38 +00:00
if ( DI :: config () -> get ( 'system' , 'update' , Update :: SUCCESS ) == Update :: FAILED ) {
2019-03-24 21:51:30 +00:00
return ;
}
2020-01-19 20:21:13 +00:00
$build = DI :: config () -> get ( 'system' , 'build' );
2019-03-09 23:28:48 +00:00
if ( empty ( $build )) {
2023-01-05 20:42:35 +00:00
// legacy option - check if there's something in the Config table
if ( DBStructure :: existsTable ( 'config' )) {
$dbConfig = DBA :: selectFirst ( 'config' , [ 'v' ], [ 'cat' => 'system' , 'k' => 'build' ]);
if ( ! empty ( $dbConfig )) {
$build = $dbConfig [ 'v' ];
}
}
if ( empty ( $build )) {
DI :: config () -> set ( 'system' , 'build' , DB_UPDATE_VERSION - 1 );
$build = DB_UPDATE_VERSION - 1 ;
}
2019-03-09 23:28:48 +00:00
}
2018-10-14 11:26:53 +00:00
// We don't support upgrading from very old versions anymore
2022-10-17 09:24:32 +00:00
if ( $build < self :: NEW_TABLE_STRUCTURE_VERSION ) {
2021-01-21 15:36:52 +00:00
$error = DI :: l10n () -> t ( 'Updates from version %s are not supported. Please update at least to version 2021.01 and wait until the postupdate finished version 1383.' , $build );
2021-01-17 20:32:13 +00:00
if ( DI :: mode () -> getExecutor () == Mode :: INDEX ) {
die ( $error );
} else {
throw new InternalServerErrorException ( $error );
}
}
2021-01-30 22:03:53 +00:00
// The postupdate has to completed version 1288 for the new post views to take over
2022-12-29 19:18:13 +00:00
$postupdate = DI :: keyValue () -> get ( 'post_update_version' ) ? ? self :: NEW_TABLE_STRUCTURE_VERSION ;
2022-10-17 09:24:32 +00:00
if ( $postupdate < self :: NEW_TABLE_STRUCTURE_VERSION ) {
2021-01-21 15:36:52 +00:00
$error = DI :: l10n () -> t ( 'Updates from postupdate version %s are not supported. Please update at least to version 2021.01 and wait until the postupdate finished version 1383.' , $postupdate );
2021-01-17 20:32:13 +00:00
if ( DI :: mode () -> getExecutor () == Mode :: INDEX ) {
die ( $error );
} else {
throw new InternalServerErrorException ( $error );
}
2018-10-14 11:26:53 +00:00
}
2019-03-09 23:28:48 +00:00
if ( $build < DB_UPDATE_VERSION ) {
2019-02-24 10:52:40 +00:00
if ( $via_worker ) {
2022-06-27 10:09:31 +00:00
/*
* 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 .
*/
2019-02-24 10:52:40 +00:00
self :: run ( $basePath );
} else {
2022-10-17 05:49:55 +00:00
Worker :: add ( Worker :: PRIORITY_CRITICAL , 'DBUpdate' );
2019-02-24 10:52:40 +00:00
}
2018-10-14 11:26:53 +00:00
}
}
2018-10-05 22:53:13 +00:00
/**
* Automatic database updates
2018-10-29 09:16:07 +00:00
*
2019-02-03 21:46:50 +00:00
* @ param string $basePath The base path of this application
2020-11-21 14:58:48 +00:00
* @ param bool $force Force the Update - Check even if the database version doesn ' t match
* @ param bool $override Overrides any running / stuck updates
* @ 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 09:16:07 +00:00
* @ return string Empty string if the update is successful , error messages otherwise
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-05 22:53:13 +00:00
*/
2022-06-27 10:09:31 +00:00
public static function run ( string $basePath , bool $force = false , bool $override = false , bool $verbose = false , bool $sendMail = true ) : string
2018-10-05 22:53:13 +00:00
{
2018-10-29 09:16:07 +00:00
// In force mode, we release the dbupdate lock first
// Necessary in case of an stuck update
2019-02-24 11:24:09 +00:00
if ( $override ) {
2020-01-06 23:24:10 +00:00
DI :: lock () -> release ( 'dbupdate' , true );
2018-10-29 09:16:07 +00:00
}
2023-01-05 20:42:35 +00:00
$build = DI :: config () -> get ( 'system' , 'build' );
if ( empty ( $build )) {
2023-02-12 15:44:56 +00:00
$dbConfig = DBA :: selectFirst ( 'config' , [ 'v' ], [ 'cat' => 'system' , 'k' => 'build' ]);
if ( ! empty ( $dbConfig )) {
$build = $dbConfig [ 'v' ];
2023-01-05 20:42:35 +00:00
}
2019-03-09 23:28:48 +00:00
2023-01-05 20:42:35 +00:00
if ( empty ( $build ) || ( $build > DB_UPDATE_VERSION )) {
2023-02-12 15:44:56 +00:00
DI :: config () -> set ( 'system' , 'build' , DB_UPDATE_VERSION - 1 );
2023-01-05 20:42:35 +00:00
$build = DB_UPDATE_VERSION - 1 ;
}
2019-03-09 23:28:48 +00:00
}
2018-10-05 22:53:13 +00:00
2019-03-09 23:28:48 +00:00
if ( $build != DB_UPDATE_VERSION || $force ) {
2018-10-05 22:53:13 +00:00
require_once 'update.php' ;
2019-03-09 23:28:48 +00:00
$stored = intval ( $build );
$current = intval ( DB_UPDATE_VERSION );
if ( $stored < $current || $force ) {
2022-12-28 01:07:38 +00:00
DI :: config () -> reload ();
2018-10-05 22:53:13 +00:00
2019-03-09 23:28:48 +00:00
// Compare the current structure with the defined structure
// If the Lock is acquired, never release it automatically to avoid double updates
2021-10-23 08:49:27 +00:00
if ( DI :: lock () -> acquire ( 'dbupdate' , 0 , Cache\Enum\Duration :: INFINITE )) {
2018-10-06 18:38:35 +00:00
2020-11-21 12:28:06 +00:00
Logger :: notice ( 'Update starting.' , [ 'from' => $stored , 'to' => $current ]);
2019-03-10 18:59:20 +00:00
// Checks if the build changed during Lock acquiring (so no double update occurs)
2023-01-05 20:42:35 +00:00
$retryBuild = DI :: config () -> get ( 'system' , 'build' );
2023-01-06 16:50:56 +00:00
if ( $retryBuild != $build ) {
// legacy option - check if there's something in the Config table
if ( DBStructure :: existsTable ( 'config' )) {
$dbConfig = DBA :: selectFirst ( 'config' , [ 'v' ], [ 'cat' => 'system' , 'k' => 'build' ]);
if ( ! empty ( $dbConfig )) {
$retryBuild = intval ( $dbConfig [ 'v' ]);
}
}
if ( $retryBuild != $build ) {
Logger :: notice ( 'Update already done.' , [ 'from' => $build , 'retry' => $retryBuild , 'to' => $current ]);
DI :: lock () -> release ( 'dbupdate' );
return '' ;
}
2019-03-10 18:59:20 +00:00
}
2021-01-30 07:50:20 +00:00
DI :: config () -> set ( 'system' , 'maintenance' , 1 );
2022-11-12 17:01:22 +00:00
2019-03-09 23:28:48 +00:00
// run the pre_update_nnnn functions in update.php
2020-11-21 14:58:48 +00:00
for ( $version = $stored + 1 ; $version <= $current ; $version ++ ) {
Logger :: notice ( 'Execute pre update.' , [ 'version' => $version ]);
2021-01-30 07:50:20 +00:00
DI :: config () -> set ( 'system' , 'maintenance_reason' , DI :: l10n () -> t ( '%s: executing pre update %d' ,
DateTimeFormat :: utcNow () . ' ' . date ( 'e' ), $version ));
2020-11-21 14:58:48 +00:00
$r = self :: runUpdateFunction ( $version , 'pre_update' , $sendMail );
2019-03-09 23:28:48 +00:00
if ( ! $r ) {
2020-11-21 14:58:48 +00:00
Logger :: warning ( 'Pre update failed' , [ 'version' => $version ]);
2020-01-19 20:21:53 +00:00
DI :: config () -> set ( 'system' , 'update' , Update :: FAILED );
2020-01-06 23:24:10 +00:00
DI :: lock () -> release ( 'dbupdate' );
2023-01-03 16:24:05 +00:00
DI :: config () -> beginTransaction ()
2023-01-03 13:18:53 +00:00
-> set ( 'system' , 'maintenance' , false )
-> delete ( 'system' , 'maintenance_reason' )
2023-01-03 16:24:05 +00:00
-> commit ();
2019-03-24 21:51:30 +00:00
return $r ;
2020-11-21 12:28:06 +00:00
} else {
2020-11-21 14:58:48 +00:00
Logger :: notice ( 'Pre update executed.' , [ 'version' => $version ]);
2019-03-09 23:28:48 +00:00
}
}
2018-10-05 22:53:13 +00:00
2019-03-09 23:28:48 +00:00
// update the structure in one call
2020-11-21 12:28:06 +00:00
Logger :: notice ( 'Execute structure update' );
2021-01-30 13:31:59 +00:00
$retval = DBStructure :: performUpdate ( false , $verbose );
2019-03-09 23:28:48 +00:00
if ( ! empty ( $retval )) {
if ( $sendMail ) {
self :: updateFailed (
DB_UPDATE_VERSION ,
$retval
);
}
2019-03-11 20:36:41 +00:00
Logger :: error ( 'Update ERROR.' , [ 'from' => $stored , 'to' => $current , 'retval' => $retval ]);
2020-01-19 20:21:53 +00:00
DI :: config () -> set ( 'system' , 'update' , Update :: FAILED );
2020-01-06 23:24:10 +00:00
DI :: lock () -> release ( 'dbupdate' );
2023-01-03 16:24:05 +00:00
DI :: config () -> beginTransaction ()
2023-01-03 13:18:53 +00:00
-> set ( 'system' , 'maintenance' , false )
-> delete ( 'system' , 'maintenance_reason' )
2023-01-03 16:24:05 +00:00
-> commit ();
2019-03-09 23:28:48 +00:00
return $retval ;
} else {
2020-11-21 12:28:06 +00:00
Logger :: notice ( 'Database structure update finished.' , [ 'from' => $stored , 'to' => $current ]);
2018-10-06 18:38:35 +00:00
}
2018-10-05 22:53:13 +00:00
2019-03-09 23:28:48 +00:00
// run the update_nnnn functions in update.php
2020-11-21 14:58:48 +00:00
for ( $version = $stored + 1 ; $version <= $current ; $version ++ ) {
Logger :: notice ( 'Execute post update.' , [ 'version' => $version ]);
2021-01-30 07:50:20 +00:00
DI :: config () -> set ( 'system' , 'maintenance_reason' , DI :: l10n () -> t ( '%s: executing post update %d' ,
DateTimeFormat :: utcNow () . ' ' . date ( 'e' ), $version ));
2020-11-21 14:58:48 +00:00
$r = self :: runUpdateFunction ( $version , 'update' , $sendMail );
2019-03-09 23:28:48 +00:00
if ( ! $r ) {
2020-11-21 14:58:48 +00:00
Logger :: warning ( 'Post update failed' , [ 'version' => $version ]);
2020-01-19 20:21:53 +00:00
DI :: config () -> set ( 'system' , 'update' , Update :: FAILED );
2020-01-06 23:24:10 +00:00
DI :: lock () -> release ( 'dbupdate' );
2023-01-03 16:24:05 +00:00
DI :: config () -> beginTransaction ()
2023-01-03 13:18:53 +00:00
-> set ( 'system' , 'maintenance' , false )
-> delete ( 'system' , 'maintenance_reason' )
2023-01-03 16:24:05 +00:00
-> commit ();
2019-03-24 21:51:30 +00:00
return $r ;
2020-11-21 12:28:06 +00:00
} else {
2020-11-21 14:58:48 +00:00
DI :: config () -> set ( 'system' , 'build' , $version );
Logger :: notice ( 'Post update executed.' , [ 'version' => $version ]);
2019-03-09 23:28:48 +00:00
}
2018-10-05 22:53:13 +00:00
}
2018-10-06 18:38:35 +00:00
2020-11-21 12:28:06 +00:00
DI :: config () -> set ( 'system' , 'build' , $current );
DI :: config () -> set ( 'system' , 'update' , Update :: SUCCESS );
DI :: lock () -> release ( 'dbupdate' );
2023-01-03 16:24:05 +00:00
DI :: config () -> beginTransaction ()
2023-01-03 13:18:53 +00:00
-> set ( 'system' , 'maintenance' , false )
-> delete ( 'system' , 'maintenance_reason' )
2023-01-03 16:24:05 +00:00
-> commit ();
2020-11-21 12:28:06 +00:00
2019-03-11 20:36:41 +00:00
Logger :: notice ( 'Update success.' , [ 'from' => $stored , 'to' => $current ]);
2019-03-09 23:28:48 +00:00
if ( $sendMail ) {
2020-11-21 14:58:48 +00:00
self :: updateSuccessful ( $stored , $current );
2018-10-29 09:16:07 +00:00
}
2021-06-17 11:23:32 +00:00
} else {
Logger :: warning ( 'Update lock could not be acquired' );
2018-10-05 22:53:13 +00:00
}
}
}
2018-10-29 09:16:07 +00:00
return '' ;
2018-10-05 22:53:13 +00:00
}
/**
* Executes a specific update function
*
2020-11-21 14:58:48 +00:00
* @ param int $version the DB version number of the function
2020-07-13 21:22:21 +00:00
* @ param string $prefix the prefix of the function ( update , pre_update )
* @ param bool $sendMail whether to send emails on success / failure
2018-10-05 22:53:13 +00:00
* @ return bool true , if the update function worked
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-05 22:53:13 +00:00
*/
2022-06-27 10:09:31 +00:00
public static function runUpdateFunction ( int $version , string $prefix , bool $sendMail = true ) : bool
2018-10-05 22:53:13 +00:00
{
2020-11-21 14:58:48 +00:00
$funcname = $prefix . '_' . $version ;
2018-10-05 22:53:13 +00:00
2020-11-21 12:28:06 +00:00
Logger :: notice ( 'Update function start.' , [ 'function' => $funcname ]);
2018-10-31 14:22:44 +00:00
2018-10-05 22:53:13 +00: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.
2021-10-23 08:49:27 +00:00
if ( DI :: lock () -> acquire ( 'dbupdate_function' , 120 , Cache\Enum\Duration :: INFINITE )) {
2018-10-06 18:38:35 +00:00
// call the specific update
2020-11-21 12:28:06 +00:00
Logger :: notice ( 'Pre update function start.' , [ 'function' => $funcname ]);
2018-10-06 18:38:35 +00:00
$retval = $funcname ();
2020-11-21 12:28:06 +00:00
Logger :: notice ( 'Update function done.' , [ 'function' => $funcname ]);
2018-10-05 22:53:13 +00:00
2018-10-06 18:38:35 +00:00
if ( $retval ) {
2020-07-13 21:22:21 +00:00
if ( $sendMail ) {
//send the administrator an e-mail
self :: updateFailed (
2020-11-21 14:58:48 +00:00
$version ,
DI :: l10n () -> t ( 'Update %s failed. See error logs.' , $version )
2020-07-13 21:22:21 +00:00
);
}
2019-03-11 20:36:41 +00:00
Logger :: error ( 'Update function ERROR.' , [ 'function' => $funcname , 'retval' => $retval ]);
2020-01-06 23:24:10 +00:00
DI :: lock () -> release ( 'dbupdate_function' );
2018-10-06 18:38:35 +00:00
return false ;
} else {
2020-01-06 23:24:10 +00:00
DI :: lock () -> release ( 'dbupdate_function' );
2020-11-21 12:28:06 +00:00
Logger :: notice ( 'Update function finished.' , [ 'function' => $funcname ]);
2018-10-06 18:38:35 +00:00
return true ;
}
2020-08-30 18:57:09 +00:00
} else {
Logger :: error ( 'Locking failed.' , [ 'function' => $funcname ]);
2020-11-21 12:28:06 +00:00
return false ;
2018-10-05 22:53:13 +00:00
}
} else {
2020-11-21 12:28:06 +00:00
Logger :: notice ( 'Update function skipped.' , [ 'function' => $funcname ]);
2018-10-05 22:53:13 +00:00
return true ;
}
}
2018-10-29 09:16:07 +00:00
/**
* send the email and do what is needed to do on update fails
*
2019-01-06 21:06:53 +00:00
* @ param int $update_id number of failed update
* @ param string $error_message error message
2022-06-27 10:09:31 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-29 09:16:07 +00:00
*/
2022-11-12 17:01:22 +00:00
private static function updateFailed ( int $update_id , string $error_message )
{
$adminEmails = User :: getAdminListForEmailing ([ 'uid' , 'language' , 'email' ]);
if ( ! $adminEmails ) {
2019-03-11 20:36:41 +00:00
Logger :: warning ( 'Cannot notify administrators .' , [ 'update' => $update_id , 'message' => $error_message ]);
2018-10-29 09:16:07 +00:00
return ;
}
2022-11-12 17:01:22 +00:00
foreach ( $adminEmails as $admin ) {
$l10n = DI :: l10n () -> withLang ( $admin [ 'language' ] ? : 'en' );
2018-10-29 09:16:07 +00:00
2019-12-28 22:12:01 +00:00
$preamble = Strings :: deindent ( $l10n -> t ( "
2018-10-29 09:16:07 +00: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 ));
2020-11-21 14:58:48 +00:00
$body = $l10n -> t ( 'The error message is\n[pre]%s[/pre]' , $error_message );
2020-02-01 19:08:54 +00:00
$email = DI :: emailer ()
2020-02-04 20:04:08 +00:00
-> newSystemMail ()
2020-02-02 08:22:30 +00:00
-> withMessage ( $l10n -> t ( '[Friendica Notify] Database update' ), $preamble , $body )
2020-02-04 20:04:08 +00:00
-> forUser ( $admin )
2020-02-02 08:22:30 +00:00
-> withRecipient ( $admin [ 'email' ])
-> build ();
2020-02-01 19:08:54 +00:00
DI :: emailer () -> send ( $email );
2018-10-29 09:16:07 +00:00
}
2020-11-21 14:58:48 +00:00
Logger :: alert ( 'Database structure update failed.' , [ 'error' => $error_message ]);
2018-10-29 09:16:07 +00:00
}
2020-11-21 14:58:48 +00:00
/**
* Send a mail to the administrator about the successful update
*
* @ param integer $from_build
* @ param integer $to_build
* @ return void
*/
private static function updateSuccessful ( int $from_build , int $to_build )
2018-10-29 09:16:07 +00:00
{
2022-11-12 17:01:22 +00:00
foreach ( User :: getAdminListForEmailing ([ 'uid' , 'language' , 'email' ]) as $admin ) {
$l10n = DI :: l10n () -> withLang ( $admin [ 'language' ] ? : 'en' );
2018-10-29 09:16:07 +00:00
2022-11-12 17:01:22 +00:00
$preamble = Strings :: deindent ( $l10n -> t ( '
The friendica database was successfully updated from % s to % s . ' ,
$from_build , $to_build ));
2019-03-02 12:57:47 +00:00
2022-11-12 17:01:22 +00:00
$email = DI :: emailer ()
-> newSystemMail ()
-> withMessage ( $l10n -> t ( '[Friendica Notify] Database update' ), $preamble )
-> forUser ( $admin )
-> withRecipient ( $admin [ 'email' ])
-> build ();
DI :: emailer () -> send ( $email );
2018-10-29 09:16:07 +00:00
}
2019-03-11 20:36:41 +00:00
Logger :: debug ( 'Database structure update successful.' );
2018-10-29 09:16:07 +00:00
}
2018-10-06 18:38:35 +00:00
}