2020-02-09 23:02:19 +01:00
< ? php
2022-11-18 18:06:46 +01:00
namespace Core\API ;
2020-02-09 23:02:19 +01:00
2022-11-18 18:06:46 +01:00
use Core\Driver\Logger\Logger ;
use Core\Objects\Context ;
2023-02-09 23:55:30 +01:00
use Core\Objects\DatabaseEntity\TwoFactorToken ;
2024-04-23 20:14:32 +02:00
use Core\Objects\RateLimiting ;
2023-02-09 23:55:30 +01:00
use Core\Objects\TwoFactor\KeyBasedTwoFactorToken ;
2022-02-20 16:53:26 +01:00
use PhpMqtt\Client\MqttClient ;
2020-04-03 15:56:04 +02:00
2024-04-06 11:52:22 +02:00
// TODO: many things are only checked for external calls, e.g. loginRequired. If we call the API internally, we might get null-pointers for $context->user
2022-02-21 13:01:03 +01:00
abstract class Request {
2020-02-09 23:02:19 +01:00
2022-06-20 19:52:31 +02:00
protected Context $context ;
2022-05-31 16:14:49 +02:00
protected Logger $logger ;
2020-04-03 15:56:04 +02:00
protected array $params ;
protected string $lastError ;
protected array $result ;
protected bool $success ;
protected bool $isPublic ;
protected bool $loginRequired ;
protected bool $variableParamCount ;
protected bool $isDisabled ;
protected bool $apiKeyAllowed ;
2020-06-14 19:39:52 +02:00
protected bool $csrfTokenRequired ;
2024-04-23 20:14:32 +02:00
protected ? RateLimiting $rateLimiting ;
2020-04-03 15:56:04 +02:00
2021-04-02 22:41:24 +02:00
private array $defaultParams ;
2020-04-03 15:56:04 +02:00
private array $allowedMethods ;
2020-04-03 18:09:01 +02:00
private bool $externalCall ;
2020-04-03 15:56:04 +02:00
2022-06-20 19:52:31 +02:00
public function __construct ( Context $context , bool $externalCall = false , array $params = array ()) {
$this -> context = $context ;
$this -> logger = new Logger ( $this -> getAPIName (), $this -> context -> getSQL ());
2021-04-02 22:41:24 +02:00
$this -> defaultParams = $params ;
2024-03-29 16:37:42 +01:00
$this -> externalCall = $externalCall ;
$this -> variableParamCount = false ;
2020-04-03 18:09:01 +02:00
2024-03-29 16:37:42 +01:00
// result
$this -> lastError = " " ;
2020-02-09 23:02:19 +01:00
$this -> success = false ;
$this -> result = array ();
2024-03-29 16:37:42 +01:00
// restrictions
2020-02-09 23:02:19 +01:00
$this -> isPublic = true ;
$this -> isDisabled = false ;
$this -> loginRequired = false ;
$this -> apiKeyAllowed = true ;
$this -> allowedMethods = array ( " GET " , " POST " );
2020-06-23 15:31:09 +02:00
$this -> csrfTokenRequired = true ;
2024-04-23 20:14:32 +02:00
$this -> rateLimiting = null ;
2020-02-09 23:02:19 +01:00
}
2022-05-31 16:14:49 +02:00
public function getAPIName () : string {
if ( get_class ( $this ) === Request :: class ) {
return " API " ;
}
$reflection = new \ReflectionClass ( $this );
if ( $reflection -> getParentClass () -> isAbstract () && $reflection -> getParentClass () -> isSubclassOf ( Request :: class )) {
return $reflection -> getParentClass () -> getShortName () . " / " . $reflection -> getShortName ();
} else {
return $reflection -> getShortName ();
}
}
2023-01-16 21:47:23 +01:00
protected function forbidMethod ( $method ) : void {
2020-02-09 23:02:19 +01:00
if (( $key = array_search ( $method , $this -> allowedMethods )) !== false ) {
2021-04-02 21:58:06 +02:00
unset ( $this -> allowedMethods [ $key ]);
2020-02-09 23:02:19 +01:00
}
}
2022-02-20 16:53:26 +01:00
public function getDefaultParams () : array {
return $this -> defaultParams ;
}
public function isDisabled () : bool {
return $this -> isDisabled ;
}
2023-01-16 21:47:23 +01:00
protected function allowMethod ( $method ) : void {
2021-12-08 16:53:43 +01:00
$availableMethods = [ " GET " , " HEAD " , " POST " , " PUT " , " DELETE " , " PATCH " , " TRACE " , " CONNECT " ];
if ( in_array ( $method , $availableMethods ) && ! in_array ( $method , $this -> allowedMethods )) {
$this -> allowedMethods [] = $method ;
}
}
protected function getRequestMethod () {
return $_SERVER [ " REQUEST_METHOD " ];
}
2021-11-11 14:25:26 +01:00
public function parseParams ( $values , $structure = NULL ) : bool {
2020-06-26 23:32:45 +02:00
2021-11-11 14:25:26 +01:00
if ( $structure === NULL ) {
$structure = $this -> params ;
}
foreach ( $structure as $name => $param ) {
2020-06-24 21:18:26 +02:00
$value = $values [ $name ] ? ? NULL ;
2020-02-09 23:02:19 +01:00
2024-03-27 16:27:26 +01:00
$isEmpty = is_string ( $value ) && strlen ( $value ) === 0 ;
2021-04-02 21:58:06 +02:00
if ( ! $param -> optional && ( is_null ( $value ) || $isEmpty )) {
2020-06-27 22:47:12 +02:00
return $this -> createError ( " Missing parameter: $name " );
2020-02-09 23:02:19 +01:00
}
2022-02-20 16:53:26 +01:00
$param -> reset ();
2021-04-02 21:58:06 +02:00
if ( ! is_null ( $value ) && ! $isEmpty ) {
if ( ! $param -> parseParam ( $value )) {
2020-02-09 23:02:19 +01:00
$value = print_r ( $value , true );
2020-06-27 22:47:12 +02:00
return $this -> createError ( " Invalid Type for parameter: $name ' $value ' (Required: " . $param -> getTypeName () . " ) " );
2020-02-09 23:02:19 +01:00
}
}
}
2021-04-02 21:58:06 +02:00
2020-02-09 23:02:19 +01:00
return true ;
}
2023-01-16 21:47:23 +01:00
public function parseVariableParams ( $values ) : void {
2021-04-02 21:58:06 +02:00
foreach ( $values as $name => $value ) {
if ( isset ( $this -> params [ $name ])) continue ;
2020-02-09 23:02:19 +01:00
$type = Parameter\Parameter :: parseType ( $value );
$param = new Parameter\Parameter ( $name , $type , true );
$param -> parseParam ( $value );
$this -> params [ $name ] = $param ;
}
}
2021-12-08 16:53:43 +01:00
// wrapper for unit tests
protected function _die ( string $data = " " ) : bool {
die ( $data );
}
2022-02-21 13:01:03 +01:00
protected abstract function _execute () : bool ;
2024-04-05 13:01:15 +02:00
2024-04-23 12:14:28 +02:00
public static abstract function getDescription () : string ;
public static function getDefaultPermittedGroups () : array {
return [];
}
public static function hasConfigurablePermissions () : bool {
return true ;
}
2022-02-21 13:01:03 +01:00
2023-02-09 23:55:30 +01:00
protected function check2FA ( ? TwoFactorToken $tfaToken = null ) : bool {
// do not require 2FA for verifying endpoints
if ( $this instanceof \Core\API\Tfa\VerifyTotp || $this instanceof \Core\API\Tfa\VerifyKey ) {
return true ;
}
if ( $tfaToken === null ) {
$tfaToken = $this -> context -> getUser () ? -> getTwoFactorToken ();
}
if ( $tfaToken && $tfaToken -> isConfirmed () && ! $tfaToken -> isAuthenticated ()) {
if ( $tfaToken instanceof KeyBasedTwoFactorToken && ! $tfaToken -> hasChallenge ()) {
$tfaToken -> generateChallenge ();
}
$this -> lastError = '2FA-Authorization is required' ;
$this -> result [ " twoFactorToken " ] = $tfaToken -> jsonSerialize ([
" type " , " challenge " , " authenticated " , " confirmed " , " credentialID "
]);
return false ;
}
return true ;
}
2024-04-11 17:51:50 +02:00
protected function getCORS () : array {
$settings = $this -> context -> getSettings ();
return $settings -> getTrustedDomains ();
}
2022-02-21 13:01:03 +01:00
public final function execute ( $values = array ()) : bool {
2022-02-20 16:53:26 +01:00
2024-04-11 17:51:50 +02:00
if ( $this -> externalCall ) {
$trustedDomains = $this -> getCORS ();
if ( ! empty ( $trustedDomains )) {
2024-04-11 20:41:03 +02:00
// TODO: origins require a protocol, e.g. https:// or http:// as prefix.
// should we force https for all origins? or make exceptions for localhost?
2024-04-11 17:51:50 +02:00
header ( " Access-Control-Allow-Origin: " . implode ( " , " , $trustedDomains ));
}
}
2021-04-02 22:42:53 +02:00
$this -> params = array_merge ([], $this -> defaultParams );
2020-02-09 23:02:19 +01:00
$this -> success = false ;
$this -> result = array ();
$this -> lastError = '' ;
2022-06-20 19:52:31 +02:00
$session = $this -> context -> getSession ();
if ( $session ) {
$this -> result [ 'logoutIn' ] = $session -> getExpiresSeconds ();
2020-02-09 23:02:19 +01:00
}
2024-04-23 20:14:32 +02:00
if ( $this -> isDisabled ) {
$this -> lastError = " This function is currently disabled. " ;
http_response_code ( 503 );
return false ;
}
$sql = $this -> context -> getSQL ();
if ( $sql === null || ! $sql -> isConnected ()) {
$this -> lastError = $sql ? $sql -> getLastError () : " Database not connected yet. " ;
http_response_code ( 503 );
return false ;
}
2021-04-02 21:58:06 +02:00
if ( $this -> externalCall ) {
2024-04-23 20:14:32 +02:00
if ( ! $this -> isPublic ) {
$this -> lastError = 'This function is private.' ;
http_response_code ( 403 );
return false ;
}
2020-02-10 00:52:25 +01:00
$values = $_REQUEST ;
2023-01-15 00:32:17 +01:00
if ( $_SERVER [ 'REQUEST_METHOD' ] === 'POST' && in_array ( " application/json " , explode ( " ; " , $_SERVER [ " CONTENT_TYPE " ] ? ? " " ))) {
2020-02-09 23:02:19 +01:00
$jsonData = json_decode ( file_get_contents ( 'php://input' ), true );
2021-11-11 14:25:26 +01:00
if ( $jsonData !== null ) {
2020-06-14 22:35:01 +02:00
$values = array_merge ( $values , $jsonData );
} else {
$this -> lastError = 'Invalid request body.' ;
2021-12-08 16:53:43 +01:00
http_response_code ( 400 );
2020-06-14 22:35:01 +02:00
return false ;
}
2020-02-09 23:02:19 +01:00
}
2021-04-07 00:03:14 +02:00
2021-12-08 16:53:43 +01:00
if ( $_SERVER [ 'REQUEST_METHOD' ] === 'OPTIONS' ) {
http_response_code ( 204 ); # No content
header ( " Allow: OPTIONS, " . implode ( " , " , $this -> allowedMethods ));
return $this -> _die ();
}
2021-04-07 00:03:14 +02:00
// check the request method
if ( ! in_array ( $_SERVER [ 'REQUEST_METHOD' ], $this -> allowedMethods )) {
$this -> lastError = 'This method is not allowed' ;
2021-12-08 16:53:43 +01:00
http_response_code ( 405 );
2021-04-07 00:03:14 +02:00
return false ;
}
2020-06-14 19:39:52 +02:00
$apiKeyAuthorized = false ;
2022-06-20 19:52:31 +02:00
if ( ! $session && $this -> apiKeyAllowed ) {
2021-12-08 16:53:43 +01:00
if ( isset ( $_SERVER [ " HTTP_AUTHORIZATION " ])) {
2021-11-11 14:25:26 +01:00
$authHeader = $_SERVER [ " HTTP_AUTHORIZATION " ];
if ( startsWith ( $authHeader , " Bearer " )) {
$apiKey = substr ( $authHeader , strlen ( " Bearer " ));
2022-06-20 19:52:31 +02:00
$apiKeyAuthorized = $this -> context -> loadApiKey ( $apiKey );
2021-11-11 14:25:26 +01:00
}
2020-06-27 01:18:10 +02:00
}
2021-12-08 16:53:43 +01:00
}
2020-06-27 01:18:10 +02:00
2021-12-08 16:53:43 +01:00
// Logged in or api key authorized?
if ( $this -> loginRequired ) {
2022-06-20 19:52:31 +02:00
if ( ! $session && ! $apiKeyAuthorized ) {
2020-06-27 01:18:10 +02:00
$this -> lastError = 'You are not logged in.' ;
2024-04-06 11:52:22 +02:00
$this -> result [ " loggedIn " ] = false ;
2021-12-08 16:53:43 +01:00
http_response_code ( 401 );
2020-06-27 01:18:10 +02:00
return false ;
2023-02-09 23:55:30 +01:00
} else if ( $session && ! $this -> check2FA ()) {
http_response_code ( 401 );
return false ;
2020-06-27 01:18:10 +02:00
}
2020-06-27 22:47:12 +02:00
}
2020-02-09 23:02:19 +01:00
2020-06-27 22:47:12 +02:00
// CSRF Token
2022-06-20 19:52:31 +02:00
if ( $this -> csrfTokenRequired && $session ) {
2020-06-27 22:47:12 +02:00
// csrf token required + external call
2023-01-07 15:34:05 +01:00
// if it's not a call with API_KEY, check for csrfToken
$csrfToken = $values [ " csrfToken " ] ? ? $_SERVER [ " HTTP_XSRF_TOKEN " ] ? ? null ;
2022-06-20 19:52:31 +02:00
if ( ! $csrfToken || strcmp ( $csrfToken , $session -> getCsrfToken ()) !== 0 ) {
2020-06-27 22:47:12 +02:00
$this -> lastError = " CSRF-Token mismatch " ;
2021-12-08 16:53:43 +01:00
http_response_code ( 403 );
2020-06-27 22:47:12 +02:00
return false ;
2020-06-14 19:39:52 +02:00
}
2020-02-09 23:02:19 +01:00
}
2020-06-27 01:18:10 +02:00
// Check for permission
2024-03-27 15:15:46 +01:00
$req = new \Core\API\Permission\Check ( $this -> context );
2024-04-04 12:46:58 +02:00
$this -> success = $req -> execute ([ " method " => self :: getEndpoint ()]);
2024-03-27 15:15:46 +01:00
$this -> lastError = $req -> getLastError ();
if ( ! $this -> success ) {
2024-04-07 18:29:33 +02:00
$res = $req -> getResult ();
2024-04-06 11:52:22 +02:00
if ( ! $this -> context -> getUser ()) {
$this -> result [ " loggedIn " ] = false ;
2024-04-07 18:29:33 +02:00
} else if ( isset ( $res [ " twoFactorToken " ])) {
$this -> result [ " twoFactorToken " ] = $res [ " twoFactorToken " ];
2024-04-06 11:52:22 +02:00
}
2024-03-27 15:15:46 +01:00
return false ;
2020-06-27 01:18:10 +02:00
}
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
if ( ! $this -> parseParams ( $values )) {
2020-02-09 23:02:19 +01:00
return false ;
2021-04-02 21:58:06 +02:00
}
2020-02-09 23:02:19 +01:00
2021-04-02 21:58:06 +02:00
if ( $this -> variableParamCount ) {
2020-02-10 00:52:25 +01:00
$this -> parseVariableParams ( $values );
2021-04-02 21:58:06 +02:00
}
2020-02-09 23:02:19 +01:00
2024-04-23 20:14:32 +02:00
if ( $this -> externalCall && $this -> rateLimiting ) {
$settings = $this -> context -> getSettings ();
if ( $settings -> isRateLimitingEnabled ()) {
if ( ! $this -> rateLimiting -> check ( $this -> context , self :: getEndpoint ())) {
http_response_code ( 429 );
$this -> lastError = " Rate limit exceeded " ;
return false ;
}
}
2020-02-09 23:02:19 +01:00
}
2022-05-31 16:14:49 +02:00
$this -> success = true ;
2024-03-29 16:37:42 +01:00
try {
$success = $this -> _execute ();
if ( $this -> success !== $success ) {
// _execute might return a different value then it set for $this->success
// this should actually not occur, how to handle this case?
$this -> success = $success ;
}
2024-04-07 18:29:33 +02:00
} catch ( \Throwable $err ) {
2024-03-30 11:22:59 +01:00
http_response_code ( 500 );
2024-03-29 16:37:42 +01:00
$this -> createError ( $err -> getMessage ());
2024-05-04 17:06:39 +02:00
$this -> logger -> severe ( $err );
2022-02-21 13:01:03 +01:00
}
2024-03-29 16:37:42 +01:00
$sql -> setLastError ( " " );
2022-02-21 13:01:03 +01:00
return $this -> success ;
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
protected function createError ( $err ) : bool {
2020-02-09 23:02:19 +01:00
$this -> success = false ;
$this -> lastError = $err ;
return false ;
}
2022-11-19 01:15:34 +01:00
protected function getParam ( $name , $obj = NULL ) : mixed {
2021-11-11 14:25:26 +01:00
if ( $obj === NULL ) {
$obj = $this -> params ;
}
2023-01-16 21:47:23 +01:00
// I don't know why phpstorm
2021-11-11 14:25:26 +01:00
return ( isset ( $obj [ $name ]) ? $obj [ $name ] -> value : NULL );
2021-04-02 21:58:06 +02:00
}
2022-02-20 16:53:26 +01:00
public function isMethodAllowed ( string $method ) : bool {
return in_array ( $method , $this -> allowedMethods );
}
2021-04-02 21:58:06 +02:00
public function isPublic () : bool {
return $this -> isPublic ;
}
public function getLastError () : string {
return $this -> lastError ;
2020-06-23 15:31:09 +02:00
}
2020-04-03 15:56:04 +02:00
2021-04-02 21:58:06 +02:00
public function getResult () : array {
return $this -> result ;
}
public function success () : bool {
return $this -> success ;
}
public function loginRequired () : bool {
return $this -> loginRequired ;
}
public function isExternalCall () : bool {
return $this -> externalCall ;
}
2020-02-09 23:02:19 +01:00
2023-01-16 21:47:23 +01:00
public static function getEndpoint ( string $prefix = " " ) : ? string {
$reflectionClass = new \ReflectionClass ( get_called_class ());
if ( $reflectionClass -> isAbstract ()) {
return null ;
}
2024-04-04 12:46:58 +02:00
$parentClass = $reflectionClass -> getParentClass ();
if ( $parentClass === false ) {
return null ;
}
$isNestedAPI = $parentClass -> getName () !== Request :: class ;
2023-01-16 21:47:23 +01:00
if ( ! $isNestedAPI ) {
# e.g. /api/stats or /api/info
$methodName = $reflectionClass -> getShortName ();
return $prefix . lcfirst ( $methodName );
2024-04-04 12:46:58 +02:00
} else if ( $parentClass -> getName () === \TestRequest :: class ) {
$methodName = $reflectionClass -> getShortName ();
return $prefix . " /e2e-test/ " . lcfirst ( $methodName );
2023-01-16 21:47:23 +01:00
} else {
# e.g. /api/user/login
$methodClass = $reflectionClass ;
$nestedClass = $reflectionClass -> getParentClass ();
while ( ! endsWith ( $nestedClass -> getName (), " API " )) {
$methodClass = $nestedClass ;
$nestedClass = $nestedClass -> getParentClass ();
2024-04-04 12:46:58 +02:00
if ( ! $nestedClass ) {
return null ;
}
2023-01-16 21:47:23 +01:00
}
$nestedAPI = substr ( lcfirst ( $nestedClass -> getShortName ()), 0 , - 3 );
$methodName = lcfirst ( $methodClass -> getShortName ());
return $prefix . $nestedAPI . " / " . $methodName ;
}
2020-06-27 01:18:10 +02:00
}
2021-04-02 21:58:06 +02:00
public function getJsonResult () : string {
2020-02-09 23:02:19 +01:00
$this -> result [ 'success' ] = $this -> success ;
$this -> result [ 'msg' ] = $this -> lastError ;
return json_encode ( $this -> result );
}
2021-11-11 14:25:26 +01:00
2023-01-16 21:47:23 +01:00
protected function disableOutputBuffer () : void {
2021-11-11 14:25:26 +01:00
ob_implicit_flush ( true );
$levels = ob_get_level ();
for ( $i = 0 ; $i < $levels ; $i ++ ) {
ob_end_flush ();
}
flush ();
}
2021-12-08 16:53:43 +01:00
2023-01-16 21:47:23 +01:00
protected function disableCache () : void {
2022-02-20 16:53:26 +01:00
header ( " Last-Modified: " . ( new \DateTime ()) -> format ( " D, d M Y H:i:s T " ));
header ( " Expires: Sat, 26 Jul 1997 05:00:00 GMT " );
header ( " Cache-Control: no-store, no-cache, must-revalidate, max-age=0 " );
header ( " Cache-Control: post-check=0, pre-check=0 " , false );
header ( " Pragma: no-cache " );
}
2023-01-16 21:47:23 +01:00
protected function setupSSE () : void {
2022-06-20 19:52:31 +02:00
$this -> context -> sendCookies ();
$this -> context -> getSQL () ? -> close ();
2021-12-08 16:53:43 +01:00
set_time_limit ( 0 );
ignore_user_abort ( true );
header ( 'Content-Type: text/event-stream' );
header ( 'Connection: keep-alive' );
header ( 'X-Accel-Buffering: no' );
2022-02-20 16:53:26 +01:00
$this -> disableCache ();
2021-12-08 16:53:43 +01:00
$this -> disableOutputBuffer ();
}
2022-02-20 16:53:26 +01:00
/**
* @ throws \PhpMqtt\Client\Exceptions\ProtocolViolationException
* @ throws \PhpMqtt\Client\Exceptions\DataTransferException
* @ throws \PhpMqtt\Client\Exceptions\MqttClientException
*/
2023-01-16 21:47:23 +01:00
protected function startMqttSSE ( MqttClient $mqtt , callable $onPing ) : void {
2022-02-20 16:53:26 +01:00
$lastPing = 0 ;
$mqtt -> registerLoopEventHandler ( function ( MqttClient $mqtt , $elapsed ) use ( & $lastPing , $onPing ) {
if ( $elapsed - $lastPing >= 5 ) {
$onPing ();
$lastPing = $elapsed ;
}
if ( connection_status () !== 0 ) {
$mqtt -> interrupt ();
}
});
$mqtt -> loop ();
$this -> lastError = " MQTT Loop disconnected " ;
$mqtt -> disconnect ();
}
2023-01-16 21:47:23 +01:00
protected function processImageUpload ( string $uploadDir , array $allowedExtensions = [ " jpg " , " jpeg " , " png " , " gif " ], $transformCallback = null ) : bool | array {
2021-12-08 16:53:43 +01:00
if ( empty ( $_FILES )) {
return $this -> createError ( " You need to upload an image. " );
} else if ( count ( $_FILES ) > 1 ) {
return $this -> createError ( " You can only upload one image at once. " );
}
$upload = array_values ( $_FILES )[ 0 ];
if ( is_array ( $upload [ " name " ])) {
return $this -> createError ( " You can only upload one image at once. " );
} else if ( $upload [ " error " ] !== UPLOAD_ERR_OK ) {
return $this -> createError ( " There was an error uploading the image, code: " . $upload [ " error " ]);
}
$imageName = $upload [ " name " ];
$ext = strtolower ( pathinfo ( $imageName , PATHINFO_EXTENSION ));
if ( ! in_array ( $ext , $allowedExtensions )) {
return $this -> createError ( " Only the following file extensions are allowed: " . implode ( " , " , $allowedExtensions ));
}
if ( ! is_dir ( $uploadDir ) && ! mkdir ( $uploadDir , 0777 , true )) {
return $this -> createError ( " Upload directory does not exist and could not be created. " );
}
$srcPath = $upload [ " tmp_name " ];
$mimeType = mime_content_type ( $srcPath );
if ( ! startsWith ( $mimeType , " image/ " )) {
return $this -> createError ( " Uploaded file is not an image. " );
}
try {
$image = new \Imagick ( $srcPath );
// strip exif
$profiles = $image -> getImageProfiles ( " icc " , true );
$image -> stripImage ();
if ( ! empty ( $profiles )) {
$image -> profileImage ( " icc " , $profiles [ 'icc' ]);
}
} catch ( \ImagickException $ex ) {
return $this -> createError ( " Error loading image: " . $ex -> getMessage ());
}
try {
if ( $transformCallback ) {
$fileName = call_user_func ([ $this , $transformCallback ], $image , $uploadDir );
} else {
$image -> writeImage ( $srcPath );
$image -> destroy ();
$uuid = uuidv4 ();
$fileName = " $uuid . $ext " ;
$destPath = " $uploadDir / $fileName " ;
if ( ! file_exists ( $destPath )) {
if ( !@ move_uploaded_file ( $srcPath , $destPath )) {
return $this -> createError ( " Could not store uploaded file. " );
}
}
}
return [ $fileName , $imageName ];
} catch ( \ImagickException $ex ) {
return $this -> createError ( " Error processing image: " . $ex -> getMessage ());
}
}
2023-01-15 00:32:17 +01:00
protected function getFileUpload ( string $name , bool $allowMultiple = false , ? array $extensions = null ) : false | array {
if ( ! isset ( $_FILES [ $name ]) || ( is_array ( $_FILES [ $name ][ " name " ]) && empty ( $_FILES [ $name ][ " name " ])) || empty ( $_FILES [ $name ][ " name " ])) {
return $this -> createError ( " Missing form-field ' $name ' " );
}
$files = [];
if ( is_array ( $_FILES [ $name ][ " name " ])) {
$numFiles = count ( $_FILES [ $name ][ " name " ]);
if ( ! $allowMultiple && $numFiles > 1 ) {
return $this -> createError ( " Only one file allowed for form-field ' $name ' " );
} else {
for ( $i = 0 ; $i < $numFiles ; $i ++ ) {
$fileName = $_FILES [ $name ][ " name " ][ $i ];
$filePath = $_FILES [ $name ][ " tmp_name " ][ $i ];
$files [ $fileName ] = $filePath ;
if ( ! empty ( $extensions ) && ! in_array ( pathinfo ( $fileName , PATHINFO_EXTENSION ), $extensions )) {
return $this -> createError ( " File ' $fileName ' has forbidden extension, allowed: " . implode ( " , " , $extensions ));
}
}
}
} else {
$fileName = $_FILES [ $name ][ " name " ];
$filePath = $_FILES [ $name ][ " tmp_name " ];
$files [ $fileName ] = $filePath ;
if ( ! empty ( $extensions ) && ! in_array ( pathinfo ( $fileName , PATHINFO_EXTENSION ), $extensions )) {
return $this -> createError ( " File ' $fileName ' has forbidden extension, allowed: " . implode ( " , " , $extensions ));
}
}
if ( $allowMultiple ) {
return $files ;
} else {
$fileName = key ( $files );
return [ $fileName , $files [ $fileName ]];
}
}
2023-01-16 21:47:23 +01:00
public static function getApiEndpoints () : array {
// first load all direct classes
$classes = [];
$apiDirs = [ " Core " , " Site " ];
foreach ( $apiDirs as $apiDir ) {
$basePath = realpath ( WEBROOT . " / $apiDir /API/ " );
if ( ! $basePath ) {
continue ;
}
foreach ( scandir ( $basePath ) as $fileName ) {
$fullPath = $basePath . " / " . $fileName ;
if ( is_file ( $fullPath ) && endsWith ( $fileName , " .class.php " )) {
require_once $fullPath ;
$apiName = explode ( " . " , $fileName )[ 0 ];
$className = " \\ $apiDir\\API\\ $apiName " ;
if ( ! class_exists ( $className )) {
continue ;
}
$reflectionClass = new \ReflectionClass ( $className );
if ( ! $reflectionClass -> isSubclassOf ( Request :: class ) || $reflectionClass -> isAbstract ()) {
continue ;
}
$endpoint = " $className ::getEndpoint " ();
$classes [ $endpoint ] = $reflectionClass ;
}
}
}
// then load all inheriting classes
foreach ( get_declared_classes () as $declaredClass ) {
$reflectionClass = new \ReflectionClass ( $declaredClass );
if ( ! $reflectionClass -> isAbstract () && $reflectionClass -> isSubclassOf ( Request :: class )) {
$className = $reflectionClass -> getName ();
$endpoint = " $className ::getEndpoint " ();
$classes [ $endpoint ] = $reflectionClass ;
}
}
return $classes ;
}
2024-04-10 19:04:37 +02:00
protected function logUserId () : string {
$currentUser = $this -> context -> getUser ();
return $currentUser ? " userId=' " . $currentUser -> getId () . " ' " : " SYSTEM " ;
}
2024-04-22 19:01:04 +02:00
protected function formatDuration ( int $count , string $string ) : string {
if ( $count === 1 ) {
return $string ;
} else {
return " the next $count { $string } s " ;
}
}
2020-04-03 15:56:04 +02:00
}