Proyectos de Subversion LeadersLinked - Backend

Rev

Rev 17267 | Rev 17269 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |

<?php
declare(strict_types=1);

namespace LeadersLinked\Controller;

use Laminas\Db\Adapter\AdapterInterface;
use Laminas\Log\LoggerInterface;
use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\ViewModel;
use Laminas\View\Model\JsonModel;
use Laminas\Hydrator\ArraySerializableHydrator;
use Laminas\Db\ResultSet\HydratingResultSet;
use Laminas\Paginator\Adapter\DbSelect;
use Laminas\Paginator\Paginator;
use Laminas\Mvc\I18n\Translator;
use LeadersLinked\Mapper\MicrolearningTopicMapper;
use LeadersLinked\Mapper\MicrolearningUserMapper;
use LeadersLinked\Mapper\UserMapper;
use LeadersLinked\Mapper\QueryMapper;
use LeadersLinked\Mapper\ApplicationMapper;
use LeadersLinked\Mapper\PushMapper;
use LeadersLinked\Mapper\PushTemplateMapper;
use LeadersLinked\Mapper\DeviceHistoryMapper;
use LeadersLinked\Mapper\ApplicationVariantMapper;
use LeadersLinked\Mapper\NotificationMapper;
use LeadersLinked\Mapper\NetworkMapper;
use LeadersLinked\Mapper\CompanyMapper;
use LeadersLinked\Mapper\CompanyFollowerMapper;
use LeadersLinked\Mapper\ConnectionMapper;
use LeadersLinked\Mapper\UserPasswordMapper;
use LeadersLinked\Mapper\MicrolearningExtendUserMapper;
use LeadersLinked\Mapper\MicrolearningExtendUserCompanyMapper;
use LeadersLinked\Mapper\MicrolearningExtendUserFunctionMapper;
use LeadersLinked\Mapper\MicrolearningExtendUserGroupMapper;
use LeadersLinked\Mapper\MicrolearningExtendUserInstitutionMapper;
use LeadersLinked\Mapper\MicrolearningExtendUserProgramMapper;
use LeadersLinked\Mapper\MicrolearningExtendUserPartnerMapper;
use LeadersLinked\Mapper\MicrolearningExtendUserSectorMapper;
use LeadersLinked\Mapper\MicrolearningExtendUserStudentTypeMapper;
use LeadersLinked\Mapper\MicrolearningExtendUserCountryMapper;
use LeadersLinked\Model\MicrolearningUser;
use LeadersLinked\Model\Push;
use LeadersLinked\Model\Application;
use LeadersLinked\Model\Notification;
use LeadersLinked\Model\Network;
use LeadersLinked\Model\Connection;
use LeadersLinked\Model\CompanyFollower;
use LeadersLinked\Model\User;
use LeadersLinked\Model\UserType;
use LeadersLinked\Model\UserPassword;
use LeadersLinked\Model\MicrolearningExtendUser;
use LeadersLinked\Model\MicrolearningExtendUserCompany;
use LeadersLinked\Model\MicrolearningExtendUserFunction;
use LeadersLinked\Model\MicrolearningExtendUserGroup;
use LeadersLinked\Model\MicrolearningExtendUserInstitution;
use LeadersLinked\Model\MicrolearningExtendUserProgram;
use LeadersLinked\Model\MicrolearningExtendUserPartner;
use LeadersLinked\Model\MicrolearningExtendUserSector;
use LeadersLinked\Model\MicrolearningExtendUserStudentType;
use LeadersLinked\Model\MicrolearningExtendUserCountry;
use LeadersLinked\Form\Microlearning\TopicUserForm;
use LeadersLinked\Form\Microlearning\PushMicrolearningNotificationForm;
use LeadersLinked\Form\Microlearning\TopicCustomerUploadForm;
use LeadersLinked\Library\Functions;
use LeadersLinked\Cache\CacheInterface;
use LeadersLinked\Cache\CacheImpl;
use PhpOffice\PhpSpreadsheet\IOFactory;
use LeadersLinked\Mapper\MicrolearningTopicUserMapper;
use LeadersLinked\Model\MicrolearningTopicUser;
use LeadersLinked\Library\Storage;

class MicrolearningAccessForStudentsController extends AbstractActionController
{
    /**
     * @var AdapterInterface
     */
    private $adapter;
    
    /**
     * @var CacheInterface
     */
    private $cache;
    
    /**
     * @var LoggerInterface
     */
    private $logger;
    
    /**
     * @var array
     */
    private $config;
    
    /**
     * @var Translator
     */
    private $translator;
    
    /**
     * @param AdapterInterface $adapter
     * @param CacheInterface $cache
     * @param LoggerInterface $logger
     * @param array $config
     * @param Translator $translator
     */
    public function __construct($adapter, $cache, $logger, $config, $translator)
    {
        $this->adapter = $adapter;
        $this->cache = $cache;
        $this->logger = $logger;
        $this->config = $config;
        $this->translator = $translator;
    }
    
    public function indexAction()
    {
        try {
            $currentUserPlugin = $this->plugin('currentUserPlugin');
            $currentUser = $currentUserPlugin->getUser();
            $currentCompany = $currentUserPlugin->getCompany();
            
            $request = $this->getRequest();
            
            if($request->isGet())
            {
                $headers  = $request->getHeaders();
                $isJson = false;
                
                if($headers->has('Accept')) {
                    $accept = $headers->get('Accept');
                    $prioritized = $accept->getPrioritized();
                    
                    foreach($prioritized as $key => $value) {
                        $raw = trim($value->getRaw());
                        if(!$isJson) $isJson = strpos($raw, 'json');
                    }
                }
                
                if($isJson) {
                    try {
                        $topic_uuid     = Functions::sanitizeFilterString($request->getQuery('topic_uuid'));
                        
                        $data = [
                            'link_upload' => '',
                            'items' => [] ,
                            'total' => 0,
                        ];      
                        
                        if(!$topic_uuid) {
                            return new JsonModel([
                                'success' => true,
                                'data' => $data
                            ]);     
                        }              
                        
                        $topicMapper = MicrolearningTopicMapper::getInstance($this->adapter);
                        $topic = $topicMapper->fetchOneByUuid($topic_uuid);
                        
                        if(!$topic) {
                            return new JsonModel([
                                'success' => true,
                                'data' => 'ERROR_TOPIC_NOT_FOUND'
                            ]);
                        }
                        
                        if($topic->company_id != $currentCompany->id) {
                            return new JsonModel([
                                'success' => true,
                                'data' => 'ERROR_UNAUTHORIZED'
                            ]);
                        }

                        $data['link_upload'] = $this->url()->fromRoute('microlearning/access-for-students/upload',['topic_uuid' => $topic->uuid]);
                        $data['link_notification'] = $this->url()->fromRoute('microlearning/access-for-students/notification',['topic_uuid' => $topic->uuid]);
                        
                        $search = $this->params()->fromQuery('search', []);
                        $search = empty($search['value']) ? '' :  Functions::sanitizeFilterString($search['value']);
                        
                        $page               = intval($this->params()->fromQuery('start', 1), 10);
                        $records_x_page     = intval($this->params()->fromQuery('length', 10), 10);
                        $order =  $this->params()->fromQuery('order', []);
                        $order_field        = empty($order[0]['column']) ? 99 :  intval($order[0]['column'], 10);
                        $order_direction    = empty($order[0]['dir']) ? 'ASC' : strtoupper(Functions::sanitizeFilterString($order[0]['dir']));
                        
                        $fields =  ['uuid', 'first_name', 'last_name', 'email'];
                        $order_field = isset($fields[$order_field]) ? $fields[$order_field] : 'first_name';
                        
                        if(!in_array($order_direction, ['ASC', 'DESC'])) {
                            $order_direction = 'ASC';
                        }
                        
                        $acl = $this->getEvent()->getViewModel()->getVariable('acl');
                        $allowRevoke = $acl->isAllowed($currentUser->usertype_id, 'microlearning/access-for-students/revoke');
                        $allowUnlimit = $acl->isAllowed($currentUser->usertype_id, 'microlearning/access-for-students/unlimit');
                        $allowCancel = $acl->isAllowed($currentUser->usertype_id, 'microlearning/access-for-students/cancel');
                        $allowReactive = $acl->isAllowed($currentUser->usertype_id, 'microlearning/access-for-students/reactive');
                        
                        $queryMapper = QueryMapper::getInstance($this->adapter);
                        $sql = $queryMapper->getSql();
                        $select = $sql->select();
                        $select->columns(['access', 'paid_from', 'paid_to', 'added_on', 'updated_on']);
                        $select->from(['tb1' => MicrolearningTopicUserMapper::_TABLE] );
                        $select->join(['tb2' => UserMapper::_TABLE], 'tb1.user_id = tb2.id', ['uuid', 'first_name', 'last_name', 'email']);
                        $select->where->equalTo('tb1.company_id', $topic->company_id);
                        $select->where->equalTo('tb1.topic_id', $topic->id);
                        
                        if($search) {
                            $select->where->nest()
                            ->like('first_name', '%' . $search . '%')
                            ->or->like('last_name', '%' . $search . '%')
                            ->or->like('email', '%' . $search . '%')
                            ->unnest();
                            
                        }
                        
                        
                        $select->order($order_field . ' ' . $order_direction);
                        
                        $hydrator   = new ArraySerializableHydrator();
                        $resultset  = new HydratingResultSet($hydrator);
                        
                        $adapter = new DbSelect($select, $sql, $resultset);
                        $paginator = new Paginator($adapter);
                        $paginator->setItemCountPerPage($records_x_page);
                        $paginator->setCurrentPageNumber($page);
                        
                        
                        $items = [ ];
                        $records = $paginator->getCurrentItems();
                        foreach($records as $record)
                        {
                            $params = [
                                'topic_uuid' => $topic->uuid,
                                'user_uuid' => $record['uuid']
                            ];
                            
                            $actions = [];
                            
                            switch($record['access'])
                            {
                                case MicrolearningTopicUser::ACCESS_UNLIMITED :
                                    $actions['link_revoke'] = $allowRevoke ? $this->url()->fromRoute('microlearning/access-for-students/revoke', $params) : '';
                                    $details['access'] = 'LABEL_UNLIMIT';
                                    break;
                                    
                                case MicrolearningTopicUser::ACCESS_REVOKE :
                                    $actions['link_unlimit'] = $allowUnlimit ? $this->url()->fromRoute('microlearning/access-for-students/unlimit', $params) : '';
                                    $details['access'] = 'LABEL_REVOKED';
                                    break;
                                    
                                case MicrolearningTopicUser::ACCESS_PAY_PERIOD :
                                    $actions['link_cancel'] = $allowCancel ? $this->url()->fromRoute('microlearning/access-for-students/cancel', $params) : '';
                                    
                                    $dt_paid_from = \DateTime::createFromFormat('Y-m-d', $record['paid_from']);
                                    $dt_paid_to = \DateTime::createFromFormat('Y-m-d', $record['paid_to']);
                                    
                                    $details['access'] = 'LABEL_PAY_PERIOD';
                                    $details['paid_from'] = $dt_paid_from->format('d/m/Y');
                                    $details['paid_to'] = $dt_paid_to->format('d/m/Y');
                                    break;
                                    
                                case MicrolearningTopicUser::ACCESS_SUPENDED :
                                    $dt_paid_from = \DateTime::createFromFormat('Y-m-d', $record['paid_from']);
                                    $dt_paid_to = \DateTime::createFromFormat('Y-m-d', $record['paid_to']);
                                    
                                    $details['access'] = 'LABEL_SUSPENDED';
                                    $details['paid_from'] = $dt_paid_from->format('d/m/Y');
                                    $details['paid_to'] = $dt_paid_to->format('d/m/Y');
                                    break;
                                    
                                case MicrolearningTopicUser::ACCESS_CANCELLED :
                                    
                                    $date = date('Y-m-d');
                                    if($allowCancel && $record['paid_from'] <= $date && $record['paid_to'] >= $date) {
                                        $actions['link_reactive'] = $allowReactive ? $this->url()->fromRoute('microlearning/access-for-students/reactive', $params) : '';
                                    }
                                    
                                    $dt_paid_from = \DateTime::createFromFormat('Y-m-d', $record['paid_from']);
                                    $dt_paid_to = \DateTime::createFromFormat('Y-m-d', $record['paid_to']);
                                    
                                    $details['access'] = 'LABEL_CANCELLED';
                                    $details['paid_from'] = $dt_paid_from->format('d/m/Y');
                                    $details['paid_to'] = $dt_paid_to->format('d/m/Y');
                                    break;
                                    
                            }
                            
                            $dt_added_on = \DateTime::createFromFormat('Y-m-d H:i:s', $record['added_on']);
                            $details['added_on'] = $dt_added_on->format('d/m/Y h:i a');
                            
                            $dt_updated_on = \DateTime::createFromFormat('Y-m-d H:i:s', $record['updated_on']);
                            $details['updated_on'] = $dt_updated_on->format('d/m/Y h:i a');
                            
                            $item = [
                                'uuid' => $record['uuid'],
                                'first_name' => $record['first_name'],
                                'last_name' => $record['last_name'],
                                'email' => $record['email'],
                                'details' => $details,
                                'actions' => $actions
                            ];

                            array_push($items, $item);
                        }
                        
                        $data['items'] = $items;
                        $data['total'] = $paginator->getTotalItemCount();
                        
                        return new JsonModel([
                            'success' => true,
                            'data' => $data
                        ]);
                    } catch (\Exception $e) {
                        $this->logger->err('Error in indexAction: ' . $e->getMessage());
                        return new JsonModel([
                            'success' => false,
                            'data' => 'An error occurred while processing your request'
                        ]);
                    }
                } else {
                    $topicMapper = MicrolearningTopicMapper::getInstance($this->adapter);
                    $topics = $topicMapper->fetchAllByCompanyId($currentCompany->id);
                    
                    if($topics) {
                        $topic_id = $topics[0]->id;
                    }  else {
                        $topic_id = 0;
                    }
                    
                    $form = new TopicUserForm($this->adapter, $currentCompany->id, $topic_id);
                    $formPushNotification = new PushMicrolearningNotificationForm($this->adapter, $currentCompany->id);
                    $formTopicCustomer = new TopicCustomerUploadForm();
                    
                    $this->layout()->setTemplate('layout/layout-backend');
                    $viewModel = new ViewModel();
                    $viewModel->setTemplate('leaders-linked/microlearning-access-for-students/index.phtml');
                    $viewModel->setVariables([
                        'form' => $form,
                        'formPushNotification' => $formPushNotification,
                        'formTopicCustomer' => $formTopicCustomer
                    ]);
                    
                    return $viewModel ;
                }
                
            } else {
                return new JsonModel([
                    'success' => false,
                    'data' => 'ERROR_METHOD_NOT_ALLOWED'
                ]);
            }
        } catch (\Exception $e) {
            $this->logger->err('Fatal error in indexAction: ' . $e->getMessage());
            return new JsonModel([
                'success' => false,
                'data' => $e->getMessage()
            ]);
        }
    }

    /**
     * Revokes unlimited access for a user to a specific microlearning topic
     * 
     * This action handles the revocation of unlimited access privileges for a user.
     * It checks various conditions before proceeding with the revocation:
     * - Validates current user and company
     * - Verifies topic and user existence
     * - Ensures proper authorization
     * - Confirms the user has unlimited access before revoking
     * 
     * @return JsonModel Returns a JSON response indicating success or failure
     */
    public function revokeAction()
    {
        // Get the current request object
        $request = $this->getRequest();
        
        // Get current user and company from plugin
        $currentUserPlugin = $this->plugin('currentUserPlugin');
        $currentUser    = $currentUserPlugin->getUser();
        $currentCompany = $currentUserPlugin->getCompany();
        
        // Get topic and user UUIDs from route parameters
        $request    = $this->getRequest();
        $topic_uuid     = $this->params()->fromRoute('topic_uuid');
        $user_uuid   = $this->params()->fromRoute('user_uuid');

        // Validate current user exists
        if(!$currentUser) {
            return new JsonModel([
                'success'   => false,
                'data'   => 'ERROR_UNAUTHORIZED'
            ]);
        }

        // Validate current company exists
        if(!$currentCompany) {
            return new JsonModel([
                'success'   => false,
                'data'   => 'ERROR_UNAUTHORIZED'
            ]);
        }

        // Validate topic UUID was provided
        if(!$topic_uuid) {
            return new JsonModel([
                'success'   => false,
                'data'   => 'ERROR_TOPIC_NOT_FOUND'
            ]);
        }

        // Validate user UUID was provided
        if(!$user_uuid) {
            return new JsonModel([
                'success'   => false,
                'data'   => 'ERROR_USER_NOT_FOUND'
            ]);
        }
        
        // Fetch topic by UUID and validate it exists
        $topicMapper = MicrolearningTopicMapper::getInstance($this->adapter);
        $topic = $topicMapper->fetchOneByUuid($topic_uuid);
        if(!$topic) {
            return new JsonModel([
                'success'   => false,
                'data'   => 'ERROR_TOPIC_NOT_FOUND'
            ]);
        }
        
        // Validate topic belongs to current company
        if($topic->company_id != $currentCompany->id) {
            return new JsonModel([
                'success'   => false,
                'data'   => 'ERROR_UNAUTHORIZED'
            ]);
        }
        
        // Fetch user by UUID and validate it exists
        $userMapper = UserMapper::getInstance($this->adapter);
        $user = $userMapper->fetchOneByUuid($user_uuid);
        
        if(!$user) {
            return new JsonModel([
                'success'   => false,
                'data'   => 'ERROR_USER_NOT_FOUND'
            ]);
        }
        
        // Fetch topic-user relationship and validate it exists
        $topicUserMapper = MicrolearningTopicUserMapper::getInstance($this->adapter);
        $topicUser = $topicUserMapper->fetchOneByUserIdAndTopicId($user->id, $topic->id);        
        if(!$topicUser) {
            return new JsonModel([
                'success'   => false,
                'data'   => 'ERROR_UNAUTHORIZED'
            ]);
        }
        
        // Process revocation only for POST requests
        if($request->isPost()) {
            // Validate user has unlimited access before revoking
            if($topicUser->access != MicrolearningTopicUser::ACCESS_UNLIMITED) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_USER_ACCESS_CANNT_BE_REVOKE'
                ]);
            }
            
            // Update access to revoked status
            $topicUser->access = MicrolearningTopicUser::ACCESS_REVOKE;
            if($topicUserMapper->update($topicUser)) { 
                
                // Refresh topic user data after update
                $topicUser = $topicUserMapper->fetchOne($topicUser->id);
                if($topicUser) {
                    // Update or create microlearning user record
                    $microlearningUserMapper = MicrolearningUserMapper::getInstance($this->adapter);
                    $microlearningUser = $microlearningUserMapper->fetchOneByUserIdAndCompanyId($topicUser->user_id, $topicUser->company_id);
                    if($microlearningUser) {
                        // Update existing microlearning user
                        $microlearningUser->updated_on = $topicUser->updated_on;
                        $microlearningUserMapper->update($microlearningUser);
                        
                    } else {
                        // Create new microlearning user
                        $microlearningUser = new MicrolearningUser();
                        $microlearningUser->company_id = $topicUser->company_id;
                        $microlearningUser->user_id = $topicUser->user_id;
                        $microlearningUser->added_on = $topicUser->added_on;
                        $microlearningUser->updated_on = $topicUser->updated_on;
                        
                        $microlearningUserMapper->insert($microlearningUser);
                    }
                }
                
                return new JsonModel([
                    'success' => true,
                    'data' => 'LABEL_USER_ACCESS_HAS_BEEN_REVOKE'
                ]);  
                
            } else {
                
                return new JsonModel([
                    'success'   => false,
                    'data'      => $topicUserMapper->getError()
                ]);
            }
        }
        
        // Return error for non-POST requests
        return new JsonModel([
            'success' => false,
            'data' => 'ERROR_METHOD_NOT_ALLOWED'
        ]);
    }
    
    public function unlimitAction()
    {
        try {
            $request    = $this->getRequest();

            $currentUserPlugin = $this->plugin('currentUserPlugin');
            $currentUser    = $currentUserPlugin->getUser();
            $currentCompany = $currentUserPlugin->getCompany();

            $topic_uuid     = $this->params()->fromRoute('topic_uuid');
            $user_uuid   = $this->params()->fromRoute('user_uuid');

            if(!$currentUser) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_UNAUTHORIZED'
                ]);
            }

            if(!$currentCompany) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_UNAUTHORIZED'
                ]);
            }

            if(!$topic_uuid) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_TOPIC_NOT_FOUND'
                ]);
            }

            if(!$user_uuid) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_USER_NOT_FOUND'
                ]);
            }

            if(!$request->isPost()) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_METHOD_NOT_ALLOWED'
                ]);
            }

            $topicMapper = MicrolearningTopicMapper::getInstance($this->adapter);
            $topic = $topicMapper->fetchOneByUuid($topic_uuid);
            if(!$topic) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_TOPIC_NOT_FOUND'
                ]);
            }   
        
            if($topic->company_id != $currentCompany->id) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_UNAUTHORIZED'
                ]);
            }
            
            $userMapper = UserMapper::getInstance($this->adapter);
            $user = $userMapper->fetchOneByUuid($user_uuid);
            
            if(!$user) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_USER_NOT_FOUND'
                ]);
            }
            
            $topicUserMapper = MicrolearningTopicUserMapper::getInstance($this->adapter);
            $topicUser = $topicUserMapper->fetchOneByUserIdAndTopicId($user->id, $topic->id);
            if(!$topicUser) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_UNAUTHORIZED'
                ]);
            }
        
            if($topicUser->access != MicrolearningTopicUser::ACCESS_REVOKE) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_USER_ACCESS_CANNT_BE_UNLIMIT'
                ]);
            }

            $topicUser->access = MicrolearningTopicUser::ACCESS_UNLIMITED;
            $topicUser->updated_on = date('Y-m-d H:i:s');
            
            try {
                $topicUserMapper->update($topicUser);

                $microlearningUserMapper = MicrolearningUserMapper::getInstance($this->adapter);
                $microlearningUser = $microlearningUserMapper->fetchOneByUserIdAndCompanyId($topicUser->user_id, $topicUser->company_id);

                if(!$microlearningUser) {
                    $microlearningUser = new MicrolearningUser();
                    $microlearningUser->company_id = $topicUser->company_id;
                    $microlearningUser->user_id = $topicUser->user_id;
                    $microlearningUser->added_on = $topicUser->added_on;
                    $microlearningUser->updated_on = $topicUser->updated_on;
                    $microlearningUserMapper->insert($microlearningUser);
                } else {
                    $microlearningUser->updated_on = $topicUser->updated_on;
                    $microlearningUserMapper->update($microlearningUser);
                }
            } catch (\Exception $e) {
                $this->logger->err('Error updating topic user: ' . $e->getMessage());
                return new JsonModel([
                    'success'   => false,
                    'data'      => 'ERROR_UPDATING_TOPIC_USER'
                ]);
            }
            
            return new JsonModel([
                'success' => true,
                'data' => 'LABEL_USER_ACCESS_HAS_BEEN_UNLIMITED'
            ]);
        } catch (\Exception $e) {
            $this->logger->err('Fatal error in unlimitAction: ' . $e->getMessage());
            return new JsonModel([
                'success' => false,
                'data' => $e->getMessage()
            ]);
        }
    }
    
    public function uploadAction()
    {
        try {
            $request = $this->getRequest();
            
            $currentUserPlugin = $this->plugin('currentUserPlugin');
            $currentNetworkPlugin = $this->plugin('currentNetworkPlugin');

            $currentUser    = $currentUserPlugin->getUser();
            $currentCompany = $currentUserPlugin->getCompany();
            $currentNetwork = $currentNetworkPlugin->getNetwork();
            
            $topic_uuid     = $this->params()->fromRoute('topic_uuid');

            if(!$currentUser) {
                $this->logger->err('Upload failed: Current user not found');
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_USER_NOT_FOUND'
                ]);
            }

            if(!$currentCompany) {
                $this->logger->err('Upload failed: Current company not found');
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_COMPANY_NOT_FOUND'
                ]);
            }

            if(!$currentNetwork) {
                $this->logger->err('Upload failed: Current network not found');
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_NETWORK_NOT_FOUND'
                ]);
            }

            if(!$topic_uuid) {
                $this->logger->err('Upload failed: Topic UUID not provided');
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_TOPIC_NOT_FOUND'
                ]);
            }

            if(!$request->isPost()) {
                $this->logger->err('Upload failed: Request method not POST');
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_METHOD_NOT_ALLOWED'
                ]);
            }
            
            $topicMapper = MicrolearningTopicMapper::getInstance($this->adapter);
            $topic = $topicMapper->fetchOneByUuid($topic_uuid);
            
            if(!$topic) {
                $this->logger->err('Upload failed: Topic not found for UUID: ' . $topic_uuid);
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_TOPIC_NOT_FOUND'
                ]);
            }
            
            if($topic->company_id != $currentCompany->id) {
                $this->logger->err('Upload failed: Topic does not belong to current company');
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_UNAUTHORIZED'
                ]);
            }
            
            $step = Functions::sanitizeFilterString($this->params()->fromPost('step')); 

            $storage = Storage::getInstance($this->config, $this->adapter);
            
            // Log uploaded files info
            $files = $request->getFiles()->toArray();
            $this->logger->info('Uploaded files: ' . print_r($files, true));
            
            $storage->setFiles($files);

            if(!$storage->setCurrentFilename('file')) {
                $this->logger->err('Upload failed: Could not set current filename');
                return new JsonModel([
                    'success' => false,
                    'data' => 'ERROR_UPLOAD_FILE'
                ]);
            }
                
            if($step == 'validation') { 
                try {
                    $userMapper = UserMapper::getInstance($this->adapter);
                    $topicUserMapper = MicrolearningTopicUserMapper::getInstance($this->adapter);

                    $tmp_filename = $storage->getTmpFilename();
                    $filename = $storage->getFilename();
                    $target_filename = $storage->composePathToFilename(
                        Storage::TYPE_MICROLEARNING_ACCESS_FOR_STUDENTS,
                        $topic->uuid,
                        $filename
                    );
                    
                    $this->logger->info('Processing file upload: ' . $target_filename);
                    
                    if(!$storage->putFile($tmp_filename, $target_filename)) {
                        $this->logger->err('Upload failed: Could not move file to target location');
                        return new JsonModel([
                            'success' => false,
                            'data' => 'ERROR_UPLOAD_FILE'
                        ]);
                    }
                    
                    $count_users = 0;
                    $users = [];
                    $count_errors = 0;
                    $errors = [];
                    
                    try {
                        $this->logger->info('Loading spreadsheet: ' . $target_filename);
                        $spreadsheet = IOFactory::load($target_filename);
                        $records = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
                    } catch (\Exception $e) {
                        $this->logger->err('Spreadsheet processing failed: ' . $e->getMessage());
                        return new JsonModel([
                            'success' => false,
                            'data' => 'ERROR_UPLOAD_FILE: ' . $e->getMessage()
                        ]);
                    }
                    
                    $emails = [];
                    $row = 0;
                    
                    foreach($records as $record)
                    {
                        /*
                        A = Nombre      
                        B = Apellido    
                        C = Email       
                        D = Contraseña 
                        E = Empresa     
                        F = Función    
                        G = Grupo       
                        H = Institución        
                        I = Programa    
                        J = Socio       
                        K = Sector      
                        L = Tipo de Estudiante  
                        M = País       
                        N = Es adulto
                        */
                        $row++;
                        
                        $first_name = Functions::sanitizeFilterString($record['A']);
                        $last_name = Functions::sanitizeFilterString($record['B']);
                        $email = trim(filter_var($record['C'], FILTER_SANITIZE_EMAIL));
                        $password = Functions::sanitizeFilterString($record['D']);
                        
                        $company =  isset($record['E']) ? Functions::sanitizeFilterString($record['E']) : '';
                        $function = isset($record['F']) ? Functions::sanitizeFilterString($record['F']) : '';
                        $group = isset($record['G']) ? Functions::sanitizeFilterString($record['G']) : '';
                        $institution = isset($record['H']) ? Functions::sanitizeFilterString($record['H']) : '';
                        $program = isset($record['I']) ? Functions::sanitizeFilterString($record['I']) : '';
                        $partner = isset($record['J']) ? Functions::sanitizeFilterString($record['J']) : '';
                        $sector = isset($record['K']) ? Functions::sanitizeFilterString($record['K']) : '';
                        $studentType = isset($record['L']) ? Functions::sanitizeFilterString($record['L']) : '';
                        $country =  isset($record['M']) ? Functions::sanitizeFilterString($record['M']) : '';
                        $isAdult = isset($record['N']) ? Functions::sanitizeFilterString($record['N']) : '';
                        
                        if($row == 1) {
                            continue;
                        }
                        
                        //||  empty($password)
                        if(empty($first_name) || empty($last_name) || !filter_var($email, FILTER_VALIDATE_EMAIL) ) {
                            continue;                            
                        }
                        
                        if(!in_array($email, $emails)) {
                            $user = $userMapper->fetchOneByEmail($email);
                            $assigned_topics = $user ? $topicUserMapper->fetchCountByCompanyIdAndTopicIdAndUserId($topic->company_id, $topic->id, $user->id) : 0;
                            $count_users++;
                            
                            array_push($emails, $email);
                            array_push($users, [
                                'id' => $count_users,
                                'first_name' => $first_name,
                                'last_name' => $last_name,
                                'password'  => $password,
                                'email' => $email,
                                'assigned_topics' => $assigned_topics, 
                                'company' => $company, 
                                'function' => $function, 
                                'group' => $group, 
                                'institution' => $institution, 
                                'program' => $program,
                                'partner' => $partner,
                                'sector' => $sector, 
                                'studentType' => $studentType,
                                'country' => $country,
                                'isAdult' => $isAdult,
                            ]);
                        } else {
                            $count_errors++;
                            array_push($errors,[
                                'id' => $count_errors,
                                'first_name' => $first_name,
                                'last_name' => $last_name,
                                'password'  => $password,
                                'email' => $email,
                                'status' => 'DUPLICATE IN EXCEL'
                            ]);
                        }
                    }

                    try {
                        $key = md5($currentUser->id . '-' . $topic->uuid . '-' . $topic->uuid);
                        $this->cache->setItem($key, serialize($users));
                    } catch (\Exception $e) {
                        $this->logger->err('Cache operation failed: ' . $e->getMessage());
                        return new JsonModel([
                            'success' => false,
                            'data' => 'ERROR_CACHE_SET_ITEM: ' . $e->getMessage()
                        ]);
                    }

                    return new JsonModel([
                        'success' => true,
                        'data' => [
                            'key' => $key,
                            'topic' => $topic->name,
                            'items' => [
                                'ok' => $users,
                                'error' => $errors,
                            ],     
                        ]
                    ]);
                } catch (\Exception $e) {
                    $this->logger->err('Validation step failed: ' . $e->getMessage());
                    throw $e;
                }
            }
            
            return new JsonModel([
                'success' => false,
                'data' => 'ERROR_PARAMETERS_ARE_INVALID'
            ]);
            
        } catch (\Exception $e) {
            $this->logger->err('Fatal error in uploadAction: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
            return new JsonModel([
        }
    }
    
    /**
     * Handles sending push notifications to selected users about a microlearning topic
     * 
     * This action processes push notifications for microlearning topics:
     * 1. Validates user permissions and input data
     * 2. Processes the notification form
     * 3. Sends push notifications to selected users' devices
     * 
     * Required parameters:
     * - topic_uuid: UUID of the microlearning topic
     * - customer_uuids: Array of user UUIDs to receive the notification
     * - push_template_id: UUID of the push notification template to use
     * 
     * @return JsonModel Returns JSON response with:
     *                   - success: true/false
     *                   - data: Contains push_to_send count or error message
     * @throws \Throwable When an error occurs during processing
     */
    public function notificationAction()
    {
        try {
            // Get current request and user context
            $request = $this->getRequest();
        
            $currentUserPlugin = $this->plugin('currentUserPlugin');
            $currentUser    = $currentUserPlugin->getUser();
            $currentCompany = $currentUserPlugin->getCompany();
            
            $topic_uuid     = $this->params()->fromRoute('topic_uuid');
    
            // Validate topic UUID exists
            if(!$topic_uuid) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_TOPIC_UUID_NOT_FOUND'
                ]);
            }
            
            // Validate company exists
            if(!$currentCompany) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_COMPANY_NOT_FOUND'
                ]);
            }
    
            // Fetch and validate topic
            $topicMapper = MicrolearningTopicMapper::getInstance($this->adapter);
            $topic = $topicMapper->fetchOneByUuid($topic_uuid);
            if(!$topic) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_TOPIC_NOT_FOUND'
                ]);
            }
        
            // Validate topic belongs to current company
            if($topic->company_id != $currentCompany->id) {
                return new JsonModel([
                    'success'   => false,
                    'data'   => 'ERROR_UNAUTHORIZED'
                ]);
            }
            
            // Validate request method
            if(!$request->isPost()) {
                return new JsonModel([
                    'success' => false,
                    'data' => 'ERROR_METHOD_NOT_ALLOWED'
                ]);
            }

            // Process notification form
            $dataPost = $request->getPost()->toArray();
            $form = new PushMicrolearningNotificationForm($this->adapter, $currentCompany->id);
            
            $form->setData($dataPost);
            
            // Validate form data
            if(!$form->isValid()) {      
                $messages = [];
                $form_messages = (array) $form->getMessages();
                foreach($form_messages  as $fieldname => $field_messages)
                {
                    $messages[$fieldname] = array_values($field_messages);
                }
                
                return new JsonModel([
                    'success'   => false,
                    'data'      => $messages
                ]);
            }

            // Validate selected users
            $customer_uuids = $this->params()->fromPost('customer_uuids');
            if(!$customer_uuids) {
                return new JsonModel([
                    'success' => false,
                    'data' => 'ERROR_NOT_SELECTED_CUSTOMERS'
                ]);
                
            }
            
            // Get push template
            $push_template_uuid = Functions::sanitizeFilterString($form->get('push_template_id')->getValue());
            $pushMapper = PushMapper::getInstance($this->adapter);
            $pushTemplateMapper = PushTemplateMapper::getInstance($this->adapter);
            $pushTemplate = $pushTemplateMapper->fetchOneByUuid($push_template_uuid);
            
            if(!$pushTemplate) {
                return new JsonModel([
                    'success' => false,
                    'data' => 'ERROR_PUSH_TEMPLATE_NOT_FOUND'
                ]);
            }

            // Initialize push notification process
            $applicationMapper = ApplicationMapper::getInstance($this->adapter);
            $application = $applicationMapper->fetchOne(Application::TWOGETSKILLS);
            
            $topicUserMapper = MicrolearningTopicUserMapper::getInstance($this->adapter);
            
            $push_to_send = 0;

            // Process each selected user
            $userMapper = UserMapper::getInstance($this->adapter);
            $deviceHistoryMapper = DeviceHistoryMapper::getInstance($this->adapter);
            foreach($customer_uuids as $customer_uuid)
            {    
                $user = $userMapper->fetchOneByUuid($customer_uuid);
                if(!$user) {
                    continue;
                }
                
                $topicUser = $topicUserMapper->fetchOneByUserIdAndTopicId($user->id, $topic->id);
                if(!$topicUser) {
                    continue;
                }
                
                // Get user's latest device
                $device = $deviceHistoryMapper->fetchLastDeviceByApplicationIdAndUserId(Application::TWOGETSKILLS, $user->id);
                
                if($device && $device->token) {
                    
                    $key = $application->key;
                    if($device->variant_id) {
                        
                        $applicationVariantMapper = ApplicationVariantMapper::getInstance($this->adapter);
                        $applicationVariant = $applicationVariantMapper->fetchOneByApplicationIdAndVariantId($device->application_id, $device->variant_id);
                        if($applicationVariant) {
                            $key = $applicationVariant->key;
                        } else {
                            $applicationVariant = $applicationVariantMapper->fetchOneByApplicationIdAndDefault($device->application_id);
                            if($applicationVariant) {
                                $key = $applicationVariant->key;
                            }
                        }
                        
                    }
                    
                    // Create push notification
                    $push = new Push();
                    $push->status = Push::STATUS_PENDING;
                    $push->data = json_encode([
                        'server' => [
                            'key' => $key,
                        ],
                        'push' => [
                            'registration_ids'   => [
                                $device->token,
                            ],
                            'notification' => [
                                'body' =>  $pushTemplate->body,
                                'title' => $pushTemplate->title,
                                'vibrate' => 1,
                                'sound' =>  1
                            ],
                            'data' => [
                                'command' => 'content-refresh',
                                'new_topics' => '0',
                            ]
                        ]
                    ]);
                    
                    if($pushMapper->insert($push)) {
                        $push_to_send = $push_to_send + 1;
                    }
                    
                }
            }
            
            // Validate notifications were created
            if(0 == $push_to_send) {
                return new JsonModel([
                    'success' => false,
                    'data' => 'ERROR_NO_USER_DEVICES_WERE_FOUND_TO_SEND_PUSH'
                ]);
            }
            
            return new JsonModel([
                'success' => true,
                'data' => [
                    'push_to_send' => $push_to_send,
                ]
            ]);
        } catch (\Throwable $e) {
            $this->logger->error($e->getMessage());
            return new JsonModel([
                'success' => false,
                'data' => 'ERROR_INTERNAL_SERVER_ERROR'
            ]);
        }
    } 
}