diff --git a/api/index.php b/api/index.php index cb77fc2b4..71345c139 100644 --- a/api/index.php +++ b/api/index.php @@ -2,7 +2,9 @@ function serverError() { - if (!empty(error_get_last())) { + $error = error_get_last(); + if ($error['type'] == E_ERROR) { + ob_end_clean(); die(API::error('serverError')); } } @@ -21,29 +23,35 @@ header('Access-Control-Allow-Origin: *'); // Attenzione: al momento l'API permette la lettura di tutte le tabelle rpesenti nel database (non limitate a quelle del progetto). -// Controlli sulla chiave di accesso try { - $api = new API(filter('token')); + // Controlli sulla chiave di accesso + $api = new API(); - $resource = filter('resource'); + // Lettura delle informazioni + $request = API::getRequest(); $method = $_SERVER['REQUEST_METHOD']; switch ($method) { case 'PUT': - $result = $api->update($resource); + $result = $api->update($request); break; case 'POST': - $result = $api->create($resource); + $result = $api->create($request); break; case 'GET': - if (!empty($resource)) { - $result = $api->retrieve($resource); + if (empty($request)) { + $request = Filter::getGET(); + unset($request['token']); + } + + if (!empty($request)) { + $result = $api->retrieve($request); } else { $result = API::response(API::getResources()['retrieve']); } break; case 'DELETE': - $result = $api->delete($resource); + $result = $api->delete($request); break; } } catch (InvalidArgumentException $e) { diff --git a/composer.json b/composer.json index 00853da82..1ce70ed3c 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "openstamanager/openstamanager", + "name": "devcode/openstamanager", "description": "Gestionale open source per assistenza tecnica e fatturazione", "version": "2.3.0", "license": "GPL-3.0", @@ -45,9 +45,6 @@ "lib/deprecated.php" ] }, - "scripts": { - "post-create-project-cmd": "yarn run release-OSM" - }, "config": { "sort-packages": true, "optimize-autoloader": true, diff --git a/core.php b/core.php index fd57efb81..a862940e7 100644 --- a/core.php +++ b/core.php @@ -124,7 +124,7 @@ if (!API::isAPIRequest()) { $dbo = Database::getConnection(); -$continue = $dbo->isInstalled() && Auth::check() && !Update::isUpdateAvailable(); +$continue = $dbo->isInstalled() && (Auth::check() || API::isAPIRequest()) && !Update::isUpdateAvailable(); // Controllo sulla presenza dei permessi di accesso basilari if (!$continue && slashes($_SERVER['SCRIPT_FILENAME']) != slashes(DOCROOT.'/index.php')) { diff --git a/lib/classes/API.php b/lib/classes/API.php index a297d6cb9..a00d7d18d 100644 --- a/lib/classes/API.php +++ b/lib/classes/API.php @@ -7,8 +7,10 @@ */ 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, @@ -32,7 +34,10 @@ class API extends \Util\Singleton ], ]; - public function __construct($token) + /** + * @throws InvalidArgumentException + */ + public function __construct() { $user = Auth::user(); @@ -41,18 +46,25 @@ class API extends \Util\Singleton } } - public function retrieve($resource) + /** + * Gestisce le richieste di informazioni riguardanti gli elementi esistenti. + * + * @param array $request + * + * @return string + */ + public function retrieve($request) { $table = ''; $select = '*'; // Selezione personalizzata - $display = filter('display'); + $display = $request['display']; $select = !empty($display) ? explode(',', substr($display, 1, -1)) : $select; $where = []; // Ricerca personalizzata - $filter = (array) filter('filter'); + $filter = (array) $request['filter']; foreach ($filter as $key => $value) { $value = substr($value, 1, -1); $result = []; @@ -75,20 +87,21 @@ class API extends \Util\Singleton $order = []; // Ordinamento personalizzato - $order_request = (array) filter('order'); + $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 = filter('upd'); - $created = filter('crd'); + $updated = $request['upd']; + $created = $request['crd']; $dbo = Database::getConnection(); $kind = 'retrieve'; $resources = self::getResources()[$kind]; + $resource = $request['resource']; if (!in_array($resource, $resources)) { $excluded = explode(',', Settings::get('Tabelle escluse per la sincronizzazione API automatica')); @@ -105,7 +118,7 @@ class API extends \Util\Singleton } // Paginazione dell'API - $page = (int) filter('page') ?: 0; + $page = (int) $request['page'] ?: 0; $length = Settings::get('Lunghezza pagine per API'); // Generazione automatica delle query @@ -129,24 +142,53 @@ class API extends \Util\Singleton return self::response($results); } - public function create($resource) + /** + * Gestisce le richieste di creazione nuovi elementi. + * + * @param array $request + * + * @return string + */ + public function create($request) { - return $this->fileRequest($resource, 'create'); + return $this->fileRequest($request, 'create'); } - public function update($resource) + /** + * Gestisce le richieste di aggiornamento di elementi esistenti. + * + * @param array $request + * + * @return string + */ + public function update($request) { - return $this->fileRequest($resource, 'update'); + return $this->fileRequest($request, 'update'); } - public function delete($resource) + /** + * Gestisce le richieste di eliminazione di elementi esistenti. + * + * @param array $request + * + * @return string + */ + public function delete($request) { - return $this->fileRequest($resource, 'delete'); + return $this->fileRequest($request, 'delete'); } - protected function fileRequest($resource, $kind) + /** + * 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) { $resources = self::getResources()[$kind]; + $resource = $request['resource']; if (!in_array($resource, array_keys($resources))) { return self::error('notFound'); @@ -157,10 +199,6 @@ class API extends \Util\Singleton $dbo->query('START TRANSACTION'); - // Variabili GET e POST - $post = Filter::getPOST(); - $get = Filter::getGET(); - $filename = DOCROOT.'/modules/'.$resources[$resource].'/api/'.$kind.'.php'; include $filename; @@ -169,17 +207,31 @@ class API extends \Util\Singleton 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 : end($keys); + http_response_code(self::$status[$error]['code']); + return self::response([ 'status' => self::$status[$error]['code'], 'message' => self::$status[$error]['message'], ]); } + /** + * Restituisce l'elenco delle risorse disponibili per l'API, suddivise per categoria. + * + * @return array + */ public static function getResources() { if (!is_array(self::$resources)) { @@ -217,6 +269,13 @@ class API extends \Util\Singleton return self::$resources; } + /** + * Formatta i contentuti della risposta secondo il formato JSON. + * + * @param array $array + * + * @return string + */ public static function response($array) { if (empty($array['status'])) { @@ -225,15 +284,38 @@ class API extends \Util\Singleton } $flags = JSON_FORCE_OBJECT; - if (filter('beautify') !== null) { + 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() + { + return (array) json_decode(file_get_contents('php://input'), true); + } } diff --git a/lib/classes/Auth.php b/lib/classes/Auth.php index e312e0d2b..03686b315 100644 --- a/lib/classes/Auth.php +++ b/lib/classes/Auth.php @@ -7,6 +7,7 @@ */ class Auth extends \Util\Singleton { + /** @var array Stati previsti dal sistema di autenticazione */ protected static $status = [ 'success' => [ 'code' => 1, @@ -26,12 +27,15 @@ class Auth extends \Util\Singleton ], ]; + /** @var array Opzioni di sicurezza relative all'hashing delle password */ protected static $passwordOptions = [ 'algorithm' => PASSWORD_BCRYPT, 'options' => [], ]; + /** @var array Informazioni riguardanti l'utente autenticato */ protected $infos; + /** @var string Nome del primo modulo su cui l'utente ha permessi di navigazione */ protected $first_module; protected function __construct() @@ -40,7 +44,8 @@ class Auth extends \Util\Singleton if ($database->isInstalled()) { if (API::isAPIRequest()) { - $token = filter('token'); + $token = API::getRequest()['token']; + $token = !empty($token) ? $token : get('token'); $id = $database->fetchArray('SELECT `id_utente` FROM `zz_tokens` WHERE `token` = '.prepare($token))[0]['id_utente']; } @@ -59,6 +64,14 @@ class Auth extends \Util\Singleton } } + /** + * Effettua un tentativo di accesso con le credenziali fornite. + * + * @param string $username + * @param string $password + * + * @return bool + */ public function attempt($username, $password) { session_regenerate_id(); @@ -113,6 +126,13 @@ class Auth extends \Util\Singleton return $this->isAuthenticated(); } + /** + * Controlla la corrispondeza delle password ed eventalmente effettua un rehashing. + * + * @param string $password + * @param string $hash + * @param int $user_id + */ protected function password_check($password, $hash, $user_id = null) { $result = false; @@ -140,6 +160,9 @@ class Auth extends \Util\Singleton return $result; } + /** + * Memorizza le informazioni riguardanti l'utente all'interno della sessione. + */ protected function saveToSession() { foreach ($this->infos as $key => $value) { @@ -153,6 +176,11 @@ class Auth extends \Util\Singleton } } + /** + * Identifica l'utente interessato dall'autenticazione. + * + * @param int $user_id + */ protected function identifyUser($user_id) { $database = Database::getConnection(); @@ -165,21 +193,39 @@ class Auth extends \Util\Singleton } } + /** + * Controlla se l'utente è autenticato. + * + * @return bool + */ public function isAuthenticated() { return !empty($this->infos); } + /** + * Controlla se l'utente appartiene al gruppo degli Amministratori. + * + * @return bool + */ public function isAdmin() { return $this->isAuthenticated() && !empty($this->infos['is_admin']); } + /** + * Restituisce le informazioni riguardanti l'utente autenticato. + * + * @return array + */ public function getUser() { return $this->infos; } + /** + * Distrugge le informazioni riguardanti l'utente autenticato, forzando il logout. + */ public function destory() { if ($this->isAuthenticated() || !empty($_SESSION['idutente'])) { @@ -197,6 +243,11 @@ class Auth extends \Util\Singleton } } + /** + * Restituisce il nome del primo modulo navigabile dall'utente autenticato. + * + * @return string + */ public function getFirstModule() { if (empty($this->first_module)) { @@ -225,6 +276,13 @@ class Auth extends \Util\Singleton return $this->first_module; } + /** + * Restituisce l'hashing della password per la relativa memorizzazione nel database. + * + * @param string $password + * + * @return string + */ public static function hashPassword($password) { return password_hash($password, self::$passwordOptions['algorithm'], self::$passwordOptions['options']); @@ -255,6 +313,11 @@ class Auth extends \Util\Singleton return self::getInstance()->getFirstModule(); } + /** + * Restituisce l'elenco degli stati del sistema di autenticazione. + * + * @return array + */ public static function getStatus() { return self::$status; diff --git a/modules/anagrafiche/actions.php b/modules/anagrafiche/actions.php index fd89388d5..1bd7aa66c 100644 --- a/modules/anagrafiche/actions.php +++ b/modules/anagrafiche/actions.php @@ -79,18 +79,11 @@ switch (post('op')) { // Se l'agente di default è stato elencato anche tra gli agenti secondari lo rimuovo if(!empty($post['idagente'])){ - $dbo->query('DELETE FROM an_anagrafiche_agenti WHERE idanagrafica='.prepare($id_record).' AND idagente='.prepare($post['idagente'])); + $dbo->query('DELETE FROM an_anagrafiche_agenti WHERE idanagrafica='.prepare($id_record).' AND idagente='.prepare($post['idagente'])); } // Aggiorno le tipologie di anagrafica - $dbo->query('DELETE FROM an_tipianagrafiche_anagrafiche WHERE idanagrafica='.prepare($id_record)); - - $tipi = array_unique($post['idtipoanagrafica']); - if (!empty($tipi)) { - foreach ($tipi as $idtipoanagrafica) { - $dbo->query('INSERT INTO an_tipianagrafiche_anagrafiche(idtipoanagrafica, idanagrafica) VALUES('.prepare($idtipoanagrafica).', '.prepare($id_record).')'); - } - } + $dbo->sync('an_tipianagrafiche_anagrafiche', ['idanagrafica' => $id_record], ['idtipoanagrafica' => (array) $post['idtipoanagrafica']]); // Verifico se esiste già l'associazione dell'anagrafica a conti del partitario $rs = $dbo->fetchArray('SELECT idconto_cliente, idconto_fornitore FROM an_anagrafiche WHERE idanagrafica='.prepare($id_record)); @@ -180,10 +173,7 @@ switch (post('op')) { } // Inserisco il rapporto dell'anagrafica (cliente, tecnico, ecc) - for ($t = 0; $t < count($idtipoanagrafica); ++$t) { - $query = 'INSERT INTO an_tipianagrafiche_anagrafiche(idanagrafica, idtipoanagrafica) VALUES ('.prepare($new_id).', '.prepare($idtipoanagrafica[$t]).')'; - $dbo->query($query); - } + $dbo->sync('an_tipianagrafiche_anagrafiche', ['idanagrafica' => $new_id], ['idtipoanagrafica' => (array) $idtipoanagrafica]); if (str_contains($tipoanagrafica_dst, 'Azienda')) { $dbo->query('UPDATE zz_settings SET valore='.prepare($new_id)." WHERE nome='Azienda predefinita'"); @@ -228,7 +218,7 @@ switch (post('op')) { break; case 'delete': - // Disattivo l'anagrafica, solo se questa non è l'azienda principale + // Se l'anagrafica non è l'azienda principale, la disattivo if (str_contains($records[0]['idtipianagrafica'], $id_azienda) === false) { $dbo->query('UPDATE an_anagrafiche SET deleted = 1 WHERE idanagrafica = '.prepare($id_record).Modules::getAdditionalsQuery($id_module)); diff --git a/modules/anagrafiche/api/create.php b/modules/anagrafiche/api/create.php index 2d5feb1c9..89011bb70 100644 --- a/modules/anagrafiche/api/create.php +++ b/modules/anagrafiche/api/create.php @@ -7,12 +7,12 @@ switch ($resource) { // Inserisco l'anagrafica $dbo->insert('an_anagrafiche', [ - 'ragione_sociale' => $post['data']['ragione_sociale'], + 'ragione_sociale' => $request['data']['ragione_sociale'], 'codice' => $codice, ]); // Inserisco il rapporto dell'anagrafica (cliente, tecnico, ecc) - $dbo->sync('an_tipianagrafiche_anagrafiche', ['idanagrafica' => $dbo->lastInsertedID()], ['idtipoanagrafica' => (array) $post['data']['tipi']]); + $dbo->sync('an_tipianagrafiche_anagrafiche', ['idanagrafica' => $dbo->lastInsertedID()], ['idtipoanagrafica' => (array) $request['data']['tipi']]); break; } diff --git a/modules/anagrafiche/api/delete.php b/modules/anagrafiche/api/delete.php new file mode 100644 index 000000000..ca0df7fee --- /dev/null +++ b/modules/anagrafiche/api/delete.php @@ -0,0 +1,20 @@ +fetchArray("SELECT idtipoanagrafica FROM an_tipianagrafiche WHERE descrizione='Azienda'")[0]['idtipoanagrafica']; + + $records = $dbo->fetchArray('SELECT an_tipianagrafiche.idtipoanagrafica FROM an_tipianagrafiche INNER JOIN an_tipianagrafiche_anagrafiche ON an_tipianagrafiche.idtipoanagrafica=an_tipianagrafiche_anagrafiche.idtipoanagrafica WHERE idanagrafica='.prepare($request['id'])); + $tipi = array_column($records, 'idtipoanagrafica'); + + // Se l'anagrafica non è l'azienda principale, la disattivo + if (!in_array($id_azienda, $tipi)) { + $dbo->query('UPDATE an_anagrafiche SET deleted = 1 WHERE idanagrafica = '.prepare($request['id'])); + } + + break; +} + +return [ + 'delete_anagrafica', +]; diff --git a/modules/anagrafiche/api/update.php b/modules/anagrafiche/api/update.php new file mode 100644 index 000000000..51d4d9bdb --- /dev/null +++ b/modules/anagrafiche/api/update.php @@ -0,0 +1,18 @@ +update('an_anagrafiche', [ + 'ragione_sociale' => $request['data']['ragione_sociale'], + ], ['idanagrafica' => $request['id']]); + + // Inserisco il rapporto dell'anagrafica (cliente, tecnico, ecc) + $dbo->sync('an_tipianagrafiche_anagrafiche', ['idanagrafica' => $request['id']], ['idtipoanagrafica' => (array) $request['data']['tipi']]); + + break; +} + +return [ + 'update_anagrafica', +]; diff --git a/modules/anagrafiche/bulk.php b/modules/anagrafiche/bulk.php index dda8062ef..64dfbaa39 100644 --- a/modules/anagrafiche/bulk.php +++ b/modules/anagrafiche/bulk.php @@ -10,7 +10,7 @@ switch (post('op')) { $records = $dbo->fetchArray('SELECT an_tipianagrafiche.idtipoanagrafica FROM an_tipianagrafiche INNER JOIN an_tipianagrafiche_anagrafiche ON an_tipianagrafiche.idtipoanagrafica=an_tipianagrafiche_anagrafiche.idtipoanagrafica WHERE idanagrafica='.prepare($id)); $tipi = array_column($records, 'idtipoanagrafica'); - // Disattivo l'anagrafica, solo se questa non è l'azienda principale + // Se l'anagrafica non è l'azienda principale, la disattivo if (!in_array($id_azienda, $tipi)) { $dbo->query('UPDATE an_anagrafiche SET deleted = 1 WHERE idanagrafica = '.prepare($id).Modules::getAdditionalsQuery($id_module)); }