| 96 | efrain | 1 | <?php
 | 
        
           | 1389 | ariadna | 2 | // Configuración de headers para respuesta JSON y CORS
 | 
        
           | 1409 | ariadna | 3 | // header('Content-Type: application/json'); // MOVED: Will be set only when outputting JSON
 | 
        
           |  |  | 4 | // header('Access-Control-Allow-Origin: *'); // MOVED: Will be set only when outputting JSON
 | 
        
           | 1389 | ariadna | 5 |   | 
        
           |  |  | 6 | // Definición de constantes para autenticación y encriptación
 | 
        
           | 96 | efrain | 7 | define('LLWS_USERNAME', 'leaderdslinked-ws');
 | 
        
           |  |  | 8 | define('LLWS_PASSWORD', 'KFB3lFsLp&*CpB0uc2VNc!zVn@w!EZqZ*jr$J0AlE@sYREUcyP');
 | 
        
           | 1389 | ariadna | 9 | define('LLWS_RSA_N', 34589927);  // Clave pública RSA N
 | 
        
           |  |  | 10 | define('LLWS_RSA_D', 4042211);   // Clave privada RSA D
 | 
        
           |  |  | 11 | define('LLWS_RSA_E', 7331);      // Clave pública RSA E
 | 
        
           |  |  | 12 | define('LLWS_CATEGORY_ID', 6);   // ID de categoría para cursos
 | 
        
           | 96 | efrain | 13 |   | 
        
           | 1389 | ariadna | 14 | // Inclusión de archivos necesarios de Moodle
 | 
        
           | 96 | efrain | 15 | require_once(__DIR__ . '/../config.php');
 | 
        
           |  |  | 16 | global $DB, $CFG;
 | 
        
           |  |  | 17 |   | 
        
           | 1389 | ariadna | 18 | // Inclusión de librerías de Moodle
 | 
        
           | 1383 | ariadna | 19 | require_once($CFG->libdir . '/moodlelib.php');
 | 
        
           | 96 | efrain | 20 | require_once($CFG->libdir . '/externallib.php');
 | 
        
           | 1383 | ariadna | 21 | require_once($CFG->libdir . '/authlib.php');
 | 
        
           |  |  | 22 | require_once($CFG->libdir . '/gdlib.php');
 | 
        
           |  |  | 23 | require_once($CFG->dirroot . '/user/lib.php');
 | 
        
           | 1389 | ariadna | 24 | require_once(__DIR__ . '/rsa.php');
 | 
        
           |  |  | 25 | require_once(__DIR__ . '/lib.php');
 | 
        
           | 96 | efrain | 26 |   | 
        
           | 1401 | ariadna | 27 | // Obtención y sanitización de parámetros de la petición
 | 
        
           |  |  | 28 | $username   = trim(isset($_REQUEST['username']) ? filter_var($_REQUEST['username'], FILTER_SANITIZE_STRING) : '');
 | 
        
           |  |  | 29 | $password   = trim(isset($_REQUEST['password']) ? filter_var($_REQUEST['password'], FILTER_SANITIZE_STRING) : '');
 | 
        
           |  |  | 30 | $timestamp  = trim(isset($_REQUEST['timestamp']) ? filter_var($_REQUEST['timestamp'], FILTER_SANITIZE_STRING) : '');
 | 
        
           |  |  | 31 | $rand       = intval(isset($_REQUEST['rand']) ? filter_var($_REQUEST['rand'], FILTER_SANITIZE_NUMBER_INT) : 0, 10);
 | 
        
           |  |  | 32 | $data       = trim(isset($_REQUEST['data']) ? filter_var($_REQUEST['data'], FILTER_SANITIZE_STRING) : '');
 | 
        
           |  |  | 33 |   | 
        
           |  |  | 34 | /*
 | 
        
           |  |  | 35 | $data = '';
 | 
        
           |  |  | 36 | $input = fopen("php://input", "r");
 | 
        
           |  |  | 37 |   | 
        
           |  |  | 38 |   | 
        
           |  |  | 39 | while ($line = fread($input, 1024))
 | 
        
           | 1388 | ariadna | 40 | {
 | 
        
           | 1401 | ariadna | 41 |     $data .= $line;
 | 
        
           |  |  | 42 | }
 | 
        
           |  |  | 43 | fclose($input);*/
 | 
        
           | 1388 | ariadna | 44 |   | 
        
           | 1401 | ariadna | 45 | /*
 | 
        
           |  |  | 46 | echo 'username = '. $username . PHP_EOL;
 | 
        
           |  |  | 47 | echo 'password = '. $password . PHP_EOL;
 | 
        
           |  |  | 48 | echo 'rand = '. $rand . PHP_EOL;
 | 
        
           |  |  | 49 | echo 'timestamp = '. $timestamp . PHP_EOL;
 | 
        
           |  |  | 50 | echo 'data = '. $data . PHP_EOL;
 | 
        
           | 1388 | ariadna | 51 |   | 
        
           |  |  | 52 |   | 
        
           | 1401 | ariadna | 53 | /*
 | 
        
           |  |  | 54 | list($usec, $sec) = explode(' ', microtime());
 | 
        
           |  |  | 55 | $seed = intval($sec + ((float) $usec * 100000));
 | 
        
           | 1388 | ariadna | 56 |   | 
        
           | 1401 | ariadna | 57 | $username   = LLWS_USERNAME;
 | 
        
           |  |  | 58 | $timestamp  = date('Y-m-d\TH:i:s');
 | 
        
           |  |  | 59 | mt_srand($seed, MT_RAND_MT19937);
 | 
        
           |  |  | 60 | $rand =  mt_rand();
 | 
        
           | 1388 | ariadna | 61 |   | 
        
           | 1401 | ariadna | 62 | $password   = LLWS_PASSWORD;
 | 
        
           |  |  | 63 | $password  = password_hash($username.'-'. $password. '-' . $rand. '-' . $timestamp, PASSWORD_DEFAULT);
 | 
        
           | 1388 | ariadna | 64 |   | 
        
           | 1401 | ariadna | 65 | $data       = $rsa->encrypt(json_encode(['email' => 'usuario4@test.com', 'first_name' => 'usuario4', 'last_name' => 'test']));
 | 
        
           |  |  | 66 | */
 | 
        
           | 1388 | ariadna | 67 |   | 
        
           | 1409 | ariadna | 68 | // Helper function to output JSON error and exit
 | 
        
           |  |  | 69 | function output_json_error($errorCode)
 | 
        
           |  |  | 70 | {
 | 
        
           |  |  | 71 |     // It's good practice to ensure session is closed before output,
 | 
        
           |  |  | 72 |     // though Moodle's exit handling might cover this.
 | 
        
           |  |  | 73 |     if (function_exists('session_write_close')) {
 | 
        
           |  |  | 74 |         session_write_close();
 | 
        
           |  |  | 75 |     }
 | 
        
           |  |  | 76 |     header('Content-Type: application/json');
 | 
        
           |  |  | 77 |     header('Access-Control-Allow-Origin: *'); // Assuming CORS is needed for errors too
 | 
        
           |  |  | 78 |     echo json_encode(['success' => false, 'data' => $errorCode]);
 | 
        
           |  |  | 79 |     exit;
 | 
        
           |  |  | 80 | }
 | 
        
           |  |  | 81 |   | 
        
           | 1401 | ariadna | 82 | // Validación de parámetros de seguridad
 | 
        
           |  |  | 83 | if (empty($username) || empty($password) || empty($timestamp) || empty($rand) || !is_integer($rand)) {
 | 
        
           | 1409 | ariadna | 84 |     output_json_error('ERROR_SECURITY1');
 | 
        
           | 96 | efrain | 85 | }
 | 
        
           |  |  | 86 |   | 
        
           | 1401 | ariadna | 87 | // Validación del nombre de usuario
 | 
        
           |  |  | 88 | if ($username != LLWS_USERNAME) {
 | 
        
           | 1409 | ariadna | 89 |     output_json_error('ERROR_SECURITY2');
 | 
        
           | 1401 | ariadna | 90 | }
 | 
        
           | 96 | efrain | 91 |   | 
        
           | 1401 | ariadna | 92 | // Validación del formato del timestamp
 | 
        
           |  |  | 93 | $dt = \DateTime::createFromFormat('Y-m-d\TH:i:s', $timestamp);
 | 
        
           |  |  | 94 | if (!$dt) {
 | 
        
           | 1409 | ariadna | 95 |     output_json_error('ERROR_SECURITY3');
 | 
        
           | 1401 | ariadna | 96 | }
 | 
        
           | 96 | efrain | 97 |   | 
        
           | 1401 | ariadna | 98 | // Validación del rango de tiempo del timestamp (±5 minutos)
 | 
        
           |  |  | 99 | $t0 = $dt->getTimestamp();
 | 
        
           |  |  | 100 | $t1 = strtotime('-5 minutes');
 | 
        
           |  |  | 101 | $t2 = strtotime('+5 minutes');
 | 
        
           | 96 | efrain | 102 |   | 
        
           | 1401 | ariadna | 103 | if ($t0 < $t1 || $t0 > $t2) {
 | 
        
           |  |  | 104 |     //echo json_encode(['success' => false, 'data' => 'ERROR_SECURITY4']) ;
 | 
        
           | 1409 | ariadna | 105 |     //output_json_error('ERROR_SECURITY4'); // Kept commented as in original for this line
 | 
        
           | 1401 | ariadna | 106 |     //exit;
 | 
        
           |  |  | 107 | }
 | 
        
           | 96 | efrain | 108 |   | 
        
           | 1401 | ariadna | 109 | // Validación de la contraseña
 | 
        
           |  |  | 110 | if (!password_verify($username . '-' . LLWS_PASSWORD . '-' . $rand . '-' . $timestamp, $password)) {
 | 
        
           | 1409 | ariadna | 111 |     output_json_error('ERROR_SECURITY5');
 | 
        
           | 1401 | ariadna | 112 | }
 | 
        
           | 96 | efrain | 113 |   | 
        
           | 1401 | ariadna | 114 | // Validación de datos
 | 
        
           |  |  | 115 | if (empty($data)) {
 | 
        
           | 1409 | ariadna | 116 |     output_json_error('ERROR_PARAMETERS1');
 | 
        
           | 1401 | ariadna | 117 | }
 | 
        
           | 96 | efrain | 118 |   | 
        
           | 1401 | ariadna | 119 | // Decodificación de datos en base64
 | 
        
           |  |  | 120 | $data = base64_decode($data);
 | 
        
           |  |  | 121 | if (empty($data)) {
 | 
        
           | 1409 | ariadna | 122 |     output_json_error('ERROR_PARAMETERS2');
 | 
        
           | 96 | efrain | 123 | }
 | 
        
           |  |  | 124 |   | 
        
           | 1401 | ariadna | 125 | // Desencriptación de datos usando RSA
 | 
        
           |  |  | 126 | try {
 | 
        
           |  |  | 127 |     $rsa = new rsa();
 | 
        
           |  |  | 128 |     $rsa->setKeys(LLWS_RSA_N, LLWS_RSA_D, LLWS_RSA_E);
 | 
        
           |  |  | 129 |     $data = $rsa->decrypt($data);
 | 
        
           |  |  | 130 | } catch (Throwable $e) {
 | 
        
           | 1409 | ariadna | 131 |     output_json_error('ERROR_PARAMETERS3');
 | 
        
           | 1401 | ariadna | 132 | }
 | 
        
           | 1383 | ariadna | 133 |   | 
        
           | 1401 | ariadna | 134 | // Conversión de datos a array
 | 
        
           |  |  | 135 | $data = (array) json_decode($data);
 | 
        
           |  |  | 136 | if (empty($data)) {
 | 
        
           | 1409 | ariadna | 137 |     output_json_error('ERROR_PARAMETERS4');
 | 
        
           | 1401 | ariadna | 138 | }
 | 
        
           | 96 | efrain | 139 |   | 
        
           | 1401 | ariadna | 140 | // Extracción y validación de datos del usuario
 | 
        
           |  |  | 141 | $email      = trim(isset($data['email']) ? filter_var($data['email'], FILTER_SANITIZE_EMAIL) : '');
 | 
        
           |  |  | 142 | $first_name = trim(isset($data['first_name']) ? filter_var($data['first_name'], FILTER_SANITIZE_STRING) : '');
 | 
        
           |  |  | 143 | $last_name  = trim(isset($data['last_name']) ? filter_var($data['last_name'], FILTER_SANITIZE_STRING) : '');
 | 
        
           | 96 | efrain | 144 |   | 
        
           | 1401 | ariadna | 145 | if (!filter_var($email, FILTER_VALIDATE_EMAIL) || empty($first_name) || empty($last_name)) {
 | 
        
           | 1409 | ariadna | 146 |     output_json_error('ERROR_PARAMETERS5');
 | 
        
           | 1401 | ariadna | 147 | }
 | 
        
           | 96 | efrain | 148 |   | 
        
           | 1401 | ariadna | 149 | // Búsqueda o creación de usuario
 | 
        
           |  |  | 150 | $user = ll_get_user_by_email($email);
 | 
        
           |  |  | 151 | if ($user) {
 | 
        
           |  |  | 152 |     $new_user = false;
 | 
        
           |  |  | 153 | } else {
 | 
        
           |  |  | 154 |     $new_user = true;
 | 
        
           |  |  | 155 |     $username = ll_get_username_available($first_name, $last_name);
 | 
        
           |  |  | 156 |     $user = ll_create_user($username, $email, $first_name, $last_name);
 | 
        
           |  |  | 157 |   | 
        
           |  |  | 158 |     // Procesamiento de imagen de perfil si se proporciona
 | 
        
           |  |  | 159 |     if ($user) {
 | 
        
           |  |  | 160 |         $filename   = trim(isset($data['image_filename']) ? filter_var($data['image_filename'], FILTER_SANITIZE_EMAIL) : '');
 | 
        
           |  |  | 161 |         $content    = trim(isset($data['image_content']) ? filter_var($data['image_content'], FILTER_SANITIZE_STRING) : '');
 | 
        
           |  |  | 162 |   | 
        
           |  |  | 163 |         if ($filename && $content) {
 | 
        
           |  |  | 164 |             $tempfile = __DIR__ . DIRECTORY_SEPARATOR . $filename;
 | 
        
           |  |  | 165 |             try {
 | 
        
           |  |  | 166 |                 file_put_contents($filename, base64_decode($content));
 | 
        
           |  |  | 167 |                 if (file_exists($tempfile)) {
 | 
        
           |  |  | 168 |                     $usericonid = process_new_icon(context_user::instance($user->id, MUST_EXIST), 'user', 'icon', 0, $tempfile);
 | 
        
           |  |  | 169 |                     if ($usericonid) {
 | 
        
           |  |  | 170 |                         $DB->set_field('user', 'picture', $usericonid, array('id' => $user->id));
 | 
        
           |  |  | 171 |                     }
 | 
        
           | 96 | efrain | 172 |                 }
 | 
        
           | 1401 | ariadna | 173 |             } catch (\Throwable $e) {
 | 
        
           |  |  | 174 |             } finally {
 | 
        
           |  |  | 175 |                 if (file_exists($tempfile)) {
 | 
        
           |  |  | 176 |                     unlink($tempfile);
 | 
        
           |  |  | 177 |                 }
 | 
        
           | 96 | efrain | 178 |             }
 | 
        
           |  |  | 179 |         }
 | 
        
           |  |  | 180 |     }
 | 
        
           | 1401 | ariadna | 181 | }
 | 
        
           | 96 | efrain | 182 |   | 
        
           | 1401 | ariadna | 183 | // Verificación de creación de usuario
 | 
        
           |  |  | 184 | if (!$user) {
 | 
        
           | 1409 | ariadna | 185 |     output_json_error('ERROR_MOODLE1');
 | 
        
           | 1383 | ariadna | 186 | }
 | 
        
           | 96 | efrain | 187 |   | 
        
           | 1401 | ariadna | 188 | // Inscripción en cursos para usuarios nuevos
 | 
        
           |  |  | 189 | if ($new_user) {
 | 
        
           | 96 | efrain | 190 |     $role = $DB->get_record('role', array('archetype' => 'student'));
 | 
        
           |  |  | 191 |     $enrolmethod = 'manual';
 | 
        
           | 1383 | ariadna | 192 |   | 
        
           | 96 | efrain | 193 |     $courses = get_courses();
 | 
        
           | 1383 | ariadna | 194 |     foreach ($courses as $course) {
 | 
        
           |  |  | 195 |         if ($course->categoy_id == LLWS_CATEGORY_ID) {
 | 
        
           | 96 | efrain | 196 |             $context = context_course::instance($course->id);
 | 
        
           |  |  | 197 |             if (!is_enrolled($context, $user)) {
 | 
        
           |  |  | 198 |                 $enrol = enrol_get_plugin($enrolmethod);
 | 
        
           |  |  | 199 |                 if ($enrol === null) {
 | 
        
           | 1401 | ariadna | 200 |                     return false;
 | 
        
           | 96 | efrain | 201 |                 }
 | 
        
           |  |  | 202 |                 $instances = enrol_get_instances($course->id, true);
 | 
        
           |  |  | 203 |                 $manualinstance = null;
 | 
        
           |  |  | 204 |                 foreach ($instances as $instance) {
 | 
        
           |  |  | 205 |                     if ($instance->name == $enrolmethod) {
 | 
        
           |  |  | 206 |                         $manualinstance = $instance;
 | 
        
           |  |  | 207 |                         break;
 | 
        
           |  |  | 208 |                     }
 | 
        
           |  |  | 209 |                 }
 | 
        
           |  |  | 210 |                 if ($manualinstance !== null) {
 | 
        
           |  |  | 211 |                     $instanceid = $enrol->add_default_instance($course);
 | 
        
           |  |  | 212 |                     if ($instanceid === null) {
 | 
        
           |  |  | 213 |                         $instanceid = $enrol->add_instance($course);
 | 
        
           |  |  | 214 |                     }
 | 
        
           |  |  | 215 |                     $instance = $DB->get_record('enrol', array('id' => $instanceid));
 | 
        
           | 1383 | ariadna | 216 |                     if ($instance) {
 | 
        
           | 96 | efrain | 217 |                         $enrol->enrol_user($instance, $user->id, $role->id);
 | 
        
           |  |  | 218 |                     }
 | 
        
           |  |  | 219 |                 }
 | 
        
           |  |  | 220 |             }
 | 
        
           |  |  | 221 |         }
 | 
        
           |  |  | 222 |     }
 | 
        
           |  |  | 223 | }
 | 
        
           |  |  | 224 |   | 
        
           | 1401 | ariadna | 225 | // Obtención de datos completos del usuario y login
 | 
        
           |  |  | 226 | $user = get_complete_user_data('id', $user->id);
 | 
        
           |  |  | 227 | if ($user) {
 | 
        
           |  |  | 228 |     // Si hay una sesión existente, cerrarla primero
 | 
        
           | 1392 | ariadna | 229 |     if (isloggedin()) {
 | 
        
           | 1410 | ariadna | 230 |         \core\session\manager::kill_all_sessions();
 | 
        
           |  |  | 231 |         \core\session\manager::terminate_current();
 | 
        
           |  |  | 232 |         session_destroy();
 | 
        
           | 1413 | ariadna | 233 |         // Delete MoodleSession cookie
 | 
        
           |  |  | 234 |         setcookie('MoodleSession', '', time() - 3600, '/');
 | 
        
           | 1392 | ariadna | 235 |     }
 | 
        
           |  |  | 236 |   | 
        
           | 1401 | ariadna | 237 |     // Verificar si la cuenta está confirmada
 | 
        
           | 1392 | ariadna | 238 |     if (empty($user->confirmed)) {
 | 
        
           | 1409 | ariadna | 239 |         output_json_error('ACCOUNT_NOT_CONFIRMED');
 | 
        
           | 1392 | ariadna | 240 |     }
 | 
        
           |  |  | 241 |   | 
        
           | 1401 | ariadna | 242 |     // Verificar si la contraseña ha expirado (solo para autenticación LDAP)
 | 
        
           | 1392 | ariadna | 243 |     $userauth = get_auth_plugin($user->auth);
 | 
        
           |  |  | 244 |     if (!isguestuser() && !empty($userauth->config->expiration) && $userauth->config->expiration == 1) {
 | 
        
           |  |  | 245 |         $days2expire = $userauth->password_expire($user->username);
 | 
        
           |  |  | 246 |         if (intval($days2expire) < 0) {
 | 
        
           | 1409 | ariadna | 247 |             output_json_error('PASSWORD_EXPIRED');
 | 
        
           | 1391 | ariadna | 248 |         }
 | 
        
           | 1392 | ariadna | 249 |     }
 | 
        
           | 1391 | ariadna | 250 |   | 
        
           | 1401 | ariadna | 251 |     // Completar el proceso de inicio de sesión
 | 
        
           | 1413 | ariadna | 252 |     complete_user_login($user);
 | 
        
           | 1401 | ariadna | 253 |   | 
        
           |  |  | 254 |     // Aplicar límite de inicio de sesión concurrente
 | 
        
           | 1392 | ariadna | 255 |     \core\session\manager::apply_concurrent_login_limit($user->id, session_id());
 | 
        
           | 1391 | ariadna | 256 |   | 
        
           | 1401 | ariadna | 257 |     // Configurar cookie de nombre de usuario
 | 
        
           | 1392 | ariadna | 258 |     if (!empty($CFG->nolastloggedin)) {
 | 
        
           |  |  | 259 |         // No almacenar último usuario conectado en cookie
 | 
        
           |  |  | 260 |     } else if (empty($CFG->rememberusername)) {
 | 
        
           | 1401 | ariadna | 261 |         // Sin cookies permanentes, eliminar la anterior si existe
 | 
        
           | 1392 | ariadna | 262 |         set_moodle_cookie('');
 | 
        
           |  |  | 263 |     } else {
 | 
        
           |  |  | 264 |         set_moodle_cookie($user->username);
 | 
        
           |  |  | 265 |     }
 | 
        
           | 1391 | ariadna | 266 |   | 
        
           | 1401 | ariadna | 267 |     // Limpiar mensajes de error antes de la última redirección
 | 
        
           | 1392 | ariadna | 268 |     unset($SESSION->loginerrormsg);
 | 
        
           |  |  | 269 |     unset($SESSION->logininfomsg);
 | 
        
           | 1401 | ariadna | 270 |   | 
        
           |  |  | 271 |     // Descartar loginredirect si estamos redirigiendo
 | 
        
           | 1392 | ariadna | 272 |     unset($SESSION->loginredirect);
 | 
        
           | 1391 | ariadna | 273 |   | 
        
           | 1401 | ariadna | 274 |     // Configurar la URL de destino
 | 
        
           | 1398 | ariadna | 275 |     $urltogo = $CFG->wwwroot . '/my';
 | 
        
           |  |  | 276 |     $SESSION->wantsurl = $urltogo;
 | 
        
           | 1391 | ariadna | 277 |   | 
        
           | 1401 | ariadna | 278 |     // Verificar que la sesión se haya iniciado correctamente
 | 
        
           | 1398 | ariadna | 279 |     if (isloggedin() && !isguestuser()) {
 | 
        
           | 1401 | ariadna | 280 |         // Enviar respuesta exitosa con datos del usuario
 | 
        
           | 1409 | ariadna | 281 |         // Instead of echoing JSON here and then redirecting (which is problematic),
 | 
        
           |  |  | 282 |         // we will just redirect. The browser will follow the redirect, and the
 | 
        
           |  |  | 283 |         // new session (with its cookie) will be active for the target page.
 | 
        
           |  |  | 284 |         /*
 | 
        
           | 1401 | ariadna | 285 |         echo json_encode([
 | 
        
           | 1398 | ariadna | 286 |             'success' => true,
 | 
        
           |  |  | 287 |             'data' => [
 | 
        
           |  |  | 288 |                 'userid' => $user->id,
 | 
        
           |  |  | 289 |                 'username' => $user->username,
 | 
        
           |  |  | 290 |                 'fullname' => fullname($user),
 | 
        
           |  |  | 291 |                 'email' => $user->email,
 | 
        
           |  |  | 292 |                 'redirect' => $urltogo
 | 
        
           |  |  | 293 |             ]
 | 
        
           | 1401 | ariadna | 294 |         ]);
 | 
        
           | 1409 | ariadna | 295 |         */
 | 
        
           | 1401 | ariadna | 296 |   | 
        
           |  |  | 297 |         // Redirigir al usuario
 | 
        
           | 1409 | ariadna | 298 |         // redirect() will handle session_write_close(), send Location header, and exit.
 | 
        
           | 1401 | ariadna | 299 |         redirect($urltogo);
 | 
        
           |  |  | 300 |     } else {
 | 
        
           | 1409 | ariadna | 301 |         output_json_error('LOGIN_FAILED');
 | 
        
           | 1399 | ariadna | 302 |     }
 | 
        
           | 1401 | ariadna | 303 | } else {
 | 
        
           | 1409 | ariadna | 304 |     output_json_error('USER_NOT_FOUND');
 | 
        
           | 96 | efrain | 305 | }
 | 
        
           | 1409 | ariadna | 306 | // The script should have exited by now either via output_json_error() or redirect().
 | 
        
           |  |  | 307 | exit; // Explicit exit here as a fallback, though not strictly necessary if all paths exit.
 |