openstamanager/lib/classes/API.php

363 lines
9.9 KiB
PHP

<?php
/**
* Classe per la gestione delle API del progetto.
*
* @since 2.3
*/
class API extends \Util\Singleton
{
/** @var array Elenco delle risorse disponibili suddivise per categoria */
protected static $resources;
/** @var array Stati previsti dall'API */
protected static $status = [
'ok' => [
'code' => 200,
'message' => 'OK',
],
'internalError' => [
'code' => 400,
'message' => "Errore interno dell'API",
],
'unauthorized' => [
'code' => 401,
'message' => 'Non autorizzato',
],
'notFound' => [
'code' => 404,
'message' => 'Non trovato',
],
'serverError' => [
'code' => 500,
'message' => 'Errore del server',
],
'incompatible' => [
'code' => 503,
'message' => 'Servizio non disponibile',
],
];
/**
* @throws InvalidArgumentException
*/
public function __construct()
{
$user = Auth::user();
if (!self::isAPIRequest() || (empty($user) && self::getRequest()['resource'] != 'login')) {
throw new InvalidArgumentException();
}
}
/**
* Gestisce le richieste di informazioni riguardanti gli elementi esistenti.
*
* @param array $request
*
* @return string
*/
public function retrieve($request)
{
$user = Auth::user();
$table = '';
$select = '*';
// Selezione personalizzata
$display = $request['display'];
$select = !empty($display) ? explode(',', substr($display, 1, -1)) : $select;
$where = [];
// Ricerca personalizzata
$filter = (array) $request['filter'];
foreach ($filter as $key => $value) {
$value = substr($value, 1, -1);
$result = [];
if (str_contains($value, ',')) {
$or = [];
$temp = explode(',', $value);
foreach ($temp as $value) {
$or[] = [$key => $value];
}
$result[] = ['OR' => $or];
} else {
$result[$key] = $value;
}
$where[] = $result;
}
$order = [];
// Ordinamento personalizzato
$order_request = (array) $request['order'];
foreach ($order_request as $value) {
$pieces = explode('|', $value);
$order[] = empty($pieces[1]) ? $pieces[0] : [$pieces[0] => $pieces[1]];
}
// Date di interesse
$updated = $request['upd'];
$created = $request['crd'];
// Paginazione dell'API
$page = (int) $request['page'] ?: 0;
$length = Settings::get('Lunghezza pagine per API');
$database = Database::getConnection();
$dbo = $database;
$kind = 'retrieve';
$resources = self::getResources()[$kind];
$resource = $request['resource'];
if (!in_array($resource, array_keys($resources))) {
$excluded = explode(',', Settings::get('Tabelle escluse per la sincronizzazione API automatica'));
if (!in_array($resource, $excluded)) {
$table = $resource;
if (empty($order)) {
$order[] = $dbo->fetchArray('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '.prepare($table)." AND EXTRA LIKE '%AUTO_INCREMENT%' AND TABLE_SCHEMA = ".prepare($dbo->getDatabaseName()))[0]['COLUMN_NAME'];
}
}
} else {
$filename = DOCROOT.'/modules/'.$resources[$resource].'/api/'.$kind.'.php';
include $filename;
}
// Generazione automatica delle query
if (empty($results) && !empty($table)) {
try {
// Query per ottenere le informazioni
$results = $dbo->select($table, $select, $where, $order, [$page * $length, $length]);
// Informazioni aggiuntive
$query = $dbo->select($table, $select, $where, $order, [], true);
$cont = $dbo->fetchArray('SELECT COUNT(*) as `records`, CEIL(COUNT(*) / '.$length.') as `pages` FROM ('.$query.') AS `count`');
if (!empty($cont)) {
$results['records'] = $cont[0]['records'];
$results['pages'] = $cont[0]['pages'];
}
} catch (PDOException $e) {
return self::error('internalError');
}
}
return self::response($results);
}
/**
* Gestisce le richieste di creazione nuovi elementi.
*
* @param array $request
*
* @return string
*/
public function create($request)
{
return $this->fileRequest($request, 'create');
}
/**
* Gestisce le richieste di aggiornamento di elementi esistenti.
*
* @param array $request
*
* @return string
*/
public function update($request)
{
return $this->fileRequest($request, 'update');
}
/**
* Gestisce le richieste di eliminazione di elementi esistenti.
*
* @param array $request
*
* @return string
*/
public function delete($request)
{
return $this->fileRequest($request, 'delete');
}
/**
* Gestisce le richieste in modo generalizzato, con il relativo richiamo ai file specifici responsabili dell'operazione.
*
* @param array $request
*
* @return string
*/
protected function fileRequest($request, $kind)
{
$user = Auth::user();
$resources = self::getResources()[$kind];
$resource = $request['resource'];
if (!in_array($resource, array_keys($resources))) {
return self::error('notFound');
}
// Database
$database = Database::getConnection();
$dbo = $database;
$dbo->query('START TRANSACTION');
$filename = DOCROOT.'/modules/'.$resources[$resource].'/api/'.$kind.'.php';
include $filename;
$dbo->query('COMMIT');
return self::response($results);
}
/**
* Genera i contenuti di risposta nel caso si verifichi un errore.
*
* @param string|int $error
*
* @return string
*/
public static function error($error)
{
$keys = array_keys(self::$status);
$error = (in_array($error, $keys)) ? $error : 'serverError';
$code = self::$status[$error]['code'];
http_response_code($code);
return self::response([
'status' => $code,
]);
}
/**
* Restituisce l'elenco delle risorse disponibili per l'API, suddivise per categoria.
*
* @return array
*/
public static function getResources()
{
if (!is_array(self::$resources)) {
$resources = [];
$operations = glob(DOCROOT.'/modules/*/api/{retrieve,create,update,delete}.php', GLOB_BRACE);
if (!empty($operations)) {
foreach ($operations as $operation) {
$module = basename(dirname(dirname($operation)));
$kind = basename($operation, '.php');
$resources[$kind] = (array) $resources[$kind];
$temp = str_replace('/api/', '/custom/api/', $operation);
$operation = file_exists($temp) ? $temp : $operation;
$api = include $operation;
$api = is_array($api) ? array_unique($api) : [];
$keys = array_keys($resources[$kind]);
$results = [];
foreach ($api as $value) {
$value .= in_array($value, $keys) ? $module : '';
$results[$value] = $module;
}
$resources[$kind] = array_merge($resources[$kind], $results);
}
}
self::$resources = $resources;
}
return self::$resources;
}
/**
* Formatta i contentuti della risposta secondo il formato JSON.
*
* @param array $array
*
* @return string
*/
public static function response($array)
{
if (!self::isCompatible()) {
$array = [
'status' => self::$status['incompatible']['code'],
];
}
if (empty($array['status'])) {
$array['status'] = self::$status['ok']['code'];
}
if (empty($array['message'])) {
$codes = array_column(self::$status, 'code');
$messages = array_column(self::$status, 'message');
$array['message'] = $messages[array_search($array['status'], $codes)];
}
$flags = JSON_FORCE_OBJECT;
if (get('beautify') !== null) {
$flags |= JSON_PRETTY_PRINT;
}
return json_encode($array, $flags);
}
/**
* Restituisce l'elenco degli stati dell'API.
*
* @return array
*/
public static function getStatus()
{
return self::$status;
}
/**
* Controlla se la richiesta effettuata è rivolta all'API.
*
* @return bool
*/
public static function isAPIRequest()
{
return slashes($_SERVER['SCRIPT_FILENAME']) == slashes(DOCROOT.'/api/index.php');
}
/**
* Restituisce i parametri specificati dalla richiesta.
*/
public static function getRequest()
{
$request = (array) json_decode(file_get_contents('php://input'), true);
if ($_SERVER['REQUEST_METHOD'] == 'GET' && empty($request)) {
$request = Filter::getGET();
}
return $request;
}
/**
* Controlla se il database è compatibile con l'API.
*
* @return bool
*/
public static function isCompatible()
{
$database = Database::getConnection();
return version_compare($database->getMySQLVersion(), '5.6.5') >= 0;
}
}