From a688230c0d71f7d2f53c3cadb91111b804c5d85b Mon Sep 17 00:00:00 2001 From: Thomas Zilio Date: Mon, 4 Sep 2017 16:04:45 +0200 Subject: [PATCH] Miglioramento della struttura delle classi --- docs/API.md | 2 +- lib/classes/API.php | 136 +++++++++++++++++----------------- lib/classes/Auth.php | 7 ++ lib/classes/Database.php | 55 ++++++++------ modules/utenti/api/create.php | 2 + update/2_3.sql | 2 +- 6 files changed, 111 insertions(+), 93 deletions(-) diff --git a/docs/API.md b/docs/API.md index e7cba5fb7..9728877c8 100644 --- a/docs/API.md +++ b/docs/API.md @@ -90,7 +90,7 @@ In particolare, ogni modulo può specificare una determinata serie di operazioni Di seguito lo schema attraverso cui l'API individua la presenza delle possibili richieste supportate dai moduli (cartelle **api/** e **custom/api/**): - `POST` - File `create.php`. -- `GET` - File `eetrieve.php`. +- `GET` - File `retrieve.php`. - `PUT` - File `update.php`. - `DELETE` - File `delete.php`. diff --git a/lib/classes/API.php b/lib/classes/API.php index 017c16918..7c983e5a5 100644 --- a/lib/classes/API.php +++ b/lib/classes/API.php @@ -43,9 +43,7 @@ class API extends \Util\Singleton */ public function __construct() { - $user = Auth::user(); - - if (!self::isAPIRequest() || (empty($user) && self::getRequest()['resource'] != 'login')) { + if (!self::isAPIRequest() || (!Auth::check() && self::getRequest()['resource'] != 'login')) { throw new InvalidArgumentException(); } } @@ -62,81 +60,70 @@ class API extends \Util\Singleton $user = Auth::user(); $table = ''; - $select = '*'; - // Selezione personalizzata - $display = $request['display']; - $select = !empty($display) ? explode(',', substr($display, 1, -1)) : $select; - $where = []; + $order = []; + + // Selezione personalizzata + $select = !empty($request['display']) ? explode(',', substr($request['display'], 1, -1)) : $select; + // Ricerca personalizzata - $filter = (array) $request['filter']; - foreach ($filter as $key => $value) { + foreach ((array) $request['filter'] as $key => $value) { + // Rimozione delle parentesi $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; + // Individuazione della tipologia (array o string) + $where[$key] = str_contains($value, ',') ? explode(',', $value) : $value; } - $order = []; // Ordinamento personalizzato - $order_request = (array) $request['order']; - foreach ($order_request as $value) { + foreach ((array) $request['order'] 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 + // Paginazione automatica 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 (in_array($resource, array_keys($resources))) { + $dbo = $database; - 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 { + // Esecuzione delle operazioni personalizzate $filename = DOCROOT.'/modules/'.$resources[$resource].'/api/'.$kind.'.php'; include $filename; + } elseif (!in_array($resource, explode(',', Settings::get('Tabelle escluse per la sincronizzazione API automatica')))) { + $table = $resource; + + // Individuazione della colonna AUTO_INCREMENT per l'ordinamento automatico + if (empty($order)) { + $order[] = $database->fetchArray('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '.prepare($table)." AND EXTRA LIKE '%AUTO_INCREMENT%' AND TABLE_SCHEMA = ".prepare($database->getDatabaseName()))[0]['COLUMN_NAME']; + } } // Generazione automatica delle query if (empty($results) && !empty($table)) { try { + // Date di interesse + if (!empty($request['upd'])) { + $where['#updated_at'] = 'updated_at >= '.prepare($request['upd']); + } + if (!empty($request['crd'])) { + $where['#created_at'] = 'created_at >= '.prepare($request['crd']); + } + // Query per ottenere le informazioni - $results = $dbo->select($table, $select, $where, $order, [$page * $length, $length]); + $results = $database->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`'); + $query = $database->select($table, $select, $where, $order, [], true); + $cont = $database->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']; @@ -207,12 +194,13 @@ class API extends \Util\Singleton $database = Database::getConnection(); $dbo = $database; - $dbo->query('START TRANSACTION'); + $database->query('START TRANSACTION'); + // Esecuzione delle operazioni $filename = DOCROOT.'/modules/'.$resources[$resource].'/api/'.$kind.'.php'; include $filename; - $dbo->query('COMMIT'); + $database->query('COMMIT'); return self::response($results); } @@ -249,29 +237,32 @@ class API extends \Util\Singleton $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'); + foreach ($operations as $operation) { + // Individua la tipologia e il modulo delle operazioni + $module = basename(dirname(dirname($operation))); + $kind = basename($operation, '.php'); - $resources[$kind] = (array) $resources[$kind]; + $resources[$kind] = (array) $resources[$kind]; - $temp = str_replace('/api/', '/custom/api/', $operation); - $operation = file_exists($temp) ? $temp : $operation; + // Controllo sulla presenza di eventuali personalizzazioni + $temp = str_replace('/api/', '/custom/api/', $operation); + $operation = file_exists($temp) ? $temp : $operation; - $api = include $operation; - $api = is_array($api) ? array_unique($api) : []; + // Individuazione delle operazioni + $api = include $operation; + $api = is_array($api) ? array_unique($api) : []; - $keys = array_keys($resources[$kind]); + $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); + // Registrazione delle operazioni individuate + $results = []; + foreach ($api as $value) { + $value .= in_array($value, $keys) ? $module : ''; + $results[$value] = $module; } + + // Salvataggio delle operazioni + $resources[$kind] = array_merge($resources[$kind], $results); } self::$resources = $resources; @@ -289,16 +280,19 @@ class API extends \Util\Singleton */ public static function response($array) { + // Controllo sulla compatibilità dell'API if (!self::isCompatible()) { $array = [ 'status' => self::$status['incompatible']['code'], ]; } + // Agiunta dello status di default if (empty($array['status'])) { $array['status'] = self::$status['ok']['code']; } + // Aggiunta del messaggio in base allo status if (empty($array['message'])) { $codes = array_column(self::$status, 'code'); $messages = array_column(self::$status, 'message'); @@ -307,6 +301,7 @@ class API extends \Util\Singleton } $flags = JSON_FORCE_OBJECT; + // Beautify forzato dei risultati if (get('beautify') !== null) { $flags |= JSON_PRETTY_PRINT; } @@ -339,10 +334,15 @@ class API extends \Util\Singleton */ public static function getRequest() { - $request = (array) json_decode(file_get_contents('php://input'), true); + $request = []; - if ($_SERVER['REQUEST_METHOD'] == 'GET' && empty($request)) { - $request = Filter::getGET(); + if (self::isAPIRequest()) { + $request = (array) json_decode(file_get_contents('php://input'), true); + + // Fallback nel caso la richiesta sia effettuata da browser + if ($_SERVER['REQUEST_METHOD'] == 'GET' && empty($request)) { + $request = Filter::getGET(); + } } return $request; diff --git a/lib/classes/Auth.php b/lib/classes/Auth.php index 7057ab0c1..c7a340c5c 100644 --- a/lib/classes/Auth.php +++ b/lib/classes/Auth.php @@ -51,6 +51,7 @@ class Auth extends \Util\Singleton $database = Database::getConnection(); if ($database->isInstalled()) { + // Controllo dell'accesso da API if (API::isAPIRequest()) { $token = API::getRequest()['token']; @@ -81,6 +82,7 @@ class Auth extends \Util\Singleton { session_regenerate_id(); + // Controllo sulla disponibilità dell'accesso (brute-forcing non in corso) if (self::isBrute()) { return false; } @@ -105,15 +107,18 @@ class Auth extends \Util\Singleton $this->password_check($password, $user['password'], $user['id_utente']) && !empty($module) ) { + // Accesso completato $log['id_utente'] = $this->infos['id_utente']; $log['stato'] = self::$status['success']['code']; + // Salvataggio nella sessione $this->saveToSession(); } else { if (empty($module)) { $log['stato'] = self::$status['unauthorized']['code']; } + // Logout automatico $this->destory(); } } else { @@ -121,6 +126,7 @@ class Auth extends \Util\Singleton } } + // Salvataggio dello stato nella sessione if ($log['stato'] != self::$status['success']['code']) { foreach (self::$status as $key => $value) { if ($log['stato'] == $value['code']) { @@ -130,6 +136,7 @@ class Auth extends \Util\Singleton } } + // Salvataggio del tentativo nel database $database->insert('zz_logs', $log); return $this->isAuthenticated(); diff --git a/lib/classes/Database.php b/lib/classes/Database.php index 2030d6dfd..f5611ae1d 100644 --- a/lib/classes/Database.php +++ b/lib/classes/Database.php @@ -472,7 +472,7 @@ class Database extends Util\Singleton { if ( !is_string($table) || - (!empty($order) && !is_string($order) && !is_array($limit)) || + (!empty($order) && !is_string($order) && !is_array($order)) || (!empty($limit) && !is_string($limit) && !is_array($limit)) ) { throw new UnexpectedValueException(); @@ -541,7 +541,7 @@ class Database extends Util\Singleton $sync = array_unique((array) current($list)); if (!empty($field)) { - $results = array_column($this->fetchArray('SELECT '.$this->quote($field).' FROM '.$this->quote($table).' WHERE '.$this->whereStatement($conditions)), $field); + $results = array_column($this->select($table, $field, $conditions), $field); $detachs = array_unique(array_diff($results, $sync)); $this->detach($table, $conditions, [$field => $detachs]); @@ -573,7 +573,7 @@ class Database extends Util\Singleton $sync = array_unique((array) current($list)); if (!empty($field)) { - $results = array_column($this->fetchArray('SELECT '.$this->quote($field).' FROM '.$this->quote($table).' WHERE '.$this->whereStatement($conditions)), $field); + $results = array_column($this->select($table, $field, $conditions), $field); $inserts = array_unique(array_diff($sync, $results)); foreach ($inserts as $insert) { @@ -605,14 +605,9 @@ class Database extends Util\Singleton $sync = array_unique((array) current($list)); if (!empty($field) && !empty($sync)) { - $where = $this->whereStatement($conditions); + $conditions[$field] = $sync; - $in = []; - foreach ($sync as $value) { - $in[] = $this->prepare($value); - } - - $this->query('DELETE FROM '.$this->quote($table).' WHERE '.$where.(!empty($where) ? ' AND ' : '').$this->quote($field).' IN ('.implode(', ', $in).')'); + $this->query('DELETE FROM '.$this->quote($table).' WHERE '.$this->whereStatement($conditions)); } } @@ -653,26 +648,40 @@ class Database extends Util\Singleton { $result = []; - if (is_array($where)) { - foreach ($where as $key => $value) { - if (is_array($value)) { - $key = strtoupper($key); - $key = (in_array($key, ['AND', 'OR'])) ? $key : 'AND'; - + foreach ($where as $key => $value) { + // Query personalizzata + if (starts_with($key, '#')) { + $result[] = $this->prepareValue($key, $value); + } else { + // Ulteriori livelli di complessità + if (is_array($value) && in_array(strtoupper($key), ['AND', 'OR'])) { $result[] = '('.$this->whereStatement($value, $key == 'AND').')'; - } elseif (starts_with($value, '#') && ends_with($value, '#')) { - $result[] = substr($value, 1, -1); - } elseif (starts_with($value, '%') || ends_with($value, '%')) { + } + // Condizione IN + elseif (is_array($value)) { + if (!empty($value)) { + $in = []; + foreach ($value as $v) { + $in[] = $this->prepareValue($key, $v); + } + + $result[] = $this->quote($key).' IN ('.implode(',', $in).')'; + } + } + // Condizione LIKE + elseif (str_contains($value, '%') || str_contains($value, '_')) { $result[] = $this->quote($key).' LIKE '.$this->prepareValue($key, $value); - } elseif (str_contains($value, '|')) { + } + // Condizione BETWEEN + elseif (str_contains($value, '|')) { $pieces = explode('|', $value); $result[] = $this->quote($key).' BETWEEN '.$this->prepareValue($key, $pieces[0]).' AND '.$this->prepareValue($key, $pieces[1]); - } else { + } + // Condizione di uguaglianza + else { $result[] = $this->quote($key).' = '.$this->prepareValue($key, $value); } } - } else { - $result[] = $where; } $cond = !empty($and) ? 'AND' : 'OR'; diff --git a/modules/utenti/api/create.php b/modules/utenti/api/create.php index a95da3b68..ebf5fbda3 100644 --- a/modules/utenti/api/create.php +++ b/modules/utenti/api/create.php @@ -10,6 +10,7 @@ switch ($resource) { $tokens = $database->fetchArray('SELECT `token` FROM `zz_tokens` WHERE `enabled` = 1 AND `id_utente` = '.prepare($user['id_utente'])); if (empty($tokens)) { $token = secure_random_string(); + $database->insert('zz_tokens', [ 'id_utente' => $user['id_utente'], 'token' => $token, @@ -38,6 +39,7 @@ switch ($resource) { // Operazione di logout case 'logout': if (!empty($request['token']) && !empty($user)) { + // Cancellazione della chiave $database->query('DELETE FROM `zz_tokens` WHERE `token` = '.prepare($request['token']).' AND `id_utente` = '.prepare($user['id_utente'])); } else { $results = [ diff --git a/update/2_3.sql b/update/2_3.sql index a3ead6b28..430843d25 100644 --- a/update/2_3.sql +++ b/update/2_3.sql @@ -680,7 +680,7 @@ ALTER TABLE `zz_semaphores` ADD FOREIGN KEY (`id_utente`) REFERENCES `zz_users`( CREATE TABLE IF NOT EXISTS `zz_tokens` ( `id` int(11) NOT NULL AUTO_INCREMENT, `id_utente` int(11) NOT NULL, - `token` varchar(255) NOT NULL UNIQUE, + `token` varchar(255) NOT NULL, `descrizione` varchar(255), `enabled` boolean NOT NULL DEFAULT 1, PRIMARY KEY (`id`),