2007-12-05 03:27:49 +01:00
< ? php
/**
* eGgroupWare setup - abstract baseclass for all setup commands , extending admin_cmd
*
* @ link http :// www . egroupware . org
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
* @ package setup
2016-04-27 09:10:04 +02:00
* @ copyright ( c ) 2007 - 16 by Ralf Becker < RalfBecker - AT - outdoor - training . de >
2007-12-05 03:27:49 +01:00
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
2008-11-18 20:58:11 +01:00
* @ version $Id $
2007-12-05 03:27:49 +01:00
*/
2016-05-01 17:56:49 +02:00
use EGroupware\Api ;
2016-04-27 09:10:04 +02:00
use EGroupware\Api\Egw ;
2007-12-05 03:27:49 +01:00
/**
* setup command : abstract baseclass for all setup commands , extending admin_cmd
*/
2008-11-18 20:58:11 +01:00
abstract class setup_cmd extends admin_cmd
2007-12-05 03:27:49 +01:00
{
2007-12-09 09:03:15 +01:00
/**
* Defaults set for empty options while running the command
*
* @ var array
*/
public $set_defaults = array ();
2007-12-05 03:27:49 +01:00
/**
* Should be called by every command usually requiring header admin rights
*
2016-05-01 17:56:49 +02:00
* @ throws Api\Exception\NoPermission ( lang ( 'Wrong credentials to access the header.inc.php file!' ), 2 );
2007-12-05 03:27:49 +01:00
*/
protected function _check_header_access ()
{
2007-12-21 02:54:20 +01:00
if ( ! $this -> header_secret && $this -> header_admin_user ) // no secret specified but header_admin_user/password
2007-12-05 03:27:49 +01:00
{
2007-12-21 02:54:20 +01:00
if ( ! $this -> uid ) $this -> uid = true ;
$this -> set_header_secret ( $this -> header_admin_user , $this -> header_admin_password );
}
$secret = $this -> _calc_header_secret ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'header_admin_user' ],
$GLOBALS [ 'egw_info' ][ 'server' ][ 'header_admin_password' ]);
if ( $this -> uid === true ) unset ( $this -> uid );
2008-11-18 20:58:11 +01:00
2007-12-21 02:54:20 +01:00
if ( $this -> header_secret != $secret )
{
//echo "_check_header_access: header_secret='$this->header_secret' != '$secret'=_calc_header_secret({$GLOBALS['egw_info']['server']['header_admin_user']},{$GLOBALS['egw_info']['server']['header_admin_password']})\n";
2016-05-01 17:56:49 +02:00
throw new Api\Exception\NoPermission ( lang ( 'Wrong credentials to access the header.inc.php file!' ), 5 );
2007-12-05 03:27:49 +01:00
}
2008-11-18 20:58:11 +01:00
2007-12-05 03:27:49 +01:00
}
2008-11-18 20:58:11 +01:00
2007-12-05 03:27:49 +01:00
/**
* Set the user and pw required for any operation on the header file
*
* @ param string $user
* @ param string $pw password or md5 hash of it
*/
public function set_header_secret ( $user , $pw )
{
if ( $this -> uid || parent :: save ( false )) // we need to save first, to get the uid
{
$this -> header_secret = $this -> _calc_header_secret ( $user , $pw );
}
else
{
throw new Exception ( 'failed to set header_secret!' );
}
}
2008-11-18 20:58:11 +01:00
2007-12-05 03:27:49 +01:00
/**
* Calculate the header_secret used to access the header from this command
2008-11-18 20:58:11 +01:00
*
2007-12-05 03:27:49 +01:00
* It ' s an md5 over the uid , header - admin - user and - password .
*
* @ param string $header_admin_user
* @ param string $header_admin_password
* @ return string
*/
private function _calc_header_secret ( $header_admin_user = null , $header_admin_password = null )
{
if ( ! self :: is_md5 ( $header_admin_password )) $header_admin_password = md5 ( $header_admin_password );
$secret = md5 ( $this -> uid . $header_admin_user . $header_admin_password );
//echo "header_secret='$secret' = md5('$this->uid'.'$header_admin_user'.'$header_admin_password')\n";
return $secret ;
}
2008-11-18 20:58:11 +01:00
2007-12-09 09:03:15 +01:00
/**
* Saving the object to the database , reimplemented to not do it in setup context
*
2016-04-27 09:10:04 +02:00
* @ param boolean $set_modifier = true set the current user as modifier or 0 ( = run by the system )
2007-12-09 09:03:15 +01:00
* @ return boolean true on success , false otherwise
*/
function save ( $set_modifier = true )
{
2012-08-13 14:14:37 +02:00
if ( isset ( $GLOBALS [ 'egw' ] -> db ) && is_object ( $GLOBALS [ 'egw' ] -> db ) && $GLOBALS [ 'egw' ] -> db -> Database )
2007-12-09 09:03:15 +01:00
{
return parent :: save ( $set_modifier );
}
return true ;
}
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
/**
* Reference to the setup object , after calling check_setup_auth method
*
* @ var setup
*/
static protected $egw_setup ;
2008-11-18 20:58:11 +01:00
2007-12-14 00:46:44 +01:00
static private $egw_accounts_backup ;
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
/**
2018-04-11 15:13:21 +02:00
* Create the setup enviroment ( for running within setup or EGw )
2007-12-10 05:59:01 +01:00
*/
static protected function _setup_enviroment ( $domain = null )
{
if ( ! is_object ( $GLOBALS [ 'egw_setup' ]))
{
2008-11-18 20:58:11 +01:00
require_once ( EGW_INCLUDE_ROOT . '/setup/inc/class.setup.inc.php' );
2007-12-10 05:59:01 +01:00
$GLOBALS [ 'egw_setup' ] = new setup ( true , true );
}
self :: $egw_setup = $GLOBALS [ 'egw_setup' ];
self :: $egw_setup -> ConfigDomain = $domain ;
2007-12-21 02:54:20 +01:00
if ( isset ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'header_admin_user' ]) && ! isset ( $GLOBALS [ 'egw_domain' ]) &&
2016-04-27 09:10:04 +02:00
is_object ( $GLOBALS [ 'egw' ]) && $GLOBALS [ 'egw' ] instanceof Egw )
2007-12-10 05:59:01 +01:00
{
2018-04-11 15:13:21 +02:00
// we run inside EGw, not setup --> read egw_domain array from the header via the showheader cmd
2007-12-10 05:59:01 +01:00
$cmd = new setup_cmd_showheader ( null ); // null = only header, no db stuff, no hashes
$header = $cmd -> run ();
$GLOBALS [ 'egw_domain' ] = $header [ 'egw_domain' ];
2008-11-18 20:58:11 +01:00
2007-12-14 00:46:44 +01:00
if ( is_object ( $GLOBALS [ 'egw' ] -> accounts ) && is_null ( self :: $egw_accounts_backup ))
{
self :: $egw_accounts_backup = $GLOBALS [ 'egw' ] -> accounts ;
unset ( $GLOBALS [ 'egw' ] -> accounts );
}
2007-12-13 03:41:55 +01:00
if ( $this -> config ) self :: $egw_setup -> setup_account_object ( $this -> config );
2007-12-10 05:59:01 +01:00
}
if ( is_object ( $GLOBALS [ 'egw' ] -> db ) && $domain )
{
$GLOBALS [ 'egw' ] -> db -> disconnect ();
2016-05-01 17:56:49 +02:00
$GLOBALS [ 'egw' ] -> db = new Api\Db ( $GLOBALS [ 'egw_domain' ][ $domain ]);
2014-04-16 19:57:44 +02:00
// change caching to managed instance
2016-05-01 17:56:49 +02:00
Api\Cache :: unset_instance_key ();
2007-12-10 05:59:01 +01:00
}
}
/**
2018-04-11 15:13:21 +02:00
* Restore EGw ' s db connection
2007-12-10 05:59:01 +01:00
*
*/
static function restore_db ()
{
if ( is_object ( $GLOBALS [ 'egw' ] -> db ))
{
$GLOBALS [ 'egw' ] -> db -> disconnect ();
2016-05-01 17:56:49 +02:00
$GLOBALS [ 'egw' ] -> db = new Api\Db ( $GLOBALS [ 'egw_info' ][ 'server' ]);
2012-06-19 19:43:37 +02:00
2014-04-16 19:57:44 +02:00
// change caching back to own instance
2016-05-01 17:56:49 +02:00
Api\Cache :: unset_instance_key ();
2014-04-16 19:57:44 +02:00
2007-12-14 00:46:44 +01:00
if ( ! is_null ( self :: $egw_accounts_backup ))
{
$GLOBALS [ 'egw' ] -> accounts = self :: $egw_accounts_backup ;
2016-05-01 17:56:49 +02:00
Api\Accounts :: cache_invalidate ();
2007-12-14 00:46:44 +01:00
unset ( self :: $egw_accounts_backup );
}
2007-12-10 05:59:01 +01:00
}
}
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
/**
* Creates a setup like enviroment and checks for the header user / pw or config_user / pw if domain given
*
* @ param string $user
* @ param string $pw
2016-04-27 09:10:04 +02:00
* @ param string $domain = null if given we also check agains config user / pw
2016-05-01 17:56:49 +02:00
* @ throws Api\Exception\NoPermission ( lang ( 'Access denied: wrong username or password for manage-header !!!' ), 21 );
* @ throws Api\Exception\NoPermission ( lang ( " Access denied: wrong username or password to configure the domain '%1(%2)' !!! " , $domain , $GLOBALS [ 'egw_domain' ][ $domain ][ 'db_type' ]), 40 );
2007-12-10 05:59:01 +01:00
*/
static function check_setup_auth ( $user , $pw , $domain = null )
{
self :: _setup_enviroment ( $domain );
// check the authentication if a header_admin_password is set, if not we dont have a header yet and no authentication
if ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'header_admin_password' ]) // if that's not given we dont have a header yet
{
if ( ! self :: $egw_setup -> check_auth ( $user , $pw , $GLOBALS [ 'egw_info' ][ 'server' ][ 'header_admin_user' ],
$GLOBALS [ 'egw_info' ][ 'server' ][ 'header_admin_password' ]) &&
( is_null ( $domain ) || ! isset ( $GLOBALS [ 'egw_domain' ][ $domain ]) || // if valid domain given check it's config user/pw
! self :: $egw_setup -> check_auth ( $user , $pw , $GLOBALS [ 'egw_domain' ][ $domain ][ 'config_user' ],
$GLOBALS [ 'egw_domain' ][ $domain ][ 'config_passwd' ])))
{
if ( is_null ( $domain ))
{
2016-05-01 17:56:49 +02:00
throw new Api\Exception\NoPermission ( lang ( 'Access denied: wrong username or password for manage-header !!!' ), 21 );
2007-12-10 05:59:01 +01:00
}
else
{
2016-05-01 17:56:49 +02:00
throw new Api\Exception\NoPermission ( lang ( " Access denied: wrong username or password to configure the domain '%1(%2)' !!! " , $domain , $GLOBALS [ 'egw_domain' ][ $domain ][ 'db_type' ]), 40 );
2007-12-10 05:59:01 +01:00
}
}
}
}
2011-06-18 12:44:56 +02:00
/**
* Applications which are currently not installed ( set after call to check_installed , for the last / only domain only )
*
* @ var array
*/
static public $apps_to_install = array ();
/**
* Applications which are currently need update ( set after call to check_installed , for the last / only domain only )
*
* @ var array
*/
static public $apps_to_upgrade = array ();
2007-12-10 05:59:01 +01:00
/**
2018-04-11 15:13:21 +02:00
* Check if EGw is installed , which versions and if an update is needed
2008-11-18 20:58:11 +01:00
*
2011-06-18 12:44:56 +02:00
* Sets self :: $apps_to_update and self :: $apps_to_install for the last / only domain only !
*
2016-04-27 09:10:04 +02:00
* @ param string $domain = '' domain to check , default '' = all
* @ param int / array $stop = 0 stop checks before given exit - code ( s ), default 0 = all checks
* @ param boolean $verbose = false echo messages as they happen , instead returning them
2007-12-10 05:59:01 +01:00
* @ return array with translated messages
*/
2007-12-21 02:54:20 +01:00
static function check_installed ( $domain = '' , $stop = 0 , $verbose = false )
2007-12-10 05:59:01 +01:00
{
self :: _setup_enviroment ( $domain );
global $setup_info ;
static $header_checks = true ; // output the header checks only once
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
$messages = array ();
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
if ( $stop && ! is_array ( $stop )) $stop = array ( $stop );
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
$versions =& $GLOBALS [ 'egw_info' ][ 'server' ][ 'versions' ];
2008-11-18 20:58:11 +01:00
2016-05-25 21:36:41 +02:00
if ( ! $versions [ 'api' ])
2007-12-10 05:59:01 +01:00
{
2016-05-25 21:36:41 +02:00
if ( ! include ( EGW_INCLUDE_ROOT . '/api/setup/setup.inc.php' ))
2007-12-10 05:59:01 +01:00
{
2016-05-25 21:36:41 +02:00
throw new Api\Exception\WrongUserinput ( lang ( " EGroupware sources in '%1' are not complete, file '%2' missing !!! " , realpath ( '..' ), 'api/setup/setup.inc.php' ), 99 ); // should not happen ;-)
2007-12-10 05:59:01 +01:00
}
2016-05-25 21:36:41 +02:00
$versions [ 'api' ] = $setup_info [ 'api' ][ 'version' ];
2007-12-10 05:59:01 +01:00
unset ( $setup_info );
}
if ( $header_checks )
{
2016-05-25 21:36:41 +02:00
$messages [] = self :: _echo_message ( $verbose , lang ( 'EGroupware API version %1 found.' , $versions [ 'api' ]));
2007-12-10 05:59:01 +01:00
}
$header_stage = self :: $egw_setup -> detection -> check_header ();
if ( $stop && in_array ( $header_stage , $stop )) return true ;
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
switch ( $header_stage )
{
2016-05-25 21:36:41 +02:00
case 1 : throw new Api\Exception\WrongUserinput ( lang ( 'EGroupware configuration file (header.inc.php) does NOT exist.' ) . " \n " . lang ( 'Use --create-header to create the configuration file (--usage gives more options).' ), 1 );
2008-11-18 20:58:11 +01:00
2016-05-25 21:36:41 +02:00
// case 2: throw new Api\Exception\WrongUserinput(lang('EGroupware configuration file (header.inc.php) version %1 exists%2',$versions['header'],'.')."\n".lang('No header admin password set! Use --edit-header <password>[,<user>] to set one (--usage gives more options).'),2);
2008-11-18 20:58:11 +01:00
2016-05-25 21:36:41 +02:00
case 3 : throw new Api\Exception\WrongUserinput ( lang ( 'EGroupware configuration file (header.inc.php) version %1 exists%2' , $versions [ 'header' ], '.' ) . " \n " . lang ( 'No EGroupware domains / database instances exist! Use --edit-header --domain to add one (--usage gives more options).' ), 3 );
2008-11-18 20:58:11 +01:00
2016-05-25 21:36:41 +02:00
case 4 : throw new Api\Exception\WrongUserinput ( lang ( 'EGroupware configuration file (header.inc.php) version %1 exists%2' , $versions [ 'header' ], '.' ) . " \n " . lang ( 'It needs upgrading to version %1! Use --update-header <password>[,<user>] to do so (--usage gives more options).' , $versions [ 'current_header' ]), 4 );
2007-12-10 05:59:01 +01:00
}
if ( $header_checks )
{
2016-05-25 21:36:41 +02:00
$messages [] = self :: _echo_message ( $verbose , lang ( 'EGroupware configuration file (header.inc.php) version %1 exists%2' ,
2007-12-10 05:59:01 +01:00
$versions [ 'header' ], ' ' . lang ( 'and is up to date' )));
}
2014-07-08 18:50:54 +02:00
unset ( $header_checks ); // no further output of the header checks
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
$domains = $GLOBALS [ 'egw_domain' ];
if ( $domain ) // domain to check given
{
2016-05-01 17:56:49 +02:00
if ( ! isset ( $GLOBALS [ 'egw_domain' ][ $domain ])) throw new Api\Exception\WrongUserinput ( lang ( " Domain '%1' does NOT exist !!! " , $domain ), 92 );
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
$domains = array ( $domain => $GLOBALS [ 'egw_domain' ][ $domain ]);
}
foreach ( $domains as $domain => $data )
{
self :: $egw_setup -> ConfigDomain = $domain ; // set the domain the setup class operates on
if ( count ( $GLOBALS [ 'egw_domain' ]) > 1 )
{
self :: _echo_message ( $verbose );
2016-05-25 21:36:41 +02:00
$messages [] = self :: _echo_message ( $verbose , lang ( 'EGroupware domain/instance %1(%2):' , $domain , $data [ 'db_type' ]));
2007-12-10 05:59:01 +01:00
}
$setup_info = self :: $egw_setup -> detection -> get_versions ();
// check if there's already a db-connection and close if, otherwise the db-connection of the previous domain will be used
if ( is_object ( self :: $egw_setup -> db ))
{
self :: $egw_setup -> db -> disconnect ();
}
self :: $egw_setup -> loaddb ();
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
$db = $data [ 'db_type' ] . '://' . $data [ 'db_user' ] . ':' . $data [ 'db_pass' ] . '@' . $data [ 'db_host' ] . '/' . $data [ 'db_name' ];
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
$db_stage =& $GLOBALS [ 'egw_info' ][ 'setup' ][ 'stage' ][ 'db' ];
if (( $db_stage = self :: $egw_setup -> detection -> check_db ( $setup_info )) != 1 )
{
$setup_info = self :: $egw_setup -> detection -> get_db_versions ( $setup_info );
$db_stage = self :: $egw_setup -> detection -> check_db ( $setup_info );
}
if ( $stop && in_array ( 10 + $db_stage , $stop ))
{
return $messages ;
}
switch ( $db_stage )
{
2016-05-01 17:56:49 +02:00
case 1 : throw new Api\Exception\WrongUserinput ( lang ( 'Your Database is not working!' ) . " $db : " . self :: $egw_setup -> db -> Error , 11 );
2008-11-18 20:58:11 +01:00
2016-05-25 21:36:41 +02:00
case 3 : throw new Api\Exception\WrongUserinput ( lang ( 'Your database is working, but you dont have any applications installed' ) . " ( $db ). " . lang ( " Use --install to install EGroupware. " ), 13 );
2008-11-18 20:58:11 +01:00
2019-09-17 22:03:22 +02:00
case 4 : throw new Api\Exception\WrongUserinput ( lang ( 'EGroupware API needs a database (schema) update from version %1 to %2!' , $setup_info [ 'api' ][ 'currentver' ], $versions [ 'api' ]) . ' ' . lang ( 'Use --update to do so.' ), 14 );
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
case 10 : // also check apps of updates
2011-06-18 12:44:56 +02:00
self :: $apps_to_upgrade = self :: $apps_to_install = array ();
2007-12-10 05:59:01 +01:00
foreach ( $setup_info as $app => $data )
{
2009-10-28 22:08:23 +01:00
if ( $data [ 'currentver' ] && $data [ 'version' ] && $data [ 'version' ] != 'deleted' && $data [ 'version' ] != $data [ 'currentver' ])
2007-12-10 05:59:01 +01:00
{
2011-06-18 12:44:56 +02:00
self :: $apps_to_upgrade [] = $app ;
2007-12-10 05:59:01 +01:00
}
2014-05-05 11:09:47 +02:00
if ( ! isset ( $data [ 'enabled' ]) && isset ( $data [ 'version' ])) // jdots eg. is no app, but a template
2007-12-10 05:59:01 +01:00
{
2011-06-18 12:44:56 +02:00
self :: $apps_to_install [] = $app ;
2007-12-10 05:59:01 +01:00
}
}
2019-08-15 10:31:43 +02:00
// add autodeinstall apps
self :: $apps_to_upgrade = array_unique ( array_merge ( self :: $apps_to_upgrade , self :: check_autodeinstall ()));
2011-06-18 12:44:56 +02:00
if ( self :: $apps_to_install )
2007-12-10 05:59:01 +01:00
{
self :: _echo_message ( $verbose );
2011-06-18 12:44:56 +02:00
$messages [] = self :: _echo_message ( $verbose , lang ( 'The following applications are NOT installed:' ) . ' ' . implode ( ', ' , self :: $apps_to_install ));
2007-12-10 05:59:01 +01:00
}
2011-06-18 12:44:56 +02:00
if ( self :: $apps_to_upgrade )
2007-12-10 05:59:01 +01:00
{
$db_stage = 4 ;
if ( $stop && in_array ( 10 + $db_stage , $stop )) return $messages ;
2016-05-01 17:56:49 +02:00
throw new Api\Exception\WrongUserinput ( lang ( 'The following applications need to be upgraded:' ) . ' ' . implode ( ', ' , self :: $apps_to_upgrade ) . '! ' . lang ( 'Use --update to do so.' ), 14 );
2007-12-10 05:59:01 +01:00
}
break ;
}
2016-05-25 21:36:41 +02:00
$messages [] = self :: _echo_message ( $verbose , lang ( " database is version %1 and up to date. " , $setup_info [ 'api' ][ 'currentver' ]));
2008-11-18 20:58:11 +01:00
2007-12-10 05:59:01 +01:00
self :: $egw_setup -> detection -> check_config ();
if ( $GLOBALS [ 'egw_info' ][ 'setup' ][ 'config_errors' ] && $stop && ! in_array ( 15 , $stop ))
{
2016-05-25 21:36:41 +02:00
throw new Api\Exception\WrongUserinput ( lang ( 'You need to configure EGroupware:' ) . " \n - " .@ implode ( " \n - " , $GLOBALS [ 'egw_info' ][ 'setup' ][ 'config_errors' ]), 15 );
2007-12-10 05:59:01 +01:00
}
}
return $messages ;
}
2014-07-08 18:50:54 +02:00
/**
* Check if there are apps which should be autoinstalled
*
* @ return array with app - names
*/
static function check_autoinstall ()
{
$ret = array_filter ( self :: $apps_to_install , function ( $app )
{
global $setup_info ;
return $setup_info [ $app ][ 'autoinstall' ];
});
//error_log(__METHOD__."() apps_to_install=".array2string(self::$apps_to_install).' returning '.array2string($ret));
return $ret ;
}
2019-08-15 10:31:43 +02:00
/**
* Check if app should be automatically deinstalled
*
* @ return array with app - names to automatic deinstall
*/
static function check_autodeinstall ()
{
global $setup_info ;
$ret = array_values ( array_filter ( array_keys ( $setup_info ), function ( $app )
{
global $setup_info ;
if ( empty ( $setup_info [ $app ][ 'autodeinstall' ]))
{
return false ;
}
$autodeinstall = $setup_info [ $app ][ 'autodeinstall' ];
if ( ! is_bool ( $autodeinstall ))
{
try {
$autodeinstall = ( bool ) $GLOBALS [ 'egw_setup' ] -> db -> query ( $autodeinstall , __LINE__ , __FILE__ ) -> fetchColumn ();
}
catch ( \Exception $e ) {
_egw_log_exception ( $e );
$autodeinstall = false ;
}
}
return $autodeinstall ;
}));
//error_log(__METHOD__."() apps=".json_encode(array_keys($setup_info)).' returning '.json_encode($ret));
return $ret ;
}
2007-12-10 05:59:01 +01:00
/**
* Echo the given message , if $verbose
*
* @ param boolean $verbose
* @ param string $msg
* @ return string $msg
*/
static function _echo_message ( $verbose , $msg = '' )
{
if ( $verbose ) echo $msg . " \n " ;
return $msg ;
}
2007-12-05 03:27:49 +01:00
}