Pārlūkot izejas kodu

Add new files and make code improvements

Alejandro Rosales 1 gadu atpakaļ
vecāks
revīzija
0a712e1864
55 mainītis faili ar 9362 papildinājumiem un 961 dzēšanām
  1. 6 7
      action/action_auditoria.php
  2. 1 1
      action/action_estado_supervisor.php
  3. 12 7
      action/action_grupo.php
  4. 15 0
      action/action_usuarios_delete.php
  5. 33 0
      action/asignacion_delete.php
  6. 80 14
      action/asignacion_insert.php
  7. 81 0
      action/asignacion_select.php
  8. 135 0
      action/asignacion_update.php
  9. 46 0
      action/horario_profesor.php
  10. 7 0
      action/mail_javier.php
  11. 198 93
      action/reposicion_autoriza.php
  12. 20 17
      action/reposicion_insert.php
  13. 3 0
      action/reposicion_select.php
  14. 3 3
      action/rutas.php
  15. 33 29
      action/rutas_salón_horario.php
  16. 3 15
      action/usuario_find.php
  17. 52 41
      asignacion_crear.php
  18. 927 0
      cambio_crear.php
  19. 1 1
      consultar_horario.php
  20. 208 0
      horarios_historicos.php
  21. 5 2
      import/periodo.php
  22. 63 0
      include/bd_pdo_rest.php
  23. BIN
      include/db/postgrest
  24. 20 0
      include/db/postgrest.conf
  25. 4 0
      js/puestos.js
  26. 1 0
      materias.php
  27. 3 4
      profesores.php
  28. 13 6
      puestos.php
  29. 317 105
      reposiciones_autorizar.php
  30. 30 13
      reposiciones_crear.php
  31. 57 0
      rest/LogCambios.php
  32. 430 0
      rest/horarios.php
  33. 364 0
      rest/horarios_new.php
  34. 403 0
      rest/horarios_otro.php
  35. 95 0
      rest/include/mailer.php
  36. 50 0
      rest/include/phpmailer/PHPMailerAutoload.php
  37. 3884 0
      rest/include/phpmailer/class.phpmailer.php
  38. 1181 0
      rest/include/phpmailer/class.smtp.php
  39. 3 0
      rest/info.php
  40. 4 0
      rest/log/cambios_2023_08.log
  41. 252 0
      rest/materias.php
  42. 149 0
      rest/salon.php
  43. 35 0
      rest/token.php
  44. 41 0
      rest/token.php.save
  45. 41 0
      rest/token.php.save.1
  46. 0 27
      service/_4564_periodo/auto.php
  47. 0 76
      service/_4564_periodo/backend/carreras.php
  48. 0 62
      service/_4564_periodo/backend/periodos.php
  49. 0 155
      service/_4564_periodo/client.html
  50. 0 81
      service/_4564_periodo/horarios.php
  51. 0 39
      service/_4564_periodo/periodos.v1.php
  52. 0 41
      service/_4564_periodo/periodos.v2.php
  53. 0 108
      service/auditoria/index.php
  54. 28 11
      supervisor.php
  55. 25 3
      usuarios.php

+ 6 - 7
action/action_auditoria.php

@@ -31,7 +31,7 @@ try {
             "WITH horarios AS (
                 SELECT
                     horario_id,
-                    facultad.facultad_id,
+                    horario.facultad_id,
                     horario_fecha_inicio,
                     horario_fecha_fin,
                     horario_grupo,
@@ -40,20 +40,19 @@ try {
                     PERIODO.periodo_fecha_fin,
                     salon,
                     COALESCE(materia_nombre, materia_asignacion_materia) as materia,
-                    carrera_nombre as carrera,
+                    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 carrera using (carrera_id)
                 left join materia_asignacion using (horario_id)
-                -- JOIN carrera USING (carrera_id) but if carrera_id is null then 0
-                JOIN nivel USING (nivel_id)
-                JOIN facultad ON facultad.facultad_id = COALESCE(carrera.facultad_id, 0)
+                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, facultad.facultad_id) = (:periodo_id, COALESCE(:facultad_id, facultad.facultad_id))
+                WHERE (PERIODO.periodo_id, horario.facultad_id) =  (:periodo_id, COALESCE(:facultad_id, horario.facultad_id))
             ),
             fechas AS (
                 SELECT fechas_clase(h.horario_id, true) as registro_fecha_ideal, h.horario_id  

+ 1 - 1
action/action_estado_supervisor.php

@@ -1,4 +1,4 @@
-<?
+ <?
 #input $_GET['id_espacio_sgu']
 define("INFORMATION", [
     'GET' => [

+ 12 - 7
action/action_grupo.php

@@ -23,7 +23,8 @@ if (!isset($_GET['carrera_id'])) {
     exit();
 }
 
-$grupos = $db->query(<<<SQL
+try {
+    $grupos = $db->query(<<<SQL
     SELECT distinct substring(horario_grupo, 7, 3)::int - 1 as horario_grupo FROM horario_view WHERE
         PERIODO_ID = :periodo_id AND
         (FACULTAD_ID = :facultad_id OR :facultad_id IS NULL) AND
@@ -31,11 +32,15 @@ $grupos = $db->query(<<<SQL
     GROUP BY horario_grupo
         ORDER BY horario_grupo ASC
     SQL,
-    [
-        ':periodo_id' => $user->periodo_id,
-        ':facultad_id' => $user->facultad['facultad_id'],
-        ':carrera_id' => $_GET['carrera_id']
-    ]
-);
+        [
+            ':periodo_id' => $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));

+ 15 - 0
action/action_usuarios_delete.php

@@ -0,0 +1,15 @@
+<?php
+ini_set('display_errors', 1);
+ini_set('display_startup_errors', 1);
+error_reporting(E_ALL);
+$ruta = "../";
+require_once "../include/bd_pdo.php";
+global $db;
+
+try {
+    $db->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;
+}

+ 33 - 0
action/asignacion_delete.php

@@ -0,0 +1,33 @@
+<?php
+/* 
+ * Borra reposición
+ */
+$pag = "../reposiciones_crear.php";
+$ruta = "../";
+require_once "../class/c_login.php";
+
+// check if the session is started
+if (!isset($_SESSION['user']))
+    die('No se ha iniciado sesión');
+
+$user = unserialize($_SESSION['user']);
+
+//--- Objeto para validar usuario. El id de usuario lo lee desde sesión
+if(!isset($_POST["id"])){
+    $return["error"] = "Error! No se recibió la información necesaria.";
+}else{
+    $id = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT);//limpia texto
+    $creador = $user->user["id"];
+
+    try{
+        $db->query('SELECT * from fd_asignacion_solicitud(:id, :creador)', [":id"=> $id, ":creador"=>$creador]);
+        $return["ok"] = "La solicitud se borró correctamente";
+    
+    }catch(Exception $e){
+        $return["error"] = "Ocurrió un error al borrar la solicitud de salón.";
+    }
+
+    
+}
+echo json_encode($return);
+?>

+ 80 - 14
action/asignacion_insert.php

@@ -23,6 +23,8 @@ $hora_ini = filter_input(INPUT_POST, "hora_ini", FILTER_SANITIZE_NUMBER_INT);//l
 $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(isset($_POST["salon"]) && $_POST["salon"] != "")
+    $salon = filter_input(INPUT_POST, "salon", FILTER_SANITIZE_NUMBER_INT);//limpia texto
 
 if(empty($_POST["prof"]))
     $prof = $user["id"];
@@ -32,7 +34,7 @@ else
 //$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
 
-$aula_rs = $db->querySingle("select tipoaula_nombre from tipoaula where tipoaula_id = :id", [":id"=>$aula]);
+$aula_rs = $db->querySingle("select tipoaula_nombre, tipoaula_supervisor from tipoaula where tipoaula_id = :id", [":id"=>$aula]);
 
 
 $duracion_rs = $db->querySingle("select * from duracion where duracion_id = :id", [":id"=>$duracion_id]);
@@ -45,16 +47,52 @@ $fecha_new =  DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d')." ".$
 $fecha_fin_new = date("Y-m-d", strtotime($fecha_new))." ".$duracion_tiempo;
 $dia_new = date('w', strtotime($fecha_new));
 
+//Datos de dependencia de usuario
+$fac_rs = $db->querySingle("SELECT facultad_nombre, clave_dependencia from facultad where facultad_id = :id_fac",[':id_fac' => $user->facultad["facultad_id"]] );
 
+$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 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"];
+//Obtiene correos
+$correos_rs = $db->query('SELECT 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 hv.facultad_id = :id_fac',
+            [':rol_coord' => COORDINADOR, ':id_fac' => $user->facultad["facultad_id"]]
+        );
+$coord_correos=[];
+foreach($correos_rs as $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->querySingle("SELECT DISTINCT sup.usuario_correo as coordinador_correo
+    FROM horario_supervisor hs
+    inner join usuario sup on sup.usuario_id =hs.usuario_id 
+    where :id_fac = ANY(hs.facultad_id_array)
+        and sup.usuario_correo is not null and sup.usuario_correo != ''",
+    [':id_fac' => $user->facultad["facultad_id"]] );
+
+
 // Valida que grupo no tenga clases
 /*$result = validaConflictoHoras($pdo, $gpo, $dia_new, $hora, $materia, "-", $fecha_new, $fecha_fin_new, $duracion);
 if($result != ""){//error
@@ -77,10 +115,14 @@ if($traslape){
 }
 
 try{
-    //echo "SELECT * from fi_asignacion_solicitud( $fecha_new,  $hora, $prof, 1, $comentario, $alumnos, $aula, $duracion_tiempo, ".$user->user["id"].")";
-    $db->query('SELECT * from fi_asignacion_solicitud(:f_nueva, :hora_nueva, :prof, 1, :desc, :alumnos, :aula, :duracion, :usr)',
+    $edo = 1;
+    if(!$user->jefe_carrera)
+        $edo = 2;
+
+    //echo "SELECT * from fi_asignacion_solicitud( $fecha_new,  $hora, $prof, $edo, $comentario, $alumnos, $aula, $duracion_tiempo, ".$user->user["id"].")"; exit();
+    $db->query('SELECT * from fi_asignacion_solicitud(:f_nueva, :hora_nueva, :prof, :edo, :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"]
+        ':prof' => $prof, ':edo'=>$edo, ':desc' => $comentario, ':alumnos' => $alumnos, ':aula' => $aula, ':duracion' => $duracion_tiempo, ':usr'=>$user->user["id"]
         ]
     );
 }catch(Exception $e){
@@ -88,9 +130,34 @@ try{
     //header("Location: ".$pag."?error=1");
     exit();
 }
-$texto = "<p>Se creó una asignación nueva.</p>";
-$texto .= "<p><b>Se solicita un espacio de tipo ".mb_strtoupper($aula_rs["tipoaula_nombre"])."</b> del día <b>".$fecha_new." hrs. </b>";
-$texto .= "<p>Ingresa al <a href='https://paad.lci.ulsa.mx'>sistema PAAD</a> para autorizarla.</p>";
+
+$asunto = "";
+$texto = "";
+$to = "";
+switch($edo){
+    case 1://Correo a coordinador
+        if( count($coord_correos) > 0 ){
+            $to = join(",", $coord_correos);
+        }
+        $asunto = "Solicitud de salon nueva";
+        $texto = "<p>Se creó una solicitud de asignación de salón nueva.</p>";
+        $texto .= "<p><b>Se solicita un espacio de tipo ".mb_strtoupper($aula_rs["tipoaula_nombre"])."</b> para el día <b>".$fecha." a las ".$hora." hrs. </b>";
+        $texto .= "<p>Ingresa al <a href='https://paad.lci.ulsa.mx'>sistema PAAD</a> para autorizarla.</p>";
+    break;
+    case 2://Correo a supervisor
+        $asunto = "Solicitud de salon nueva - ".$fac_rs["clave_dependencia"]." ".$fac_rs["facultad_nombre"];
+        //crear plantilla
+        $texto = "<p>Se creó una solicitud de asignación de salón nueva para: <b>".$fac_rs["clave_dependencia"]." ".$fac_rs["facultad_nombre"]."</b>.</p>";
+        $texto .= "<p>Para el día <b>".$fecha." a las ".$hora." hrs. </b>";
+        if(!$aula_rs["tipoaula_supervisor"]){
+            $texto .= " en el salón: <b>".$salon_desc."</b></p>";
+        }else{
+            $texto .= " en un salón de tipo: <b>".mb_strtoupper($aula_rs["tipoaula_nombre"])."</b></p>";
+        }
+        $texto .= "<p>Ingresa al <a href='https://paad.lci.ulsa.mx'>sistema PAAD</a> para autorizarla.</p>";
+        $to = join(",", $correosSup_rs);
+    break;
+}
 
 /*
 $log = new LogActividad();
@@ -100,7 +167,6 @@ $log->appendLog($_SESSION["usuario_id"], $_SESSION["usuario_nombre"]." ".$_SESSI
 
 
 if($to!= "" && ENVIO_CORREOS){
-    $asunto = "Reposición nueva - solicitud";
         //crear plantilla
     $texto = '<body >
             <img src="https://paad.lci.ulsa.mx/imagenes/logo_lasalle.png" alt="La Salle" style="margin-bottom:60px">

+ 81 - 0
action/asignacion_select.php

@@ -0,0 +1,81 @@
+<?php
+
+/* 
+ * Obtiene datos de reposición
+ */
+$ruta = "../";
+require_once "../class/c_login.php";
+
+// check if the session is started
+if (!isset($_SESSION['user']))
+    die('No se ha iniciado sesión');
+
+$user = unserialize($_SESSION['user']);
+
+
+//--- Objeto para validar usuario. El id de usuario lo lee desde sesión
+/*if(!$objSesion->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_asignacion_solicitud(:id, NULL, NULL, :sup, NULL)',
+                [':id' => $id, ':sup'=>$user->user["id"]]
+            );
+        }else{//coordinador 
+            $rs = $db->querySingle('SELECT * from fs_asignacion_solicitud(:id, NULL, NULL, NULL, null)',
+                [':id' => $id]
+            );
+        }
+
+    }catch(Exception $e){
+        $return["error"] = "Ocurrió un error al leer los datos de la reposición.";
+        echo json_encode($return);
+        exit();
+    }
+    
+    $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_total"];
+
+//    $return["carrera"] = $rs["PlanEstudio_desc"];
+//    $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["profesor"] = $rs["profesor_id"];
+    $return["profesor_nombre"] = $rs["profesor_nombre"];
+    $return["comentario"] = $rs["descripcion"];
+    $return["alumnos"] = $rs["alumnos"];
+    $return["aula"] = $rs["tipoaula_id"];
+    $return["aula_desc"] = $rs["tipoaula_nombre"];
+    $return["aula_supervisor"] = $rs["tipoaula_supervisor"];
+    $return["dia"] = date('w', strtotime($rs["fecha_nueva"]));
+    $return["motivo_cancelacion"] = $rs["motivo_cancelacion"];
+    $return["estado"] = $rs["estado_reposicion_id"];
+    $return["facultad"] = $rs["facultad_nombre"];
+    $return["supervisor_nombre"] = $rs["supervisor_nombre"];
+}
+echo json_encode($return);
+?>

+ 135 - 0
action/asignacion_update.php

@@ -0,0 +1,135 @@
+<?php
+
+/* 
+ * Actualiza reposición
+ */
+$pag = "../asignacion_crear.php";
+$ruta = "../";
+require_once "../class/c_login.php";
+
+define("COORDINADOR", 9);
+define("ENVIO_CORREOS", true);
+
+// check if the session is started
+if (!isset($_SESSION['user']))
+    die('No se ha iniciado sesión');
+
+
+$user = unserialize($_SESSION['user']);
+
+
+/*if(!isset($_POST["id"]) || !isset($_POST["fecha_falta"]) || !isset($_POST["fecha_inicial"]) || !isset($_POST["hora_ini"]) || !isset($_POST["min_ini"]) || !isset($_POST["materia"]) || !isset($_POST["grupo"])){
+    header("Location: ".$pag."?error=0");
+    exit();
+}*/
+
+$id = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT);//limpia texto
+$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
+$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"];
+
+
+
+$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."<br>";
+//echo $fecha_fin_new."<br>";
+$fac_rs = $db->querySingle("SELECT facultad_nombre, clave_dependencia from facultad where facultad_id = :id_fac",[':id_fac' => $user->facultad["facultad_id"]] );
+
+$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 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 hv.facultad_id = :id_fac',
+            [':rol_coord' => COORDINADOR, ':id_fac' => $user->facultad["facultad_id"]]
+        );
+$coord_correos=[];
+foreach($correos_rs as $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->querySingle("SELECT DISTINCT sup.usuario_correo as coordinador_correo
+    FROM horario_supervisor hs
+    inner join usuario sup on sup.usuario_id =hs.usuario_id 
+    where :id_fac = ANY(hs.facultad_id_array)
+        and sup.usuario_correo is not null and sup.usuario_correo != ''",
+    [':id_fac' => $user->facultad["facultad_id"]] );
+
+
+//Valida que profesor no este en 2 reposiciones al mismo tiempo
+$traslape = $db->querySingle('SELECT * from traslape_profesor_reposicion(:prof, :fecha, :hora, :dur, :id)',
+[':prof' => $prof, ':fecha'=>DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d'), ':hora'=>$hora, ':dur'=>$duracion_tiempo, ':id'=>$id]
+)["traslape_profesor_reposicion"];
+echo "SELECT * from traslape_profesor_reposicion($prof, '".DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d')."', $hora, $duracion_tiempo, $id)";
+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_asignacion_solicitud(:id, :f_nueva, :hora_nueva, NULL, NULL, :desc, :alumnos, :aula, :duracion, NULL)',
+        [':id'=> $id, ':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_asignacion_solicitud(:id, :f_nueva, :hora_nueva, NULL, NULL, :desc, :alumnos, :aula, :duracion, NULL)'";
+    print_r(
+        [':id'=> $id, ':f_nueva' => $fecha_new, ':hora_nueva' => $hora,
+        ':desc' => $comentario, ':alumnos' => $alumnos, ':aula' => $aula, ':duracion' => $duracion_tiempo
+    ]);
+    exit();
+}
+header("Location: ".$pag);
+exit();
+?>

+ 46 - 0
action/horario_profesor.php

@@ -0,0 +1,46 @@
+<?php
+
+require_once "{$_SERVER['DOCUMENT_ROOT']}/class/c_login.php";
+header('Content-Type: application/json');
+
+if (!Login::is_logged()) {
+    header('HTTP/1.1 401 Unauthorized');
+    echo json_encode(['error' => '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(<<<SQL
+            SELECT * FROM horario
+
+            NATURAL JOIN horario_profesor 
+            NATURAL JOIN facultad
+            NATURAL LEFT JOIN materia
+            NATURAL LEFT JOIN carrera
+
+            WHERE periodo_id = ? AND profesor_id = ?
+            SQL,
+                [$user->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()
+    ]);
+}

+ 7 - 0
action/mail_javier.php

@@ -0,0 +1,7 @@
+<?php
+require_once "../class/c_login.php";
+require_once "../class/mailer.php";
+require_once('../include/phpmailer/PHPMailerAutoload.php');
+
+Mailer::enviarCorreo("javier.garrido@lasalle.mx", "Prueba de correo", "Este es un correo de prueba", "", true);
+echo "Correo enviado";

+ 198 - 93
action/reposicion_autoriza.php

@@ -27,47 +27,77 @@ if(!isset($_POST["id"]) || !isset($_POST["edo"]) ){
 
 $id_repo = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT);//limpia texto
 $edo = filter_input(INPUT_POST, "edo", FILTER_SANITIZE_NUMBER_INT);//limpia texto
+$tipo = filter_input(INPUT_POST, "tipo", FILTER_SANITIZE_NUMBER_INT);//limpia texto
 if(isset($_POST["salon"]) && $_POST["salon"] != "")
     $salon = filter_input(INPUT_POST, "salon", FILTER_SANITIZE_NUMBER_INT);//limpia texto
 //--------------
 
-//--------------
-//Obtiene datos reposición
-$reposicion_rs = $db->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_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 = "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);
     }
-    $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();
+
+if($tipo ==1 || $tipo == 2){
+    //--------------
+    //Obtiene datos reposición
+    $reposicion_rs = $db->querySingle('SELECT h.materia, r.fecha_nueva, r.hora_nueva, r.fecha_clase, r.descripcion, 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 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();
+}else{
+    //Obtiene datos asignación
+    $reposicion_rs = $db->querySingle('SELECT r.fecha_nueva, r.hora_nueva, r.descripcion, f.facultad_id, f.facultad_nombre as facultad, f.clave_dependencia, r.motivo_cancelacion, ta.tipoaula_supervisor , ta.tipoaula_nombre, p.profesor_nombre
+        from asignacion_solicitud r
+        inner join usuario u on u.usuario_id = r.usuario_id
+        inner join facultad f on f.facultad_id = u.facultad_id
+        inner join tipoaula ta on ta.tipoaula_id = r.tipoaula_id
+        inner join profesor p on p.profesor_id = r.profesor_id
+        where r.asignacion_solicitud_id = :id_repo',
+                [':id_repo' => $id_repo]
+            );
+
+    //Obtiene correos
+    $correos_rs = $db->query('SELECT p.profesor_nombre, p.profesor_correo, NULL as jefe_nombre, NULL as jefe_correo,
+            coor.usuario_nombre as coordinador_nombre, coor.usuario_correo as coordinador_correo 
+            from asignacion_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 usuario coor on u.facultad_id = coor.facultad_id and coor.rol_id = :rol_coord
+            where rs.asignacion_solicitud_id = :id_repo',
+                [':rol_coord' => COORDINADOR, ':id_repo' => $id_repo]
+            );
+
+}
         
 $prof_correos=array();
 $jefe_correos=[];
@@ -91,74 +121,150 @@ foreach($correos_rs as $correo){
     }
 }
 
-$correosSup_rs = $db->querySingle("SELECT DISTINCT sup.usuario_correo as 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)
-        and hs.turno_inicio <= :hora and hs.turno_fin >= :hora",
-    [':facultad'=>$reposicion_rs["facultad_id"], ':hora'=>$reposicion_rs["hora_nueva"]] );
-
-
-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]
+    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($tipo ==1 || $tipo == 2){
+    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{
-        $db->querySingle('SELECT fu_reposicion_solicitud(:id, NULL, NULL, NULL, NULL, :edo, NULL, NULL, NULL, NULL)',
-            [':id' => $id_repo, ':edo' => $edo]
-        );
+        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];
 
-$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 = "<p>Se creó una reposición nueva para: <b>".$reposicion_rs["clave_dependencia"]." ".$reposicion_rs["facultad"]."</b>.</p>";
-        $texto .= "<p><b>".mb_strtoupper($reposicion_rs["materia"])."</b> del día <b>".$fecha_clase." a las ".$hora_clase." hrs. </b> se propone reponer el <b>".$fecha_nueva." a las ".$hora_nueva." hrs.</b>";
-        if(!$reposicion_rs["tipoaula_supervisor"]){
-            $texto .= " en el salón: <b>".$salon_desc."</b></p>";
+    $asunto = "";
+    $texto = "";
+    $to = "";
+    switch($edo){
+        case 2://Correo a supervisor
+            $asunto = "Reposición nueva - ".$reposicion_rs["clave_dependencia"]." ".$reposicion_rs["facultad"];
+            //crear plantilla
+            $texto = "<p>Se creó una reposición nueva para: <b>".$reposicion_rs["clave_dependencia"]." ".$reposicion_rs["facultad"]."</b>.</p>";
+            $texto .= "<p><b>".mb_strtoupper($reposicion_rs["materia"])."</b> del día <b>".$fecha_clase." a las ".$hora_clase." hrs. </b> se propone reponer el <b>".$fecha_nueva." a las ".$hora_nueva." hrs.</b>";
+            if(!$reposicion_rs["tipoaula_supervisor"]){
+                $texto .= " en el salón: <b>".$salon_desc."</b></p>";
+            }else{
+                $texto .= " en un salón de tipo: <b>".$reposicion_rs["tipoaula_nombre"]."</b></p>";
+            }
+            $texto .= "<p style='font-style:italic; padding-left:25px'>".$reposicion_rs["descripcion"]."</p>";
+            $texto .= "<p>Ingresa al <a href='https://paad.lci.ulsa.mx'>sistema PAAD</a> para autorizarla.</p>";
+            $to = join(",", $sup_correos);
+            $ok = 0;
+        break;
+        case 3://Correo a coordinador, profesor y jefe
+            $asunto = "Reposición autorizada - ".$reposicion_rs["materia"];
+            $texto = "<p>La resposición de la clase de <b>".$reposicion_rs["materia"]."</b> del día <b>".$fecha_clase." a las ".$hora_clase." hrs. </b> está autorizada para realizarse el día <b>".$fecha_nueva." a las ".$hora_nueva." hrs. en: $salon_desc</b></p>";
+            $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 = "<p>La resposición de la clase de <b>".$reposicion_rs["materia"]." planeada para el día ".$fecha_nueva." a las ".$hora_nueva." hrs.</b> ha sido declinada por el siguiente motivo:</p>";
+            $texto .= "<p style='font-style:italic; padding-left:25px'>".$motivo."</p>";
+            $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;
+    }
+}else{
+    if($edo == 4){//cancelación
+        $motivo = "";
+        if(isset($_POST["motivo"]) && $_POST["motivo"] != "")
+            $motivo = trim($_POST["motivo"]);
+        $db->querySingle('SELECT fu_asignacion_cancela(:id, :motivo)',
+            [':id' => $id_repo, ':motivo' => $motivo]
+        );
+    }else{
+        if(!empty($salon)){
+            $db->querySingle('SELECT fu_asignacion_solicitud(:id, NULL, NULL, :sal, :edo, NULL, NULL, NULL, NULL, NULL)',
+                [':id' => $id_repo, ':sal' => $salon, ':edo' => $edo]
+            );
         }else{
-            $texto .= " en un salón de tipo: <b>".$reposicion_rs["tipoaula_nombre"]."</b></p>";
+            $db->querySingle('SELECT fu_asignacion_solicitud(:id, NULL, NULL, NULL, :edo, NULL, NULL, NULL, NULL, NULL)',
+                [':id' => $id_repo, ':edo' => $edo]
+            );
         }
-        $texto .= "<p>Ingresa al <a href='https://paad.lci.ulsa.mx'>sistema PAAD</a> para autorizarla.</p>";
-        $to = join(",", $correosSup_rs);
-        $ok = 0;
-    break;
-    case 3://Correo a coordinador, profesor y jefe
-        $asunto = "Reposición autorizada - ".$reposicion_rs["materia"];
-        $texto = "<p>La resposición de la clase de <b>".$reposicion_rs["materia"]."</b> del día <b>".$fecha_clase." a las ".$hora_clase." hrs. </b> está autorizada para realizarse el día <b>".$fecha_nueva." a las ".$hora_nueva." hrs. en: $salon_desc</b></p>";
-        $to = join(",", $coord_correos).",".join(",", $prof_correos).",".join(",", $jefe_correos);
-        $ok = 0;
-    break;
-    case 4://Correo a coordinador, profesor y jefe
-        $asunto = "Reposición declinada - ".$reposicion_rs["materia"];
-        $texto = "<p>La resposición de la clase de <b>".$reposicion_rs["materia"]." planeada para el día ".$fecha_nueva." a las ".$hora_nueva." hrs.</b> ha sido declinada por el siguiente motivo:</p>";
-        $texto .= "<p style='font-style:italic; padding-left:25px'>".$motivo."</p>";
-        $to = join(",", $coord_correos).",".join(",", $prof_correos).",".join(",", $jefe_correos);
-        $ok = 1;
-    break;
+    }
+    
+    $fecha_nueva = date('d/m/Y', strtotime($reposicion_rs["fecha_nueva"]));
+    $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 = "Asignación nueva - ".$reposicion_rs["clave_dependencia"]." ".$reposicion_rs["facultad"];
+            //crear plantilla
+            $texto = "<p>Se creó una solicitud de asignación nueva para: <b>".$reposicion_rs["clave_dependencia"]." ".$reposicion_rs["facultad"]."</b>.</p>";
+            $texto .= "<p>Se solicita el día <b>".$fecha_nueva." a las ".$hora_nueva." hrs.</b> para el profesor: <b>".$reposicion_rs["profesor_nombre"]."</b>.";
+            if(!$reposicion_rs["tipoaula_supervisor"]){
+                $texto .= " en el salón: <b>".$salon_desc."</b></p>";
+            }else{
+                $texto .= " en un salón de tipo: <b>".$reposicion_rs["tipoaula_nombre"]."</b></p>";
+            }
+            $texto .= "<p style='font-style:italic; padding-left:25px'>".$reposicion_rs["descripcion"]."</p>";
+            $texto .= "<p>Ingresa al <a href='https://paad.lci.ulsa.mx'>sistema PAAD</a> para autorizarla.</p>";
+            $to = join(",", $sup_correos);
+            $ok = 0;
+        break;
+        case 3://Correo a coordinador, profesor y jefe
+            $asunto = "Asignación autorizada - ".$reposicion_rs["profesor_nombre"];
+            $texto = "<p>La asignación de espacio para el profesor <b>".$reposicion_rs["profesor_nombre"]."</b> está autorizada para realizarse el día <b>".$fecha_nueva." a las ".$hora_nueva." hrs. en: $salon_desc</b></p>";
+            $to = join(",", $coord_correos).",".join(",", $prof_correos).",".join(",", $jefe_correos);
+            $ok = 0;
+            $db->querySingle('SELECT fu_asignacion_solicitud_supervisor(:id, :sup)',
+                [':id' => $id_repo, ':sup'=>$user->user["id"]]
+            );
+        break;
+        case 4://Correo a coordinador, profesor y jefe
+            $asunto = "Asignación declinada - ".$reposicion_rs["profesor_nombre"];
+            $texto = "<p>La asignación de espacio para el profesor <b>".$reposicion_rs["profesor_nombre"]."</b> planeada para el día ".$fecha_nueva." a las ".$hora_nueva." hrs.</b> ha sido declinada por el siguiente motivo:</p>";
+            $texto .= "<p style='font-style:italic; padding-left:25px'>".$motivo."</p>";
+            $to = join(",", $coord_correos).",".join(",", $prof_correos).",".join(",", $jefe_correos);
+            $ok = 1;
+            $db->querySingle('SELECT fu_asignacion_solicitud_supervisor(:id, :sup)',
+                [':id' => $id_repo, ':sup'=>$user->user["id"]]
+            );
+        break;
+    }
 }
 
+
 if($to!= "" && ENVIO_CORREOS){
     $texto = '<body >
             <img src="https://paad.lci.ulsa.mx/imagenes/logo_lasalle.png" alt="La Salle" style="margin-bottom:60px">
@@ -166,13 +272,12 @@ if($to!= "" && ENVIO_CORREOS){
         </body>';
     
     require_once('../include/phpmailer/PHPMailerAutoload.php');
-    /*if($_ENV['DB_NAME'] == "paad_pruebas"){
+    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);
-    }*/
-    Mailer::enviarCorreo("alejandro.rosales@lasalle.mx", $asunto, $texto, true);
+    }
 }
 
 /*

+ 20 - 17
action/reposicion_insert.php

@@ -20,15 +20,16 @@ $user = unserialize($_SESSION['user']);
 $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"));//limpia texto
-$fecha = trim(htmlspecialchars($_POST["fecha_inicial"], ENT_QUOTES, "UTF-8"));//limpia texto fecha de reposicion
-$fecha_cambio = trim(htmlspecialchars($_POST["fecha_cambio"], ENT_QUOTES, "UTF-8"));//limpia texto
+$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"] != "")
@@ -36,7 +37,7 @@ if(!$user->jefe_carrera){//coordinador
 }
 
 if(empty($_POST["prof"]))
-    $prof = $user["id"];
+    $prof = $user->user["id"];
 else
     $prof = filter_input(INPUT_POST, "prof", FILTER_SANITIZE_NUMBER_INT);//limpia texto
 
@@ -56,11 +57,13 @@ $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));
+
 
 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{
@@ -131,7 +134,7 @@ if($tipo == 1){//Reposición
         }
     }catch(Exception $e){
         
-        echo $e->getMessage();
+        echo "ERROR Reposición<br>".$e->getMessage();
         //header("Location: ".$pag."?error=1");
         exit();
     }
@@ -151,23 +154,23 @@ if($tipo == 1){//Reposición
     
     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_cambio, ':hora_nueva' => $hora, ':hor' => $hor,
+            $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, true, :aula, :duracion, :usr, :bloque, :ciclo, :salon)',
-                [':f_falta' => $fecha_falta, ':f_nueva' => $fecha_cambio, ':hora_nueva' => $hora, ':hor' => $hor,
+            $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){
-        
-        header("Location: ".$pag."?error=1");
+        echo "ERROR Cambio<br>".$e->getMessage();
+        //header("Location: ".$pag."?error=1");
         exit();
     }
     $texto = "<p>Se creó un cambio de salón nuevo.</p>";
@@ -191,13 +194,13 @@ if($to!= "" && ENVIO_CORREOS){
         </body>';
     
     require_once('../include/phpmailer/PHPMailerAutoload.php');
-    /*if($_ENV['DB_NAME'] == "paad_pruebas"){
+    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);
-    }*/
-    Mailer::enviarCorreo("alejandro.rosales@lasalle.mx", $asunto, $texto, true);
+    }
+    
 }
 
 header("Location: ".$pag."?ok=0");

+ 3 - 0
action/reposicion_select.php

@@ -80,6 +80,9 @@ $user = unserialize($_SESSION['user']);
     $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"];
+    $return["supervisor_nombre"] = $rs["supervisor_nombre"];
 }
 echo json_encode($return);
 ?>

+ 3 - 3
action/rutas.php

@@ -5,7 +5,7 @@ require_once "../class/c_login.php";
 
 $universidad_la_salle = $db
     ->where('salon', 'UNIVERSIDAD LA SALLE', 'ILIKE')
-    ->getOne('salon_view');
+    ->getOne('salon_view_mat');
 
 $rutas =
     array_map(
@@ -14,14 +14,14 @@ $rutas =
                 $db
                     ->where('id_espacio_padre', $ruta['id_espacio_sgu'])
                     ->orderBy('salon')
-                    ->get('salon_view');
+                    ->get('salon_view_mat');
             return $ruta;
 
         },
         $db
             ->where('id_espacio_padre', $universidad_la_salle['id_espacio_sgu'])
             ->orderBy('salon')
-            ->get('salon_view')
+            ->get('salon_view_mat')
     );
 
 // echo json_encode($universidad_la_salle, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); EXIT;

+ 33 - 29
action/rutas_salón_horario.php

@@ -31,7 +31,7 @@ try {
         $data = $db
             ->where('tiene_salones')
             ->where("{$_GET['id_espacio_sgu']} = ANY(id_espacio_sgu_array)")
-            ->get('salon_view', columns: 'id_espacio_sgu, salon, salon_id, salon_array');
+            ->get('salon_view_mat', columns: 'id_espacio_sgu, salon, salon_id, salon_array');
 
         $columns = [
             // horario
@@ -61,47 +61,51 @@ try {
             'reposicion_hora_fin',
             'salon_reposicion.salon as reposicion_salon',
         ];
+        $fecha = ($_GET['fecha'] != 'null') ? ("'{$_GET['fecha']}'" ?: 'CURRENT_DATE') : 'CURRENT_DATE';
         $data = array_map(
             fn($ruta) => array_merge(
                 [
-                    'horarios' => $db
-                        ->join('periodo', 'periodo.periodo_id = horario_view.periodo_id')
-                        ->join('bloque_horario', '(bloque_horario.hora_inicio, bloque_horario.hora_fin) OVERLAPS (horario_view.horario_hora, horario_view.horario_hora + horario_view.duracion)')
-                        ->join('salon_view', 'salon_view.salon_id = horario_view.salon_id')
-                        ->join('horario_profesor', 'horario_profesor.horario_id = horario_view.horario_id')
-                        ->join('profesor', 'profesor.profesor_id = horario_profesor.profesor_id')
-                        ->join('registro', '(registro.profesor_id, registro.horario_id, registro.registro_fecha_ideal) = (profesor.profesor_id, horario_view.horario_id, CURRENT_DATE)', 'LEFT')
-                        ->join('reposicion', 'reposicion.reposicion_id = registro.reposicion_id', 'LEFT')
-                        ->join('salon as salon_reposicion', 'salon_reposicion.salon_id = reposicion.salon_id', 'LEFT')
-                        ->where('CURRENT_DATE BETWEEN periodo.periodo_fecha_inicio AND periodo.periodo_fecha_fin')
-                        ->where('horario_dia = EXTRACT(DOW FROM CURRENT_DATE)')
-                        ->where('bloque_horario.id', $_GET['bloque_horario_id'])
-                        ->where('salon_view.id_espacio_padre', $ruta['id_espacio_sgu'])
-                        ->orderBy('horario_hora')
-                        ->orderBy('salon_view.salon')
-                        ->get(
-                            'horario_view',
-                            columns: $columns
-                        ),
+                    'horarios' => $db->query(
+                        "SELECT " . implode(', ', $columns) . <<<SQL
+                            FROM horario_view
+                            NATURAL JOIN periodo
+                            JOIN bloque_horario ON (bloque_horario.hora_inicio, bloque_horario.hora_fin) OVERLAPS (horario_view.horario_hora, horario_view.horario_hora + horario_view.duracion)
+                            NATURAL JOIN salon_view_mat
+                            NATURAL JOIN horario_profesor
+                            NATURAL JOIN profesor
+                            LEFT JOIN registro ON (registro.profesor_id, registro.horario_id, registro.registro_fecha_ideal) = (profesor.profesor_id, horario_view.horario_id, $fecha::DATE)
+                            NATURAL LEFT JOIN reposicion
+                            LEFT JOIN salon AS salon_reposicion ON salon_reposicion.salon_id = reposicion.salon_id
+                            WHERE $fecha::DATE BETWEEN periodo.periodo_fecha_inicio AND periodo.periodo_fecha_fin
+                            AND horario_dia = EXTRACT(DOW FROM $fecha::DATE)
+                            AND bloque_horario.id = :bloque_horario_id
+                            AND salon_view_mat.id_espacio_padre = :id_espacio_sgu
+                            ORDER BY horario_hora, salon_view_mat.salon;
+                    SQL,
+                        [
+                            'bloque_horario_id' => $_GET['bloque_horario_id'],
+                            'id_espacio_sgu' => $ruta['id_espacio_sgu'],
+                        ]
+                    ),
                     // 'query' => $db->getLastQuery(),
                     'reposiciones' => $db->query(
                         'SELECT ' . implode(', ', $columns) . <<<SQL
-                            , reposicion_hora + horario_view.duracion as reposicion_fin
+                            , reposicion_hora + horario_view.duracion as reposicion_fin, registro_fecha_ideal
                             FROM horario_view
-                                JOIN periodo USING (periodo_id)
-                                JOIN registro USING (horario_id)
-                                JOIN reposicion USING (reposicion_id)
+                                NATURAL JOIN periodo
+                                NATURAL JOIN registro
+                                NATURAL JOIN reposicion
                                 JOIN bloque_horario ON (bloque_horario.hora_inicio, bloque_horario.hora_fin) OVERLAPS (reposicion_hora, reposicion_hora + horario_view.duracion)
-                                JOIN profesor USING (profesor_id)
-                                JOIN salon_view as salon_reposicion ON (salon_reposicion.salon_id = reposicion.salon_id)
+                                NATURAL JOIN profesor
+                                JOIN salon_view_mat as salon_reposicion ON (salon_reposicion.salon_id = reposicion.salon_id)
                             WHERE
-                                CURRENT_DATE BETWEEN periodo.periodo_fecha_inicio
+                                $fecha::DATE BETWEEN periodo.periodo_fecha_inicio
                                 AND periodo.periodo_fecha_fin
-                                AND reposicion_fecha = CURRENT_DATE
+                                AND reposicion_fecha = $fecha::DATE
                                 AND bloque_horario.id = :bloque_horario_id
                                 AND salon_reposicion.id_espacio_padre = :id_espacio_sgu
                                 ORDER BY reposicion_hora
-                            SQL,
+                    SQL,
                         [
                             'bloque_horario_id' => $_GET['bloque_horario_id'],
                             'id_espacio_sgu' => $ruta['id_espacio_sgu'],

+ 3 - 15
action/usuario_find.php

@@ -3,21 +3,9 @@ $ruta = '../';
 require_once '../include/bd_pdo.php';
 global $pdo;
 
-if ($_POST['nombre'] == "") {
-    $nombre = null;
-} else {
-    $nombre = $_POST['nombre'];
-}
-if ($_POST['clave'] == "") {
-    $clave = null;
-} else {
-    $clave = $_POST['clave'];
-}
-if ($_POST['facultad'] == "") {
-    $facultad = null;
-} else {
-    $facultad = $_POST['facultad'];
-}
+$nombre = $_POST['nombre'] ?: null;
+$clave = $_POST['clave'] ?: null;
+$facultad = $_POST['facultad'] ?: null;
 
 echo json_encode($db->query("SELECT * FROM fs_profesores(:nombre, :clave, :facultad) ORDER BY profesor_nombre", [':nombre' => $nombre, ':clave' => $clave, ':facultad' => $facultad]));
 ?>

+ 52 - 41
asignacion_crear.php

@@ -24,11 +24,15 @@ if ($user->acceso === null && !$user->admin){
     exit();
 }
 
-//if (!$user->admin && in_array($user->acceso, ['n']))
-    //die(header('Location: main.php?error=1'));
-//$user->print_to_log('Reposiciones');
+$jefatura = false;
+$coordinador = false;
+if($user->rol["rol_id"]==11){
+    $jefatura = true;
+}
+if($user->rol["rol_id"]==9){
+    $coordinador = true;
+}
 
-//$write = $user->admin || in_array($user->acceso, ['w']);
 $write = true; //
 
 $en_fecha = $db->querySingle("SELECT ESTA_EN_PERIODO(NOW()::DATE, :periodo_id)", [':periodo_id' => $user->periodo_id])['esta_en_periodo'];
@@ -166,14 +170,14 @@ if(!is_null($user->periodo_id)){
         </form>
 
         <?php
-            $reposiciones_rs = $db->query('SELECT * FROM fs_reposiciones_solicitud(:f_ini, :f_fin, :usr ,NULL, NULL)', [':f_ini' => $fecha_ini_db, ':f_fin' => $fecha_fin_db, ':usr' => $user->user["id"]]);
+            $asignaciones_rs = $db->query('SELECT * FROM fs_asignaciones_solicitud(:f_ini, :f_fin, :usr ,NULL)', [':f_ini' => $fecha_ini_db, ':f_fin' => $fecha_fin_db, ':usr' => $user->user["id"]]);
         }
         ?>
 
         <div class="row">
             <?php
-            if(isset($reposiciones_rs) && count($reposiciones_rs)>0){ ?>
-            <h3 class="mb-3">Reposiciones creadas</h3>
+            if(isset($asignaciones_rs) && count($asignaciones_rs)>0){ ?>
+            <h3 class="mb-3">Asignaciones creadas</h3>
             <div class="col-12 table-responsive px-0">
                 <table class="table table-sm table-striped table-white">
                     <thead class="thead-dark">
@@ -188,16 +192,16 @@ if(!is_null($user->periodo_id)){
                     </thead>
                     <tbody>
                         <?php
-                        foreach($reposiciones_rs as $reposicion){
+                        foreach($asignaciones_rs as $asignacion){
                         ?>
-                        <tr data-id="<?php echo $reposicion["reposicion_id"]; ?>" id="id<?php echo $reposicion["reposicion_id"]; ?>">
-                            <td class="align-middle text-center" style="color:<?php echo $reposicion["estado_color"];?>" title="<?php echo $reposicion["estado_nombre"];?>">
-                                <?php if($reposicion["estado_reposicion_id"]<3){ ?>
-                                <div class="wizard <?php if(intval($reposicion["estado_reposicion_id"])==2) echo "active";?> d-flex mx-auto">
+                        <tr data-id="<?php echo $asignacion["asignacion_solicitud_id"]; ?>" id="id<?php echo $asignacion["asignacion_solicitud_id"]; ?>">
+                            <td class="align-middle text-center" style="color:<?php echo $asignacion["estado_color"];?>" title="<?php echo $asignacion["estado_nombre"];?>">
+                                <?php if($asignacion["estado_reposicion_id"]<3){ ?>
+                                <div class="wizard <?php if(intval($asignacion["estado_reposicion_id"])==2) echo "active";?> d-flex mx-auto">
                                     <div class="w-50 h-100"></div>
                                     <div class=""></div>
                                 </div>
-                                <?php } else if($reposicion["estado_reposicion_id"]==3){?>
+                                <?php } else if($asignacion["estado_reposicion_id"]==3){?>
                                 <div class="text-success text-center pt-1">
                                     <span class="ing-autorizar ing-lg"></span>
                                 </div>
@@ -207,19 +211,19 @@ if(!is_null($user->periodo_id)){
                                 </div>
                                 <?php } ?>
                             </td>
-                            <td class="align-middle"><?php echo $reposicion["profesor_nombre"]; ?></td>
+                            <td class="align-middle"><?php echo $asignacion["profesor_nombre"]; ?></td>
                             <td class="align-middle text-center"><?php
                                 
-                                echo date("d/m/Y", strtotime($reposicion["fecha_nueva"])) ."<br>".substr($reposicion["hora_nueva"],0,-3)." a ".substr($reposicion["hora_nueva_fin"],0,-3)." hrs.";
+                                echo date("d/m/Y", strtotime($asignacion["fecha_nueva"])) ."<br>".substr($asignacion["hora_nueva"],0,-3)." a ".substr($asignacion["hora_nueva_fin"],0,-3)." hrs.";
                                 ?>
                             </td>
                             <td class="align-middle text-center"><?php
-                                echo $reposicion["duracion_total"];
+                                echo $asignacion["duracion_total"];
                                 ?>
                             </td>
                             <td class="align-middle text-center"><?php
-                                if($reposicion["salon_id"] != ""){
-                                    echo $reposicion["salon_id"];
+                                if($asignacion["salon_id"] != ""){
+                                    echo $asignacion["salon_id"];
                                 }else
                                     echo "Pendiente";
                                 ?>
@@ -228,9 +232,9 @@ if(!is_null($user->periodo_id)){
                             <?php if($write){ ?>
                             <td class="align-middle text-center icono-acciones">
                                 <?php
-                                
                                 //no se ha aprobado
-                                if(($reposicion["estado_reposicion_id"] == 1 && $user->jefe_carrera) || ($reposicion["estado_reposicion_id"] == 2 && !$user->jefe_carrera)){?>
+                                
+                                if(($asignacion["estado_reposicion_id"] == 1 && $jefatura) || ($asignacion["estado_reposicion_id"] == 2 && $coordinador)){?>
                                 <a href="#" data-tipo="2" title="Editar" data-toggle="modal" data-target="#modal"><?php echo $ICO["editar"];?></a>
                                 <a href="#" data-toggle="modal" data-target="#modal_confirm" title="Borrar"><?php echo $ICO["cancelar"];?></a>
                                 <?php } ?>
@@ -349,9 +353,11 @@ if(!is_null($user->periodo_id)){
                                             <div class="datalist-input">Salón</div>
                                             <span class="ing-buscar icono"></span>
                                             <ul style="display:none">
-                                                <li data-id="1">Salón</li>
-                                                <li data-id="2">Sala de cómputo</li>
-                                                <li data-id="3">Salón/Taller de la facultad</li>
+                                                <?php
+                                                $tipoaula_rs = $db->query('select * from tipoaula t order by t.tipoaula_id ');
+                                                foreach($tipoaula_rs as $ta){ ?>
+                                                    <li data-id="<?php echo $ta["tipoaula_id"];?>"><?php echo $ta["tipoaula_nombre"];?></li>
+                                                <?php } ?>
                                             </ul>
                                             <input type="hidden" id="aula" name="aula" value="1">
                                         </div>
@@ -367,12 +373,19 @@ if(!is_null($user->periodo_id)){
                                 </div>
                             </div>
                             
-                            <div class="form-group row mt-3">
+                            <div class="form-group row mt-3" id="submitGroup">
                                 <div class="offset-4 col-8">
                                     <button type="submit" class="btn btn-outline-primary  materia-block" id="submitBtn" data-tipo="1"><?php echo $ICO["aceptar"];?> Guardar</button>
                                     <button type="reset" class="btn btn-outline-danger" data-dismiss="modal"><?php echo $ICO["cancelar"];?> Cancelar</button>
                                 </div>
                             </div>
+                            <div class="form-group row mt-3" id="loadingGroup" style="display:none">
+                                <div class="col-12 text-center">
+                                    <div class="spinner-border text-primary" role="status">
+                                        <span class="sr-only">Loading...</span>
+                                    </div>
+                                </div>
+                            </div>
                         </form>
                     </div>
                 </div>
@@ -507,33 +520,27 @@ if(!is_null($user->periodo_id)){
         $("#fecha_inicial").removeClass("is-invalid");
         $("#fecha_falta").removeClass("is-invalid");
         $("#fecha_cambio").removeClass("is-invalid");
-
         
-        if($("#tipo").val() == 1){//reposición
-            if($("#fecha_falta").val() == ""){
-                $("#fecha_falta").addClass("is-invalid");
-                error = true;
-            }
-            if($("#fecha_inicial").val() == ""){//fecha reposición
-                $("#fecha_inicial").addClass("is-invalid");
-                error = true;
-            }    
-        }else{
-            if($("#fecha_cambio").val() == ""){
-                $("#fecha_cambio").addClass("is-invalid");
-                error = true;
-            }
+        if($("#fecha_falta").val() == ""){
+            $("#fecha_falta").addClass("is-invalid");
+            error = true;
         }
-        if($("#horario").val().trim() == "" || $("#horario").val() === null){
-            invalidDatalist("#horario", true);
+        if($("#fecha_inicial").val() == ""){//fecha reposición
+            $("#fecha_inicial").addClass("is-invalid");
             error = true;
         }
         
+        console.log(myBtn.data("tipo"));
+        
         if(myBtn.data("tipo") == 2 ){
             $('#formaModal').prop("action", "./action/asignacion_update.php");
         }else{
             $('#formaModal').prop("action", "./action/asignacion_insert.php");
         }
+        if(!error){
+            $("#loadingGroup").show();
+            $("#submitGroup").hide();
+        }
         return !error;
     }
 
@@ -653,6 +660,7 @@ if(!is_null($user->periodo_id)){
             var button = $(event.relatedTarget); // Button that triggered the modal
             var id = button.parents("tr").data("id");
             $("#id_borrar").val(id);
+            
         });
         
         $(".btn-borrar").click(function(){
@@ -682,9 +690,12 @@ if(!is_null($user->periodo_id)){
             var button = $(event.relatedTarget); // Button that triggered the modal
             var tipo = button.data('tipo'); // 1 alta, 2 edicion
             var modal = $(this);
+            $("#loadingGroup").hide();
+            $("#submitGroup").show();
             
             $("#modal .is-invalid").removeClass("is-invalid");
             //$(this).find(".form-control:first-child").focus();
+            $('#submitBtn').attr("disabled", false);
             
 
             $("#errorBox").collapse('hide');

+ 927 - 0
cambio_crear.php

@@ -0,0 +1,927 @@
+<?php
+
+require_once 'class/c_login.php';
+if (!isset($_SESSION['user'])){
+    die(header('Location: index.php'));
+}
+
+//$user = unserialize($_SESSION['user']);
+$user = Login::get_user();
+
+$user->access();
+//profesor, admin, rol, facultad
+if ($user->acceso === null && !$user->admin){
+    die(header('Location: index.php'));
+    exit();
+}
+
+
+$write = true; //
+
+$en_fecha = $db->querySingle("SELECT ESTA_EN_PERIODO(NOW()::DATE, :periodo_id)", [':periodo_id' => $user->periodo_id])['esta_en_periodo'];
+
+
+if($user->jefe_carrera){
+    //$prof_rs = $db->query('SELECT DISTINCT * FROM fs_profesores(null, null, :fac) ORDER BY PROFESOR_NOMBRE', [':fac' => $user->facultad["facultad_id"]]);
+    $prof_rs = $db->query('SELECT DISTINCT PROFESOR.* FROM PUESTO_USUARIO
+        JOIN PUESTO_MATERIA USING (PUESTO_ID)
+        JOIN HORARIO_VIEW USING (MATERIA_ID)
+        JOIN HORARIO_PROFESOR USING (HORARIO_ID)
+        JOIN PROFESOR USING (PROFESOR_ID)
+        WHERE USUARIO_ID = :usr', [':usr' => $user->user["id"]]);
+}else{
+    $prof_rs = $db->query('SELECT DISTINCT PROFESOR.* FROM PROFESOR
+        JOIN horario_profesor USING (profesor_id)
+        JOIN HORARIO_VIEW USING (horario_id)
+        WHERE FACULTAD_ID = :fac ORDER BY profesor.profesor_nombre', [':fac' => $user->facultad["facultad_id"]]);
+}
+
+//Duraciones
+$duracion_rs = $db->query("select * from duracion order by duracion_interval");
+
+if(!is_null($user->periodo_id)){
+    //Periodo
+    $periodo_rs = $db->querySingle('SELECT periodo_fecha_inicio, periodo_fecha_fin FROM periodo WHERE periodo_id = :periodo_id', [':periodo_id' => $user->periodo_id]);
+    $periodo_fin = $periodo_rs["periodo_fecha_fin"];
+    if(strtotime($periodo_rs["periodo_fecha_inicio"])>strtotime(date("Y-m-d")) )
+        $fecha_man = date("d/m/Y", strtotime($periodo_rs["periodo_fecha_inicio"]));
+    else{
+        $dia_actual = intval(date("w"));
+        $dias = 2;//días mínimos Lun a Jue
+        if($dia_actual ==5 || $dia_actual ==4 )//Vie
+            $dias=4;
+        else if( $dia_actual ==6 )//Sab
+            $dias=3;
+        else if( $dia_actual ==0 )//Do
+            $dias=2;
+        
+        $fecha_man = date("d/m/Y", strtotime("+".$dias." day"));
+    }
+    /*
+    // Materias
+    $id_prof = $user->profesor;
+    //$facultad_id = 28;
+    $materias_rs = $db->query('SELECT * FROM fs_materiasprofesor(:id)', [':id' => $id_prof]);
+    */
+    if(isset($_POST["fecha_inicial"]))
+        $fecha_ini = $_POST["fecha_inicial"];
+    else
+        $fecha_ini = date("d/m/Y", strtotime($periodo_rs["periodo_fecha_inicio"]));
+
+    if(isset($_POST["fecha_final"]))
+        $fecha_fin = $_POST["fecha_final"];
+    else
+        $fecha_fin = date("d/m/Y", strtotime($periodo_rs["periodo_fecha_fin"]));
+
+    $date = DateTime::createFromFormat('d/m/Y', $fecha_ini);
+    $fecha_ini_db = $date->format('Y-m-d');
+
+    $date = DateTime::createFromFormat('d/m/Y', $fecha_fin);
+    $fecha_fin_db = $date->format('Y-m-d');
+}
+
+?>
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Reposiciones crear |
+        <?= $user->facultad['facultad'] ?? "Administrador"; ?>
+    </title>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
+        integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
+        crossorigin="anonymous" referrerpolicy="no-referrer" />
+    <?php
+    include 'import/html_css_files.php';
+    ?>
+    <link rel="stylesheet" href="css/jquery-ui.css">
+    <link rel="stylesheet" href="css/calendar.css">
+    <style>
+        .wizard { height: 20px; width: 80%; background: #D0D0D0; }
+        .wizard.full { background: #D0D0D0; }
+        .wizard.active > div:first-child { background: #00A6CE;  }
+        .wizard.active > div:last-child { width: 0px; height: 0px; border-style: solid; border-width: 10px 0 10px 6px; border-color: transparent transparent transparent #00a6ce; transform: rotate(0deg); }
+    </style>
+    <script src="js/jquery.min.js"></script>
+    <script src="js/bootstrap/popper.min.js"></script>
+    <script src="js/bootstrap/bootstrap.min.js"></script>
+    <script src="js/jquery-ui.js"></script>
+    <script src="js/datepicker-es.js"></script>
+</head>
+
+
+<!--  -->
+
+<body style="display: block;">
+    <?php
+    include('include/constantes.php');
+    include("import/html_header.php");
+    html_header("Cambios permanentes de horario", "Sistema de gestión de checador");
+    ?>
+
+    <main class="container content marco content-margin" id="local-app">
+        <?php
+        if($write==true && isset($prof_rs) && count($prof_rs)>0)  {?>
+        <!-- Botón para abrir el modal -->
+        <div class="row mb-4">
+            <div class="col-12 text-right">
+                <button type="button" class="btn btn-outline-secondary" data-tipo="1" data-toggle="modal" data-target="#modal" <?php if (!$en_fecha ) { echo "disabled"; } ?>><span class="ing-mas ing-fw"></span>Solicitar cambio</button>
+            </div>
+        </div>
+        <?php }?>
+        <section id="message"></section>
+        <?php require('import/periodo.php') ?>
+        <?php if(!is_null($user->periodo_id)) { ?>
+        <form id="asistencia" method="post" onsubmit="return validaFechas()">
+            <div class="form-box">
+                <input type="hidden" name="facultad" value="">
+                
+                <div class="form-group row">
+                    <label for="filtro_inicial" class="col-4 col-form-label">Fecha inicial</label>
+                    <div class="col-8 col-sm-4">
+                        <input id="filtro_inicial" name="fecha_inicial" type="text" class="form-control date-picker-filtro" placeholder="dd/mm/aaaa" maxlength="10" required="required" readonly="" value="<?php echo $fecha_ini;?>">
+                        <div class="invalid-feedback">No es una fecha válida.</div>
+                    </div>
+                </div>
+                <div class="form-group row">
+                    <label for="filtro_final" class="col-4 col-form-label">Fecha final</label>
+                    <div class="col-8 col-sm-4">
+                        <input id="filtro_final" name="fecha_final" type="text" class="form-control date-picker-filtro" placeholder="dd/mm/aaaa" maxlength="10" required="required" readonly="" value="<?php echo $fecha_fin;?>">
+                        <div class="invalid-feedback">El rango de fechas no es válido.</div>
+                    </div>
+                </div>
+            </div>
+            <div class="form-group row justify-content-center">
+                <button type="submit" class="btn btn-outline-primary mr-2" id="btn-buscar"><span class="ing-buscar ing-fw"></span> Buscar</button>
+                <button type="button" class="btn btn-outline-danger" onclick="window.location.href = window.location.href"><span class="ing-borrar ing-fw"></span> Limpiar</button>
+            </div>
+        </form>
+
+        <?php
+        
+            $reposiciones_rs = $db->query('SELECT * FROM fs_cambios_solicitud(:f_ini, :f_fin, :usr ,NULL)', [':f_ini' => $fecha_ini_db, ':f_fin' => $fecha_fin_db, ':usr' => $user->user["id"]]);
+        }
+        ?>
+
+        <div class="row">
+            <?php
+            if(isset($reposiciones_rs) && count($reposiciones_rs)>0){ ?>
+            <h3 class="mb-3">Reposiciones creadas</h3>
+            <div class="col-12 table-responsive px-0">
+                <table class="table table-sm table-striped table-white">
+                    <thead class="thead-dark">
+                        <tr >
+                            <th>Estado</th>
+                            <th>Materia</th>
+                            <th>Profesor</th>
+                            <th style="width:160px">Fecha</th>
+                            <th style="width:160px">Duración</th>
+                            <th>Salón</th>
+                            <?php if($write){ ?><th>Acciones</th><?php } ?>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        <?php
+                        foreach($reposiciones_rs as $reposicion){
+                        ?>
+                        <tr data-id="<?php echo $reposicion["reposicion_id"]; ?>" id="id<?php echo $reposicion["reposicion_id"]; ?>">
+                            <td class="align-middle text-center" style="color:<?php echo $reposicion["estado_color"];?>" title="<?php echo $reposicion["estado_nombre"];?>">
+                                <?php if($reposicion["estado_reposicion_id"]<3){ ?>
+                                <div class="wizard <?php if(intval($reposicion["estado_reposicion_id"])==2) echo "active";?> d-flex mx-auto">
+                                    <div class="w-50 h-100"></div>
+                                    <div class=""></div>
+                                </div>
+                                <?php } else if($reposicion["estado_reposicion_id"]==3){?>
+                                <div class="text-success text-center pt-1">
+                                    <span class="ing-autorizar ing-lg"></span>
+                                </div>
+                                <?php } else {?>
+                                <div class="text-danger text-center pt-1">
+                                    <span class="ing-negar ing-lg"></span>
+                                </div>
+                                <?php } ?>
+                            </td>
+                            <td class="align-middle"><?php echo $reposicion["materia_nombre"]; ?></td>
+                            <td class="align-middle">
+                                <?php if($reposicion["es_reposicion"]) echo "Reposición"; else echo "Cambio"; ?>
+                            </td>
+                            <td class="align-middle text-center"><?php
+                                echo date("d/m/Y", strtotime($reposicion["fecha_clase"]))."<br>".substr($reposicion["horario_hora"],0,-3)." a ".substr($reposicion["horario_hora_fin"],0,-3)." hrs.";;
+                                ?>
+                            </td>
+                            <td class="align-middle text-center"><?php
+                                
+                                echo date("d/m/Y", strtotime($reposicion["fecha_nueva"])) ."<br>".substr($reposicion["hora_nueva"],0,-3)." a ".substr($reposicion["hora_nueva_fin"],0,-3)." hrs.";
+                                ?>
+                            </td>
+                            <td class="align-middle text-center"><?php
+                                echo $reposicion["duracion_total"];
+                                ?>
+                            </td>
+                            <td class="align-middle text-center"><?php
+                                if($reposicion["salon_id"] != ""){
+                                    echo $reposicion["salon_id"];
+                                }else
+                                    echo "Pendiente";
+                                ?>
+                            </td>
+                            
+                            <?php if($write){ ?>
+                            <td class="align-middle text-center icono-acciones">
+                                <?php
+                                
+                                //no se ha aprobado
+                                if(($reposicion["estado_reposicion_id"] == 1 && $user->jefe_carrera) || ($reposicion["estado_reposicion_id"] == 2 && !$user->jefe_carrera)){?>
+                                <a href="#" data-tipo="2" title="Editar" data-toggle="modal" data-target="#modal"><?php echo $ICO["editar"];?></a>
+                                <a href="#" data-toggle="modal" data-target="#modal_confirm" title="Borrar"><?php echo $ICO["cancelar"];?></a>
+                                <?php } ?>
+                            </td>
+                            <?php } ?>
+                        </tr>
+                        <?php }
+                    ?>
+                    </tbody>
+                </table>
+            </div>
+            <?php } else { 
+                if(is_null($user->periodo_id)){ ?>
+            <div class="col-12 text-center">
+                <h4 class="mt-4 text-danger">Selecciona un periodo</h4>
+            </div>
+            <?php } else {?>    
+            <div class="col-12 text-center">
+                <h4 class="mt-4 text-danger">No hay solicitudes de cambio disponibles que cumplan con los filtros</h4>
+            </div>
+            <?php }
+            } ?>
+        </div>
+
+        <div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="modal" aria-hidden="true">
+            <div class="modal-dialog modal-dialog-centered modal-xl" role="document">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h4 class="col-12 modal-title text-center"><span id="modalLabel">Solicitar Cambio</span>
+                        <button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
+                            <span aria-hidden="true">&times;</span>
+                        </button></h4>
+                    </div>
+                    <div class="modal-body">
+                        <form action="./action/cambio_insert.php" method="post" id="formaModal" onsubmit="return submitForm()">
+                            <input type="hidden" name="id" id="id">
+                            <input type="hidden" name="estado" value="1">
+                            <input type="hidden" name="ciclo" id="ciclo" value="0">
+                            <input type="hidden" name="bloque" id="bloque" value="0">
+                            
+                            <div class="form-box">
+
+                                <div class="form-group row" id="profBlock">
+                                    <label for="prof" class="col-4 col-form-label">Profesor *</label> 
+                                    <div class="col-8">
+                                        <div class="datalist datalist-select mb-1 w-100" id="dlProfesor">
+                                            <div class="datalist-input">Selecciona un profesor</div>
+                                            <span class="ing-buscar icono"></span>
+                                            <ul style="display:none">
+                                                <?php foreach($prof_rs as $prof){?>
+                                                    <li data-id="<?php echo $prof["profesor_id"];?>" <?php if($prof["profesor_id"]==$user->profesor){ echo "class='selected'";} ?> ><?php echo $prof["profesor_nombre"];?></li>
+                                                <?php } ?>
+                                            </ul>
+                                            <input type="hidden" id="prof" name="prof" value="">
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="form-box prof-selected">
+                                <div class="form-group row" id="materiaBlock">
+                                    <label for="horario" class="col-4 col-form-label">Materia *</label> 
+                                    <div class="col-8">
+                                        <div class="datalist datalist-select mb-1 w-100" id="dlMateria">
+                                            <div class="datalist-input">Selecciona una materia</div>
+                                            <span class="ing-buscar icono"></span>
+                                            <ul style="display:none">
+                                            
+                                            </ul>
+                                            <input type="hidden" id="horario" name="horario" value="">
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <div class="form-group row" id="profBlock">
+                                    <label for="prof" class="col-4 col-form-label">Profesor nuevo*</label> 
+                                    <div class="col-8">
+                                        <div class="datalist datalist-select mb-1 w-100" id="dlProfesor">
+                                            <div class="datalist-input">Selecciona un profesor</div>
+                                            <span class="ing-buscar icono"></span>
+                                            <ul style="display:none">
+                                                <?php foreach($prof_rs as $prof){?>
+                                                    <li data-id="<?php echo $prof["profesor_id"];?>" <?php if($prof["profesor_id"]==$user->profesor){ echo "class='selected'";} ?> ><?php echo $prof["profesor_nombre"];?></li>
+                                                <?php } ?>
+                                            </ul>
+                                            <input type="hidden" id="prof" name="prof" value="">
+                                        </div>
+                                    </div>
+                                </div>
+                                
+
+                                <div class="form-group row materia-block">
+                                    <label for="duracion" class="col-4 col-form-label">Duración</label> 
+                                    <div class="col-4">
+                                        <select name="duracion" id="duracion" class="form-control" required="required">
+                                            <?php foreach($duracion_rs as $dura){?>
+                                            <option value="<?php echo $dura["duracion_id"];?>" data-duracion="<?php echo $dura["duracion_interval"];?>" ><?php echo $dura["duracion_nombre"];?></option>
+                                            <?php } ?>
+                                        </select>
+                                        <small class="form-text text-muted">Original: <span id="duracion_original">0</span></small>
+                                    </div>
+                                </div>
+                                
+                                <div class="form-group row cambio_block materia-block">
+                                    <label for="fecha_cambio" class="col-4 col-form-label">Fecha de cambio *</label> 
+                                    <div class="col-8">
+                                        <small class="form-text text-muted">Selecciona el día a partir del cuál se aplicará</small>
+                                        <input id="fecha_cambio" name="fecha_cambio" type="text" class="form-control date-picker" placeholder="dd/mm/aaaa" maxlength="10" required="required" readonly="readonly" value="">
+                                    </div>
+                                </div>
+
+                                
+                                <div class="form-group row materia-block">
+                                    <label for="hora_ini" class="col-4 col-form-label" id="hora_nombre">Hora cambio *</label>
+                                    <?php
+                                    //define("HORA_FINAL", 22);
+                                    //define("FRACCION_HORA", 15);
+                                    $default_h = 7; $default_m = 15;
+                                    ?>
+                                    <div class="col-4">
+                                        <select name="hora_ini" id="hora_ini" class="form-control" required="required">
+                                            <?php for($h = $default_h; $h < HORA_FINAL; $h++){?>
+                                            <option value="<?php echo sprintf( '%02d', $h );?>" <?php if($default_h == $h){ echo 'selected="selected"';}?>><?php echo sprintf( '%02d', $h );?></option>
+                                            <?php } ?>
+                                        </select>
+                                        <small class="form-text text-muted">Original: <span id="hora_original">0</span></small>
+                                    </div>
+                                    <div class="col-4">
+                                        <select name="min_ini" id="min_ini" class="form-control" required="required">
+                                            <?php for($m = 0; $m < 60; $m+=(60/FRACCION_HORA)){?>
+                                            <option value="<?php echo sprintf( '%02d', $m );?>" <?php if($default_m == $m){ echo 'selected="selected"';}?>><?php echo sprintf( '%02d', $m );?></option>
+                                            <?php } ?>
+                                        </select>
+                                    </div>
+                                </div>
+
+                                
+                                <div class="form-group row materia-block">
+                                    <label for="salon" class="col-4 col-form-label">Alumnos aproximados *</label> 
+                                    <div class="col-8 col-md-4">
+                                        <input type="number" name="alumnos" id="alumnos" class="form-control" value="1" min="1" max="50">
+                                    </div>
+                                </div>
+                                
+                                <div class="form-group row materia-block">
+                                    <label for="aula" class="col-4 col-form-label">Tipo aula *</label> 
+                                    <div class="col-8">
+                                        <div class="datalist datalist-select mb-1 w-100" id="dlAula">
+                                            <div class="datalist-input">Salón</div>
+                                            <span class="ing-buscar icono"></span>
+                                            <ul style="display:none">
+                                                <?php
+                                                $tipoaula_rs = $db->query('select * from tipoaula t order by t.tipoaula_id ');
+                                                foreach($tipoaula_rs as $ta){ ?>
+                                                    <li data-id="<?php echo $ta["tipoaula_id"];?>"><?php echo $ta["tipoaula_nombre"];?></li>
+                                                <?php } ?>
+                                            </ul>
+                                            <input type="hidden" id="aula" name="aula" value="1">
+                                        </div>
+                                    </div>
+                                </div>
+                                <?php if(!$user->jefe_carrera){//es coordinador
+                                    $salones_rs = $db->query('SELECT * from salon_view where es_salon is true');
+                                ?>
+                                <div class="row" id="salon-editar" style="display: none;">
+                                    <div class="col-6 col-sm-4 barra-right text-right">
+                                        <p class="font-weight-bold">Salón *</p>
+                                    </div>
+                                    <div class="col-6">
+                                        <input list="lista_salones" name="dlSalon" id="dlSalon" class="form-control" placeholder="Salón">
+                                        <div class="valid-feedback">
+                                            Salón encontrado
+                                        </div>
+                                        <div class="invalid-feedback">
+                                            Salón no encontrado
+                                        </div>
+                                        <datalist id="lista_salones">
+                                            <?php
+                                            foreach ($salones_rs as $salon) {
+                                                extract($salon);
+                                                $salon_json = json_decode($salon_array, true);
+                                                if($salon_json[0]== "UNIVERSIDAD LA SALLE"){
+                                                    unset($salon_json[0]);
+                                                }
+                                                $salon_nombre = join(" / ",$salon_json);
+                                            ?>
+                                                <option data-id="<?= $salon_id ?>" data-nombre="<?= $salon_nombre ?>" value="<?= $salon_nombre ?>"></option>
+                                            <?php
+                                            }
+                                            ?>
+                                        </datalist>
+                                        <!-- <ul class="list-group" id="salones"></ul> -->
+                                        <input type="hidden" id="salon" name="salon" value="">
+                                    </div>
+                                </div>
+                                <?php } ?>
+                            
+                                <div class="form-group row materia-block">
+                                    <label for="comentario" class="col-4 col-form-label">Comentarios</label> 
+                                    <div class="col-8">
+                                        <p><i>Requerimientos específicos del salón, software especializado, etc.</i></p>
+                                        <textarea rows="3" class="form-control" id="comentario" name="comentario"></textarea>
+                                    </div>
+                                </div>
+                            </div>
+                            
+                            <div class="form-group row mt-3" id="submitGroup">
+                                <div class="offset-4 col-8">
+                                    <button type="submit" class="btn btn-outline-primary  materia-block" id="submitBtn" data-tipo="1"><?php echo $ICO["aceptar"];?> Guardar</button>
+                                    <button type="reset" class="btn btn-outline-danger" data-dismiss="modal"><?php echo $ICO["cancelar"];?> Cancelar</button>
+                                </div>
+                            </div>
+                            <div class="form-group row mt-3" id="loadingGroup" style="display:none">
+                                <div class="col-12 text-center">
+                                    <div class="spinner-border text-primary" role="status">
+                                        <span class="sr-only">Loading...</span>
+                                    </div>
+                                </div>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+
+        
+    
+        <div class="modal fade" id="modal_confirm" tabindex="-1" role="dialog" aria-labelledby="modal" aria-hidden="true">
+            <div class="modal-dialog modal-dialog-centered" role="document">
+                <div class="modal-content">
+                    <div class="modal-body">
+                        <div class="row">
+                            <div class="col">
+                                <p class="font-weight-bold">¿Estás seguro de que quieres borrar la reposición?</p>
+                                <p>Esta acción no se puede deshacer.</p>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="modal-footer">
+                        <input type="hidden" id="id_borrar" value="">
+                        <button type="button" class="btn btn-outline-primary btn-borrar"><?php echo $ICO["aceptar"];?> Borrar</button>
+                        <button type="button" class="btn btn-outline-danger" data-dismiss="modal" aria-label="Close"><?php echo $ICO["cancelar"];?> Cancelar</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </main>
+    <? include "import/html_footer.php"; ?>
+    
+    <?php
+    //--Manejo de errores y mensajes de exito
+    if(isset($_GET["error"]) && is_numeric($_GET["error"])){
+        switch ($_GET["error"]){
+            case 0: $errorDesc = "No se reciberon los datos de la reposición."; break;
+            case 1: $errorDesc = "Ocurrió un error al insertar los datos de la reposición/cambio."; break;
+            case 2: $errorDesc = "Ocurrió un error al actualizar los datos de la reposición/cambio."; break;
+            case 3: $errorDesc = "No tienes permisos para realizar esa acción."; break;
+            case 4: $errorDesc = "Ocurrió un error al cargar los datos de la reposición/cambio."; break;
+            case 6: $errorDesc = "La reposición/cambio que buscas no existe. Consulta la lista de reopsiciones disponibles en esta sección."; break;
+            case 7: $errorDesc = "La reposición/cambio se empalma con el horario del grupo y no se puede guardar."; break;
+            case 8: $errorDesc = "El salón de la reposición está siendo utilizado ese día a esa hora y no se puede guardar."; break;
+            case 9: $errorDesc = "El profesor está asigndo a otra clase o reposición el mismo día a la misma hora y no se puede guardar."; break;
+            case 10: $errorDesc = "El profesor está asigndo a una materia el mismo día a la misma hora y no se puede guardar."; break;
+            case 11: $errorDesc = "No hay clases asignadas para esa materia y grupo en la fecha de falta."; break;
+        }
+    }
+    if(isset($_GET["ok"]) && is_numeric($_GET["ok"])){
+        switch ($_GET["ok"]){
+            case 0: $successDesc = "La reposición se guardó correctamente."; break;
+            case 1: $successDesc = "La reposición se actualizó correctamente."; break;
+        }
+    }
+    require_once 'js/messages.php';
+    ?>
+    <script>
+    <?php if(isset($errorDesc)){ ?>
+    triggerMessage("<?php echo $errorDesc;?>", "Error");
+    <?php } else if(isset($successDesc)){ ?>
+    triggerMessage("<?php echo $successDesc;?>", "Éxito", "success");
+    <?php } ?>
+
+    var vacaciones=[
+        <?php
+        $vacaciones_rs = $db->query('SELECT diasfestivos_dia from diasfestivos d where :periodo = any(d.periodos_id)', [':periodo' => $user->periodo_id ]); 
+        
+            foreach($vacaciones_rs as $v){ echo '"'.$v["diasfestivos_dia"].'",';}
+        ?>
+    ];
+    var _dias_asistencia = [];//ya registró asistencia, cambia con ajax
+    var _dia_valido = 0;
+    var _fecha_manhana = "<?php echo $fecha_man; ?>";
+    var _periodo_fecha_inicial = "<?php echo date("d/m/Y", strtotime($periodo_rs["periodo_fecha_inicio"])); ?>";
+    var _periodo_fecha_final = "<?php echo date("d/m/Y", strtotime($periodo_rs["periodo_fecha_fin"])); ?>";
+    var datepickerOptions_filtro = { dateFormat: "dd/mm/yy", minDate:_periodo_fecha_inicial, maxDate:_periodo_fecha_final};
+
+    var datepickerOptions = { dateFormat: "dd/mm/yy", minDate:_periodo_fecha_inicial, maxDate:_periodo_fecha_final,
+        beforeShowDay: function(date) {
+            var day = date.getDay();
+            var dateString = $.datepicker.formatDate("yy-mm-dd", date);
+            
+                if (vacaciones.indexOf(dateString) !== -1 || _dias_asistencia.indexOf(dateString) !== -1) 
+                    return [false];
+                else
+                    return [true];
+            
+            
+        }
+    };
+    var datepickerOptions_future = { dateFormat: "dd/mm/yy", minDate:_fecha_manhana, maxDate:_periodo_fecha_final,
+        beforeShowDay: function(date) {
+            var day = date.getDay();
+            var dateString = $.datepicker.formatDate("yy-mm-dd", date);
+            if (day === 0) {// 0 representa el domingo
+                return [false];
+            } else {
+                if (vacaciones.indexOf(dateString) !== -1) {
+                    return [false];
+                } else {
+                    return [true];
+                }
+            }
+            
+        }
+    };
+
+    function diaAAno(fecha_str){//de dd/mm/yyyy a yyyy-mm-dd 
+        if(fecha_str.charAt(2) == "/" && fecha_str.charAt(5) == "/"){//dd/mm/yyyy
+            var fecha_arr = fecha_str.split("/");
+            return fecha_arr[2]+"-"+fecha_arr[1]+"-"+fecha_arr[0];
+        }
+        return fecha_str;
+    }
+    function fechaMayor(fechaI, fechaF) {//cual es mayor >0 I mayor   <0 F mayor
+        return (Date.parse(diaAAno(fechaI)) - Date.parse(diaAAno(fechaF)));
+    }
+
+    function validaFechas(){
+        if(fechaMayor($('#filtro_inicial').val().trim(), $('#filtro_final').val().trim()) > 0){
+            $('#filtro_final').addClass("is-invalid");
+            return false;
+        }
+        return true;
+    }
+
+    function submitForm(){
+        var myBtn = $('#submitBtn');
+        var error = false;
+        
+        $("#gpo").removeClass("is-invalid");
+        invalidDatalist("#materia", false);
+        $("#fecha_inicial").removeClass("is-invalid");
+        $("#fecha_falta").removeClass("is-invalid");
+        $("#fecha_cambio").removeClass("is-invalid");
+
+        
+        if($("#tipo").val() == 1){//reposición
+            if($("#fecha_falta").val() == ""){
+                $("#fecha_falta").addClass("is-invalid");
+                error = true;
+            }
+            if($("#fecha_inicial").val() == ""){//fecha reposición
+                $("#fecha_inicial").addClass("is-invalid");
+                error = true;
+            }    
+        }else{
+            if($("#fecha_cambio").val() == ""){
+                $("#fecha_cambio").addClass("is-invalid");
+                error = true;
+            }
+        }
+        if($("#horario").val().trim() == "" || $("#horario").val() === null){
+            invalidDatalist("#horario", true);
+            error = true;
+        }
+        
+        if(myBtn.data("tipo") == 2 ){
+            $('#formaModal').prop("action", "./action/cambio_update.php");
+        }else{
+            $('#formaModal').prop("action", "./action/cambio_insert.php");
+        }
+        if(!error){
+            $("#loadingGroup").show();
+            $("#submitGroup").hide();
+        }
+        return !error;
+    }
+
+    function cambiaTipo(tipo){
+        if (tipo == 1){//reposición
+            $(".repo_block").show();
+            $(".cambio_block").hide();
+            $(".repo_block").find("input[type=text]").attr("required", true);
+            $(".cambio_block").find("input[type=text]").removeAttr("required");
+            $("#hora_nombre").text("Hora reposición *");
+        }else{//Cambio de salón
+            $(".repo_block").hide();
+            $(".cambio_block").show();
+            $(".repo_block").find("input[type=text]").removeAttr("required");
+            $(".cambio_block").find("input[type=text]").attr("required", true);
+            $("#hora_nombre").text("Hora cambio *");
+            var hora = $("#dlMateria ul li.selected").data("hr");
+            var min = $("#dlMateria ul li.selected").data("min");
+            $("#hora_ini").val(hora)
+            $("#min_ini").val(min)
+
+        }
+    }
+    
+    $(document).ready(function(){
+        $(".prof-selected").hide();
+        //fecha de clase
+        $(".date-picker" ).datepicker(datepickerOptions);
+        $(".date-picker" ).datepicker( $.datepicker.regional[ "es" ] );
+
+        //fecha de clase
+        $(".date-picker-filtro" ).datepicker(datepickerOptions_filtro);
+        $(".date-picker-filtro" ).datepicker( $.datepicker.regional[ "es" ] );
+        
+        
+        function creaOpcion(id_horario, dia, hora, min, nombre, gpo, duracion){
+            return '<li data-id="'+id_horario+'" data-dia="'+dia+'" data-hr="'+hora+'" data-min="'+min+'" data-gpo="'+gpo+'" data-duracion="'+duracion+'">'+nombre+'</li>';
+        }
+
+        $('#filtro_final').focus(function(){
+            $("#filtro_final").removeClass("is-invalid");
+        });
+
+        function obtieneProf(pid){
+            return $.ajax({
+                url:  './action/reposicion_profesor_materias.php',
+                type: 'POST', 
+                dataType: 'json',
+                data: { id: pid, },
+                //async: false,
+                success: function(result) {
+                    if(result["error"]!= "" &&  result["error"] !== undefined){
+                        triggerMessage(result["error"], "Error");
+                        $("#modal").modal('hide');
+                        $(".prof-selected").hide();
+                    }else{
+                        $(".prof-selected").show();
+                        $("#dlMateria ul").html("");
+                        for(i=0; i<result["materias"].length; i++){
+                            var html = creaOpcion(result["materias"][i]["horario_id"],
+                                result["materias"][i]["horario_dia"],
+                                result["materias"][i]["horario_hora"],
+                                result["materias"][i]["horario_min"],
+                                result["materias"][i]["materia_nombre"],
+                                result["materias"][i]["grupo"],
+                                result["materias"][i]["duracion"]
+                                );
+                            $("#dlMateria ul").append(html);
+                        }
+                    }
+                },
+                error: function(jqXHR, textStatus, errorThrown ){
+                    triggerMessage(errorThrown, "Error");
+                }
+            });//ajax
+        }
+
+        $(document).on( "click", "#dlProfesor ul li", function(event){//cambia datalist
+            var pid = $(this).data('id');
+            //busca materias del profesor
+            var profCarga = obtieneProf(pid);
+            profCarga.done(function(){
+                $("#dlMateria ul li:first").click();
+            });
+        });
+        
+        
+        //Actualiza días elegibles de calendario
+        $(document).on( "click", "#dlMateria ul li", function(event){//manda al frente de todos
+            _dia_valido = $(this).data('dia');//variable global
+            var grupo = $(this).data("gpo");
+            var duracionMateria = $(this).data("duracion");
+
+            $(".date-picker" ).datepicker(datepickerOptions);
+            var hora = $(this).data("hr");
+            var min = $(this).data("min");
+            $("#hora_ini").val(hora)
+            $("#min_ini").val(min)
+            
+            $("#ciclo").val(parseInt(grupo[6]));
+            $("#bloque").val(parseInt(grupo[8])-1);
+            
+            $('#duracion option').each(function() {
+                if ($(this).data("duracion") === duracionMateria) {
+                    // Selecciona la opción correspondiente en el select de "duracion"
+                    $(this).prop('selected', true);
+                }
+            });
+            $("#duracion_original").text(duracionMateria);
+            $("#hora_original").text(hora+":"+min);
+/*
+            return $.ajax({
+                url:  './action/asistenciasprofesor_select.php',
+                type: 'POST', 
+                dataType: 'json',
+                data: { "id": $("#prof").val(), "hor": $(this).data("id") },
+                //async: false,
+                success: function(result) {
+                    if(result["error"]!= "" &&  result["error"] !== undefined){
+                        triggerMessage(result["error"], "Error");
+                        $('#modal').modal("hide");
+                    }else{
+                        _dias_asistencia = result["asistenciaArr"];
+                        //Cambiar ciclo [6] y bloque [8]
+                        $("#ciclo").val(parseInt(grupo[6]));
+                        $("#bloque").val(parseInt(grupo[8])-1);
+                        
+                        $('#duracion option').each(function() {
+                            if ($(this).data("duracion") === duracionMateria) {
+                                // Selecciona la opción correspondiente en el select de "duracion"
+                                $(this).prop('selected', true);
+                            }
+                        });
+                        $("#duracion_original").text(duracionMateria);
+                        $("#hora_original").text(hora+":"+min);
+                    }
+
+                },
+                error: function(jqXHR, textStatus, errorThrown ){
+                    triggerMessage(errorThrown, "Error");
+                }
+            });//ajax
+  */          
+            
+        });
+        
+        $("#dlTipo ul li").click(function(){//cambia datalist
+            cambiaTipo($(this).data('id'));
+            $(".date-picker" ).datepicker(datepickerOptions);
+        });
+        $("#dlAula ul li").click(function(){//cambia datalist
+            if($(this).data("id") == 1){
+                $("#salon-editar").hide();
+                $("#dlSalon").val("");
+                $("#salon").val("");
+
+            }else{
+                $("#salon-editar").show();
+            }
+            
+        });
+
+        $('#modal_confirm').on('show.bs.modal', function (event) {
+            var button = $(event.relatedTarget); // Button that triggered the modal
+            var id = button.parents("tr").data("id");
+            $("#id_borrar").val(id);
+        });
+        
+        $(".btn-borrar").click(function(){
+            var r_id =  $("#id_borrar").val();
+            $.ajax({
+                url:  './action/cambio_delete.php',
+                type: 'POST', 
+                dataType: 'json',
+                data: { id: r_id},
+                success: function(result) {
+                    if(result["error"]!= "" &&  result["error"] !== undefined){
+                        triggerMessage(result["error"], "Error");
+                    }else{
+                        triggerMessage(result["ok"], "Éxito", "success");
+                        $("#id"+r_id).remove();
+                    }
+                },
+                error: function(jqXHR, textStatus, errorThrown ){
+                    triggerMessage(errorThrown, "Error");
+                }
+            });//ajax
+            $('#modal_confirm').modal("hide");
+        });
+
+        
+        $('#modal').on('show.bs.modal', function (event) {
+            var button = $(event.relatedTarget); // Button that triggered the modal
+            var tipo = button.data('tipo'); // 1 alta, 2 edicion
+            var modal = $(this);
+
+            $("#loadingGroup").hide();
+            $("#submitGroup").show();
+            
+            $("#modal .is-invalid").removeClass("is-invalid");
+            //$(this).find(".form-control:first-child").focus();
+            
+
+            $("#errorBox").collapse('hide');
+            $("#errorBox_text").html("");
+            if(tipo == 1){//alta
+                $("#submitBtn").data('tipo', 1);
+                $("#modalLabel").html("Solicitar cambio");
+                modal.find("input[type=text]").val("");
+                modal.find("#alumnos").val("15");
+                $("#plan").attr("readonly", false);
+                $("#sem").attr("readonly", false);
+                $("#gpo").attr("readonly", false);
+                
+                //$("#prof").attr("readonly", false);
+                disableDatalist("#horario", false);
+                disableDatalist("#tipo", false);
+                if($("#prof").length>0)
+                    disableDatalist("#prof", false);
+                setDatalistFirst("#tipo");
+                setDatalistFirst("#aula");
+                setDatalistFirst("#horario");
+                $("#dlMateria ul li:first").click();
+                
+            }else{//editar
+                $("#submitBtn").data('tipo', 2);
+                $("#modalLabel").html("Editar Reposición");
+                $("#plan").attr("readonly", true);
+                $("#sem").attr("readonly", true);
+                $("#gpo").attr("readonly", true);
+                //$("#materia").attr("readonly", true);
+                disableDatalist("#horario");
+                disableDatalist("#tipo");
+                disableDatalist("#prof");
+                /*if($("#prof").length>0)
+                    disableDatalist("#prof");
+                $("#prof").attr("readonly", true);*/
+                var r_id = $(button).parents("tr").data("id");
+                $("#id").val(r_id);
+                $.ajax({
+                    url:  './action/cambio_select.php',
+                    type: 'POST', 
+                    dataType: 'json',
+                    data: { id: r_id },
+                    async: true,
+                    success: function(result) {
+                        if(result["error"]!= "" &&  result["error"] !== undefined){
+                            triggerMessage(result["error"], "Error");
+                            $("#modal").modal('hide');
+                        }else{
+                            //setDatalist("#prof", result["profesor"]);
+                            setDatalist("#prof", result["profesor"]);
+                            
+                            var profCarga = obtieneProf(result["profesor"]);
+
+                            //$('#salon').val(result["salon"]);
+                            $("#fecha_falta").val(result["fecha_clase"]);
+                            
+                            
+                            $('#comentario').val(result["comentario"]);
+                            $('#alumnos').val(result["alumnos"]);
+                            $('#ciclo').val(result["ciclo"]);
+                            $('#bloque').val(result["bloque"]);
+                            
+                            if(result["tipo"]){
+                                setDatalist("#tipo", 1);
+                                cambiaTipo(1);
+                                $("#fecha_inicial").val(result["fecha_nueva"]);
+                            }else{
+                                setDatalist("#tipo", 2);
+                                cambiaTipo(2);
+                                $("#fecha_cambio").val(result["fecha_nueva"]);
+                            }
+                            _dia_valido = parseInt(result["dia"]);
+                            $(".date-picker" ).datepicker(datepickerOptions);
+                            $("#dlTipo ul li:selected").click();
+                            
+                            
+                            profCarga.done(function(){
+                                setDatalist("#horario", result["horario"]);// No se actualiza TODO
+                                $('#hora_ini').val(result["hora_ini"]);
+                                $('#min_ini').val(result["min_ini"]);
+                            });
+                            setDatalist("#aula", result["aula"]);
+                            modal.modal('show');
+                        }
+                    },
+                    error: function(jqXHR, textStatus, errorThrown ){
+                        triggerMessage(errorThrown, "Error");
+                        $("#modal").modal('hide');
+                        //$('#messageBox')[0].scrollIntoView({ block: "end" });
+                    }
+                });//ajax
+            }
+        });//show
+        
+    });
+
+    $(function() {
+        $('[data-toggle="tooltip"]').tooltip()
+    })
+</script>
+<script src="js/messages.js"></script>
+
+</body>
+
+</html>

+ 1 - 1
consultar_horario.php

@@ -181,7 +181,7 @@ $write = $user->admin || in_array($user->acceso, ['r']);
                             :key="`${hour}-${block}-${día}`"
                             :class="horarios.getHorarioData(hour, block, día) ? 'bg-light' : ''"
                             class="align-middle h-100"
-                            :style="`width: ${(100 - 6) / (horarios.structure?.sábado ? 6 : 5)}%;`">
+                            :style="`width: ${(100 - 6) / (horarios.structure?.sábado ? 6 : 5)}%;`" :data-id="horarios.getHorarioData(hour, block, día)?.horario_id">
                             <!-- Content Container -->
                             <div class="overflow-auto" :style="`max-height: ${horarios.getHorarioData(hour, block, día)?.bloques * 2}em;
                                 min-height: 2em;

+ 208 - 0
horarios_historicos.php

@@ -0,0 +1,208 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>
+        Histórico de horarios
+    </title>
+    <?php
+    include 'import/html_css_files.php';
+    ?>
+    <style>
+        [v-cloak] {
+            display: none;
+        }
+    </style>
+    <script src="js/jquery.min.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
+        integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
+        crossorigin="anonymous"></script>
+    <script src="js/bootstrap/bootstrap.min.js"></script>
+</head>
+
+<body>
+    <?
+    $redirect = $_SERVER['PHP_SELF'];
+    include "import/html_header.php";
+    global $user;
+
+    html_header(
+        "Histórico de horarios",
+        "Sistema de gestión de checador",
+    );
+
+
+
+    if (!$user->periodo_id) { ?>
+        <script defer src="js/jquery.min.js"></script>
+        <script src="js/bootstrap/bootstrap.min.js"></script>
+
+        <div class="modal" id="seleccionar-periodo" tabindex="-1">
+            <div class="modal-dialog modal-dialog-centered modal-xl">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h2 class="modal-title">Seleccionar periodo</h2>
+                    </div>
+                    <div class="modal-body container">
+                        <? include 'import/periodo.php' ?>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <script>
+            $('#seleccionar-periodo').modal({
+                backdrop: 'static',
+                keyboard: false,
+            });
+            $('#seleccionar-periodo').modal('show');
+        </script>
+        <? exit;
+    } ?>
+    <main class="container-fluid px-4 mt-4" id="app" v-cloak @vue:mounted="mounted" style="min-height: 60vh;"
+        v-scope="">
+        <?php include "import/periodo.php" ?>
+
+        <form class="marco" v-scope="{profesor: null}">
+            <!-- datalist profesores -->
+            <div class="row">
+                <div class="col-12">
+                    <div class="form-box">
+                        <div class="form-group row">
+                            <label for="profesor" class="col-form-label col-4">Profesor</label>
+                            <input list="profesores" class="form-control col-6 mx-3" id="profesor" v-model="profesor"
+                                @input="buscarHorarios(profesor)">
+                            <datalist id="profesores">
+                                <option v-for="profesor in profesores" :value="profesor.profesor_clave">
+                                    {{profesor.profesor_nombre}}
+                                </option>
+                            </datalist>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+        </form>
+
+        <div class="table-responsive marco" v-if="horarios.length > 0" v-scope="">
+            <table class="table table-hover table-striped table-bordered table-sm">
+                <thead class="thead-dark">
+                    <tr>
+                        <th scope="col" class="text-center align-middle px-2">
+                            Carrera
+                        </th>
+                        <th scope="col" class="text-center align-middle px-2">
+                            Materia
+                        </th>
+                        <th scope="col" class="text-center align-middle px-2">
+                            Grupo
+                        </th>
+                        <th scope="col" class="text-center align-middle px-2">
+                            Horario
+                        </th>
+                        <th scope="col" class="text-center align-middle px-2">
+                            Alta
+                        </th>
+                        <th scope="col" class="text-center align-middle px-2">
+                            Baja
+                        </th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr v-for="horario in horarios" :key="`horario-${horario.horario_id}`">
+                        <td class="align-middle px-2">
+                            <small>
+                                <strong>{{horario.facultad_nombre}}</strong>
+                            </small>
+                            {{horario.carrera_nombre}}
+                        </td>
+                        <td class="align-middle px-2 text-center">
+                            {{horario.materia_nombre}}
+                        </td>
+                        <td class="align-middle px-2 text-center">
+                            {{horario.horario_grupo}}
+                        </td>
+                        <td class="align-middle px-2 text-center"
+                            v-scope="{días: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado']}">
+                            {{días[horario.horario_dia]}} - {{horario.horario_hora}} - {{horario.horario_fin}}
+                        </td>
+                        <td class="align-middle px-2 text-center">
+                            {{horario.horario_fecha_inicio}}
+                        </td>
+                        <td class="align-middle px-2 text-center">
+                            {{horario.horario_fecha_fin}}
+                        </td>
+
+                    </tr>
+                </tbody>
+            </table>
+        </div>
+        <div class="modal" tabindex="-1" id="cargando" data-backdrop="static" data-keyboard="false">
+            <div class="modal-dialog modal-dialog-centered">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h4 class="modal-title">Cargando datos...</h4>
+
+                    </div>
+                    <div class="modal-body container">
+                        <div class="row">
+                            <div class="col-12 text-center">
+                                <span class="spinner-border spinner-border-lg"></span>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="modal" tabindex="-1" id="mensaje">
+            <div class="modal-dialog modal-dialog-centered">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h4 class="modal-title">{{mensaje.titulo}}</h4>
+                        <button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
+                            <span aria-hidden="true">&times;</span>
+                        </button>
+                    </div>
+                    <div class="modal-body container">
+                        <div class="row">
+                            <div class="col-12 text-center">
+                                {{mensaje.texto}}
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+
+    </main>
+
+    <!-- <script src=" js/datalist.js"></script> -->
+    <script type="module">
+        import { createApp } from 'https://unpkg.com/petite-vue?module'
+        createApp({
+            horarios: [],
+            profesores: [],
+
+            async buscarHorarios(profesor_clave) {
+                // if existe la clave del profesor
+                if (!this.profesores.find(profesor => profesor.profesor_clave === profesor_clave)) {
+                    this.horarios = []
+                    return
+                }
+
+                const horarios = await fetch(`/action/horario_profesor.php?profesor=${profesor_clave}`)
+                this.horarios = await horarios.json()
+            },
+
+            async mounted() {
+                const profesores = await fetch('/action/action_profesor.php')
+                this.profesores = await profesores.json()
+            },
+        }).mount('#app')
+    </script>
+    <script src="js/scrollables.js"></script>
+</body>
+
+</html>

+ 5 - 2
import/periodo.php

@@ -7,7 +7,9 @@ $user or die("Error: no se pudo cargar el usuario");
         <div class="col-12">
             <?php
             $target = $target ?? strtok($_SERVER["REQUEST_URI"], '?');
-            $niveles = $db->get("nivel");
+            $niveles = $db
+                ->orderBy('nivel_nombre')
+                ->get("nivel");
 
             // collect facultad_id's with facultad from $periodos
             ?>
@@ -33,7 +35,8 @@ $user or die("Error: no se pudo cargar el usuario");
                                         WHERE 
                                             nivel_id = :nivel_id AND
                                             (facultad_id = :facultad_id OR :facultad_id IS NULL)
-                                        GROUP BY periodo_id, periodo_nombre',
+                                        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']

+ 63 - 0
include/bd_pdo_rest.php

@@ -0,0 +1,63 @@
+<?php
+require_once "/usr/share/nginx/html/paad/vendor/autoload.php";
+$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
+$dotenv->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));
+}

BIN
include/db/postgrest


+ 20 - 0
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_pruebas"
+
+# 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 = "*"

+ 4 - 0
js/puestos.js

@@ -1,4 +1,6 @@
 import { createApp } from 'https://unpkg.com/petite-vue?module';
+
+// añade una ventana de confirmación al intentar cambiar de página
 const app = createApp({
     message: null,
     puestos: [],
@@ -57,6 +59,8 @@ const app = createApp({
             });
             const data = await res.json();
             this.message = data.msg;
+
+            modificado = false;
             // after 3 seconds, remove the message
             setTimeout(() => {
                 this.message = null;

+ 1 - 0
materias.php

@@ -163,6 +163,7 @@ if ($user->admin) {
                                 ?>
                                 <tr data-id="<?= $materia["materia_id"]; ?>" id="<?= $materia["materia_id"]; ?>">
                                     <td class="text-primary">
+                                        <small>(<?= $materia["clave_materia"]; ?>)</small>
                                         <?= $materia["materia_nombre"]; ?>
                                     </td>
                                     <td class="text-primary">

+ 3 - 4
profesores.php

@@ -68,13 +68,12 @@ if ($user->admin) { //si es admin su facultad es null (todas las facultades)
         }
     }
     $fs_profesores = $db->query(
-        "SELECT DISTINCT PROFESOR.*, FACULTAD.* FROM profesor 
+        "SELECT DISTINCT PROFESOR.*, horario_view.facultad as facultad_nombre, horario_view.facultad_id FROM profesor 
         JOIN horario_profesor USING (profesor_id)
-        JOIN horario USING (horario_id)
-        JOIN facultad ON horario.facultad_id = facultad.facultad_id
+        JOIN horario_view USING (horario_id)
         WHERE 
         profesor_nombre ILIKE COALESCE(:nombre, profesor_nombre) AND profesor_clave ILIKE COALESCE(:clave, profesor_clave)
-        AND facultad.facultad_id = COALESCE(:facultad, facultad.facultad_id)
+        AND facultad_id = COALESCE(:facultad, facultad_id)
         LIMIT :maxc",
         array(
             ":nombre" => "%$desc%",

+ 13 - 6
puestos.php

@@ -34,7 +34,7 @@
 
         <main class="container-fluid px-4 mt-4" id="app" v-cloak @vue:mounted="mounted" style="min-height: 70vh;"
             v-scope="{new_puesto: null}">
-
+            <!-- {{modificado}} -->
             <div class="marco alert alert-success" role="alert" v-if="message">
                 {{message}}
             </div>
@@ -55,7 +55,7 @@
 
             <div class="d-flex flex-wrap marco justify-content-around">
                 <div class="accordion col-10 mx-auto my-5" id="puestos"
-                    v-scope="{selected_carrera_id: -1, current_materia: null, current_encargado: null}"
+                    v-scope="{selected_carrera_id: 0, current_materia: null, current_encargado: null}"
                     v-if="puestos.length">
 
                     <div class="card mb-3 shadow-lg" v-for="(puesto, index) in puestos" :key="puesto.puesto_id">
@@ -106,7 +106,7 @@
                                 <hr>
 
                                 <?php if ($user->acceso == 'w') { ?>
-                                <div class="form-row justify-content-around align-items-center mb-2"
+<!--                                 <div class="form-row justify-content-around align-items-center mb-2"
                                     v-show="carreras.length">
                                     <label :for="`carrera-${puesto.puesto_id}`" class="col-2 barra-right">
                                         Carrera
@@ -129,7 +129,7 @@
                                         </ul>
                                         <input type="hidden" id="carrera_id" name="id">
                                     </div>
-                                </div>
+                                </div> -->
                                 
                                 <div class="form-row justify-content-around align-items-center"
                                     v-scope="{to_add_materia: null}">
@@ -186,10 +186,9 @@
                                         <li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
                                             v-for="materia in puesto.materias" :key="materia.materia_id"
                                             <?php if ($user->acceso == 'w') { ?>
-                                            @click="puesto.materias.splice(puesto.materias.indexOf(materia), 1); materias.push(materia)"
+                                            @click="modificado = true; puesto.materias.splice(puesto.materias.indexOf(materia), 1); materias.push(materia)"
                                             <?php } ?>
                                             style="cursor: pointer; transition: background-color 0.3s ease;">
-
                                             <span class="flex-grow-1">
                                                 {{materia.clave_materia}} - {{materia.materia_nombre}}
                                             </span>
@@ -312,8 +311,16 @@
     <script src="js/puestos.js?<?= rand(0, 2) ?>" type="module"></script>
     <script src="js/scrollables.js"></script>
     <script>
+        let modificado = false;
+
         $(document).ready(function () {
             $('.collapse').collapse
+
+            window.onbeforeunload = function () {
+            if (modificado) {
+                return true;
+            }
+        };
         });
     </script>
 </body>

+ 317 - 105
reposiciones_autorizar.php

@@ -27,10 +27,10 @@ if ($user->acceso === null && !$user->admin){
 
 $supervisor = false;
 $coordinador = false;
-if($user->rol["rol_id"]==7){
+if($user->rol["rol_id"]==7 || $user->rol["rol_id"]==8){
     $supervisor = true;
 }
-if($user->rol["rol_id"]==9){
+if($user->rol["rol_id"]==9 || $user->rol["rol_id"]==8){
     $coordinador = true;
 }
 
@@ -87,7 +87,9 @@ if($user->periodo_id!= ""){
     $repEdo_rs = $db->query('SELECT * FROM fs_estado_reposicion' );
 
     $repoParams = array();
+    $asigParams = array();
     $query = "";
+
     if($user->rol["rol_id"] == 9){//es coordinador
         $query .= ":facultad, ";
         $repoParams[":facultad"] = $user->facultad["facultad_id"];
@@ -101,7 +103,8 @@ if($user->periodo_id!= ""){
     }else{
         $query .= "NULL,";
     }
-    $query .= ":f_ini, :f_fin, :edo, ";
+    $query .= ":f_ini, :f_fin, ";
+    $queryAsig = ":f_ini, :f_fin,";
 
 
     $date = DateTime::createFromFormat('d/m/Y', $fecha_ini);
@@ -112,6 +115,9 @@ if($user->periodo_id!= ""){
     $repoParams[":f_ini"] = $fecha_ini_db;
     $repoParams[":f_fin"] = $fecha_fin_db;
     $repoParams[":edo"] = 1;//se sobreescribe
+
+    $asigParams[":f_ini"] = $fecha_ini_db;
+    $asigParams[":f_fin"] = $fecha_fin_db;
 }
 ?>
 <!DOCTYPE html>
@@ -190,8 +196,14 @@ if($user->periodo_id!= ""){
                 <button type="submit" class="btn btn-outline-primary mr-2" id="btn-buscar"><span class="ing-buscar ing-fw"></span> Buscar</button>
                 <button type="button" class="btn btn-outline-danger" onclick="window.location.href = window.location.href"><span class="ing-borrar ing-fw"></span> Limpiar</button>
             </div>
+
+            <p class="text-right">
+                <button class="btn btn-secondary" id="exportar"><span class="ing-descargar"></span>Descargar xls</button>
+            </p>
         </form>
 
+        
+
         <ul class="nav nav-tabs d-print-none mb-4" id="myTab" role="tablist">
             <li class="nav-item">
                 <a class="nav-link" id="tab1-tab" data-toggle="tab" href="#tab1" role="tab" aria-controls="calendario" aria-selected="true">Nuevas reposiciones</a>
@@ -212,17 +224,52 @@ if($user->periodo_id!= ""){
             foreach($repEdo_rs as $redo){ ?>
             <div class="tab-pane fade" id="tab<?php echo $i;?>" role="tabpanel" aria-labelledby="tab<?php echo $i;?>-tab">
                 <?php
+
+                $tablaArr = [];
+
                 $repoParams[":edo"]=$redo["estado_reposicion_id"];
+                $asigParams[":edo"]=$redo["estado_reposicion_id"];
                 if($user->rol["rol_id"] == 7){//es supervisor
                     $repoParams[":sup"] = $user->user["id"];
-                    $reposiciones_rs = $db->query('SELECT * FROM fs_reposicion_solicitud(NULL, '.$query.'0, NULL, :sup) ', $repoParams );
+                    $reposiciones_rs = $db->query('SELECT * FROM fs_reposicion_solicitud(NULL, '.$query.':edo, 0, NULL, :sup) ', $repoParams );
                 }else{
-                    $reposiciones_rs = $db->query('SELECT * FROM fs_reposicion_solicitud(NULL, '.$query.'0, NULL) ', $repoParams );
+                    $reposiciones_rs = $db->query('SELECT * FROM fs_reposicion_solicitud(NULL, '.$query.':edo, 0, NULL) ', $repoParams );
+                }
+                foreach($reposiciones_rs as $repo){
+                    $tablaArr[] = array("id"=>$repo["reposicion_solicitud_id"], "estado"=>$repo["estado_reposicion_id"], "tipo"=>($repo["es_reposicion"]==true?1:2),
+                        "profesor_clave"=>$repo["profesor_clave"], "profesor_nombre"=>$repo["profesor_nombre"], "materia_nombre"=>$repo["materia_nombre"], "horario_grupo"=>$repo["horario_grupo"],
+                        "fecha_falta"=>$repo["fecha_clase"], "fecha_nueva"=>$repo["fecha_nueva"], "hora_original"=> $repo["horario_hora"], "hora_nueva"=>$repo["hora_nueva"], "hora_nueva_fin"=>$repo["hora_nueva_fin"],
+                        "salon_id"=>$repo["salon_id"], "salon_array"=>$repo["salon_array"]);
+                }
+                
+                if($user->rol["rol_id"] == 7){//es supervisor
+                    $asigParams[":sup"] = $user->user["id"];
+                    $asignaciones_rs = $db->query('SELECT * FROM fs_asignacion_solicitud(NULL, '.$queryAsig.' :sup, :edo) ', $asigParams );
+                }else{
+                    $asignaciones_rs = $db->query('SELECT * FROM fs_asignacion_solicitud(NULL, '.$queryAsig.' NULL, :edo) ', $asigParams );
                 }
                 
+
+                foreach($asignaciones_rs as $asig){
+                    $tablaArr[] = array("id"=>$asig["asignacion_solicitud_id"], "estado"=>$asig["estado_reposicion_id"], "tipo"=>3,
+                        "profesor_clave"=>$asig["profesor_clave"], "profesor_nombre"=>$asig["profesor_nombre"], "materia_nombre"=>"", "horario_grupo"=>"",
+                        "fecha_falta"=>"", "fecha_nueva"=>$asig["fecha_nueva"], "hora_original"=>"", "hora_nueva"=>$asig["hora_nueva"], "hora_nueva_fin"=>$asig["hora_nueva_fin"],
+
+                        "salon_id"=>$asig["salon_id"], "salon_array"=>$asig["salon_array"]);
+                }
+
+                if(count($tablaArr)>0){
+                    //ordena $tablaArr por fecha_repo
+                    usort($tablaArr, function($a, $b) {
+                        return strtotime($a['fecha_nueva']) - strtotime($b['fecha_nueva']);
+                    });
+                    
+                }else{
+                    echo "No hay reposiciones en este estado";
+                }
                 ?>
                 
-                <h4 class="mb-4" <?php echo "style='color:".$redo["estado_color"]."'>".$redo["estado_nombre"]; ?> </h4>
+                <h4 class="mb-4" <?php echo "style='color:".$redo["estado_color"]."'";?> > <?php echo $redo["estado_nombre"]; ?> </h4>
 
                 <table class="table table-sm table-striped table-white">
                     <thead class="thead-dark">
@@ -238,17 +285,17 @@ if($user->periodo_id!= ""){
                     </thead>
                     <tbody>
                         <?php
-                    if(isset($reposiciones_rs)){
-                        foreach($reposiciones_rs as $reposicion){
+                    if(isset($tablaArr)){
+                        foreach($tablaArr as $reposicion){
                         ?>
-                        <tr data-id="<?php echo $reposicion["reposicion_solicitud_id"]; ?>" data-edo="<?php echo $reposicion["estado_reposicion_id"];?>" id="id<?php echo $reposicion["reposicion_solicitud_id"]; ?>">
+                        <tr data-id="<?php echo $reposicion["id"]; ?>" data-edo="<?php echo $reposicion["estado"];?>" id="id<?php echo $reposicion["id"]; ?>">
                             <td class="align-middle">
-                                <?php if($reposicion["estado_reposicion_id"]<3){ ?>
-                                <div class="wizard <?php if(intval($reposicion["estado_reposicion_id"])==2) echo "active";?> d-flex mx-auto">
+                                <?php if($reposicion["estado"]<3){ ?>
+                                <div class="wizard <?php if(intval($reposicion["estado"])==2) echo "active";?> d-flex mx-auto">
                                     <div class="w-50 h-100"></div>
                                     <div class=""></div>
                                 </div>
-                                <?php } else if($reposicion["estado_reposicion_id"]==3){?>
+                                <?php } else if($reposicion["estado"]==3){?>
                                 <div class="text-success text-center pt-1">
                                     <span class="ing-autorizar ing-lg"></span>
                                 </div>
@@ -259,7 +306,11 @@ if($user->periodo_id!= ""){
                                 <?php } ?>
                             </td>
                             <td class="align-middle">
-                                <?php if($reposicion["es_reposicion"]) echo "Resposición"; else echo "Cambio"; ?>
+                                <?php switch($reposicion["tipo"]){
+                                    case 1: echo "Resposición"; break;
+                                    case 2: echo "Cambio"; break;
+                                    case 3: echo "Asignación"; break;
+                                }?>
                             </td>
                             <td><?php
                                 echo $reposicion["profesor_clave"]." - ".$reposicion["profesor_nombre"];
@@ -267,14 +318,19 @@ if($user->periodo_id!= ""){
                                 <br>
                                 <small>
                                 <?php echo $reposicion["materia_nombre"]; ?>
-                                (<?php
-                                    echo $reposicion["horario_grupo"];
-                                ?>)
+                                <?php
+                                if($reposicion["horario_grupo"]!="")
+                                    echo "(".$reposicion["horario_grupo"].")";
+                                ?>
                                 </small>
                             </td>
                             <td class="text-center align-middle text-nowrap"><?php
-                                $fechaI = date("d/m/Y", strtotime($reposicion["fecha_clase"]));
-                                echo $fechaI."<br>".substr($reposicion["horario_hora"],0, 5);
+                                if($reposicion["fecha_falta"]!=""){
+                                    $fechaI = date("d/m/Y", strtotime($reposicion["fecha_falta"]));
+                                    echo $fechaI."<br>".substr($reposicion["hora_original"],0, 5);
+                                }else{
+                                    echo " - ";
+                                }
                                 ?>
                             </td>
                             <td class="text-center align-middle text-nowrap"><?php
@@ -295,24 +351,24 @@ if($user->periodo_id!= ""){
                             <td class="text-center align-middle icono-acciones text-nowrap">
                                 <?php if (duracionMinutos($reposicion["fecha_nueva"], date("Y-m-d H:i:00")) < 0){ ?>
                                     <?php //no se cumple la fecha de la reposicion, es jefe de carrera
-                                    if((!$user->jefe_carrera || $user->admin || !$coordinador) && $reposicion["estado_reposicion_id"] == 1){?>
-                                    <a href="#" data-toggle="modal" data-target="#modal_aprobar" data-tipo="2" title="Aprobar"><?php echo $ICO["ver"];?></a>
+                                    if((!$user->jefe_carrera || $user->admin || !$coordinador) && $reposicion["estado"] == 1){?>
+                                    <a href="#" data-toggle="modal" data-target="#modal_aprobar" data-estado="2" data-tipo="<?php echo $reposicion["tipo"];?>" title="Aprobar"><?php echo $ICO["ver"];?></a>
                                     <?php } //no se cumple la fecha de la reposicion, no es jefe de carrera
-                                    else if(($supervisor || $user->admin) && $reposicion["estado_reposicion_id"] == 2){?>
-                                    <a href="#" data-toggle="modal" data-target="#modal_aprobar" data-tipo="3" title="Autorizar" ><?php echo $ICO["ver"];?></a>
+                                    else if(($supervisor || $user->admin) && $reposicion["estado"] == 2){?>
+                                    <a href="#" data-toggle="modal" data-target="#modal_aprobar" data-estado="3" data-tipo="<?php echo $reposicion["tipo"];?>" title="Autorizar" ><?php echo $ICO["ver"];?></a>
                                     <?php } else { ?>
-                                        <a href="#" data-toggle="modal" data-target="#modal_aprobar" data-tipo="1" title="Ver detalle"><?php echo $ICO["ver"];?></a>
+                                        <a href="#" data-toggle="modal" data-target="#modal_aprobar" data-estado="1" data-tipo="<?php echo $reposicion["tipo"];?>" title="Ver detalle"><?php echo $ICO["ver"];?></a>
                                     <?php } ?>
                                     <?php
                                 }else{ //fecha ya pasó?>
-                                    <a href="#" data-toggle="modal" data-target="#modal_aprobar" data-tipo="1" title="Ver detalle"><span class="text-danger"><?php echo $ICO["ver"];?></span></a>
+                                    <a href="#" data-toggle="modal" data-target="#modal_aprobar" data-estado="1" data-tipo="<?php echo $reposicion["tipo"];?>" title="Ver detalle"><span class="text-danger"><?php echo $ICO["ver"];?></span></a>
                                 <?php } ?>
 
                                 <?php
-                                    if($reposicion["estado_reposicion_id"]<4){
+                                    if($reposicion["estado"]<4){
                                         if(
-                                            (($user->jefe_carrera || $user->admin || $coordinador) && $reposicion["estado_reposicion_id"] == 1)/* nueva */
-                                            || (($user->admin || $coordinador || $supervisor) && $reposicion["estado_reposicion_id"] == 2)/* aprobado facultad */
+                                            (($user->jefe_carrera || $user->admin || $coordinador) && $reposicion["estado"] == 1)/* nueva */
+                                            || (($user->admin || $coordinador || $supervisor) && $reposicion["estado"] == 2)/* aprobado facultad */
                                         ){
                                 ?>
                                 <a href="#" data-toggle="modal" data-target="#modal_confirm" title="Cancelar"><span class="text-danger"><?php echo $ICO["cancelar"];?></span></a>
@@ -348,6 +404,7 @@ if($user->periodo_id!= ""){
                         <form action="./action/reposicion_autoriza.php" method="post" id="formaModal">
                             <input type="hidden" name="id" id="id">
                             <input type="hidden" name="edo" id="edo" value="">
+                            <input type="hidden" name="tipo" id="tipo" value="">
                             
                             <div class="row">
                                 <div class="col-6 col-sm-4 barra-right text-right">
@@ -365,6 +422,14 @@ if($user->periodo_id!= ""){
                                     <p class="rep-fac"></p>
                                 </div>
                             </div>
+                            <div class="row">
+                                <div class="col-6 col-sm-4 barra-right text-right">
+                                    <p class="font-weight-bold">Carrera</p>
+                                </div>
+                                <div class="col-6">
+                                    <p class="rep-carr"></p>
+                                </div>
+                            </div>
                             <div class="row">
                                 <div class="col-6 col-sm-4 barra-right text-right">
                                     <p class="font-weight-bold">Materia</p>
@@ -373,6 +438,14 @@ if($user->periodo_id!= ""){
                                     <p class="rep-mat"></p>
                                 </div>
                             </div>
+                            <div class="row">
+                                <div class="col-6 col-sm-4 barra-right text-right">
+                                    <p class="font-weight-bold">Grupo</p>
+                                </div>
+                                <div class="col-6">
+                                    <p class="rep-gpo"></p>
+                                </div>
+                            </div>
                             <div class="row">
                                 <div class="col-6 col-sm-4 barra-right text-right">
                                     <p class="font-weight-bold">Ciclo y bloque</p>
@@ -466,6 +539,15 @@ if($user->periodo_id!= ""){
                                     <input type="hidden" id="salon" name="salon" value="">
                                 </div>
                             </div>
+
+                            <div class="row" id="supervisor" style="display: none;">
+                                <div class="col-6 col-sm-4 barra-right text-right">
+                                    <p class="font-weight-bold">Supervisor</p>
+                                </div>
+                                <div class="col-6">
+                                    <p class="rep-sup"></p>
+                                </div>
+                            </div>
                             
                             
                             <div class="row mt-4">
@@ -486,7 +568,7 @@ if($user->periodo_id!= ""){
                                 </div>
                             </div>
 
-                            <div class="form-group row mt-3">
+                            <div class="form-group row mt-3" id="submitGroup">
                                 <div class="col-12 text-center">
                                     
                                     <p class="aprobar-block">Una vez realizada la acción no se puede deshacer.</p>
@@ -497,6 +579,13 @@ if($user->periodo_id!= ""){
                                     
                                 </div>
                             </div>
+                            <div class="form-group row mt-3" id="loadingGroup" style="display:none">
+                                <div class="col-12 text-center">
+                                    <div class="spinner-border text-primary" role="status">
+                                        <span class="sr-only">Loading...</span>
+                                    </div>
+                                </div>
+                            </div>
                         </form>
                     </div>
                 </div>
@@ -629,103 +718,224 @@ if($user->periodo_id!= ""){
         $('#modal_aprobar').on('show.bs.modal', function (event) {
             var button = $(event.relatedTarget); // Button that triggered the modal
             var id = button.parents("tr").data("id");
-            var edo = button.data('tipo');
+            var edo = button.data('estado');
+            var tipo = button.data('tipo');
+            $("#loadingGroup").hide();
+            $("#submitGroup").show();
 
             //1 ver, 2 aprobar, 3 autorizar
             $("#edo").val(edo);
             $("#id").val(id);
+            $("#tipo").val(tipo);
             
-
-            $.ajax({
-                url:  './action/reposicion_select.php',
-                type: 'POST', 
-                dataType: 'json',
-                data: { id: id},
-                success: function(result) {
-                    if(result["error"]!= "" &&  result["error"] !== undefined){
-                        triggerMessage(result["error"], "Error");
-                        $('#modal_aprobar').modal("hide");
-                    }else{
-                        $("#dlSalon").val("");
-                        $("#modal_aprobar .rep-prof").text(result["profesor_nombre"]);
-                        $("#modal_aprobar .rep-fac").text(result["facultad"]);
-                        $("#modal_aprobar .rep-mat").text(result["materia_desc"]);
-                        $("#modal_aprobar .rep-ciclo").text(result["ciclo"]);
-                        $("#modal_aprobar .rep-bloque").text(result["bloque"]);
-                        if(result["tipo"])
-                            $("#modal_aprobar .rep-tipo").text("Reposición");
-                        else
-                            $("#modal_aprobar .rep-tipo").text("Cambio");
-                        $("#modal_aprobar .rep-aula").text(result["aula_desc"])
-                        $("#modal_aprobar .rep-aula").data("aula",result["aula"]);
-                        $("#modal_aprobar .rep-falta").text(result["fecha_clase"]);
-                        $("#modal_aprobar .rep-fecha").text(result["fecha_nueva"]+" de "+result["hora_ini"]+":"+result["min_ini"]+" a "+result["hora_fin"]+":"+result["min_fin"]);
-                        if(result["salon"] =="" || result["salon"] === undefined){
-                            $('#salon').prop("selectedIndex", 0);
-                        }else{
-                            $('#salon').val(result["salon"]);
-                        }
-                        $("#modal_aprobar .rep-salon").text(result["salon_desc"]);
-                        $("#modal_aprobar .rep-comentarios").text(result["comentario"]);
-                        $('#modal_aprobar .rep-alumnos').text(result["alumnos"]);
-
-                        if(result["estado"] == 4){//cancelada
-                            $('#modal_aprobar .rep-motivo').text(result["motivo_cancelacion"]);
-                            $("#cancelada-block").show();
+            var action_pag;
+            if(tipo == 1 || tipo == 2){
+                $.ajax({
+                    url:  './action/reposicion_select.php',
+                    type: 'POST', 
+                    dataType: 'json',
+                    data: { id: id},
+                    success: function(result) {
+                        if(result["error"]!= "" &&  result["error"] !== undefined){
+                            triggerMessage(result["error"], "Error");
+                            $('#modal_aprobar').modal("hide");
                         }else{
-                            $("#cancelada-block").hide();
-                        }
+                            $("#dlSalon").val("");
+                            $("#modal_aprobar .rep-prof").text(result["profesor_nombre"]);
+                            $("#modal_aprobar .rep-fac").text(result["facultad"]);
+                            $("#modal_aprobar .rep-carr").parents(".row").show();
+                            $("#modal_aprobar .rep-carr").text(result["carrera"]);
+                            $("#modal_aprobar .rep-gpo").parents(".row").show();
+                            $("#modal_aprobar .rep-gpo").text(result["grupo"]);
+                            $("#modal_aprobar .rep-mat").parents(".row").show();
+                            $("#modal_aprobar .rep-mat").text(result["materia_desc"]);
+                            $("#modal_aprobar .rep-ciclo").parents(".row").show();
+                            $("#modal_aprobar .rep-ciclo").text(result["ciclo"]);
+                            $("#modal_aprobar .rep-bloque").text(result["bloque"]);
+                            if(result["tipo"])
+                                $("#modal_aprobar .rep-tipo").text("Reposición");
+                            else
+                                $("#modal_aprobar .rep-tipo").text("Cambio");
+                            $("#modal_aprobar .rep-aula").text(result["aula_desc"])
+                            $("#modal_aprobar .rep-aula").data("aula",result["aula"]);
+                            $("#modal_aprobar .rep-falta").parents(".row").show();
+                            $("#modal_aprobar .rep-falta").text(result["fecha_clase"]);
+                            $("#modal_aprobar .rep-fecha").text(result["fecha_nueva"]+" de "+result["hora_ini"]+":"+result["min_ini"]+" a "+result["hora_fin"]+":"+result["min_fin"]);
+                            if(result["salon"] =="" || result["salon"] === undefined){
+                                $('#salon').prop("selectedIndex", 0);
+                            }else{
+                                $('#salon').val(result["salon"]);
+                            }
+                            $("#modal_aprobar .rep-salon").text(result["salon_desc"]);
+                            $("#modal_aprobar .rep-comentarios").text(result["comentario"]);
+                            $('#modal_aprobar .rep-alumnos').text(result["alumnos"]);
 
-                        if(edo == 1){// 1  ver
-                            $("#modalLabel").text("Detalle de reposición");
-                            $(".aprobar-block").hide();
+                            if(result["supervisor_nombre"]!=""){
+                                $("#supervisor").show();
+                                $("#modal_aprobar .rep-sup").text(result["supervisor_nombre"]);
+                            }else{
+                                $("#supervisor").hide();
+                            }
 
-                            /*if(parseInt($("#modal_aprobar .rep-aula").data("aula")) != 1){//tipo aula 1 (salon normal)  - ver
-                                $("#salon-ver").hide();
-                                $("#salon-editar").show();
+                            if(result["estado"] == 4){//cancelada
+                                $('#modal_aprobar .rep-motivo').text(result["motivo_cancelacion"]);
+                                $("#cancelada-block").show();
                             }else{
+                                $("#cancelada-block").hide();
+                            }
+
+                            if(edo == 1){// 1  ver
+                                $("#modalLabel").text("Detalle de reposición");
+                                $(".aprobar-block").hide();
+
+                                /*if(parseInt($("#modal_aprobar .rep-aula").data("aula")) != 1){//tipo aula 1 (salon normal)  - ver
+                                    $("#salon-ver").hide();
+                                    $("#salon-editar").show();
+                                }else{
+                                    $("#salon-ver").show();
+                                    $("#salon-editar").hide();
+                                }*/
                                 $("#salon-ver").show();
                                 $("#salon-editar").hide();
-                            }*/
-                            $("#salon-ver").show();
-                            $("#salon-editar").hide();
-                            
+                                
+                            }else{
+                                $("#modalLabel").text("Aprobar reposición");
+                                $(".aprobar-block").show();
+
+                                if(edo == 2 && parseInt($("#modal_aprobar .rep-aula").data("aula")) == 1){//tipo aula 1 (salon normal)  - ver
+                                    $("#salon-ver").show();
+                                    $("#salon-editar").hide();
+                                }else if(edo == 3 && parseInt($("#modal_aprobar .rep-aula").data("aula")) != 1){//aprobar (con salón especial)
+                                    $("#salon-ver").show();
+                                    $("#salon-editar").hide();
+                                }else{
+                                    $("#salon-ver").hide();
+                                    $("#salon-editar").show();
+                                }
+                            }
+
+                            if(result["aula_supervisor"]){//Solo supervisor
+                                <?php if($supervisor){ ?>
+                                    $("#salon-editar").attr("disabled", false);
+                                <?php }else{?>
+                                    $("#salon-editar").attr("disabled", true);
+                                <?php } ?>
+                            }else{// Facultad
+                                <?php if(!$supervisor){ ?>
+                                    $("#salon-editar").attr("disabled", false);
+                                <?php }else{?>
+                                    $("#salon-editar").attr("disabled", true);
+                                <?php } ?>
+                            }                        
+
+                        }
+                    },
+                    error: function(jqXHR, textStatus, errorThrown ){
+                        triggerMessage(errorThrown, "Error");
+                    }
+                });//ajax
+            }else{
+                
+                $.ajax({
+                    url:  './action/asignacion_select.php',
+                    type: 'POST', 
+                    dataType: 'json',
+                    data: { id: id},
+                    success: function(result) {
+                        if(result["error"]!= "" &&  result["error"] !== undefined){
+                            triggerMessage(result["error"], "Error");
+                            $('#modal_aprobar').modal("hide");
                         }else{
-                            $("#modalLabel").text("Aprobar reposición");
-                            $(".aprobar-block").show();
+                            $("#dlSalon").val("");
+                            $("#modal_aprobar .rep-prof").text(result["profesor_nombre"]);
+                            $("#modal_aprobar .rep-fac").text(result["facultad"]);
+                            $("#modal_aprobar .rep-carr").parents(".row").hide();
+                            $("#modal_aprobar .rep-gpo").parents(".row").hide();
+                            $("#modal_aprobar .rep-mat").parents(".row").hide();
+                            $("#modal_aprobar .rep-ciclo").parents(".row").hide();
+                            
+                            $("#modal_aprobar .rep-tipo").text("Asignación");
+                            
+                            $("#modal_aprobar .rep-aula").text(result["aula_desc"])
+                            $("#modal_aprobar .rep-aula").data("aula",result["aula"]);
+                            $("#modal_aprobar .rep-falta").parents(".row").hide();
+                            $("#modal_aprobar .rep-fecha").text(result["fecha_nueva"]+" de "+result["hora_ini"]+":"+result["min_ini"]+" a "+result["hora_fin"]+":"+result["min_fin"]);
+                            if(result["salon"] =="" || result["salon"] === undefined){
+                                $('#salon').prop("selectedIndex", 0);
+                            }else{
+                                $('#salon').val(result["salon"]);
+                            }
+                            $("#modal_aprobar .rep-salon").text(result["salon_desc"]);
+                            $("#modal_aprobar .rep-comentarios").text(result["comentario"]);
+                            $('#modal_aprobar .rep-alumnos').text(result["alumnos"]);
 
-                            if(edo == 2 && parseInt($("#modal_aprobar .rep-aula").data("aula")) == 1){//tipo aula 1 (salon normal)  - ver
-                                $("#salon-ver").show();
-                                $("#salon-editar").hide();
-                            }else if(edo == 3 && parseInt($("#modal_aprobar .rep-aula").data("aula")) != 1){//aprobar (con salón especial)
+                            if(result["supervisor_nombre"]!=""){
+                                $("#supervisor").show();
+                                $("#modal_aprobar .rep-sup").text(result["supervisor_nombre"]);
+                            }else{
+                                $("#supervisor").hide();
+                            }
+
+                            if(result["estado"] == 4){//cancelada
+                                $('#modal_aprobar .rep-motivo').text(result["motivo_cancelacion"]);
+                                $("#cancelada-block").show();
+                            }else{
+                                $("#cancelada-block").hide();
+                            }
+
+                            if(edo == 1){// 1  ver
+                                $("#modalLabel").text("Detalle de reposición");
+                                $(".aprobar-block").hide();
+
+                                /*if(parseInt($("#modal_aprobar .rep-aula").data("aula")) != 1){//tipo aula 1 (salon normal)  - ver
+                                    $("#salon-ver").hide();
+                                    $("#salon-editar").show();
+                                }else{
+                                    $("#salon-ver").show();
+                                    $("#salon-editar").hide();
+                                }*/
                                 $("#salon-ver").show();
                                 $("#salon-editar").hide();
+                                
                             }else{
-                                $("#salon-ver").hide();
-                                $("#salon-editar").show();
+                                $("#modalLabel").text("Aprobar reposición");
+                                $(".aprobar-block").show();
+
+                                if(edo == 2 && parseInt($("#modal_aprobar .rep-aula").data("aula")) == 1){//tipo aula 1 (salon normal)  - ver
+                                    $("#salon-ver").show();
+                                    $("#salon-editar").hide();
+                                }else if(edo == 3 && parseInt($("#modal_aprobar .rep-aula").data("aula")) != 1){//aprobar (con salón especial)
+                                    $("#salon-ver").show();
+                                    $("#salon-editar").hide();
+                                }else{
+                                    $("#salon-ver").hide();
+                                    $("#salon-editar").show();
+                                }
                             }
-                        }
 
-                        if(result["aula_supervisor"]){//Solo supervisor
-                            <?php if($supervisor){ ?>
-                                $("#salon-editar").attr("disabled", false);
-                            <?php }else{?>
-                                $("#salon-editar").attr("disabled", true);
-                            <?php } ?>
-                        }else{// Facultad
-                            <?php if(!$supervisor){ ?>
-                                $("#salon-editar").attr("disabled", false);
-                            <?php }else{?>
-                                $("#salon-editar").attr("disabled", true);
-                            <?php } ?>
-                        }                        
+                            if(result["aula_supervisor"]){//Solo supervisor
+                                <?php if($supervisor){ ?>
+                                    $("#salon-editar").attr("disabled", false);
+                                <?php }else{?>
+                                    $("#salon-editar").attr("disabled", true);
+                                <?php } ?>
+                            }else{// Facultad
+                                <?php if(!$supervisor){ ?>
+                                    $("#salon-editar").attr("disabled", false);
+                                <?php }else{?>
+                                    $("#salon-editar").attr("disabled", true);
+                                <?php } ?>
+                            }                        
 
+                        }
+                    },
+                    error: function(jqXHR, textStatus, errorThrown ){
+                        triggerMessage(errorThrown, "Error");
                     }
-                },
-                error: function(jqXHR, textStatus, errorThrown ){
-                    triggerMessage(errorThrown, "Error");
-                }
-            });//ajax
+                });//ajax
+            }
+            
+            
         });
         /*
         $(".btn-borrar").click(function(){
@@ -760,6 +970,8 @@ if($user->periodo_id!= ""){
             var edo =  parseInt($("#edo").val());
             console.log(edo)
             if((edo == 3 && valida()) || edo == 2){
+                $("#loadingGroup").show();
+                $("#submitGroup").hide();
                 $("#formaModal").submit();
             }      
         });

+ 30 - 13
reposiciones_crear.php

@@ -59,13 +59,14 @@ if(!is_null($user->periodo_id)){
     if(strtotime($periodo_rs["periodo_fecha_inicio"])>strtotime(date("Y-m-d")) )
         $fecha_man = date("d/m/Y", strtotime($periodo_rs["periodo_fecha_inicio"]));
     else{
-        $dias = 3;
-        if( intval(date("w")) >=3 && intval(date("w"))<=5 )//Mie a Vie
-            $dias+=3;
-        else if( intval(date("w")) ==6 )//Sab
-            $dias+=2;
-        else if( intval(date("w")) ==0 )//Do
-            $dias+=1;
+        $dia_actual = intval(date("w"));
+        $dias = 2;//días mínimos Lun a Jue
+        if($dia_actual ==5 || $dia_actual ==4 )//Vie
+            $dias=4;
+        else if( $dia_actual ==6 )//Sab
+            $dias=3;
+        else if( $dia_actual ==0 )//Do
+            $dias=2;
         
         $fecha_man = date("d/m/Y", strtotime("+".$dias." day"));
     }
@@ -363,7 +364,7 @@ if(!is_null($user->periodo_id)){
                                     <label for="fecha_inicial" class="col-4 col-form-label">Fecha de reposicion *</label> 
                                     <div class="col-8">
                                         <input id="fecha_inicial" name="fecha_inicial" type="text" class="form-control date-picker-future" placeholder="dd/mm/aaaa" maxlength="10" required="required" readonly="readonly" value="">
-                                        <small class="form-text text-muted">Las reposiciones se deben solicitar con al menos 72hrs de anticipación.<br>
+                                        <small class="form-text text-muted">Las reposiciones se deben solicitar con al menos 48hrs de anticipación.<br>
                                                     Recuerda que en sábado el límite para terminar la clase es a las 15:00hrs.
                                         </small>
                                     </div>
@@ -406,9 +407,11 @@ if(!is_null($user->periodo_id)){
                                             <div class="datalist-input">Salón</div>
                                             <span class="ing-buscar icono"></span>
                                             <ul style="display:none">
-                                                <li data-id="1">Salón</li>
-                                                <li data-id="2">Sala de cómputo</li>
-                                                <li data-id="3">Salón/Taller de la facultad</li>
+                                                <?php
+                                                $tipoaula_rs = $db->query('select * from tipoaula t order by t.tipoaula_id ');
+                                                foreach($tipoaula_rs as $ta){ ?>
+                                                    <li data-id="<?php echo $ta["tipoaula_id"];?>"><?php echo $ta["tipoaula_nombre"];?></li>
+                                                <?php } ?>
                                             </ul>
                                             <input type="hidden" id="aula" name="aula" value="1">
                                         </div>
@@ -459,12 +462,19 @@ if(!is_null($user->periodo_id)){
                                 </div>
                             </div>
                             
-                            <div class="form-group row mt-3">
+                            <div class="form-group row mt-3" id="submitGroup">
                                 <div class="offset-4 col-8">
                                     <button type="submit" class="btn btn-outline-primary  materia-block" id="submitBtn" data-tipo="1"><?php echo $ICO["aceptar"];?> Guardar</button>
                                     <button type="reset" class="btn btn-outline-danger" data-dismiss="modal"><?php echo $ICO["cancelar"];?> Cancelar</button>
                                 </div>
                             </div>
+                            <div class="form-group row mt-3" id="loadingGroup" style="display:none">
+                                <div class="col-12 text-center">
+                                    <div class="spinner-border text-primary" role="status">
+                                        <span class="sr-only">Loading...</span>
+                                    </div>
+                                </div>
+                            </div>
                         </form>
                     </div>
                 </div>
@@ -759,6 +769,10 @@ if(!is_null($user->periodo_id)){
         }else{
             $('#formaModal').prop("action", "./action/reposicion_insert.php");
         }
+        if(!error){
+            $("#loadingGroup").show();
+            $("#submitGroup").hide();
+        }
         return !error;
     }
 
@@ -945,6 +959,9 @@ if(!is_null($user->periodo_id)){
             var button = $(event.relatedTarget); // Button that triggered the modal
             var tipo = button.data('tipo'); // 1 alta, 2 edicion
             var modal = $(this);
+
+            $("#loadingGroup").hide();
+            $("#submitGroup").show();
             
             $("#modal .is-invalid").removeClass("is-invalid");
             //$(this).find(".form-control:first-child").focus();
@@ -1050,7 +1067,7 @@ if(!is_null($user->periodo_id)){
     })
 </script>
 <script src="js/messages.js"></script>
-<script type="module" src="js/reposiciones.js"></script>
+
 </body>
 
 </html>

+ 57 - 0
rest/LogCambios.php

@@ -0,0 +1,57 @@
+<?php
+
+/* 
+ * Objeto para escribir los cambios
+ */
+
+class LogCambios {
+    //put your code here
+    private $file, $month, $year;
+    private $dir;
+    
+    function __construct($ruta = "/log/"){
+        $this->month = date("m");
+        $this->year = date("Y");
+        //$this->dir = $_SERVER['DOCUMENT_ROOT'].$ruta;
+        $this->dir = $ruta;
+        $this->updateFilename();
+    }
+    
+    function setMes($mes){
+        $this->month = $mes;
+        $this->updateFilename();
+    }
+    function setAno($ano){
+        $this->year = $ano;
+        $this->updateFilename();
+    }
+    private function updateFilename(){
+        $this->file = "cambios_".$this->year."_".$this->month.".log";
+    }
+
+    private function cleanLog($text){//remueve || de los textos para no afectar estructura
+        return trim(str_ireplace( "||" , "" , $text));
+    }
+    
+    function appendLog($desc){
+        $filename = $this->dir.$this->file;
+        /*
+        if (file_exists($this->dir)){
+            $data = date('Y-m-d H:i:s')."||".$this->cleanLog($desc)."\n";
+            //echo $filename;
+            $res = file_put_contents($filename, $data, FILE_APPEND);
+            //echo " result: $res<br>";
+        }*/
+    }
+/*
+    function getLog($mes ="", $ano = ""){
+        if($mes != "") $this->setMes($mes);
+        if($ano != "") $this->setAno($ano);
+        $filename = $this->dir.$this->file;
+        if (file_exists($filename)){
+            return file ($filename , FILE_SKIP_EMPTY_LINES);
+        }else{
+            return array();
+        }
+    }*/
+}

+ 430 - 0
rest/horarios.php

@@ -0,0 +1,430 @@
+<?php
+/*
+idPeriodo: identificador del periodo a consultar (obligatorio, número entero)
+claveFacultad: clave de la facultad a consultar (opcional, cadena)
+claveCarrera: clave de la carrera a consultar (opcional, cadena)
+claveProfesor: clave del empleado a consultar (opcional, cadena)
+fecha: fecha de la clase (opcional, cadena en formato yyyy-MM-dd)
+ */
+ini_set('display_errors', 1);
+ini_set('display_startup_errors', 1);
+ini_set('post_max_size', 1);
+ini_set('max_execution_time', 8*60);
+error_reporting(E_ALL);
+date_default_timezone_set('America/Mexico_City');
+
+$ruta = "../";
+$ruta_superior = dirname(__DIR__);
+require_once $ruta_superior."/include/bd_pdo_rest.php";
+require_once __DIR__."/token.php";
+require_once __DIR__."/LogCambios.php";
+
+//--------------ACTUALIZA HORARIOS--------------------------
+if(!empty($_GET["fecha"])){
+    $hoy = $_GET["fecha"];
+}else{
+    $hoy = date("Y-m-d");
+}
+$dia_hoy = date("w", strtotime($hoy));
+
+
+function compareByHash($a, $b) {
+    return strcmp($a['hash'], $b['hash']);
+}
+
+
+$debug = false;
+if(isset($_GET["debug"])){
+    $debug = true;
+    print_r( $db->querySingle('select now()'));
+}
+$periodo_sgu_old = 0;
+$log_desc = "";
+
+
+/*$cambiocarreras_rs = $db->query('SELECT CLAVE_MATERIA, clave_carrera FROM materia join carrera using(carrera_id)');
+
+function getCarrera($claveBuscar){
+    global $cambiocarreras_rs;
+    $i = array_search($claveBuscar, array_column($cambiocarreras_rs, 'clave_materia'));
+    if($i>=0)
+        return $cambiocarreras_rs[$i]["clave_carrera"];
+    return $cambiocarreras_rs[0]["clave_carrera"];
+}*/
+
+//------------------ACTUALIZA SALONES----------------------
+
+$pag = "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/catalogos/espacios/seleccionar";
+$curl = curl_init();
+curl_setopt_array($curl, [
+    CURLOPT_URL => $pag,
+    CURLOPT_RETURNTRANSFER => true,
+    //CURLOPT_ENCODING => "",
+    //CURLOPT_MAXREDIRS => 10,
+    // CURLOPT_TIMEOUT => 0,
+    //CURLOPT_CUSTOMREQUEST => "POST",
+    CURLOPT_POSTFIELDS => json_encode([]),
+    CURLOPT_HTTPHEADER => [
+        "token: ".$token,
+        "username: SGU_APSA_AUD_ASIST",
+        "Content-Type: application/json",
+        'Transfer-Encoding: chunked'
+    ],
+]);
+
+$response = curl_exec($curl);
+$err = curl_error($curl);
+
+curl_close($curl);
+
+if ($err)
+    die("cURL Error #:$err");
+$salonesData = json_decode($response, true);
+
+//$salonesTotal_rs = $db->count('salon');
+$salonesTotal_rs = $db->where('salon_id', 0, '>')->count('salon');
+echo "$salonesTotal_rs tiene " . count($salonesData) . " salones<br>";
+//if($salonesTotal_rs < count($salonesData)){//faltan salones en BD
+    $salones_rs = $db->query('SELECT id_espacio_sgu, salon FROM salon');
+    //claves de espacios
+    $arreglo_espacios = array_map(function ($item) {
+        return $item['id_espacio_sgu'];
+    }, $salones_rs);
+    $arreglo_nombres = array_map(function ($item) {
+        return $item['salon'];
+    }, $salones_rs);
+
+    foreach($salonesData as $data){
+        if( !in_array($data["IdEspacio"], $arreglo_espacios) || !in_array($data["NombreEspacio"], $arreglo_nombres)){
+            //Insertar espacio
+            if($debug){
+                echo "Espacio nuevo: ".$data["NombreEspacio"]."<br>";
+            }else{
+                $db->query('INSERT INTO SALON (salon, id_espacio_sgu, id_espacio_padre) VALUES (:salon, :id, :id_padre)
+                    ON CONFLICT (id_espacio_sgu) DO UPDATE SET salon = :salon',
+                    [":salon"=>$data["NombreEspacio"], ":id"=>$data["IdEspacio"], ":id_padre"=>$data["IdEspacioPadre"]]);
+            }
+        }
+    }
+//}
+// -----------------------------
+
+$pag = "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/seleccionar";
+$elementos_bd_total = [];
+$elementos_sgu_total = [];
+$periodo_sgu_old = 0;
+try{
+    $pdo->beginTransaction();
+
+    
+    $periodos_rs = $db->query('SELECT periodo_id, id_periodo_sgu, nivel_id FROM periodo WHERE :hoy BETWEEN periodo_fecha_inicio AND periodo_fecha_fin AND id_periodo_sgu != 0 ORDER BY periodo_id',
+        [":hoy"=>$hoy]);
+    foreach ($periodos_rs as $per){
+
+        //Verifica si el día de hoy es festivo
+        $vacacion_rs = $db->querySingle('select es_festivo(:per, :hoy)', [":per"=>$per["periodo_id"], ":hoy"=>$hoy]);
+        if($vacacion_rs["es_festivo"]){
+            if($debug){
+                echo "<h3>Dia festivo en Periodo: ".$per["id_periodo_sgu"]."[".$per["periodo_id"]."]</h3>";
+            }
+            continue;
+        }
+        
+        $carreras_rs = $db->query('SELECT c.clave_carrera, c.carrera_id FROM carrera c WHERE nivel_id = :nivel',
+        [":nivel"=>$per["nivel_id"]]);
+        if($debug){
+            echo "<h3>Periodo: ".$per["id_periodo_sgu"]."[".$per["periodo_id"]."] nivel: ".$per["nivel_id"]."</h3>";
+            //print_r($carreras_rs);
+        }
+        if ($periodo_sgu_old != $per["id_periodo_sgu"]){
+            $periodo_sgu_old = $per["id_periodo_sgu"];
+            $params = [
+                'idPeriodo'=>$per["id_periodo_sgu"],
+                'fecha'=>$hoy
+            ];
+        
+            $curl = curl_init();
+            curl_setopt_array($curl, [
+                CURLOPT_URL => $pag,
+                CURLOPT_RETURNTRANSFER => true,
+                //CURLOPT_ENCODING => "",
+                //CURLOPT_MAXREDIRS => 10,
+                //CURLOPT_TIMEOUT => 0,
+                //CURLOPT_CUSTOMREQUEST => "POST",
+                CURLOPT_POSTFIELDS => json_encode($params),
+                CURLOPT_HTTPHEADER => [
+                    "token: ".$token,
+                    "username: SGU_APSA_AUD_ASIST",
+                    "Content-Type: application/json"
+                ],
+            ]);
+
+            $response = curl_exec($curl);
+            $err = curl_error($curl);
+
+            /*echo "Response<br>";
+            print_r($response);*/
+
+            curl_close($curl);
+
+            if ($err)
+                die("cURL Error #:$err");
+        }
+        $selectedData = json_decode($response, true);
+    
+        //claves de carreras en el periodo
+        $arreglo_claves = array_map(function ($item) {
+            return $item['clave_carrera'];
+        }, $carreras_rs);
+
+        //print_r($selectedData); exit();
+        $sguHash = array();
+        if(!empty($selectedData)){
+            
+            //Recorre SGU y genera hash
+            foreach( $selectedData as $row ){
+                if(!$row["EsMateriaPorReposicion"]){
+                    $carrera = $row["ClaveCarrera"];
+                    if(is_null($carrera) || empty($carrera))
+                        $carrera = '0';
+                    /*else{
+                        if(!$row["EsMateriaPorAsignacion"]) 
+                            $carrera = getCarrera($row["ClaveMateria"]);
+                    }*/
+                    
+                    $sguHash[] = array(
+                        "hash"=>( trim($row["HoraInicio"]."|".($row["NombreMateria"])."|".(trim($row["ClaveProfesor"])==""?"000000":trim($row["ClaveProfesor"]))."|".$row["IdEspacio"]."|".$per["periodo_id"]) ),
+                        "data"=>$row,
+                        "per"=>$per["periodo_id"]
+                    );
+                }else{//reposición
+                    try{
+                        if(in_array($row["ClaveCarrera"] , $arreglo_claves)){
+                            //busca yyyy-mm-dd hh:mm:ss en la cadena
+                            if (preg_match("/\d{4}-\d{2}-\d{2} de \d{2}:\d{2}:\d{2}/", $row["Observaciones"], $matches)) {
+                                $fecha_orig =  str_replace(" de", "", $matches[0]);
+                                $fecha_nueva = $row["FechaStr"]." ".$row["HoraInicio"];
+                                $hora_fin_nueva = $row["HoraFin"];
+                                if($debug){
+                                    echo "<br>SELECT * FROM fi_reposicion_sgu('$fecha_orig', '".$hora_fin_nueva."','".$fecha_nueva."' ,'".$row["ClaveProfesor"]."', ".$per["periodo_id"].", ".$row["IdEspacio"].")";
+                                }else{
+                                    $db->query('SELECT * FROM fi_reposicion_sgu(:fecha_orig, :hora_fin, :fecha_rep, :prof, :per, :salon)',
+                                        [":fecha_orig"=>$fecha_orig, ":hora_fin"=>$hora_fin_nueva, ":fecha_rep"=>$fecha_nueva, ":prof"=>$row["ClaveProfesor"], ":per"=>$per["periodo_id"], ":salon"=>$row["IdEspacio"] ]);
+                                    $log_desc .="SELECT * FROM fi_reposicion_sgu($fecha_orig, ".$fecha_nueva.", ".$row["ClaveProfesor"].", ".$per["periodo_id"].") ## ";
+                                }
+                            }else{
+                                if($debug)
+                                    echo "No se encontró fecha y hora en: ".$row["Observaciones"]."<br>";
+                            }
+                        }
+                    }catch(Exception $e){
+                        echo "ERROR Reposición<br>".$e->getMessage();
+                    }
+                }
+            }
+            unset($selectedData);
+        }
+        
+        echo count($sguHash)."Total <br>";
+    
+        $horarios_sgu = [];
+        
+        /*
+        print_r($carreras_rs); 
+        echo "<br><hr><br>";*/
+        print_r($arreglo_claves);
+        echo "<br><hr><br>";
+
+        //$areacomun = array();
+        foreach($sguHash as $sgu){
+            if(in_array($sgu["data"]["ClaveCarrera"] , $arreglo_claves) /*&& !in_array($sgu["data"]["ClaveMateria"], $areacomun)*/){
+                $horarios_sgu[] = $sgu;
+            }
+        }
+        
+        //print_r($horarios_sgu);exit();
+        unset($sguHash);
+        $elementos_sgu_total = array_merge($elementos_sgu_total, $horarios_sgu);
+        
+        //Recorre BD y genera hash
+        $horarios_rs = $db->query('SELECT * FROM fs_horarios_hash(:dia, :periodo, :fecha)',
+            [':dia' => $dia_hoy, ':periodo' => $per["periodo_id"], ':fecha'=>$hoy]);
+            //echo "**** SELECT * FROM fs_horarios_hash($dia_hoy, ".$per["periodo_id"].")<br>";
+        
+        //usort($horarios_rs, 'compareByHash');
+        $elementos_bd_total = array_merge($elementos_bd_total, $horarios_rs);
+        
+    }//foreach periodo
+    //print_r($elementos_sgu_total); 
+//exit();
+    if($debug){
+        echo "<h3>Resumen</h3>";
+        echo "<h5>SGU [".count($elementos_sgu_total)."]</h5>";
+        echo "<h5>BD [".count($elementos_bd_total)."]</h5>";
+    }
+    // Extraer los "hash" de $lista y $lista2 en arreglos separados
+    $hashes_sgu = array_column($elementos_sgu_total, 'hash');
+    $hashes_bd = array_column($elementos_bd_total, 'hash');
+
+    
+    //print_r($elementos_sgu_total); 
+    
+    //------------------
+    // Encontrar los "hash" que están en $sgu pero no están en $bd
+    $hashes_no_en_sgu = array_diff($hashes_bd, $hashes_sgu);
+    if($debug) echo "hashes_no_en_sgu ".count($hashes_no_en_sgu)."<br>";
+
+    if(count($hashes_no_en_sgu)>0){
+        // Ahora puedes obtener los elementos completos que cumplen la condición original
+        $elementos_no_en_sgu = array_filter($elementos_bd_total, function ($item) use ($hashes_no_en_sgu) {
+            return in_array($item['hash'], $hashes_no_en_sgu);
+        });
+        if($debug){
+            print_r($elementos_no_en_sgu);
+            echo "Sobran ".count($elementos_no_en_sgu)." en BD<br>";
+        }
+
+        //Update fecha_fin
+        $log_desc = "";
+        foreach($elementos_no_en_sgu as $row){
+            try{
+                if($debug){
+                    echo "<br>SELECT * FROM fu_horario_deshabilita(".$row["horario_id"].");";
+                }else{
+                    $db->query('SELECT * FROM fu_horario_deshabilita(:horario)', [":horario"=>$row["horario_id"]]);
+                    $log_desc .="SELECT * FROM fu_horario_deshabilita(".$row["horario_id"].") ## ";
+                }
+            }catch(Exception $e){
+                echo "ERROR horario deshabilita<br>".$e->getMessage();
+            }
+        }
+        if(!$debug && !empty($log_desc)){
+            $log = new LogCambios(__DIR__."/log/");
+            $log->appendLog($log_desc);
+        }
+    }
+
+    // Encontrar los "hash" que están en $sgu pero no están en $bd
+    $hashes_no_en_bd = array_diff($hashes_sgu, $hashes_bd);
+
+    //echo "hashes_no_en_bd ".count($hashes_no_en_bd)."<br>";
+    
+
+    if(count($hashes_no_en_bd)>0){
+        // Ahora puedes obtener los elementos completos que cumplen la condición original
+        $elementos_no_en_bd = array_filter($elementos_sgu_total, function ($item) use ($hashes_no_en_bd) {
+            return in_array($item['hash'], $hashes_no_en_bd);
+        });
+        if($debug){
+            echo "<br>Faltan ".count($elementos_no_en_bd)." en BD<br>";
+            print_r($elementos_no_en_bd); echo "<br>";
+        }
+        
+
+        //Inserts
+        foreach($elementos_no_en_bd as $row){
+            if($row["data"]["ClaveMateria"] == "-")
+                $row["data"]["ClaveMateria"] = "";
+            if($debug){
+                echo "<br>SELECT * FROM fi_horario($dia_hoy, '".$row["data"]["HoraInicio"]."','".$row["data"]["HoraFin"]."','"
+                .$row["data"]["ClaveDependencia"]."','"
+                .$row["data"]["ClaveCarrera"]."','"
+                .$row["data"]["NombreMateria"]."','"
+                .$row["data"]["ClaveMateria"]."','"
+                .$row["data"]["ClaveProfesor"]."','"
+                .$row["data"]["NombreProfesor"]."','"
+                .$row["data"]["CorreoElectronico"]."','"
+                .$row["data"]["Grupo"]."',"
+                .$row["data"]["IdEspacio"].","
+                .$row["per"].");";
+                if($row["data"]["EsMateriaPorAsignacion"]){ echo " ***Asignacion directa***";}
+            }else{
+                $horario_new_rs = $db->querySingle('SELECT * FROM fi_horario(:hoy, :ini, :fin, :dep, :carr, :nom_mat, :cve_mat, :cve_prof, :nom_prof, :correo, :gpo, :espacio, :periodo)',
+                    [":hoy"=>$dia_hoy,
+                    ":ini"=>$row["data"]["HoraInicio"],
+                    ":fin"=>$row["data"]["HoraFin"],
+                    ":dep"=>$row["data"]["ClaveDependencia"],
+                    ":carr"=>$row["data"]["ClaveCarrera"],
+                    ":nom_mat"=>$row["data"]["NombreMateria"],
+                    ":cve_mat"=>$row["data"]["ClaveMateria"],
+                    ":cve_prof"=>$row["data"]["ClaveProfesor"]==""?"000000":$row["data"]["ClaveProfesor"],
+                    ":nom_prof"=>$row["data"]["NombreProfesor"],
+                    ":correo"=>$row["data"]["CorreoElectronico"],
+                    ":gpo"=>$row["data"]["Grupo"],
+                    ":espacio"=>$row["data"]["IdEspacio"],
+                    ":periodo"=>$row["per"]
+                    ]
+                );
+                //echo $horario_new_rs["fi_horario"]."<br>";
+                if($horario_new_rs["fi_horario"] > 0){
+                    if($row["data"]["EsMateriaPorAsignacion"]){
+                        $matasig_rs = $db->querySingle('SELECT * FROM fi_materia_asignacion(:hor,:mat,:carr, :prof)',
+                            [":hor"=>$horario_new_rs["fi_horario"],
+                            ":mat"=>$row["data"]["NombreMateria"],
+                            ":carr"=>$row["data"]["Carrera"],
+                            ":prof"=>$row["data"]["NombreProfesor"]
+                            ]
+                        );
+                    }
+
+                    $log_desc .="SELECT * FROM fi_horario($dia_hoy, '".$row["data"]["HoraInicio"]."','".$row["data"]["HoraFin"]."','"
+                    .$row["data"]["ClaveDependencia"]."','"
+                    .$row["data"]["ClaveCarrera"]."','"
+                    .$row["data"]["NombreMateria"]."','"
+                    .$row["data"]["ClaveMateria"]."','"
+                    .$row["data"]["ClaveProfesor"]."','"
+                    .$row["data"]["NombreProfesor"]."','"
+                    .$row["data"]["CorreoElectronico"]."','"
+                    .$row["data"]["Grupo"]."',"
+                    .$row["data"]["IdEspacio"]."); [ID=".$horario_new_rs["fi_horario"]."] ##";
+                }
+                
+            }
+        }
+        if(!$debug && !empty($log_desc)){
+            $log = new LogCambios(__DIR__."/log/");
+            $log->appendLog($log_desc);
+        }
+        
+    }
+    $stmt = null; // cierra conexion
+
+    if($debug) {
+        echo "<br><br><hr><br><br>";
+        usort($elementos_sgu_total, 'compareByHash');
+        usort($elementos_bd_total, 'compareByHash');
+
+        echo "<table><tr>";
+        echo "<td valign='top'>";
+        echo "<h5>SGU [".count($elementos_sgu_total)."]</h5>";
+        foreach($elementos_sgu_total as $sgu){
+            echo $sgu["hash"]."<br>";
+        }
+        echo "</td>";
+        echo "<td valign='top'>";
+        echo "<h5>BD [".count($elementos_bd_total)."]</h5>";
+        foreach($elementos_bd_total as $sgu){
+            echo $sgu["hash"]." [".$sgu["horario_id"]."]<br>";
+        }
+        echo "</td>";
+        echo "</tr></table>";
+    }else{
+        $pdo->commit();
+        echo "Commit";
+    }
+} catch(PDOException $e) {
+    echo "Error";
+    "ERROR BD! ".$e->getMessage();
+    $pdo->rollBack();
+    if(!$debug){
+        $log = new LogCambios(__DIR__."/log/");
+        $log->appendLog("ERROR BD! ".$e->getMessage());
+    }
+    print_r($e->getMessage());
+} catch(Exception $e2){
+    echo "Error";
+    print_r($e2->getMessage());
+}
+
+
+?>

+ 364 - 0
rest/horarios_new.php

@@ -0,0 +1,364 @@
+<?php
+/*
+idPeriodo: identificador del periodo a consultar (obligatorio, número entero)
+claveFacultad: clave de la facultad a consultar (opcional, cadena)
+claveCarrera: clave de la carrera a consultar (opcional, cadena)
+claveProfesor: clave del empleado a consultar (opcional, cadena)
+fecha: fecha de la clase (opcional, cadena en formato yyyy-MM-dd)
+ */
+ini_set('display_errors', 1);
+ini_set('display_startup_errors', 1);
+ini_set('post_max_size', 1);
+ini_set('max_execution_time', 8*60);
+error_reporting(E_ALL);
+date_default_timezone_set('America/Mexico_City');
+
+$ruta = "../";
+$ruta_superior = dirname(__DIR__);
+require_once $ruta_superior."/include/bd_pdo.php";
+require_once __DIR__."/token.php";
+require_once __DIR__."/LogCambios.php";
+
+//------------------ACTUALIZA SALONES----------------------
+
+$pag = "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/catalogos/espacios/seleccionar";
+$curl = curl_init();
+curl_setopt_array($curl, [
+    CURLOPT_URL => $pag,
+    CURLOPT_RETURNTRANSFER => true,
+    //CURLOPT_ENCODING => "",
+    //CURLOPT_MAXREDIRS => 10,
+    CURLOPT_TIMEOUT => 0,
+    CURLOPT_CUSTOMREQUEST => "POST",
+    //CURLOPT_POSTFIELDS => json_encode($params),
+    CURLOPT_HTTPHEADER => [
+        "token: ".$token,
+        "username: SGU_APSA_AUD_ASIST",
+        "Content-Type: application/json",
+        'Transfer-Encoding: chunked'
+    ],
+]);
+
+$response = curl_exec($curl);
+$err = curl_error($curl);
+
+curl_close($curl);
+
+if ($err)
+    die("cURL Error #:$err");
+$salonesData = json_decode($response, true);
+print_r($response);
+
+$salonesTotal_rs = $db->querySingle('SELECT count(1) as total FROM salon');
+
+echo $salonesTotal_rs["total"];
+exit();
+
+//--------------ACTUALIZA HORARIOS--------------------------
+if(!empty($_GET["fecha"])){
+    $hoy = $_GET["fecha"];
+}else{
+    $hoy = date("Y-m-d");
+}
+$dia_hoy = date("w", strtotime($hoy));
+$pag = "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/seleccionar";
+
+function compareByHash($a, $b) {
+    return strcmp($a['hash'], $b['hash']);
+}
+$elementos_bd_total = [];
+$elementos_sgu_total = [];
+$periodo_sgu_old = 0;
+
+$debug = false;
+if(isset($_GET["debug"])){
+    $debug = true;
+    print_r( $db->querySingle('select now()'));
+}
+$periodo_sgu_old = 0;
+$log_desc = "";
+try{
+    $pdo->beginTransaction();
+
+    
+    $periodos_rs = $db->query('SELECT periodo_id, id_periodo_sgu FROM periodo WHERE :hoy BETWEEN periodo_fecha_inicio AND periodo_fecha_fin AND id_periodo_sgu != 0 ORDER BY periodo_id',
+        [":hoy"=>$hoy]);
+    foreach ($periodos_rs as $per){
+
+        //Verifica si el día de hoy es festivo
+        $vacacion_rs = $db->querySingle('select es_festivo(:per, :hoy)', [":per"=>$per["periodo_id"], ":hoy"=>$hoy]);
+        if($vacacion_rs["es_festivo"]){
+            if($debug){
+                echo "<h3>Dia festivo en Periodo: ".$per["id_periodo_sgu"]."[".$per["periodo_id"]."]</h3>";
+            }
+            continue;
+        }
+        
+        $carreras_rs = $db->query('SELECT c.clave_carrera, c.carrera_id FROM carrera c INNER JOIN periodo_carrera pc ON c.carrera_id = pc.carrera_id WHERE pc.periodo_id = :per',
+        [":per"=>$per["periodo_id"]]);
+        if($debug)
+            echo "<h3>Periodo: ".$per["id_periodo_sgu"]."[".$per["periodo_id"]."]</h3>";
+        if ($periodo_sgu_old != $per["id_periodo_sgu"]){
+            $periodo_sgu_old = $per["id_periodo_sgu"];
+            $params = [
+                'idPeriodo'=>$per["id_periodo_sgu"],
+                'fecha'=>$hoy
+            ];
+        
+            $curl = curl_init();
+            curl_setopt_array($curl, [
+                CURLOPT_URL => $pag,
+                CURLOPT_RETURNTRANSFER => true,
+                //CURLOPT_ENCODING => "",
+                //CURLOPT_MAXREDIRS => 10,
+                CURLOPT_TIMEOUT => 0,
+                CURLOPT_CUSTOMREQUEST => "POST",
+                CURLOPT_POSTFIELDS => json_encode($params),
+                CURLOPT_HTTPHEADER => [
+                    "token: ".$token,
+                    "username: SGU_APSA_AUD_ASIST",
+                    "Content-Type: application/json"
+                ],
+            ]);
+
+            $response = curl_exec($curl);
+            $err = curl_error($curl);
+
+            curl_close($curl);
+
+            if ($err)
+                die("cURL Error #:$err");
+        }
+        $selectedData = json_decode($response, true);
+    
+        //claves de carreras en el periodo
+        $arreglo_claves = array_map(function ($item) {
+            return $item['clave_carrera'];
+        }, $carreras_rs);
+
+        //print_r($selectedData); 
+        $sguHash = array();
+        if(!empty($selectedData)){
+            
+            //Recorre SGU y genera hash
+            foreach( $selectedData as $row ){
+                if(!$row["EsMateriaPorReposicion"]){
+                    $sguHash[] = array(
+                        "hash"=>( trim($row["HoraInicio"]."|".($row["NombreMateria"])."|".(trim($row["ClaveProfesor"])==""?"000000":trim($row["ClaveProfesor"]))."|".$row["IdEspacio"]) ),
+                        "data"=>$row
+                    );
+                }else{//reposición
+                    
+                    if(in_array($row["ClaveCarrera"] , $arreglo_claves)){
+                        //busca yyyy-mm-dd hh:mm:ss en la cadena
+                        if (preg_match("/\d{4}-\d{2}-\d{2} de \d{2}:\d{2}:\d{2}/", $row["Observaciones"], $matches)) {
+                            $fecha_orig =  str_replace(" de", "", $matches[0]);
+                            $fecha_nueva = $row["FechaStr"]." ".$row["HoraInicio"];
+                            
+                            if($debug){
+                                echo "<br>SELECT * FROM fi_reposicion_sgu($fecha_orig, ".$fecha_nueva.", ".$row["ClaveProfesor"].", ".$per["periodo_id"].", ".$row["IdEspacio"].")";
+                            }else{
+                                $db->query('SELECT * FROM fi_reposicion_sgu(:fecha_orig, :fecha_rep, :prof, :per, :salon)',
+                                    [":fecha_orig"=>$fecha_orig, ":fecha_rep"=>$fecha_nueva, ":prof"=>$row["ClaveProfesor"], ":per"=>$per["periodo_id"], ":salon"=>$row["IdEspacio"] ]);
+                                $log_desc .="SELECT * FROM fi_reposicion_sgu($fecha_orig, ".$fecha_nueva.", ".$row["ClaveProfesor"].", ".$per["periodo_id"].") ## ";
+                            }
+                        }else{
+                            if($debug)
+                                echo "No se encontró fecha y hora en: ".$row["Observaciones"]."<br>";
+                        }
+                    }
+                }
+            }
+            unset($selectedData);
+        }
+        
+    
+        $horarios_sgu = [];
+        
+        /*
+        print_r($carreras_rs); 
+        echo "<br><hr><br>";
+        print_r($arreglo_claves);
+        echo "<br><hr><br>";*/
+
+        //$areacomun = array();
+        foreach($sguHash as $sgu){
+            if(in_array($sgu["data"]["ClaveCarrera"] , $arreglo_claves) /*&& !in_array($sgu["data"]["ClaveMateria"], $areacomun)*/){
+                $horarios_sgu[] = $sgu;
+            }
+        }
+        
+        //print_r($horarios_sgu);exit();
+        unset($sguHash);
+        $elementos_sgu_total = array_merge($elementos_sgu_total, $horarios_sgu);
+        
+        
+        //Recorre BD y genera hash
+        $horarios_rs = $db->query('SELECT * FROM fs_horarios_hash(:dia, :periodo, :fecha)',
+            [':dia' => $dia_hoy, ':periodo' => $per["periodo_id"], ':fecha'=>$hoy]);
+            //echo "**** SELECT * FROM fs_horarios_hash($dia_hoy, ".$per["periodo_id"].")<br>";
+        
+        //usort($horarios_rs, 'compareByHash');
+        $elementos_bd_total = array_merge($elementos_bd_total, $horarios_rs);
+        
+    }//foreach periodo
+    //print_r($elementos_sgu_total); 
+//exit();
+    if($debug){
+        echo "<h3>Resumen</h3>";
+        echo "<h5>SGU [".count($elementos_sgu_total)."]</h5>";
+        echo "<h5>BD [".count($elementos_bd_total)."]</h5>";
+    }
+    // Extraer los "hash" de $lista y $lista2 en arreglos separados
+    $hashes_sgu = array_column($elementos_sgu_total, 'hash');
+    $hashes_bd = array_column($elementos_bd_total, 'hash');
+
+    
+    //------------------
+    // Encontrar los "hash" que están en $sgu pero no están en $bd
+    $hashes_no_en_sgu = array_diff($hashes_bd, $hashes_sgu);
+    if($debug) echo "hashes_no_en_sgu ".count($hashes_no_en_sgu)."<br>";
+
+    if(count($hashes_no_en_sgu)>0){
+        // Ahora puedes obtener los elementos completos que cumplen la condición original
+        $elementos_no_en_sgu = array_filter($elementos_bd_total, function ($item) use ($hashes_no_en_sgu) {
+            return in_array($item['hash'], $hashes_no_en_sgu);
+        });
+        if($debug){
+            print_r($elementos_no_en_sgu);
+            echo "Sobran ".count($elementos_no_en_sgu)." en BD<br>";
+        }
+
+        //Update fecha_fin
+        $log_desc = "";
+        foreach($elementos_no_en_sgu as $row){
+            if($debug){
+                echo "<br>SELECT * FROM fu_horario_deshabilita(".$row["horario_id"].")";
+            }else{
+                $db->query('SELECT * FROM fu_horario_deshabilita(:horario)', [":horario"=>$row["horario_id"]]);
+                $log_desc .="SELECT * FROM fu_horario_deshabilita(".$row["horario_id"].") ## ";
+            }
+        }
+        if(!$debug && !empty($log_desc)){
+            $log = new LogCambios(__DIR__."/log/");
+            $log->appendLog($log_desc);
+        }
+    }
+
+    // Encontrar los "hash" que están en $sgu pero no están en $bd
+    $hashes_no_en_bd = array_diff($hashes_sgu, $hashes_bd);
+
+    //echo "hashes_no_en_bd ".count($hashes_no_en_bd)."<br>";
+    
+    if(count($hashes_no_en_bd)>0){
+        // Ahora puedes obtener los elementos completos que cumplen la condición original
+        $elementos_no_en_bd = array_filter($elementos_sgu_total, function ($item) use ($hashes_no_en_bd) {
+            return in_array($item['hash'], $hashes_no_en_bd);
+        });
+        if($debug){
+            echo "<br>Faltan ".count($elementos_no_en_bd)." en BD<br>";
+            print_r($elementos_no_en_bd); echo "<br>";
+        }
+
+        //Inserts
+        foreach($elementos_no_en_bd as $row){
+            if($debug){
+                echo "<br>SELECT * FROM fi_horario($dia_hoy, '".$row["data"]["HoraInicio"]."','".$row["data"]["HoraFin"]."','"
+                .$row["data"]["ClaveDependencia"]."','"
+                .$row["data"]["ClaveCarrera"]."','"
+                .$row["data"]["NombreMateria"]."','"
+                .$row["data"]["ClaveMateria"]."','"
+                .$row["data"]["ClaveProfesor"]."','"
+                .$row["data"]["NombreProfesor"]."','"
+                .$row["data"]["CorreoElectronico"]."','"
+                .$row["data"]["Grupo"]."',"
+                .$row["data"]["IdEspacio"].");";
+                if($row["data"]["EsMateriaPorAsignacion"]){ echo " ***Asignacion directa***";}
+            }else{
+                $horario_new_rs = $db->querySingle('SELECT * FROM fi_horario(:hoy, :ini, :fin, :dep, :carr, :nom_mat, :cve_mat, :cve_prof, :nom_prof, :correo, :gpo, :espacio)',
+                    [":hoy"=>$dia_hoy,
+                    ":ini"=>$row["data"]["HoraInicio"],
+                    ":fin"=>$row["data"]["HoraFin"],
+                    ":dep"=>$row["data"]["ClaveDependencia"],
+                    ":carr"=>$row["data"]["ClaveCarrera"],
+                    ":nom_mat"=>$row["data"]["NombreMateria"],
+                    ":cve_mat"=>$row["data"]["ClaveMateria"],
+                    ":cve_prof"=>$row["data"]["ClaveProfesor"]==""?"000000":$row["data"]["ClaveProfesor"],
+                    ":nom_prof"=>$row["data"]["NombreProfesor"],
+                    ":correo"=>$row["data"]["CorreoElectronico"],
+                    ":gpo"=>$row["data"]["Grupo"],
+                    ":espacio"=>$row["data"]["IdEspacio"]]
+                );
+                echo $horario_new_rs["fi_horario"]."<br>";
+                if( !empty($horario_new_rs["fi_horario"]) ){
+                    if($row["data"]["EsMateriaPorAsignacion"]){
+                        $matasig_rs = $db->querySingle('SELECT * FROM fi_materia_asignacion(:hor,:mat,:carr, :prof)',
+                            [":hor"=>$horario_new_rs["fi_horario"],
+                            ":mat"=>$row["data"]["NombreMateria"],
+                            ":carr"=>$row["data"]["Carrera"],
+                            ":prof"=>$row["data"]["NombreProfesor"]
+                            ]
+                        );
+                    }
+
+                    $log_desc .="SELECT * FROM fi_horario($dia_hoy, '".$row["data"]["HoraInicio"]."','".$row["data"]["HoraFin"]."','"
+                    .$row["data"]["ClaveDependencia"]."','"
+                    .$row["data"]["ClaveCarrera"]."','"
+                    .$row["data"]["NombreMateria"]."','"
+                    .$row["data"]["ClaveMateria"]."','"
+                    .$row["data"]["ClaveProfesor"]."','"
+                    .$row["data"]["NombreProfesor"]."','"
+                    .$row["data"]["CorreoElectronico"]."','"
+                    .$row["data"]["Grupo"]."',"
+                    .$row["data"]["IdEspacio"]."); [ID=".$horario_new_rs["fi_horario"]."] ##";
+                }
+                
+            }
+        }
+        if(!$debug && !empty($log_desc)){
+            $log = new LogCambios(__DIR__."/log/");
+            $log->appendLog($log_desc);
+        }
+        
+    }
+    $stmt = null; // cierra conexion
+
+    if($debug) {
+        echo "<br><br><hr><br><br>";
+        usort($elementos_sgu_total, 'compareByHash');
+        usort($elementos_bd_total, 'compareByHash');
+
+        echo "<table><tr>";
+        echo "<td valign='top'>";
+        echo "<h5>SGU [".count($elementos_sgu_total)."]</h5>";
+        foreach($elementos_sgu_total as $sgu){
+            echo $sgu["hash"]."<br>";
+        }
+        echo "</td>";
+        echo "<td valign='top'>";
+        echo "<h5>BD [".count($elementos_bd_total)."]</h5>";
+        foreach($elementos_bd_total as $sgu){
+            echo $sgu["hash"]." [".$sgu["horario_id"]."]<br>";
+        }
+        echo "</td>";
+        echo "</tr></table>";
+    }else{
+        $pdo->commit();
+        echo "Commit";
+    }
+} catch(PDOException $e) {
+    echo "Error";
+    "ERROR BD! ".$e->getMessage();
+    $pdo->rollBack();
+    if(!$debug){
+        $log = new LogCambios(__DIR__."/log/");
+        $log->appendLog("ERROR BD! ".$e->getMessage());
+    }
+    print_r($e->getMessage());
+} catch(Exception $e2){
+    echo "Error";
+    print_r($e2->getMessage());
+}
+
+
+?>

+ 403 - 0
rest/horarios_otro.php

@@ -0,0 +1,403 @@
+<?php
+/*
+idPeriodo: identificador del periodo a consultar (obligatorio, número entero)
+claveFacultad: clave de la facultad a consultar (opcional, cadena)
+claveCarrera: clave de la carrera a consultar (opcional, cadena)
+claveProfesor: clave del empleado a consultar (opcional, cadena)
+fecha: fecha de la clase (opcional, cadena en formato yyyy-MM-dd)
+ */
+ini_set('display_errors', 1);
+ini_set('display_startup_errors', 1);
+ini_set('post_max_size', 1);
+ini_set('max_execution_time', 8*60);
+error_reporting(E_ALL);
+date_default_timezone_set('America/Mexico_City');
+
+$ruta = "../";
+$ruta_superior = dirname(__DIR__);
+require_once $ruta_superior."/include/bd_pdo.php";
+require_once __DIR__."/token.php";
+require_once __DIR__."/LogCambios.php";
+require_once __DIR__."/include/mailer.php";
+require_once __DIR__.'/include/phpmailer/PHPMailerAutoload.php';
+
+//--------------ACTUALIZA HORARIOS--------------------------
+if(!empty($_GET["fecha"])){
+    $hoy = $_GET["fecha"];
+}else{
+    $hoy = date("Y-m-d");
+}
+$dia_hoy = date("w", strtotime($hoy));
+
+
+function compareByHash($a, $b) {
+    return strcmp($a['hash'], $b['hash']);
+}
+
+
+$debug = false;
+if(isset($_GET["debug"])){
+    $debug = true;
+    print_r( $db->querySingle('select now()'));
+}
+$periodo_sgu_old = 0;
+$log_desc = "";
+
+//------------------ACTUALIZA SALONES----------------------
+
+$pag = "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/catalogos/espacios/seleccionar";
+$curl = curl_init();
+curl_setopt_array($curl, [
+    CURLOPT_URL => $pag,
+    CURLOPT_RETURNTRANSFER => true,
+    //CURLOPT_ENCODING => "",
+    //CURLOPT_MAXREDIRS => 10,
+    // CURLOPT_TIMEOUT => 0,
+    //CURLOPT_CUSTOMREQUEST => "POST",
+    CURLOPT_POSTFIELDS => json_encode([]),
+    CURLOPT_HTTPHEADER => [
+        "token: ".$token,
+        "username: SGU_APSA_AUD_ASIST",
+        "Content-Type: application/json",
+        'Transfer-Encoding: chunked'
+    ],
+]);
+
+$response = curl_exec($curl);
+$err = curl_error($curl);
+
+curl_close($curl);
+
+if ($err)
+    die("cURL Error #:$err");
+$salonesData = json_decode($response, true);
+
+//$salonesTotal_rs = $db->count('salon');
+$salonesTotal_rs = $db->where('salon_id', 0, '>')->count('salon');
+echo "BD $salonesTotal_rs vs SGU " . count($salonesData) . " salones<br>";
+if($salonesTotal_rs < count($salonesData)){//faltan salones en BD
+    echo "Actualizar salones<br>";
+    $salones_rs = $db->query('SELECT id_espacio_sgu FROM salon');
+    //claves de espacios
+    $arreglo_espacios = array_map(function ($item) {
+        return $item['id_espacio_sgu'];
+    }, $salones_rs);
+
+    foreach($salonesData as $data){
+        if( !in_array($data["IdEspacio"], $arreglo_espacios)){
+            //Insertar espacio
+            if($debug){
+                echo "Espacio nuevo: ".$data["NombreEspacio"]."<br>";
+            }else{
+                $db->query('INSERT INTO SALON (salon, id_espacio_sgu, id_espacio_padre) VALUES (:salon, :id, :id_padre)',
+                    [":salon"=>$data["NombreEspacio"], ":id"=>$data["IdEspacio"], ":id_padre"=>$data["IdEspacioPadre"]]);
+            }
+        }
+    }
+}
+// -----------------------------
+
+$pag = "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/seleccionar";
+$elementos_bd_total = [];
+$elementos_sgu_total = [];
+$periodo_sgu_old = 0;
+try{
+    $pdo->beginTransaction();
+
+    
+    $periodos_rs = $db->query('SELECT periodo_id, id_periodo_sgu, nivel_id FROM periodo WHERE :hoy BETWEEN periodo_fecha_inicio AND periodo_fecha_fin AND id_periodo_sgu != 0 ORDER BY periodo_id',
+        [":hoy"=>$hoy]);
+    foreach ($periodos_rs as $per){
+
+        //Verifica si el día de hoy es festivo
+        $vacacion_rs = $db->querySingle('select es_festivo(:per, :hoy)', [":per"=>$per["periodo_id"], ":hoy"=>$hoy]);
+        if($vacacion_rs["es_festivo"]){
+            if($debug){
+                echo "<h3>Dia festivo en Periodo: ".$per["id_periodo_sgu"]."[".$per["periodo_id"]."]</h3>";
+            }
+            continue;
+        }
+        
+        $carreras_rs = $db->query('SELECT c.clave_carrera, c.carrera_id FROM carrera c WHERE nivel_id = :nivel',
+        [":nivel"=>$per["nivel_id"]]);
+        if($debug){
+            echo "<h3>Periodo: ".$per["id_periodo_sgu"]."[".$per["periodo_id"]."] nivel: ".$per["nivel_id"]."</h3>";
+            //print_r($carreras_rs);
+        }
+        if ($periodo_sgu_old != $per["id_periodo_sgu"]){
+            $periodo_sgu_old = $per["id_periodo_sgu"];
+            $params = [
+                'idPeriodo'=>$per["id_periodo_sgu"],
+                'fecha'=>$hoy
+            ];
+        
+            $curl = curl_init();
+            curl_setopt_array($curl, [
+                CURLOPT_URL => $pag,
+                CURLOPT_RETURNTRANSFER => true,
+                //CURLOPT_ENCODING => "",
+                //CURLOPT_MAXREDIRS => 10,
+                //CURLOPT_TIMEOUT => 0,
+                //CURLOPT_CUSTOMREQUEST => "POST",
+                CURLOPT_POSTFIELDS => json_encode($params),
+                CURLOPT_HTTPHEADER => [
+                    "token: ".$token,
+                    "username: SGU_APSA_AUD_ASIST",
+                    "Content-Type: application/json"
+                ],
+            ]);
+
+            $response = curl_exec($curl);
+            $err = curl_error($curl);
+
+            curl_close($curl);
+
+            if ($err)
+                die("cURL Error #:$err");
+        }
+        $selectedData = json_decode($response, true);
+    
+        //claves de carreras en el periodo
+        $arreglo_claves = array_map(function ($item) {
+            return $item['clave_carrera'];
+        }, $carreras_rs);
+
+        //print_r($selectedData); exit();
+        $sguHash = array();
+        if(!empty($selectedData)){
+            
+            //Recorre SGU y genera hash
+            foreach( $selectedData as $row ){
+                if(!$row["EsMateriaPorReposicion"]){
+                    $sguHash[] = array(
+                        "hash"=>( trim($row["HoraInicio"]."|".($row["NombreMateria"])."|".(trim($row["ClaveProfesor"])==""?"000000":trim($row["ClaveProfesor"]))."|".$row["IdEspacio"]) ),
+                        "data"=>$row
+                    );
+                }else{//reposición
+                    
+                    if(in_array($row["ClaveCarrera"] , $arreglo_claves)){
+                        //busca yyyy-mm-dd hh:mm:ss en la cadena
+                        if (preg_match("/\d{4}-\d{2}-\d{2} de \d{2}:\d{2}:\d{2}/", $row["Observaciones"], $matches)) {
+                            $fecha_orig =  str_replace(" de", "", $matches[0]);
+                            $fecha_nueva = $row["FechaStr"]." ".$row["HoraInicio"];
+                            $hora_fin_nueva = $row["HoraFin"];
+                            if($debug){
+                                echo "<br>SELECT * FROM fi_reposicion_sgu($fecha_orig, ".$hora_fin_nueva.",".$fecha_nueva." ,".$row["ClaveProfesor"].", ".$per["periodo_id"].", ".$row["IdEspacio"].")";
+                            }else{
+                                $db->query('SELECT * FROM fi_reposicion_sgu(:fecha_orig, :hora_fin, :fecha_rep, :prof, :per, :salon)',
+                                    [":fecha_orig"=>$fecha_orig, ":hora_fin"=>$hora_fin_nueva, ":fecha_rep"=>$fecha_nueva, ":prof"=>$row["ClaveProfesor"], ":per"=>$per["periodo_id"], ":salon"=>$row["IdEspacio"] ]);
+                                $log_desc .="SELECT * FROM fi_reposicion_sgu($fecha_orig, ".$hora_fin_nueva.",".$fecha_nueva." ,".$row["ClaveProfesor"].", ".$per["periodo_id"].", ".$row["IdEspacio"].") ## ";
+                            }
+                        }else{
+                            if($debug)
+                                echo "No se encontró fecha y hora en: ".$row["Observaciones"]."<br>";
+                        }
+                    }
+                }
+            }
+            unset($selectedData);
+        }
+        
+    
+        $horarios_sgu = [];
+        
+        /*
+        print_r($carreras_rs); 
+        echo "<br><hr><br>";*/
+        print_r($arreglo_claves);
+        echo "<br><hr><br>";
+
+        //$areacomun = array();
+        foreach($sguHash as $sgu){
+            if(in_array($sgu["data"]["ClaveCarrera"] , $arreglo_claves) /*&& !in_array($sgu["data"]["ClaveMateria"], $areacomun)*/){
+                $horarios_sgu[] = $sgu;
+            }
+        }
+        
+        //print_r($horarios_sgu);exit();
+        unset($sguHash);
+        $elementos_sgu_total = array_merge($elementos_sgu_total, $horarios_sgu);
+        
+        
+        //Recorre BD y genera hash
+        $horarios_rs = $db->query('SELECT * FROM fs_horarios_hash(:dia, :periodo, :fecha)',
+            [':dia' => $dia_hoy, ':periodo' => $per["periodo_id"], ':fecha'=>$hoy]);
+            //echo "**** SELECT * FROM fs_horarios_hash($dia_hoy, ".$per["periodo_id"].")<br>";
+        
+        //usort($horarios_rs, 'compareByHash');
+        $elementos_bd_total = array_merge($elementos_bd_total, $horarios_rs);
+        
+    }//foreach periodo
+    //print_r($elementos_sgu_total); 
+//exit();
+    if($debug){
+        echo "<h3>Resumen</h3>";
+        echo "<h5>SGU [".count($elementos_sgu_total)."]</h5>";
+        echo "<h5>BD [".count($elementos_bd_total)."]</h5>";
+    }
+    // Extraer los "hash" de $lista y $lista2 en arreglos separados
+    $hashes_sgu = array_column($elementos_sgu_total, 'hash');
+    $hashes_bd = array_column($elementos_bd_total, 'hash');
+
+    
+    //------------------
+    // Encontrar los "hash" que están en $sgu pero no están en $bd
+    $hashes_no_en_sgu = array_diff($hashes_bd, $hashes_sgu);
+    if($debug) echo "hashes_no_en_sgu ".count($hashes_no_en_sgu)."<br>";
+
+    if(count($hashes_no_en_sgu)>0){
+        // Ahora puedes obtener los elementos completos que cumplen la condición original
+        $elementos_no_en_sgu = array_filter($elementos_bd_total, function ($item) use ($hashes_no_en_sgu) {
+            return in_array($item['hash'], $hashes_no_en_sgu);
+        });
+        if($debug){
+            print_r($elementos_no_en_sgu);
+            echo "Sobran ".count($elementos_no_en_sgu)." en BD<br>";
+        }
+
+        //Update fecha_fin
+        $log_desc = "";
+        foreach($elementos_no_en_sgu as $row){
+            if($debug){
+                echo "<br>SELECT * FROM fu_horario_deshabilita(".$row["horario_id"].")";
+            }else{
+                $db->query('SELECT * FROM fu_horario_deshabilita(:horario)', [":horario"=>$row["horario_id"]]);
+                $log_desc .="SELECT * FROM fu_horario_deshabilita(".$row["horario_id"].") ## ";
+            }
+        }
+        if(!$debug && !empty($log_desc)){
+            $log = new LogCambios(__DIR__."/log/");
+            $log->appendLog($log_desc);
+        }
+    }
+
+    // Encontrar los "hash" que están en $sgu pero no están en $bd
+    $hashes_no_en_bd = array_diff($hashes_sgu, $hashes_bd);
+
+    //echo "hashes_no_en_bd ".count($hashes_no_en_bd)."<br>";
+    
+    if(count($hashes_no_en_bd)>0){
+        // Ahora puedes obtener los elementos completos que cumplen la condición original
+        $elementos_no_en_bd = array_filter($elementos_sgu_total, function ($item) use ($hashes_no_en_bd) {
+            return in_array($item['hash'], $hashes_no_en_bd);
+        });
+        if($debug){
+            echo "<br>Faltan ".count($elementos_no_en_bd)." en BD<br>";
+            print_r($elementos_no_en_bd); echo "<br>";
+        }
+
+        //Inserts
+        foreach($elementos_no_en_bd as $row){
+            if($debug){
+                echo "<br>SELECT * FROM fi_horario($dia_hoy, '".$row["data"]["HoraInicio"]."','".$row["data"]["HoraFin"]."','"
+                .$row["data"]["ClaveDependencia"]."','"
+                .$row["data"]["ClaveCarrera"]."','"
+                .$row["data"]["NombreMateria"]."','"
+                .$row["data"]["ClaveMateria"]."','"
+                .$row["data"]["ClaveProfesor"]."','"
+                .$row["data"]["NombreProfesor"]."','"
+                .$row["data"]["CorreoElectronico"]."','"
+                .$row["data"]["Grupo"]."',"
+                .$row["data"]["IdEspacio"].","
+                .$per["periodo_id"].");";
+                if($row["data"]["EsMateriaPorAsignacion"]){ echo " ***Asignacion directa***";}
+            }else{
+                $horario_new_rs = $db->querySingle('SELECT * FROM fi_horario(:hoy, :ini, :fin, :dep, :carr, :nom_mat, :cve_mat, :cve_prof, :nom_prof, :correo, :gpo, :espacio, :periodo)',
+                    [":hoy"=>$dia_hoy,
+                    ":ini"=>$row["data"]["HoraInicio"],
+                    ":fin"=>$row["data"]["HoraFin"],
+                    ":dep"=>$row["data"]["ClaveDependencia"],
+                    ":carr"=>$row["data"]["ClaveCarrera"],
+                    ":nom_mat"=>$row["data"]["NombreMateria"],
+                    ":cve_mat"=>$row["data"]["ClaveMateria"],
+                    ":cve_prof"=>$row["data"]["ClaveProfesor"]==""?"000000":$row["data"]["ClaveProfesor"],
+                    ":nom_prof"=>$row["data"]["NombreProfesor"],
+                    ":correo"=>$row["data"]["CorreoElectronico"],
+                    ":gpo"=>$row["data"]["Grupo"],
+                    ":espacio"=>$row["data"]["IdEspacio"],
+                    ":periodo"=>$per["periodo_id"]
+                    ]
+                );
+                //echo $horario_new_rs["fi_horario"]."<br>";
+                if($horario_new_rs["fi_horario"] > 0){
+                    if($row["data"]["EsMateriaPorAsignacion"]){
+                        $matasig_rs = $db->querySingle('SELECT * FROM fi_materia_asignacion(:hor,:mat,:carr, :prof)',
+                            [":hor"=>$horario_new_rs["fi_horario"],
+                            ":mat"=>$row["data"]["NombreMateria"],
+                            ":carr"=>$row["data"]["Carrera"],
+                            ":prof"=>$row["data"]["NombreProfesor"]
+                            ]
+                        );
+                    }
+
+                    $log_desc .="SELECT * FROM fi_horario($dia_hoy, '".$row["data"]["HoraInicio"]."','".$row["data"]["HoraFin"]."','"
+                    .$row["data"]["ClaveDependencia"]."','"
+                    .$row["data"]["ClaveCarrera"]."','"
+                    .$row["data"]["NombreMateria"]."','"
+                    .$row["data"]["ClaveMateria"]."','"
+                    .$row["data"]["ClaveProfesor"]."','"
+                    .$row["data"]["NombreProfesor"]."','"
+                    .$row["data"]["CorreoElectronico"]."','"
+                    .$row["data"]["Grupo"]."',"
+                    .$row["data"]["IdEspacio"]."); [ID=".$horario_new_rs["fi_horario"]."] ##";
+                }
+                
+            }
+        }
+        if(!$debug && !empty($log_desc)){
+            $log = new LogCambios(__DIR__."/log/");
+            $log->appendLog($log_desc);
+        }
+        
+    }
+    $stmt = null; // cierra conexion
+
+    if($debug) {
+        echo "<br><br><hr><br><br>";
+
+        usort($elementos_sgu_total, 'compareByHash');
+        usort($elementos_bd_total, 'compareByHash');
+
+        echo "<table><tr>";
+        echo "<td valign='top'>";
+        echo "<h5>SGU [".count($elementos_sgu_total)."]</h5>";
+        foreach($elementos_sgu_total as $sgu){
+            echo $sgu["hash"]."<br>";
+        }
+        echo "</td>";
+        echo "<td valign='top'>";
+        echo "<h5>BD [".count($elementos_bd_total)."]</h5>";
+        foreach($elementos_bd_total as $sgu){
+            echo $sgu["hash"]." [".$sgu["horario_id"]."]<br>";
+        }
+        echo "</td>";
+        echo "</tr></table>";
+
+        
+    }else{
+        $pdo->commit();
+        echo "Commit";
+    }
+} catch(PDOException $e) {
+    echo "Error";
+    "ERROR BD! ".$e->getMessage();
+    $pdo->rollBack();
+    if(!$debug){
+        $log = new LogCambios(__DIR__."/log/");
+        $log->appendLog("ERROR BD! ".$e->getMessage());
+
+        $texto = "<h1>ERROR BD</h1><p>".$e->getMessage()."</p>";
+        $asunto = "Error correo automático";
+        Mailer::enviarCorreo("alejandro.lara@lasalle.mx", $asunto, $texto, true);
+    }
+    print_r($e->getMessage());
+} catch(Exception $e2){
+    echo "Error";
+    print_r($e2->getMessage());
+    $texto = "<h1>EXCEPTION</h1><p>".$e2->getMessage()."</p>";
+    $asunto = "Error correo automático";
+    Mailer::enviarCorreo("alejandro.lara@lasalle.mx", $asunto, $texto, true);
+}
+
+
+?>

+ 95 - 0
rest/include/mailer.php

@@ -0,0 +1,95 @@
+<?php
+//https://github.com/PHPMailer/PHPMailer
+//require_once('../include/phpmailer/PHPMailerAutoload.php');
+
+class Mailer{
+    private const FROM = "academia@lasalle.mx";
+    private const FROM_NAME = "Vicerrectoría Académica";
+    private const FROM_PASS = "Foy25193";
+    private const FOOTER = "<p style='margin-top:5em; color:#aaa;font-style:italics'><small>Este es un correo automatizado, esta cuenta no recibe correos.<small></p>";
+    //private $lista_to, $asunto, $texto;
+
+    /**
+     * Función estática para mandar correos. Los destinatarios pueden ser arreglo o cadena separada por ; incluir: include/phpmailer/PHPMailerAutoload.php
+     * 
+     * @param array|string $lista_to El destinatario o lista de destinatarios. Puede ser un arreglo de direcciones de correo electrónico o una cadena de texto con direcciones de correo separadas por ;.
+     * @param string $asunto El asunto del correo.
+     * @param string $texto El cuerpo del mensaje del correo en HTML.
+     * @param bool $bcc Indica si se debe enviar el correo como copia oculta (true) o no (false). Valor por defecto: false.
+     *
+     * @return bool True si el correo se envió exitosamente, false en caso contrario.
+     */
+    public static function enviarCorreo($lista_to, $asunto, $texto, $bcc = false){
+        try{
+            //SMTP Settings
+            $mail = new PHPMailer();
+            $mail->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;
+            }
+        }catch(phpmailerException $e){
+            echo $mail->ErrorInfo;
+            return false;
+        }catch(Exception $e2){
+            echo $mail->ErrorInfo;
+            return false;
+        }
+        return false;
+    }
+
+}

+ 50 - 0
rest/include/phpmailer/PHPMailerAutoload.php

@@ -0,0 +1,50 @@
+<?php
+/**
+ * PHPMailer SPL autoloader.
+ * PHP Version 5
+ * @package PHPMailer
+ * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
+ * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
+ * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
+ * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
+ * @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);
+    }*/
+}

+ 3884 - 0
rest/include/phpmailer/class.phpmailer.php

@@ -0,0 +1,3884 @@
+<?php
+/**
+ * PHPMailer - PHP email creation and transport class.
+ * PHP Version 5
+ * @package PHPMailer
+ * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
+ * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
+ * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
+ * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
+ * @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) <phpmailer@synchromedia.co.uk>
+ * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
+ * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
+ * @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 `<br>`, 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:
+     * <code>
+     * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
+     * </code>
+     * @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'
+                )
+                . "<br>\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 <address>" 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:
+     * <code>
+     * // 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();
+     * });
+     * </code>
+     * @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 = '<strong>' . $this->getMessage() . "</strong><br />\n";
+        return $errorMsg;
+    }
+}

+ 1181 - 0
rest/include/phpmailer/class.smtp.php

@@ -0,0 +1,1181 @@
+<?php
+/**
+ * PHPMailer RFC821 SMTP email transport class.
+ * PHP Version 5
+ * @package PHPMailer
+ * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
+ * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
+ * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
+ * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
+ * @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 <phpmailer@synchromedia.co.uk>
+ */
+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 `<br>`, 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:
+     * <code>
+     * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
+     * </code>
+     * @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'
+                )
+                . "<br>\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 <CRLF> with the message headers
+     * and the message body being separated by and additional <CRLF>.
+     * Implements rfc 821: DATA <CRLF>
+     * @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 <SP> <domain> <CRLF>
+     * 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 <SP> FROM:<reverse-path> <CRLF>
+     * @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 <CRLF>
+     * @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 <SP> TO:<forward-path> <CRLF>
+     * @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 <CRLF>
+     * @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 <SP> FROM:<reverse-path> <CRLF>
+     * @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 <CRLF>
+     * @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;
+    }
+}

+ 3 - 0
rest/info.php

@@ -0,0 +1,3 @@
+<?php 
+phpinfo();
+?>

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 4 - 0
rest/log/cambios_2023_08.log


+ 252 - 0
rest/materias.php

@@ -0,0 +1,252 @@
+<style>
+    details {
+        border: 1px solid #aaa;
+        border-radius: 4px;
+        padding: 0.5em 0.5em 0;
+        margin: 0.5em 0;
+    }
+
+    summary {
+        font-weight: bold;
+        margin: -0.5em -0.5em 0;
+        padding: 0.5em;
+    }
+
+    details[open] {
+        padding: 0.5em;
+    }
+
+    details[open] summary {
+        border-bottom: 1px solid #aaa;
+        margin-bottom: 0.5em;
+    }
+
+    table {
+        width: 100%;
+        border-collapse: collapse;
+        margin: 20px 0;
+    }
+
+    th,
+    td {
+        padding: 8px;
+        border: 1px solid #ccc;
+        text-align: left;
+    }
+
+    th {
+        background-color: #f2f2f2;
+    }
+
+    tr:nth-child(even):not(.empty):not(.area-comun) {
+        background-color: #f9f9f9;
+    }
+
+    .json-container {
+        white-space: pre-wrap;
+        /* Since JSON is formatted with whitespace, this will keep formatting */
+        word-break: break-word;
+        /* To prevent horizontal scrolling */
+        max-height: 150px;
+        /* Set a max-height and add scroll to prevent very long JSON from cluttering the table */
+        overflow-y: auto;
+    }
+
+    .empty {
+        /* rosa pastel */
+        background-color: #ffe8f4;
+    }
+
+    .area-comun {
+        /* naranja pastel */
+        background-color: #ffe9d4;
+    }
+</style>
+<?php
+/*
+idPeriodo: identificador del periodo a consultar (obligatorio, número entero)
+claveFacultad: clave de la facultad a consultar (opcional, cadena)
+claveCarrera: clave de la carrera a consultar (opcional, cadena)
+claveProfesor: clave del empleado a consultar (opcional, cadena)
+fecha: fecha de la clase (opcional, cadena en formato yyyy-MM-dd)
+ */
+ini_set('display_errors', 1);
+ini_set('display_startup_errors', 1);
+ini_set('post_max_size', 1);
+ini_set('max_execution_time', 8 * 60);
+error_reporting(E_ALL);
+date_default_timezone_set('America/Mexico_City');
+
+$ruta = "../";
+$ruta_superior = dirname(__DIR__);
+require_once $ruta_superior . "/include/bd_pdo.php";
+require_once __DIR__ . "/token.php";
+require_once __DIR__ . "/LogCambios.php";
+
+$fecha = isset($_GET["fecha"]) ? $_GET["fecha"] : date("Y-m-d");
+$periodos = $db
+    ->where("id_periodo_sgu", 0, ">")
+    ->where("periodo_fecha_inicio", $fecha, "<=")
+    ->where("periodo_fecha_fin", $fecha, ">=")
+    ->orderBy("periodo_id")
+    ->get("periodo");
+?>
+<nav>
+    <form action="" method="get">
+        <label for="fecha">Fecha</label>
+        <input type="date" name="fecha" id="fecha" value="<?= $fecha ?>">
+        <button type="submit">Buscar</button>
+    </form>
+    <details>
+        <summary>Periodos</summary>
+        <pre>
+        <code><?= json_encode($periodos, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?></code>
+    </pre>
+    </details>
+    <?php
+    $horarios = array();
+    foreach (array_column($periodos, "id_periodo_sgu") as $idPeriodo) {
+        $curl = curl_init();
+        $params = array(
+            'idPeriodo' => $idPeriodo,
+            'fecha' => $fecha,
+        );
+        curl_setopt_array($curl, [
+            CURLOPT_URL => 'https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/seleccionar',
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_POSTFIELDS => json_encode($params),
+            CURLOPT_HTTPHEADER => [
+                "token: $token",
+                'username: SGU_APSA_AUD_ASIST',
+                'Content-Type: application/json',
+            ],
+        ]);
+
+        $response = curl_exec($curl);
+        $err = curl_error($curl);
+
+        curl_close($curl);
+        $response = json_decode($response, true, 512, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+        $horarios = array_merge($horarios, $response);
+        ?>
+        <details>
+            <summary>Periodo
+                <?= $idPeriodo ?>
+            </summary>
+            <pre><code><?= json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?></code></pre>
+        </details>
+    <?php } ?>
+</nav>
+<main>
+    <p>
+        <?= count($horarios) ?> horarios encontrados para
+        <?= $fecha ?>
+    </p>
+    <table>
+        <thead>
+            <tr>
+                <th>Materia en SGU</th>
+                <th>Materia en Postgres</th>
+            </tr>
+        </thead>
+        <tbody>
+            <?php
+            // $horarios with unique "NombreMateria" field
+            $horarios = array_map("unserialize", array_unique(array_map("serialize", $horarios)));
+            foreach ($horarios as $horario) {
+                $materias = $db
+                    ->where("materia_nombre", trim($horario["NombreMateria"]), "ILIKE")
+                    ->join("carrera", "carrera.carrera_id = materia.carrera_id")
+                    ->join("facultad", "facultad.facultad_id = carrera.facultad_id")
+                    ->get("materia");
+                if (
+                    count(array_filter($materias, fn($m) =>
+                        $m["clave_materia"] == trim($horario["ClaveMateria"]) and
+                        $m["clave_carrera"] == trim($horario["ClaveCarrera"]))) > 0
+                ) {
+                    continue;
+                }
+
+                // si de las materias alguna tiene carrera_id entre 1 y 4 entonces es de área común
+                $area_comun = count(array_filter($materias, fn($m) => $m["carrera_id"] >= 1 and $m["carrera_id"] <= 4)) > 0;
+                $vacío = count($materias) == 0;
+                ?>
+                <!-- si es vacío ponle la clase empty y si es área común ponle la clase area-comun -->
+                <tr class="<?= $vacío ? "empty" : "" ?> <?= $area_comun ? "area-comun" : "" ?>">
+                    <td class="json-container">
+                        <details>
+                            <summary>Horario</summary>
+                            <pre><code><?= json_encode($horario, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?></code></pre>
+                        </details>
+                        <?= json_encode(array_intersect_key($horario, array_flip(["ClaveMateria", "NombreMateria", "ClaveCarrera", "Dependencia"])), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>
+                    </td>
+                    <td class="json-container">
+                        <?php if ($vacío) { ?>
+                            <p>No se encontraron materias</p>
+                        <?php } else { ?>
+                            <details>
+                                <summary>Materias</summary>
+                                <pre><code><?= json_encode($materias, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?></code></pre>
+                            </details>
+                            <table>
+                                <thead>
+                                    <tr>
+                                        <th>Materia</th>
+                                        <th>Carrera</th>
+                                        <th>Facultad</th>
+                                        <th>Acciones</th>
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                    <script>
+                                        async function copiar_seleccionados() {
+                                            // en mi clipboard quiero (join con ,)
+                                            const materias_seleccionadas = Array.from(document.querySelectorAll("input[name='materia_id']:checked"))
+                                                .map(input => input.value)
+                                                .join(",");
+                                            // copiar al clipboard
+                                            await navigator.clipboard.writeText(materias_seleccionadas);
+                                            // mostrar mensaje de éxito
+                                            alert("Copiado al portapapeles");
+                                        }
+                                    </script>
+                                    <?php foreach ($materias as $materia) { ?>
+                                        <tr>
+                                            <td>
+                                                <input type="checkbox" name="materia_id" id="materia_id"
+                                                    value="<?= $materia["materia_id"] ?>">
+                                            </td>
+                                            <td>
+                                                <?= $materia["materia_id"] ?>
+                                                <small>
+                                                    (
+                                                    <?= $materia["clave_materia"] ?>)
+                                                </small>
+                                                <?= $materia["materia_nombre"] ?>
+                                            </td>
+                                            <td>
+                                                <small>
+                                                    (
+                                                    <?= $materia["clave_carrera"] ?>)
+                                                </small>
+                                                <?= $materia["carrera_nombre"] ?>
+                                            </td>
+                                            <td>
+                                                <small>
+                                                    (
+                                                    <?= $materia["clave_dependencia"] ?>)
+                                                </small>
+                                                <?= $materia["facultad_nombre"] ?>
+                                            </td>
+                                        </tr>
+                                    <?php } ?>
+                                </tbody>
+                            </table>
+                        <?php } ?>
+                    </td>
+                </tr>
+            <?php } ?>
+        </tbody>
+    </table>
+
+</main>

+ 149 - 0
rest/salon.php

@@ -0,0 +1,149 @@
+<style>
+    details {
+        border: 1px solid #aaa;
+        border-radius: 4px;
+        padding: 0.5em 0.5em 0;
+        margin: 0.5em 0;
+    }
+
+    summary {
+        font-weight: bold;
+        margin: -0.5em -0.5em 0;
+        padding: 0.5em;
+    }
+
+    details[open] {
+        padding: 0.5em;
+    }
+
+    details[open] summary {
+        border-bottom: 1px solid #aaa;
+        margin-bottom: 0.5em;
+    }
+
+    table {
+        width: 100%;
+        border-collapse: collapse;
+        margin: 20px 0;
+    }
+
+    th,
+    td {
+        padding: 8px;
+        border: 1px solid #ccc;
+        text-align: left;
+    }
+
+    th {
+        background-color: #f2f2f2;
+    }
+
+    .json-container {
+        white-space: pre-wrap;
+        /* Since JSON is formatted with whitespace, this will keep formatting */
+        word-break: break-word;
+        /* To prevent horizontal scrolling */
+        max-height: 150px;
+        /* Set a max-height and add scroll to prevent very long JSON from cluttering the table */
+        overflow-y: auto;
+    }
+
+    .empty {
+        /* rosa pastel */
+        background-color: #ffe8f4;
+    }
+
+    .no-igual {
+        /* púrpura pastel */
+        background-color: #f4e8ff;
+    }
+</style>
+<?php
+/*
+idPeriodo: identificador del periodo a consultar (obligatorio, número entero)
+claveFacultad: clave de la facultad a consultar (opcional, cadena)
+claveCarrera: clave de la carrera a consultar (opcional, cadena)
+claveProfesor: clave del empleado a consultar (opcional, cadena)
+fecha: fecha de la clase (opcional, cadena en formato yyyy-MM-dd)
+ */
+ini_set('display_errors', 1);
+ini_set('display_startup_errors', 1);
+ini_set('post_max_size', 1);
+ini_set('max_execution_time', 8 * 60);
+error_reporting(E_ALL);
+date_default_timezone_set('America/Mexico_City');
+
+$ruta = "../";
+$ruta_superior = dirname(__DIR__);
+require_once $ruta_superior . "/include/bd_pdo.php";
+require_once __DIR__ . "/token.php";
+require_once __DIR__ . "/LogCambios.php";
+
+$salon = array();
+$curl = curl_init();
+curl_setopt_array($curl, [
+    CURLOPT_URL =>
+        'https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/catalogos/espacios/seleccionar',
+    CURLOPT_RETURNTRANSFER => true,
+    CURLOPT_POSTFIELDS => json_encode([]),
+    CURLOPT_HTTPHEADER => [
+        "token: $token",
+        'username: SGU_APSA_AUD_ASIST',
+        'Content-Type: application/json',
+    ],
+]);
+
+$response = curl_exec($curl);
+$err = curl_error($curl);
+
+curl_close($curl);
+$json_flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT;
+$salones = json_decode($response, true, 512, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+?>
+<main>
+    <p>
+        <?= count($salones) ?> salones encontrados
+    </p>
+    <table>
+        <thead>
+            <tr>
+                <th>Salones en SGU</th>
+                <th>SALONES en Postgres</th>
+            </tr>
+        </thead>
+        <tbody>
+            <?php
+            foreach ($salones as $salon) {
+                $salon_db = $db->where("id_espacio_sgu", $salon["IdEspacio"])->get("salon");
+                // si de el salon es igual NombreEspacio == salon
+                $vacío = empty($salon_db);
+                $igual = $salon["NombreEspacio"] == ($salon["salon"] ?? "");
+
+                if ($vacío) {
+                    $db->insert("salon", [
+                        "id_espacio_sgu" => $salon["IdEspacio"],
+                        "salon" => $salon["NombreEspacio"],
+                        "id_espacio_padre" => $salon["IdEspacioPadre"] > 0 ? $salon["IdEspacioPadre"] : null,
+                    ]);
+                } else if (!$igual) {
+                    $db->where("id_espacio_sgu", $salon["IdEspacio"])->update("salon", [
+                        "salon" => $salon["NombreEspacio"],
+                        // "id_espacio_padre" => $salon["IdEspacioPadre"] > 0 ? $salon["IdEspacioPadre"] : null,
+                    ]);
+
+                }
+
+                ?>
+                <tr class="<?= $igual ? "empty" : "no-igual" ?>">
+                    <td class="json-container">
+                        <?= json_encode($salon, $json_flags) ?>
+                    </td>
+                    <td class="json-container">
+                        <?= json_encode($salon_db, $json_flags) ?>
+                    </td>
+                </tr>
+            <?php } ?>
+        </tbody>
+    </table>
+
+</main>

+ 35 - 0
rest/token.php

@@ -0,0 +1,35 @@
+<?php
+/*
+idPeriodo: identificador del periodo a consultar (obligatorio, número entero)
+*/
+
+$ruta = "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/inicioSesion/seleccionar";
+
+$required_params = [
+    "username"=>"SGU_APSA_AUD_ASIST",
+    "password"=>"B4qa594JFPr2ufHrZdHS8A=="
+];
+try{
+    $curl = curl_init();
+    curl_setopt_array($curl, [
+        CURLOPT_URL => $ruta,
+        CURLOPT_RETURNTRANSFER => true,
+        CURLOPT_CUSTOMREQUEST => "POST",
+        CURLOPT_POSTFIELDS => json_encode($required_params),
+        CURLOPT_HTTPHEADER => array('Content-Type:application/json'),
+    ]);
+    $response = curl_exec($curl);
+    $err = curl_error($curl);
+
+    curl_close($curl);
+
+    if ($err)
+        die("cURL Error #:$err");
+
+    $token = json_decode($response, true);
+    //echo "token $token";
+}catch(Exception $e){
+    echo $e->getMessage();
+    exit();
+}
+?>

+ 41 - 0
rest/token.php.save

@@ -0,0 +1,41 @@
+<?php
+/*
+idPeriodo: identificador del periodo a consultar (obligatorio, número entero)
+*/
+
+$ruta = "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/inicioSesion/seleccionar";
+
+$required_params = [
+    "username"=>"SGU_APSA_AUD_ASIST",
+    "password"=>"B4qa594JFPr2ufHrZdHS8A=="
+];
+try{
+    $curl = curl_init();
+    curl_setopt_array($curl, [
+        CURLOPT_URL => $ruta,
+        CURLOPT_RETURNTRANSFER => true,
+        CURLOPT_ENCODING => "",
+        CURLOPT_MAXREDIRS => 10,
+        CURLOPT_TIMEOUT => 0,
+        CURLOPT_CUSTOMREQUEST => "POST",
+        CURLOPT_POSTFIELDS => json_encode($required_params),
+        CURLOPT_HTTPHEADER => [
+            "Content-Type: application/json"
+        ],
+    ]);
+    $response = curl_exec($curl);
+    print_r($response);
+    $err = curl_error($curl);
+
+    curl_close($curl);
+
+    if ($err)
+        die("cURL Error #:$err");
+
+    $token = json_decode($response, true);
+    echo "token $token";
+}catch(Exception $e){
+    echo $e->getMessage();
+    exit();
+}
+?>

+ 41 - 0
rest/token.php.save.1

@@ -0,0 +1,41 @@
+<?php
+/*
+idPeriodo: identificador del periodo a consultar (obligatorio, número entero)
+*/
+
+$ruta = "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/inicioSesion/seleccionar";
+
+$required_params = [
+    "username"=>"SGU_APSA_AUD_ASIST",
+    "password"=>"B4qa594JFPr2ufHrZdHS8A=="
+];
+try{
+    $curl = curl_init();
+    curl_setopt_array($curl, [
+        CURLOPT_URL => $ruta,
+        CURLOPT_RETURNTRANSFER => true,
+        CURLOPT_ENCODING => "",
+        CURLOPT_MAXREDIRS => 10,
+        CURLOPT_TIMEOUT => 0,
+        CURLOPT_CUSTOMREQUEST => "POST",
+        CURLOPT_POSTFIELDS => json_encode($required_params),
+        CURLOPT_HTTPHEADER => [
+            "Content-Type: application/json"
+        ],
+    ]);
+    $response = curl_exec($curl);
+    print_r($response);
+    $err = curl_error($curl);
+
+    curl_close($curl);
+
+    if ($err)
+        die("cURL Error #:$err");
+
+    $token = json_decode($response, true);
+    echo "token $token";
+}catch(Exception $e){
+    echo $e->getMessage();
+    exit();
+}
+?>

+ 0 - 27
service/_4564_periodo/auto.php

@@ -1,27 +0,0 @@
-<?
-$ruta = "../";
-require_once "$ruta/include/bd_pdo.php";
-header('Content-Type: application/json');
-
-// json data from service\periodos.v1.php (input)
-
-$urls = array(
-    'periodos.v1',
-    'periodos.v2',
-    'horarios',
-);
-
-$urls = array_map(fn($item) => "../$item.php", $urls);
-
-ob_start();
-include_once 'periodos.v1.php';
-$periodos_v1 = ob_get_contents();
-ob_end_clean();
-
-ob_start();
-include_once 'periodos.v2.php';
-$periodos_v2 = ob_get_contents();
-ob_end_clean();
-
-// echo $periodos_v1;
-echo $periodos_v2;

+ 0 - 76
service/_4564_periodo/backend/carreras.php

@@ -1,76 +0,0 @@
-<?
-$ruta = "../../";
-require_once "$ruta/include/bd_pdo.php";
-header('Content-Type: application/json');
-global $db;
-// json data from service\periodos.v1.php (input)
-$data = json_decode(file_get_contents('php://input'), true);
-
-// check if the input is empty
-
-if (is_response_empty($data)) {
-    echo json_encode([
-        'status' => 'error',
-        'message' => 'No se recibieron datos',
-        'data' => $data
-    ]);
-    exit;
-}
-
-// check if data is array
-if (!is_array($data)) {
-    echo json_encode([
-        'status' => 'error',
-        'message' => 'La información recibida no es válida',
-        'data' => $data
-    ]);
-    exit;
-}
-
-/**
- * [{
- * carrera_nombre
- * carrera_clave
- * id_nivel
- * },]
- */
-// check for this schema
-array_walk($data, function ($item) {
-    if (!isset($item['ClaveCarrera']) || !isset($item['NombreCarrera']) || !isset($item['IdNivel'])) {
-        echo json_encode([
-            'status' => 'error',
-            'message' => 'Los datos recibidos no son validos',
-            'data' => $item
-        ]);
-        exit;
-    }
-});
-
-
-
-array_walk($data, function ($item) use ($db) {
-
-    if ($db->where('carrera_nombre', "%{$item['NombreCarrera']}", 'ILIKE')->has('carrera'))
-        $db
-            ->where('carrera_nombre', "%{$item['NombreCarrera']}", 'ILIKE')
-            ->update('carrera', [
-                'carrera_nombre' => $item['NombreCarrera'],
-                'id_referencia' => $item['ClaveCarrera'],
-            ]);
-    else {
-        try {
-            $db->insert('carrera', [
-                'carrera_nombre' => $item['NombreCarrera'],
-                'id_referencia' => $item['ClaveCarrera'],
-                'nivel_id' => $item['IdNivel'],
-            ]);
-        } catch (PDOException $th) {
-            echo json_encode([
-                'success' => false,
-                'message' => $th->getMessage(),
-                'last_query' => $db->getLastQuery(),
-            ]);
-            exit;
-        }
-    }
-});

+ 0 - 62
service/_4564_periodo/backend/periodos.php

@@ -1,62 +0,0 @@
-<?
-ini_set('display_errors', 1);
-ini_set('display_startup_errors', 1);
-error_reporting(E_ALL);
-$ruta = "../../";
-require_once "$ruta/include/bd_pdo.php";
-header('Content-Type: application/json');
-
-// json data from service\periodos.v1.php (input)
-$data = json_decode(file_get_contents('php://input'), true);
-// check if the input is empty
-
-if (is_response_empty($data)) {
-    echo json_encode([
-        'status' => 'error',
-        'message' => 'No se recibieron datos',
-    ]);
-    exit;
-}
-
-/* 
-{
-    "IdNivel": 1,
-    "IdPeriodo": 635,
-    "NombreNivel": "LICENCIATURA",
-    "NombrePeriodo": "241",
-    "in_db": false
-    inicio,
-    fin
-}
-*/
-
-// insert into database
-setlocale(LC_TIME, 'es_MX.UTF-8');
-$formatter = new IntlDateFormatter('es_MX', IntlDateFormatter::FULL, IntlDateFormatter::FULL, 'America/Mexico_City', IntlDateFormatter::GREGORIAN, 'MMMM');
-$inicio = strtotime($data['inicio']);
-$fin = strtotime($data['fin']);
-try {
-
-    $result = $db->insert('periodo', [
-        'id_periodo_sgu' => $data['IdPeriodo'],
-        'periodo_nombre' => "{$data['NombreNivel']}: {$formatter->format($inicio)} - {$formatter->format($fin)} " . date('Y', $inicio),
-        'nivel_id' => $data['IdNivel'],
-        'periodo_fecha_inicio' => $data['inicio'],
-        'periodo_fecha_fin' => $data['fin'],
-        'estado_id' => 4,
-        'periodo_clave' => $data['NombrePeriodo']
-    ], ['id_periodo_sgu']);
-} catch (PDOException $th) {
-    echo json_encode([
-        'success' => false,
-        'message' => $th->getMessage()
-    ]);
-    exit;
-}
-echo json_encode($result ? [
-    'success' => true,
-    'message' => 'Periodo agregado correctamente'
-] : [
-    'success' => false,
-    'message' => 'Error al agregar el periodo'
-]);

+ 0 - 155
service/_4564_periodo/client.html

@@ -1,155 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Cliente REST</title>
-    <script type="module" src="../js/client.js" defer></script>
-    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"
-        integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
-    <link rel="stylesheet" href="../css/indivisa.css">
-</head>
-
-<body>
-    <header class="container-fluid bg-dark text-white text-center">
-        Página de Cliente REST
-    </header>
-    <main class="container" @vue:mounted="mounted">
-        <div v-for="error in store.errors" class="alert alert-danger alert-dismissible fade show" role="alert">
-            <strong>Error!</strong> {{error}}
-            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
-        </div>
-        <div class="row">
-            <div class="col-12">
-                <h1>Periodos activos</h1>
-            </div>
-        </div>
-        <ul class="list-group">
-
-            <li href="#" class="list-group-item" v-for="(periodo, index) in store.periodosV1" :key="periodo.IdPeriodo">
-                <!-- v1
-                IdNivel: number;
-                IdPeriodo: number;
-                NombreNivel: string;
-                NombrePeriodo: string;
-                -->
-                <div class="row">
-                    <span class="badge bg-secondary">{{index}}</span>
-                    <h2 class="text-center">
-                        Información
-                    </h2>
-                    <div class="col-md-3">
-                        <strong>ID Nivel:</strong> {{periodo.IdNivel}}
-                    </div>
-                    <div class="col-md-3">
-                        <strong>ID Periodo:</strong> {{periodo.IdPeriodo}}
-                    </div>
-                    <div class="col-md-3">
-                        <strong>Nombre Nivel:</strong> {{periodo.NombreNivel}}
-                    </div>
-                    <div class="col-md-3">
-                        <strong>Nombre Periodo:</strong> {{periodo.NombrePeriodo}}
-                    </div>
-                </div>
-                <!--
-                    FechaFin: string;
-                    FechaInicio: string;
-                    IdPeriodo: number;
-                    -- info(IdPeriodo) --
-                -->
-                <div class="row mt-2" v-if="complete(periodo.IdPeriodo)">
-                    <h2 class="text-center">
-                        Fechas
-                    </h2>
-                    <div class="col-md-2">
-                        <strong>Fecha Inicio:</strong> {{info(periodo.IdPeriodo).FechaInicio}}
-                    </div>
-                    <div class="col-md-2">
-                        <strong>Fecha Fin:</strong> {{info(periodo.IdPeriodo).FechaFin}}
-                    </div>
-                </div>
-                <div v-if="!periodo.in_db" class="row mt-2">
-                    <!-- 
-                        PeriodoV2
-                                ClaveCarrera: string;
-                                NombreCarrera: string;
-                        PeriodoV1
-                                IdNivel: number;
-                    -->
-                    <div class="col-md-12">
-                        <button class="btn btn-primary float-end" @click="store.addPeriodo(periodo)"
-                            :disabled="!complete(periodo.IdPeriodo)">
-                            Agregar
-                            <i class="ing ing-mas"></i>
-                        </button>
-                    </div>
-                </div>
-                <div v-else class="row mt-2">
-                    <div class="col-md-12">
-                        <button class="btn btn-success float-end disabled">
-                            Agregado
-                            <i class="ing-aceptar"></i>
-                        </button>
-                    </div>
-                </div>
-
-                <div class="row mt-2">
-                    <div class="col-md-12">
-                        <button class="btn btn-secondary float-end" @click="store.addCarreras(periodo.IdPeriodo)">
-                            Sincronizar Carreras
-                            <i class="ing ing-link"></i>
-                        </button>
-                    </div>
-                </div>
-                <div class="accordion mt-2">
-                    <div class="accordion-item">
-                        <h2 class="accordion-header">
-                            <button class="accordion-button collapsed" data-bs-toggle="collapse"
-                                :data-bs-target="`#collapse-${periodo.IdPeriodo}`">
-                                Horarios del periodo
-                            </button>
-                        </h2>
-                        <div :id="`collapse-${periodo.IdPeriodo}`" class="accordion-collapse collapse">
-                            <div class="accordion-body">
-                                <ul class="list-group">
-                                    <li class="list-group-item"
-                                        v-for="periodo in store.periodosV2.filter(periodov2 => periodov2.IdPeriodo === periodo.IdPeriodo)">
-                                        <div class="row">
-                                            <div class="col-md-3">
-                                                <strong>Clave Carrera:</strong> {{periodo.ClaveCarrera}}
-                                            </div>
-                                            <div class="col-md-6">
-                                                <strong>Nombre Carrera:</strong> {{periodo.NombreCarrera}}
-                                            </div>
-
-                                            <div class="col-md-3">
-                                                <span class="badge float-end mx-1"
-                                                    :class="periodo.linked ?'bg-success':'bg-secondary'">
-                                                    <i class="ing-link"></i>
-                                                </span>
-                                                <span class="badge float-end mx-1"
-                                                    :class="periodo.in_db ?'bg-success':'bg-secondary'">
-                                                    <i class="ing-aceptar"></i>
-                                                </span>
-                                            </div>
-                                        </div>
-                                    </li>
-                                </ul>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-            </li>
-        </ul>
-    </main>
-    <footer>
-
-    </footer>
-    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
-        integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
-        crossorigin="anonymous"></script>
-
-</body>
-
-</html>

+ 0 - 81
service/_4564_periodo/horarios.php

@@ -1,81 +0,0 @@
-<?
-/*
-•	idPeriodo: identificador del periodo a consultar (obligatorio, número entero)
-•	claveFacultad: clave de la facultad a consultar (opcional, cadena)
-•	claveCarrera: clave de la carrera a consultar (opcional, cadena)
-•	claveProfesor: clave del empleado a consultar (opcional, cadena)
-•	fecha: fecha de la clase (opcional, cadena en formato yyyy-MM-dd)
- */
-$required_params = [
-    'idPeriodo'
-];
-
-$optional_params = [
-    'claveFacultad',
-    'claveCarrera',
-    'claveProfesor',
-    'fecha'
-];
-
-// Check if all required params are present in $_GET
-$params = array_map('strtolower', $_GET); // Convert keys to lowercase for case-insensitive comparison
-
-// Check for missing required parameters
-$missing_params = array_diff($required_params, array_keys($params));
-if (!empty($missing_params)) {
-    $missing_params_str = implode(', ', $missing_params);
-    die("Missing required parameter(s): $missing_params_str");
-}
-
-// Filter and retain only the required and optional parameters
-$params = array_filter($params, function ($key) use ($required_params, $optional_params) {
-    return in_array($key, $required_params) || in_array($key, $optional_params);
-}, ARRAY_FILTER_USE_KEY);
-
-$curl = curl_init();
-curl_setopt_array($curl, [
-    CURLOPT_URL => "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/seleccionar",
-    CURLOPT_RETURNTRANSFER => true,
-    CURLOPT_ENCODING => "",
-    CURLOPT_MAXREDIRS => 10,
-    CURLOPT_TIMEOUT => 0,
-    CURLOPT_CUSTOMREQUEST => "POST",
-    CURLOPT_POSTFIELDS => json_encode($params),
-    CURLOPT_HTTPHEADER => [
-        "token: 64293fb86c06e45331ab9963822762f77b9c403ca949adcc31286d550e902fff202e4c69d1574b2082ecf0b3a28b6cfd4d88b3a7d2c2ab7d329666b9a527fb1b",
-        "username: SGU_APSA_AUD_ASIST",
-        "Content-Type: application/json"
-    ],
-]);
-
-$response = curl_exec($curl);
-$err = curl_error($curl);
-
-curl_close($curl);
-
-if ($err)
-    die("cURL Error #:$err");
-
-
-$selectedData = json_decode($response, true);
-
-$rawInput = file_get_contents('php://input');
-
-$input = json_decode($rawInput, true);
-// check for {collect: []} in raw input
-if (isset($input['collect']) && is_array($input['collect'])) {
-    $collect = $input['collect'];
-    $selectedData = array_map(function ($item) use ($collect) {
-        return array_intersect_key($item, array_flip($collect));
-    }, $selectedData);
-    // unique and distinct
-    $selectedData = array_unique($selectedData, SORT_REGULAR);
-}
-else {
-    // return invalid request error
-    die($rawInput);
-}
-
-// Output the selected data directly
-header('Content-Type: application/json');
-echo json_encode($selectedData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

+ 0 - 39
service/_4564_periodo/periodos.v1.php

@@ -1,39 +0,0 @@
-<?
-ini_set('display_errors', 1);
-ini_set('display_startup_errors', 1);
-error_reporting(E_ALL);
-
-$ruta = "../";
-require_once '../include/bd_pdo.php';
-$curl = curl_init();
-global $db;
-curl_setopt_array($curl, [
-    CURLOPT_URL => "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/catalogos/periodos/v1/seleccionar",
-    CURLOPT_RETURNTRANSFER => true,
-    CURLOPT_POSTFIELDS => "",
-    CURLOPT_HTTPHEADER => [
-        "token: 64293fb86c06e45331ab9963822762f77b9c403ca949adcc31286d550e902fff202e4c69d1574b2082ecf0b3a28b6cfd4d88b3a7d2c2ab7d329666b9a527fb1b",
-        "username: SGU_APSA_AUD_ASIST"
-    ],
-]);
-
-$response = curl_exec($curl);
-$err = curl_error($curl);
-
-curl_close($curl);
-
-if ($err)
-    die("cURL Error #:$err");
-
-$data = json_decode($response, true);
-
-$in_db = array_map(function ($item) use ($db) {
-    $item['in_db'] = $db->where('id_periodo_sgu', $item['IdPeriodo'])->has('periodo');
-    return $item;
-}, $data);
-
-$selectedData = array_unique($in_db, SORT_REGULAR);
-
-// Output the selected data directly
-header('Content-Type: application/json');
-echo json_encode($selectedData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

+ 0 - 41
service/_4564_periodo/periodos.v2.php

@@ -1,41 +0,0 @@
-<?
-ini_set('display_errors', 1);
-ini_set('display_startup_errors', 1);
-error_reporting(E_ALL);
-
-$ruta = "../";
-require_once '../include/bd_pdo.php';
-global $db;
-
-$curl = curl_init();
-
-curl_setopt_array($curl, [
-    CURLOPT_URL => "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/catalogos/periodos/v2/seleccionar",
-    CURLOPT_RETURNTRANSFER => true,
-    CURLOPT_POSTFIELDS => "",
-    CURLOPT_HTTPHEADER => [
-        "token: 64293fb86c06e45331ab9963822762f77b9c403ca949adcc31286d550e902fff202e4c69d1574b2082ecf0b3a28b6cfd4d88b3a7d2c2ab7d329666b9a527fb1b",
-        "username: SGU_APSA_AUD_ASIST"
-    ],
-]);
-
-$response = curl_exec($curl);
-$err = curl_error($curl);
-
-curl_close($curl);
-
-if ($err)
-    die("cURL Error #:$err");
-
-
-$json = json_decode($response, true);
-
-$selectedData = array_map(function ($item) use ($db) {
-    $item['in_db'] = $db->where('carrera_nombre', $item['NombreCarrera'], 'ILIKE')->has('carrera');
-    $item['linked'] = $db->where('clave_carrera', $item['ClaveCarrera'], 'ILIKE')->has('carrera');
-    return $item;
-}, $json);
-
-// Output the selected data directly
-header('Content-Type: application/json');
-echo json_encode($selectedData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

+ 0 - 108
service/auditoria/index.php

@@ -1,108 +0,0 @@
-<?
-ini_set('display_errors', 1);
-ini_set('display_startup_errors', 1);
-error_reporting(E_ALL);
-
-$ruta = '../../';
-require_once $_SERVER['DOCUMENT_ROOT'] . '/include/bd_pdo.php';
-header('Content-Type: application/json charset=utf-8');
-if (
-    (!isset($_SERVER['PHP_AUTH_USER']) || $_SERVER['PHP_AUTH_USER'] !== $_ENV['API_USER']) ||
-    (!isset($_SERVER['PHP_AUTH_PW']) || $_SERVER['PHP_AUTH_PW'] !== $_ENV['API_PASS'])
-) {
-    http_response_code(400);
-    echo json_encode(['error' => 'Usuario no autorizado']);
-    exit();
-}
-
-$input_raw = file_get_contents('php://input');
-$input = json_decode($input_raw, true);
-
-if (!isset($input['clave'])) {
-    http_response_code(400);
-    echo json_encode(['error' => 'clave no especificada']);
-    exit();
-} else if (!isset($input['fecha']) and (!isset($input['fecha_inicio']) and !isset($input['fecha_fin']))) {
-    http_response_code(400);
-    echo json_encode(['error' => 'fecha no especificada']);
-    exit();
-}
-
-try {
-    if ($_SERVER['REQUEST_METHOD'] !== 'POST')
-        throw new Exception('method not allowed');
-
-    if (!$db->where('profesor_clave', $input['clave'])->has('profesor'))
-        throw new Exception('clave no válida');
-
-    $profesor = $db->where('profesor_clave', $input['clave'])->getOne('profesor');
-
-    $data = $db->query(
-        "WITH horarios AS (
-            SELECT *,
-            CASE
-                WHEN horario_dia = 1 THEN 'Lunes'
-                WHEN horario_dia = 2 THEN 'Martes'
-                WHEN horario_dia = 3 THEN 'Miércoles'
-                WHEN horario_dia = 4 THEN 'Jueves'
-                WHEN horario_dia = 5 THEN 'Viernes'
-                WHEN horario_dia = 6 THEN 'Sábado'
-                WHEN horario_dia = 7 THEN 'Domingo'
-            END as dia,
-            horario_hora + duracion_interval as horario_fin
-            FROM horario
-            JOIN horario_profesor USING (horario_id)
-            JOIN materia USING (materia_id)
-            JOIN carrera USING (carrera_id)
-            JOIN facultad ON facultad.facultad_id = carrera.facultad_id
-            JOIN periodo_carrera USING (carrera_id)
-            JOIN periodo_view USING (periodo_id)
-            JOIN duracion USING (duracion_id)
-            WHERE :profesor_id = profesor_id and periodo_view.activo
-        ),
-        fechas AS (
-            SELECT fechas_clase(h.horario_id) as registro_fecha_ideal, h.horario_id  
-            FROM horarios h
-        )
-        SELECT distinct
-            materia_nombre as materia,
-            facultad_nombre as facultad,
-            carrera_nombre as carrera,
-            registro_fecha_ideal as fecha_clase,
-            horarios.horario_hora as hora_clase,
-            horarios.horario_fin as fin_clase,
-            horarios.dia as dia_clase,
-            CASE
-                when registro_justificada THEN 'Justificada'
-                WHEN registro_retardo THEN 'Retardo'
-                WHEN registro_reposicion THEN 'Respuesta'
-                WHEN registro_fecha IS NOT NULL THEN 'Registrado'
-            ELSE 'Sin registro' END as registro,
-            TO_CHAR(registro_fecha::TIME, 'HH24:MI:SS') as hora_registro
-        FROM horarios
-        JOIN fechas using (horario_id)
-        LEFT JOIN registro USING (horario_id, registro_fecha_ideal, profesor_id)
-        WHERE registro.registro_fecha_ideal BETWEEN :fecha_inicio AND :fecha_fin
-        ORDER BY fecha_clase DESC, materia_nombre",
-        [
-            ':fecha_inicio' => $input['fecha'] ?? $input['fecha_inicio'] ?? null,
-            ':fecha_fin' => $input['fecha'] ?? $input['fecha_fin'] ?? null,
-            ':profesor_id' => $profesor['profesor_id'],
-        ]
-    );
-
-    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
-} 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;
-}

+ 28 - 11
supervisor.php

@@ -78,13 +78,13 @@
                                     {{ JSON.parse(ruta.salon_array).splice(1).join('/') }}
                                     <span class="badge mx-3"
                                         v-if="ruta.horarios.some(({estado_supervisor_id}) => !estado_supervisor_id)"
-                                        :class="{ 'badge-success': ruta.horarios.length > 0, 'badge-danger': ruta.horarios.length == 0 }">
+                                        :class="{ 'badge-success': ruta.horarios.length > 0 || ruta.reposiciones.length > 0, 'badge-danger': ruta.horarios.length == 0  }">
                                         {{ ruta.horarios.filter(({estado_supervisor_id}) => estado_supervisor_id).length
                                         }} / {{
                                         ruta.horarios.length }}
                                     </span>
                                     <span v-else class="badge mx-3"
-                                        :class="{ 'badge-success': ruta.horarios.length > 0, 'badge-danger': ruta.horarios.length == 0 }">
+                                        :class="{ 'badge-success': ruta.horarios.length > 0 || ruta.reposiciones.length > 0, 'badge-danger': ruta.horarios.length == 0 }">
 
                                         <i class="ing-aceptar"></i>
                                     </span>
@@ -227,7 +227,16 @@
                                         </button>
                                     </div>
                                 <td class="text-center align-middle">
-                                    {{ clase.reposicion_hora?.slice(0, 5) }} - {{ clase.reposicion_fin?.slice(0, 5) }}
+                                    <fieldset>
+                                        <legend>Hora de la reposición</legend>
+                                        {{ clase.reposicion_hora?.slice(0, 5) }} - {{ clase.reposicion_fin?.slice(0, 5)
+                                        }}
+                                    </fieldset>
+                                    <fieldset>
+                                        <legend>Horario original</legend>
+                                        {{ clase.horario_hora?.slice(0, 5) }} - {{ clase.horario_fin?.slice(0, 5) }} el
+                                        día {{clase.registro_fecha_ideal}}
+                                    </fieldset>
                                 </td>
                                 <td class="text-center align-middle text-nowrap">
                                     <!-- data-toggle="button" -->
@@ -534,6 +543,11 @@
                 icon: "ing-justificar",
                 id: 4,
             },
+            {
+                color: "primary",
+                icon: "ing-ubicacion",
+                id: 5,
+            }
         ];
 
         const messages = PetiteVue.reactive({
@@ -579,8 +593,8 @@
                 this.rutas.selected = index;
             },
             order() {
-                const finals = this.rutas.data.filter(ruta => ruta.horarios.length > 0 && ruta.horarios.every(horario => horario.estado_supervisor_id));
-                const lasts = this.rutas.data.filter(ruta => ruta.horarios.length == 0);
+                const finals = this.rutas.data.filter((ruta => ruta.horarios.length > 0 && ruta.horarios.every(horario => horario.estado_supervisor_id)) || (ruta => ruta.reposiciones.length > 0 && ruta.reposiciones.every(reposicion => reposicion.estado_supervisor_id)));
+                const lasts = this.rutas.data.filter(ruta => ruta.horarios.length == 0 && ruta.reposiciones.length == 0);
                 const notLasts = this.rutas.data.filter(ruta => ruta.horarios.some(horario => !horario.estado_supervisor_id));
                 this.rutas.data = [...notLasts, ...finals, ...lasts];
             },
@@ -695,7 +709,6 @@
                 return clases;
             },
             get reposiciones() {
-                console.log('Rutas: ', store.rutas.data, store.rutas.selected);
                 const reposiciones = store.rutas.data.find(ruta => ruta.salon_id == store.rutas.selected)?.reposiciones ?? [];
                 return reposiciones;
             },
@@ -710,7 +723,9 @@
                         headers: {
                             "Content-Type": "application/json"
                         },
-                        body: JSON.stringify(store.rutas.data.map(ruta => ruta.horarios).flat(1).filter(clase => clase.pendiente).map(clase => ({
+                        body: JSON.stringify(store.rutas.data.map(ruta => ruta.horarios).flat(1).filter((clase, index, self) => clase.pendiente &&
+                            self.findIndex(c => c.horario_id == clase.horario_id && c.profesor_id == clase.profesor_id) === index
+                        ).map(clase => ({
                             horario_id: clase.horario_id,
                             estado: clase.estado_supervisor_id,
                             profesor_id: clase.profesor_id,
@@ -758,14 +773,16 @@
                 this.loading = true;
                 this.header = `Cargando rutas para ${this.catálogo_rutas.data.find(ruta => ruta.id_espacio_sgu == id_espacio_sgu).salon}`;
                 this.catálogo_rutas.selected = id_espacio_sgu;
-                const url = 'action/rutas_salón_horario.php'
+                const url = 'action/rutas_salón_horario.php';
+                // obtener parámetros del url
                 const searchParams = new URLSearchParams({
                     id_espacio_sgu: id_espacio_sgu,
-                    bloque_horario_id: store.bloquesHorario.data[store.bloquesHorario.selected].id
+                    bloque_horario_id: store.bloquesHorario.data[store.bloquesHorario.selected].id,
+                    fecha: new URL(document.location).searchParams.get('fecha') ?? null,
                 });
                 try {
                     const rutas = await fetch(`${url}?${searchParams}`).then(res => res.json());
-                    store.rutas.data = rutas.filter(ruta => ruta.horarios.length > 0);
+                    store.rutas.data = rutas.filter(ruta => ruta.horarios.length > 0 || ruta.reposiciones.length > 0);
                 } catch (error) {
                     console.error(error);
                     this.header = `Error al cargar rutas`;
@@ -787,7 +804,7 @@
 
             },
             get clase_vista() {
-                if (!store.profesor_selected.horario_id || !(store.profesor_selected.profesor_id >= 0) )
+                if (!store.profesor_selected.horario_id || !(store.profesor_selected.profesor_id >= 0))
                     return false;
 
                 return store.profesor_selected.es_reposicion

+ 25 - 3
usuarios.php

@@ -10,8 +10,7 @@ if ($user->acceso == null) {
 } else {
     $user->print_to_log('Usuarios');
 }
-$fac = $user->facultad['facultad_id'] ?? null;
-#echo $fac;
+$fac = isset($_POST['facultad']) ? $_POST['facultad'] ?: null : null;
 ?>
 <!DOCTYPE html>
 <html lang="en">
@@ -85,6 +84,10 @@ $fac = $user->facultad['facultad_id'] ?? null;
             ->orderBy('facultad_nombre', 'asc')
             ->get('facultad');
     }
+
+    $facultades = $db
+        ->orderBy('facultad_nombre', 'asc')
+        ->get('facultad');
     ?>
     <main class="content marco">
         <?php if (($_GET['error'] ?? null) == 2) { ?>
@@ -135,6 +138,23 @@ $fac = $user->facultad['facultad_id'] ?? null;
                                 </div>
                             </div>
                         </div>
+                        <div class="form-group row">
+                            <label for="filter_facultad" class="col-4 col-form-label">Facultad</label>
+                            <div class="col-8">
+                                <select id="filter_facultad" name="facultad" class="form-control" <?php if ($user->facultad['facultad_id']) { ?> disabled <?php } ?>>
+                                    <option value="">Mostrar todas</option>
+                                    <?php foreach ($facultades as $facultad) { ?>
+                                        <option value="<?php echo $facultad['facultad_id']; ?>" <?php if (isset($_POST['facultad']) && $_POST['facultad'] == $facultad['facultad_id']) {
+                                               echo 'selected';
+                                           } ?>>
+                                            <?php echo $facultad['facultad_nombre']; ?>
+                                        </option>
+                                    <?php } ?>
+                                </select>
+                            </div>
+                        </div>
+
+
                         <div class="form-group row">
                             <lab el for="filter_rol" class="col-4 col-form-label">Rol</label>
                                 <div class="col-8">
@@ -157,7 +177,6 @@ $fac = $user->facultad['facultad_id'] ?? null;
                                     </div>
                                 </div>
                         </div>
-                        </di v>
                         <div class="form-group row">
                             <div class="col-12 text-center">
                                 <button type="submit" class="btn btn-outline-primary">
@@ -221,6 +240,9 @@ $fac = $user->facultad['facultad_id'] ?? null;
                                     <td class="text-center icono-acciones">
                                         <a href="#" data-toggle="modal" data-target="#modal" data-tipo="2" title="Editar"><span
                                                 class="ing-editar ing-fw"></span></a>
+                                        <a href="action/action_usuarios_delete.php?id=<?= $usuario['id'] ?>" title="Eliminar"
+                                            onclick="return confirm('¿Estás seguro de que deseas eliminar este usuario?')"><span
+                                                class="ing-borrar ing-fw"></span></a>
                                     </td>
                                 <? } ?>
                             </tr>

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels