loadComponent('FormProtection');` * * @return void */ public function initialize(): void { parent::initialize(); $this->loadComponent('Authentication.Authentication'); $this->loadComponent('Flash'); // recuperiamo l'entità dello user (se loggato): $this->logged_user = $this->Authentication->getIdentity(); // settiamo il locale come da preferenze utente (se specificato, altrimenti ci pensa il LocaleSelectorMiddleware): if (!empty($this->logged_user->language_id)) I18n::setLocale($this->fetchTable('Languages')->get($this->logged_user->language_id)->language_code); $this->set('locale', I18n::getLocale()); /* * Enable the following component for recommended CakePHP form protection settings. * see https://book.cakephp.org/4/en/controllers/components/form-protection.html */ //$this->loadComponent('FormProtection'); } public function beforeRender(EventInterface $event) { if ($this->getRequest()->is(['json', 'xml', 'csv'])) { $this->viewBuilder()->disableAutoLayout(); } $sys_admin_menu_items = null; $menu_sections = []; $menu_items = []; $logged_user_id = null; $logged_user_description = null; $logged_user_access_type_description = null; $logged_user_timezone = null; $logged_user_organisation_description = null; $logged_user_photo = Configure::read('App.defaultUserPhoto'); $user_notifications_count = 0; $user_notifications = []; // bisogna recuperare le capabilities utente (se esiste un utente loggato) if ($this->logged_user) { // menu SYSADMIN da mostrare solo ai SYSADMIN: $sys_admin_menu_items = [ [ 'title' => __('SYSADMIN'), 'icon' => 'lock', 'menu_items' => [ [ 'title' => __('Gestione competenze'), 'icon' => 'wrench', 'link' => '/capabilities', ], [ 'title' => __('Test notifiche'), 'icon' => 'bell', 'link' => '/notifications/testAll', 'confirm' => __('Procedendo verranno inviate notifiche a tutti gli utenti e le organizzaioni che possiedono uno o più recapiti abilitati alle notifiche! Sei sicuro che vuoi continuare ?'), 'method' => 'POST', ], ], ], ]; if (!$this->logged_user->sys_admin) $sys_admin_menu_items = null; $logged_user_id = $this->logged_user->id; $logged_user_description = $this->logged_user->name . ' ' . $this->logged_user->surname; $logged_user_access_type_description = $this->logged_user->sys_admin ? 'sys_admin' : ($this->logged_user->passepartout ? 'passepartout' : ''); $logged_user_timezone = $this->logged_user->timezone ?? Configure::read('App.defaultUserTimezone'); if (isset($this->logged_user->organisation_id)) { $logged_user_organisation_description = $this->fetchTable('Organisations')->get($this->logged_user->organisation_id, contain: ['Actors'])->actor->description; } if (!empty($this->logged_user->photo)) $logged_user_photo = Configure::read('App.userPhotoSrc') . "/" . $this->logged_user->photo; $user_notifications_count = $this->fetchTable('Notifications')->find()->where(['Notifications.user_id' => $this->logged_user->id, 'Notifications.read_by_user IS NULL'])->count(); $user_notifications = $this->fetchTable('Notifications') ->find() ->contain('NotificationTypes') ->where(['Notifications.user_id' => $this->logged_user->id, 'Notifications.read_by_user IS NULL']) ->order(['Notifications.created' => 'DESC']) ->all() ->reduce(function ($acc, $user_notification) { $notification_info = json_decode($user_notification->info); $body = strlen($notification_info->body) > 200 ? substr($notification_info->body, 0, 197) . '...' : $notification_info->body; $icon = $user_notification->notification_type->icon ?? 'bell'; $time_ago = (new DateTime($user_notification->created))->timeAgoInWords(); $acc[] = ['id' => $user_notification->id, 'icon' => $icon, 'title' => $notification_info->title, 'body' => $body, 'time_ago' => $time_ago]; return $acc; }, []); $user_capabilityIds_query = $this->logged_user->getCapabilityIds(true); $menu_sections_subquery = $this->fetchTable('MenuSections') ->find() ->select(['MenuSections.id']) ->innerJoinWith('Capabilities') ->where(['Capabilities.id IN' => $user_capabilityIds_query]) ->distinct(['MenuSections.id']); $menu_sections = $this->fetchTable('MenuSections') ->find() ->where(['MenuSections.id IN' => $menu_sections_subquery]) ->order(['MenuSections.id' => 'ASC']) ->toArray(); if (count($menu_sections) > 0) { // questo blocco controlla se nella request è presente il query param 'menu_section' // se è presente e rappresenta un id di menu_section valido, lo settiamo in sessione utente if ($this->getRequest()->getQuery('menu_section')) { $valid_user_menu_section = $this->fetchTable('MenuSections')->find() ->matching('Capabilities') ->where(['Capabilities.id IN' => $user_capabilityIds_query, 'MenuSections.id' => $this->getRequest()->getQuery('menu_section')]) ->first(); $valid_user_menu_section_id = $valid_user_menu_section ? $valid_user_menu_section->id : $menu_sections[0]->id; $this->getRequest()->getSession()->write('menu_section', $valid_user_menu_section_id); } $menu_section_id = $this->getRequest()->getSession()->read('menu_section') ?? $menu_sections[0]->id; $menu_items_subquery = $this->fetchTable('MenuItems') ->find() ->select(['MenuItems.id']) ->innerJoinWith('Capabilities') ->where(['Capabilities.id IN' => $user_capabilityIds_query]) ->where(['MenuItems.menu_item_id IS NULL']) ->where(['MenuItems.menu_section_id' => $menu_section_id]) ->distinct(['MenuItems.id']); $menu_items = $this->fetchTable('MenuItems') ->find() ->where(['MenuItems.id IN' => $menu_items_subquery]) ->order(['MenuItems.menu_order' => 'ASC']) ->toArray(); } if( $this->request->getPath() !== '/notifications/index' ) { $privacy = $this->fetchTable('Privacy') ->find()->orderBy(['created' => 'DESC']) ->first(); if(isset($privacy)) { $hasAcceptedPrivacy = $this->fetchTable('PrivacyUsers')->find()->where([ 'PrivacyUsers.user_id' => $this->logged_user->id, 'PrivacyUsers.privacy_id' => $privacy->id ])->first(); if( !isset($hasAcceptedPrivacy) && $this->request->getParam('controller') != 'Privacy' && $this->request->getParam('action') != 'accept_privacy' ) { return $this->redirect(['controller' => 'Privacy', 'action' => 'accept_privacy']); } } } } $this->set('menu_sections', $menu_sections); $this->set('menu_items', $menu_items); $this->set('sys_admin_menu_items', $sys_admin_menu_items); $this->set('logged_user_id', $logged_user_id); $this->set('logged_user_description', $logged_user_description); $this->set('logged_user_access_type_description', $logged_user_access_type_description); $this->set('logged_user_timezone', $logged_user_timezone); $this->set('logged_user_organisation_description', $logged_user_organisation_description); $this->set('logged_user_photo', $logged_user_photo); $this->set('user_notifications_count', $user_notifications_count); $this->set('user_notifications', $user_notifications); } /** * gen_random_code * * @param Int $length * @return String */ protected function gen_random_code(Int $length): String { $characters = "abcdefghijklmnopqrstuvwxyzABCDERFGHIJKLMNOPQRSTUVWXYZ0123456789"; $randomString = ""; for ($i = 0; $i < $length; $i++) { $randomString .= $characters[mt_rand(0, strlen($characters)-1)]; } return $randomString; } /** * sendNotifications * * @param Int|Null $co_id * @param String $notification_code * @param Array $options * @return void */ protected function sendNotifications(Int|Null $co_id, String $notification_code, Array $options) { NotificationsHandler::dispatch($co_id, $notification_code, $options, null, $this->logged_user->id ?? null); } /** * applyFilters * * @param Query $query * @return Query */ protected function applyFilters(Query $query): Query { // recuperiamo i parametri per costruire la chiave per memorizzare i filtri in sessione. // i filtri sono "scoped" per utente e per route. Quindi per ogni utente possiamo memorizzare // filtri relativi a più liste. $logged_user_id = $this->logged_user->id; $controller = $this->request->getParam('controller'); $action = $this->request->getParam('action'); $filter_session_key = "filters-$logged_user_id-$controller-$action"; // recuperiamo i filtri o dal POST data (significa che sono stati appena applicati) // o dalla sessione (se ci sono): $filters = $this->request->is('post') ? $this->request->getData('filters') : $this->request->getSession()->read($filter_session_key); \Cake\Log\Log::write("debug", 'applyFilters - ' . ($this->request->is('post') ? 'POST received' : 'SESSION retrieved') . ' filters: ' . json_encode($filters)); // ripuliamo i filtri eliminando prima i valori non validi per ciascun filtro // e successivamente eliminiamo direttamente i fitri che non hanno valori validi (rimasti): $filters = collection($filters ?? []) ->map(function ($filter_values, $filter_id) { return collection($filter_values) ->filter(function ($filter_value) { // il controllo con === "0" serve per evitare che il sanitiser elimini // il valore "0" per filtri di tipo "bool" return !empty($filter_value) || $filter_value === "0"; }) ->reduce(function ($filter_values, $filter_value) { $filter_values[] = $filter_value; return $filter_values; }, []); }) ->filter(function ($filter_values, $filter_id) { return !empty($filter_values); }) ->toArray(); \Cake\Log\Log::write("debug", 'applyFilters - SANITISED filters: ' . json_encode($filters)); // se non si è riusciti a recuperare filtri // o è stato richesto un reset dei filtri tramite POST // o non ne sono rimasti a seguito della sanitizzazione, // cancelliamo comunque la sessione alla chiave $filter_session_key // ed usciamo senza applicare nulla alla query: if (empty($filters)) { \Cake\Log\Log::write("debug", "applyFilters - DELETING DATA SESSION with key '$filter_session_key'"); $this->request->getSession()->delete($filter_session_key); \Cake\Log\Log::write("debug", 'applyFilters - NO filters to appply!'); return $query; } // se abbiamo filtri validi e li abbiamo appena ricevuti (POST), li salviamo in sessione: if ($this->request->is('post')) { \Cake\Log\Log::write("debug", "applyFilters - WRITING DATA SESSION with key '$filter_session_key': " . json_encode($filters)); $this->request->getSession()->write($filter_session_key, $filters); } // array delle possibili associations da gestire nella query con ->leftJoinWith(). // verrà popolato durante il processo di conversione da filtri a conditions: $associations = []; // array delle conditions creato a partire dai filters ricevuti, da gestire nella query con ->where() $conditions = collection($filters) // qui trasformiamo i filtri in conditions in base alla tipologia di filtro ricevuto: ->map(function ($filter_values, $filter_id) use (&$associations) { // recuperiamo le info sul filtro dalla table 'filters' avendo ricevuto l'id del filtro: $filter = $this->fetchTable('Filters')->get((Int)explode("filter_id_", $filter_id)[1]); // creazione della condizione in base al tipo di filtro: switch ($filter->filter_type) { case 'class': case 'bool': $condition = [$filter->search . ' IN' => $filter_values]; break; case 'text': $condition = collection($filter_values) ->map(function ($filter_value) use ($filter) { return [$filter->search . ' LIKE' => $filter_value]; }) ->reduce(function ($conditions, $condition) { $conditions['OR'][] = $condition; return $conditions; }, ['OR' => []]); break; case 'date': $condition = collection($filter_values) ->map(function ($filter_value) use ($filter) { $date_array = explode(" - ", $filter_value); $from_date = $date_array[0]; $to_date = $date_array[1]; return [$filter->search . ' >=' => $from_date, $filter->search . ' <=' => $to_date]; }) ->reduce(function ($conditions, $condition) { $conditions['OR'][] = $condition; return $conditions; }, ['OR' => []]); break; default: break; } // se il filtro è stato gestito e richiede una association, la inseriamo in apposito array: if (!empty($condition) && !empty($filter->associations)) $associations[] = $filter->associations; // se il filtro non è stato gestito, restituiamo null; altrimenti restituiamo la condition return $condition ?? null; }) // qui eliminiamo eventuali conditions null (generate da filtri non (al momento) gestiti: ->filter(function ($condition) { return !empty($condition); }) // infine restituiamo l'array_values delle conditions ottenute: ->reduce(function ($conditions, $condition) { $conditions[] = $condition; return $conditions; }, []); \Cake\Log\Log::write("debug", 'applyFilters - conditions applied: ' . json_encode($conditions)); if (count($conditions) > 0) $query = $query->where($conditions); \Cake\Log\Log::write("debug", 'applyFilters - associations applied: ' . json_encode($associations)); foreach ($associations as $association) { $query = $query->leftJoinWith($association); } if (count($associations) > 0) { $main_table = $query->getRepository()->getAlias(); $query = $query->distinct(["$main_table.id"]); \Cake\Log\Log::write("debug", 'applyFilters - distinct applied: ' . "$main_table.id"); } return $query; } /** * Respond with JSON * * @param int $statusCode HTTP status code * @param null|object|array|string|null $responseBody Content of the response * @param string $contentType Content type of the response, default "application/json" * @return Response */ public function respond(int $statusCode = 200, null|object|array|string $responseBody = null, string $contentType = "application/json") : Response { $responseBody = is_object($responseBody) || is_array($responseBody) ? $responseBody : ['result' => $responseBody]; if ($paging = $this->request->getAttribute('paging')) { $responseBody['pagination'] = $paging; } try { $encodedBody = json_encode($responseBody, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } catch (Exception $e) { return $this->getResponse() ->withStatus(500) ->withStringBody(json_encode(['error' => 'Failed to encode JSON response'])) ->withType('application/json'); } $response = $this->getResponse() ->withStatus($statusCode) ->withType($contentType) ->withStringBody($encodedBody); return $response; } }