2017-08-26 08:04:21 +02:00
< ? php
2017-11-19 20:15:25 +01:00
/**
2021-03-29 08:40:20 +02:00
* @ copyright Copyright ( C ) 2010 - 2021 , the Friendica project
2020-02-09 15:45:36 +01: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 />.
*
2017-11-19 20:15:25 +01:00
*/
2020-02-09 15:45:36 +01:00
2017-08-26 08:04:21 +02:00
namespace Friendica\Core ;
2019-12-15 22:34:11 +01:00
use Friendica\DI ;
2021-10-30 01:21:07 +02:00
use Friendica\Network\HTTPException\FoundException ;
use Friendica\Network\HTTPException\MovedPermanentlyException ;
use Friendica\Network\HTTPException\TemporaryRedirectException ;
2018-01-27 17:59:10 +01:00
use Friendica\Util\XML ;
2017-08-26 08:04:21 +02:00
/**
2020-01-19 07:05:23 +01:00
* Contains the class with system relevant stuff
2017-08-26 08:04:21 +02:00
*/
2019-12-15 23:28:01 +01:00
class System
2017-11-19 20:15:25 +01:00
{
2017-08-26 12:01:50 +02:00
/**
2020-01-19 07:05:23 +01:00
* Returns a string with a callstack . Can be used for logging .
2020-01-19 10:51:37 +01:00
*
2020-07-27 06:20:30 +02:00
* @ param integer $depth How many calls to include in the stacks after filtering
* @ param int $offset How many calls to shave off the top of the stack , for example if
* this is called from a centralized method that isn ' t relevant to the callstack
2017-08-26 12:01:50 +02:00
* @ return string
*/
2020-07-27 06:20:30 +02:00
public static function callstack ( int $depth = 4 , int $offset = 0 )
2017-11-19 20:15:25 +01:00
{
2017-10-17 22:51:46 +02:00
$trace = debug_backtrace ( DEBUG_BACKTRACE_IGNORE_ARGS );
2017-08-26 12:01:50 +02:00
2020-07-27 06:20:30 +02:00
// We remove at least the first two items from the list since they contain data that we don't need.
$trace = array_slice ( $trace , 2 + $offset );
2017-08-26 12:01:50 +02:00
2018-01-15 14:05:12 +01:00
$callstack = [];
2020-07-10 09:01:28 +02:00
$previous = [ 'class' => '' , 'function' => '' , 'database' => false ];
2017-10-17 22:51:46 +02:00
// The ignore list contains all functions that are only wrapper functions
2020-07-27 13:50:36 +02:00
$ignore = [ 'call_user_func_array' ];
2017-10-17 22:51:46 +02:00
while ( $func = array_pop ( $trace )) {
2017-08-26 12:01:50 +02:00
if ( ! empty ( $func [ 'class' ])) {
2021-02-14 15:24:48 +01:00
if ( in_array ( $previous [ 'function' ], [ 'insert' , 'fetch' , 'toArray' , 'exists' , 'count' , 'selectFirst' , 'selectToArray' ,
'select' , 'update' , 'delete' , 'selectFirstForUser' , 'selectForUser' ])
&& ( substr ( $previous [ 'class' ], 0 , 15 ) === 'Friendica\Model' )) {
continue ;
}
2020-07-10 09:01:28 +02:00
// Don't show multiple calls from the Database classes to show the essential parts of the callstack
$func [ 'database' ] = in_array ( $func [ 'class' ], [ 'Friendica\Database\DBA' , 'Friendica\Database\Database' ]);
if ( ! $previous [ 'database' ] || ! $func [ 'database' ]) {
2017-10-17 22:51:46 +02:00
$classparts = explode ( " \\ " , $func [ 'class' ]);
$callstack [] = array_pop ( $classparts ) . '::' . $func [ 'function' ];
$previous = $func ;
}
} elseif ( ! in_array ( $func [ 'function' ], $ignore )) {
2020-07-10 09:01:28 +02:00
$func [ 'database' ] = ( $func [ 'function' ] == 'q' );
2017-08-26 12:01:50 +02:00
$callstack [] = $func [ 'function' ];
2018-07-01 06:15:11 +02:00
$func [ 'class' ] = '' ;
2017-10-17 22:51:46 +02:00
$previous = $func ;
2017-08-26 12:01:50 +02:00
}
}
2018-01-15 14:05:12 +01:00
$callstack2 = [];
2017-10-17 22:51:46 +02:00
while (( count ( $callstack2 ) < $depth ) && ( count ( $callstack ) > 0 )) {
$callstack2 [] = array_pop ( $callstack );
}
return implode ( ', ' , $callstack2 );
2017-08-26 12:01:50 +02:00
}
2018-01-27 17:59:10 +01:00
/**
* Generic XML return
* Outputs a basic dfrn XML status structure to STDOUT , with a < status > variable
* of $st and an optional text < message > of $message and terminates the current process .
2019-01-06 22:06:53 +01:00
*
* @ param $st
* @ param string $message
* @ throws \Exception
2018-01-27 17:59:10 +01:00
*/
public static function xmlExit ( $st , $message = '' )
{
$result = [ 'status' => $st ];
if ( $message != '' ) {
$result [ 'message' ] = $message ;
}
if ( $st ) {
2018-10-29 22:20:46 +01:00
Logger :: log ( 'xml_status returning non_zero: ' . $st . " message= " . $message );
2018-01-27 17:59:10 +01:00
}
header ( " Content-type: text/xml " );
$xmldata = [ " result " => $result ];
echo XML :: fromArray ( $xmldata , $xml );
2018-12-26 06:40:12 +01:00
exit ();
2018-01-27 17:59:10 +01:00
}
/**
2020-01-19 07:05:23 +01:00
* Send HTTP status header and exit .
2018-01-27 17:59:10 +01:00
*
2019-05-02 03:33:33 +02:00
* @ param integer $val HTTP status result value
* @ param string $message Error message . Optional .
* @ param string $content Response body . Optional .
* @ throws \Exception
2018-01-27 17:59:10 +01:00
*/
2019-05-02 03:33:33 +02:00
public static function httpExit ( $val , $message = '' , $content = '' )
2018-01-27 17:59:10 +01:00
{
2021-10-30 01:21:07 +02:00
if ( $val >= 400 ) {
Logger :: debug ( 'Exit with error' , [ 'code' => $val , 'message' => $message , 'callstack' => System :: callstack ( 20 ), 'method' => $_SERVER [ 'REQUEST_METHOD' ], 'agent' => $_SERVER [ 'HTTP_USER_AGENT' ] ? ? '' ]);
}
2019-05-02 03:33:33 +02:00
header ( $_SERVER [ " SERVER_PROTOCOL " ] . ' ' . $val . ' ' . $message );
2018-01-27 17:59:10 +01:00
2019-05-02 03:33:33 +02:00
echo $content ;
2018-01-27 17:59:10 +01:00
2018-10-22 04:24:47 +02:00
exit ();
2018-01-27 17:59:10 +01:00
}
2019-02-23 21:25:41 +01:00
public static function jsonError ( $httpCode , $data , $content_type = 'application/json' )
{
2021-10-30 01:21:07 +02:00
if ( $httpCode >= 400 ) {
Logger :: debug ( 'Exit with error' , [ 'code' => $httpCode , 'content_type' => $content_type , 'callstack' => System :: callstack ( 20 ), 'method' => $_SERVER [ 'REQUEST_METHOD' ], 'agent' => $_SERVER [ 'HTTP_USER_AGENT' ] ? ? '' ]);
}
2019-02-23 21:25:41 +01:00
header ( $_SERVER [ " SERVER_PROTOCOL " ] . ' ' . $httpCode );
self :: jsonExit ( $data , $content_type );
}
2018-01-27 17:59:10 +01:00
/**
2020-01-19 07:05:23 +01:00
* Encodes content to json .
2018-01-27 17:59:10 +01:00
*
* This function encodes an array to json format
* and adds an application / json HTTP header to the output .
* After finishing the process is getting killed .
*
2020-07-13 15:26:09 +02:00
* @ param mixed $x The input content .
* @ param string $content_type Type of the input ( Default : 'application/json' ) .
* @ param integer $options JSON options
2018-01-27 17:59:10 +01:00
*/
2020-07-13 18:24:44 +02:00
public static function jsonExit ( $x , $content_type = 'application/json' , int $options = 0 ) {
2018-06-18 23:05:44 +02:00
header ( " Content-type: $content_type " );
2020-07-13 15:26:09 +02:00
echo json_encode ( $x , $options );
2018-12-26 06:40:12 +01:00
exit ();
2018-01-27 17:59:10 +01:00
}
2018-09-26 22:03:46 +02:00
/**
* Generates a random string in the UUID format
*
2019-01-06 22:06:53 +01:00
* @ param bool | string $prefix A given prefix ( default is empty )
2018-09-26 22:03:46 +02:00
* @ return string a generated UUID
2019-01-06 22:06:53 +01:00
* @ throws \Exception
2018-09-26 22:03:46 +02:00
*/
2018-09-27 13:52:15 +02:00
public static function createUUID ( $prefix = '' )
2018-09-26 22:03:46 +02:00
{
$guid = System :: createGUID ( 32 , $prefix );
2018-11-22 17:25:43 +01:00
return substr ( $guid , 0 , 8 ) . '-' . substr ( $guid , 8 , 4 ) . '-' . substr ( $guid , 12 , 4 ) . '-' . substr ( $guid , 16 , 4 ) . '-' . substr ( $guid , 20 , 12 );
2018-09-26 22:03:46 +02:00
}
2018-07-09 21:38:16 +02:00
/**
* Generates a GUID with the given parameters
*
2019-01-06 22:06:53 +01:00
* @ param int $size The size of the GUID ( default is 16 )
* @ param bool | string $prefix A given prefix ( default is empty )
2018-07-09 21:38:16 +02:00
* @ return string a generated GUID
2019-01-06 22:06:53 +01:00
* @ throws \Exception
2018-07-09 21:38:16 +02:00
*/
public static function createGUID ( $size = 16 , $prefix = '' )
{
if ( is_bool ( $prefix ) && ! $prefix ) {
$prefix = '' ;
2018-07-09 22:10:35 +02:00
} elseif ( empty ( $prefix )) {
2019-12-16 00:47:24 +01:00
$prefix = hash ( 'crc32' , DI :: baseUrl () -> getHostname ());
2018-07-09 21:38:16 +02:00
}
while ( strlen ( $prefix ) < ( $size - 13 )) {
$prefix .= mt_rand ();
}
if ( $size >= 24 ) {
$prefix = substr ( $prefix , 0 , $size - 22 );
return str_replace ( '.' , '' , uniqid ( $prefix , true ));
} else {
$prefix = substr ( $prefix , 0 , max ( $size - 13 , 0 ));
return uniqid ( $prefix );
}
}
2018-10-13 18:57:31 +02:00
/**
* Returns the current Load of the System
*
* @ return integer
*/
public static function currentLoad ()
{
if ( ! function_exists ( 'sys_getloadavg' )) {
return false ;
}
$load_arr = sys_getloadavg ();
if ( ! is_array ( $load_arr )) {
return false ;
}
return max ( $load_arr [ 0 ], $load_arr [ 1 ]);
}
2018-10-19 20:11:27 +02:00
/**
* Redirects to an external URL ( fully qualified URL )
* If you want to route relative to the current Friendica base , use App -> internalRedirect ()
*
2019-05-04 09:16:37 +02:00
* @ param string $url The new Location to redirect
* @ param int $code The redirection code , which is used ( Default is 302 )
2018-10-19 20:11:27 +02:00
*/
2019-05-04 09:16:37 +02:00
public static function externalRedirect ( $url , $code = 302 )
2018-10-19 20:11:27 +02:00
{
2018-12-03 16:59:53 +01:00
if ( empty ( parse_url ( $url , PHP_URL_SCHEME ))) {
2021-11-01 22:21:03 +01:00
Logger :: warning ( 'No fully qualified URL provided' , [ 'url' => $url , 'callstack' => self :: callstack ( 20 )]);
DI :: baseUrl () -> redirect ( $url );
2018-10-19 20:11:27 +02:00
}
2021-10-30 01:21:07 +02:00
header ( " Location: $url " );
2019-05-04 09:16:37 +02:00
switch ( $code ) {
case 302 :
2021-10-30 01:21:07 +02:00
throw new FoundException ();
2019-05-04 09:16:37 +02:00
case 301 :
2021-10-30 01:21:07 +02:00
throw new MovedPermanentlyException ();
2019-05-04 13:42:26 +02:00
case 307 :
2021-10-30 01:21:07 +02:00
throw new TemporaryRedirectException ();
2019-05-04 09:16:37 +02:00
}
2018-10-19 20:11:27 +02:00
exit ();
}
2019-02-03 22:22:04 +01:00
/**
2020-01-19 07:05:23 +01:00
* Returns the system user that is executing the script
2019-02-03 22:22:04 +01:00
*
* This mostly returns something like " www-data " .
*
* @ return string system username
*/
public static function getUser ()
{
if ( ! function_exists ( 'posix_getpwuid' ) || ! function_exists ( 'posix_geteuid' )) {
return '' ;
}
$processUser = posix_getpwuid ( posix_geteuid ());
return $processUser [ 'name' ];
}
2019-02-05 22:30:18 +01:00
/**
2020-01-19 07:05:23 +01:00
* Checks if a given directory is usable for the system
2019-02-05 22:30:18 +01:00
*
* @ param $directory
* @ param bool $check_writable
*
* @ return boolean the directory is usable
*/
public static function isDirectoryUsable ( $directory , $check_writable = true )
{
if ( $directory == '' ) {
Logger :: log ( 'Directory is empty. This shouldn\'t happen.' , Logger :: DEBUG );
return false ;
}
if ( ! file_exists ( $directory )) {
Logger :: log ( 'Path "' . $directory . '" does not exist for user ' . static :: getUser (), Logger :: DEBUG );
return false ;
}
if ( is_file ( $directory )) {
Logger :: log ( 'Path "' . $directory . '" is a file for user ' . static :: getUser (), Logger :: DEBUG );
return false ;
}
if ( ! is_dir ( $directory )) {
Logger :: log ( 'Path "' . $directory . '" is not a directory for user ' . static :: getUser (), Logger :: DEBUG );
return false ;
}
if ( $check_writable && ! is_writable ( $directory )) {
Logger :: log ( 'Path "' . $directory . '" is not writable for user ' . static :: getUser (), Logger :: DEBUG );
return false ;
}
return true ;
}
2020-02-16 10:32:56 +01:00
/**
* Exit method used by asynchronous update modules
*
* @ param string $o
*/
public static function htmlUpdateExit ( $o )
{
header ( " Content-type: text/html " );
echo " <!DOCTYPE html><html><body> \r \n " ;
// We can remove this hack once Internet Explorer recognises HTML5 natively
echo " <section> " ;
// reportedly some versions of MSIE don't handle tabs in XMLHttpRequest documents very well
echo str_replace ( " \t " , " " , $o );
echo " </section> " ;
echo " </body></html> \r \n " ;
exit ();
}
2017-08-26 08:04:21 +02:00
/// @todo Move the following functions from boot.php
/*
function local_user ()
function public_contact ()
function remote_user ()
function notice ( $s )
function info ( $s )
function is_site_admin ()
function get_temppath ()
function get_spoolpath ()
*/
}