1 | <?php |
2 | |
3 | require_once __DIR__ . "/../../lib/php/ProblemDetails.php"; |
4 | |
5 | class Archivo |
6 | { |
7 | |
8 | public int $id; |
9 | public string $bytes; |
10 | |
11 | public function __construct(string $bytes = "", int $id = 0) |
12 | { |
13 | $this->id = $id; |
14 | $this->bytes = $bytes; |
15 | } |
16 | |
17 | public function valida() |
18 | { |
19 | if ($this->bytes === "") |
20 | throw new ProblemDetails( |
21 | status: ProblemDetails::BadRequest, |
22 | type: "/error/archivovacio.html", |
23 | title: "Archivo vacío.", |
24 | detail: "Selecciona un archivo que no esté vacío." |
25 | ); |
26 | } |
27 | } |
28 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../../lib/php/validaNombre.php"; |
4 | require_once __DIR__ . "/../../lib/php/ProblemDetails.php"; |
5 | require_once __DIR__ . "/Archivo.php"; |
6 | |
7 | class Producto |
8 | { |
9 | |
10 | public int $id; |
11 | public string $nombre; |
12 | public ?Archivo $archivo; |
13 | |
14 | public function __construct( |
15 | string $nombre = "", |
16 | Archivo $archivo = null, |
17 | int $id = 0, |
18 | ) { |
19 | $this->id = $id; |
20 | $this->nombre = $nombre; |
21 | $this->archivo = $archivo; |
22 | } |
23 | |
24 | public function validaNuevo() |
25 | { |
26 | validaNombre($this->nombre); |
27 | if ($this->archivo === null) |
28 | throw new ProblemDetails( |
29 | status: ProblemDetails::BadRequest, |
30 | type: "/error/faltaarchivo.html", |
31 | title: "Falta el archivo.", |
32 | detail: "Selecciona un archivo que no esté vacío." |
33 | ); |
34 | $this->archivo->valida(); |
35 | } |
36 | |
37 | public function valida() |
38 | { |
39 | validaNombre($this->nombre); |
40 | } |
41 | } |
42 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | require_once __DIR__ . "/../lib/php/pdFaltaId.php"; |
5 | require_once __DIR__ . "/../lib/php/ProblemDetails.php"; |
6 | require_once __DIR__ . "/../lib/php/leeEntero.php"; |
7 | require_once __DIR__ . "/bd/archivoBusca.php"; |
8 | |
9 | mb_internal_encoding("UTF-8"); |
10 | try { |
11 | // Evita que la imagen se cargue en el caché de la computadora. |
12 | header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); |
13 | header("Cache-Control: post-check=0, pre-check=0", false); |
14 | header("Pragma: no-cache"); |
15 | $id = leeEntero("id"); |
16 | if ($id === null) throw pdFaltaId(); |
17 | $archivo = archivoBusca($id); |
18 | if ($archivo === false) { |
19 | throw new ProblemDetails( |
20 | status: ProblemDetails::BadRequest, |
21 | type: "/error/archivonoencontrado.html", |
22 | title: "Archivo no encontrado.", |
23 | detail: "No se encontró ningún archivo con el id solicitado." |
24 | ); |
25 | } |
26 | echo $archivo->bytes; |
27 | } catch (ProblemDetails $details) { |
28 | procesa_problem_details($details); |
29 | } catch (Throwable $throwable) { |
30 | procesa_problem_details(new ProblemDetails( |
31 | status: ProblemDetails::InternalServerError, |
32 | type: "/error/errorinterno.html", |
33 | title: "Error interno del servidor.", |
34 | detail: $throwable->getMessage() |
35 | )); |
36 | } |
37 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | require_once __DIR__ . "/../lib/php/JsonResponse.php"; |
5 | require_once __DIR__ . "/../lib/php/leeBytes.php"; |
6 | require_once __DIR__ . "/../lib/php/leeTexto.php"; |
7 | require_once __DIR__ . "/modelo/Archivo.php"; |
8 | require_once __DIR__ . "/modelo/Producto.php"; |
9 | require_once __DIR__ . "/bd/productoAgrega.php"; |
10 | |
11 | ejecutaServicio(function () { |
12 | $bytes = leeBytes("bytes"); |
13 | $archivo = $bytes === "" ? null : new Archivo(bytes: $bytes); |
14 | |
15 | $nombre = leeTexto("nombre"); |
16 | $modelo = new Producto( |
17 | nombre: $nombre === null ? "" : trim($nombre), |
18 | archivo: $archivo |
19 | ); |
20 | |
21 | productoAgrega($modelo); |
22 | |
23 | $id = htmlentities($modelo->id); |
24 | // Los bytes se descargan con SrvArchivo; no desde aquí. |
25 | return JsonResponse::created("/srv/srvProductoBusca.php?id=$id", [ |
26 | "id" => ["value" => $modelo->id], |
27 | "nombre" => ["value" => $modelo->nombre], |
28 | "imagen" => [ |
29 | "src" => $archivo === null |
30 | ? "" |
31 | : "srv/srvArchivo.php?id=" . $archivo->id, |
32 | ] |
33 | ]); |
34 | }); |
35 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | require_once __DIR__ . "/../lib/php/pdFaltaId.php"; |
5 | require_once __DIR__ . "/../lib/php/ProblemDetails.php"; |
6 | require_once __DIR__ . "/../lib/php/leeEntero.php"; |
7 | require_once __DIR__ . "/bd/productoBusca.php"; |
8 | |
9 | ejecutaServicio(function () { |
10 | $id = leeEntero("id"); |
11 | if ($id === null) throw pdFaltaId(); |
12 | $modelo = productoBusca($id); |
13 | if ($modelo === false) { |
14 | $htmlId = htmlentities($id); |
15 | throw new ProblemDetails( |
16 | status: ProblemDetails::NotFound, |
17 | type: "/error/productonoencontrado.html", |
18 | title: "Producto no encontrado.", |
19 | detail: "No se encontró ningún producto con el id $htmlId.", |
20 | ); |
21 | } else { |
22 | $archivo = $modelo->archivo; |
23 | return [ |
24 | "id" => ["value" => $modelo->id], |
25 | "nombre" => ["value" => $modelo->nombre], |
26 | "imagen" => [ |
27 | "src" => $archivo === null |
28 | ? "" |
29 | : "srv/srvArchivo.php?id=" . $archivo->id |
30 | ] |
31 | ]; |
32 | } |
33 | }); |
34 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | require_once __DIR__ . "/bd/productoConsulta.php"; |
5 | |
6 | ejecutaServicio(function () { |
7 | $lista = productoConsulta(); |
8 | $render = ""; |
9 | foreach ($lista as $modelo) { |
10 | $prodId = htmlentities($modelo->prodId); |
11 | $prodNombre = htmlentities($modelo->prodNombre); |
12 | $archId = $modelo->archId === null |
13 | ? "" |
14 | : htmlentities($modelo->archId); |
15 | $render .= |
16 | "<div style='display: flex; flex-direction: row-reverse; |
17 | align-items: center; gap: 0.5rem'> |
18 | <dt style='flex: 1 1 0'> |
19 | <a href='modifica.html?id=$prodId'>$prodNombre</a> |
20 | </dt> |
21 | <dd style='flex: 1 1 0; margin: 0'> |
22 | <a href='modifica.html?id=$prodId'><img style='width: 100%' |
23 | alt='Imagen del producto' src='srv/srvArchivo.php?id=$archId'></a> |
24 | </dd> |
25 | </div>"; |
26 | } |
27 | return ["lista" => ["innerHTML" => $render]]; |
28 | }); |
29 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | require_once __DIR__ . "/../lib/php/JsonResponse.php"; |
5 | require_once __DIR__ . "/../lib/php/pdFaltaId.php"; |
6 | require_once __DIR__ . "/../lib/php/leeEntero.php"; |
7 | require_once __DIR__ . "/bd/productoElimina.php"; |
8 | |
9 | ejecutaServicio(function () { |
10 | $id = leeEntero("id"); |
11 | if ($id === null) throw pdFaltaId(); |
12 | productoElimina($id); |
13 | return JsonResponse::noContent(); |
14 | }); |
15 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | require_once __DIR__ . "/../lib/php/pdFaltaId.php"; |
5 | require_once __DIR__ . "/../lib/php/leeEntero.php"; |
6 | require_once __DIR__ . "/../lib/php/leeBytes.php"; |
7 | require_once __DIR__ . "/../lib/php/leeTexto.php"; |
8 | require_once __DIR__ . "/modelo/Archivo.php"; |
9 | require_once __DIR__ . "/modelo/Producto.php"; |
10 | require_once __DIR__ . "/bd/productoModifica.php"; |
11 | |
12 | ejecutaServicio(function () { |
13 | $id = leeEntero("id"); |
14 | if ($id === null) throw pdFaltaId(); |
15 | $bytes = leeBytes("bytes"); |
16 | $archivo = $bytes === "" ? null : new Archivo(bytes: $bytes); |
17 | |
18 | $nombre = leeTexto("nombre"); |
19 | $modelo = new Producto( |
20 | $nombre === null ? "" : trim($nombre), |
21 | archivo: $archivo, |
22 | id: $id |
23 | ); |
24 | |
25 | productoModifica($modelo); |
26 | |
27 | // Los bytes se descargan con SrvArchivo; no desde aquí. |
28 | $archivo = $modelo->archivo; |
29 | return [ |
30 | "id" => ["value" => $modelo->id], |
31 | "nombre" => ["value" => $modelo->nombre], |
32 | "imagen" => [ |
33 | "src" => $archivo === null |
34 | ? "" |
35 | : "srv/srvArchivo.php?id=" . $archivo->id, |
36 | ] |
37 | ]; |
38 | }); |
39 |
1 | <?php |
2 | |
3 | function bdCrea(PDO $con) |
4 | { |
5 | $con->exec( |
6 | 'CREATE TABLE IF NOT EXISTS ARCHIVO ( |
7 | ARCH_ID INTEGER, |
8 | ARCH_BYTES BLOB NOT NULL, |
9 | CONSTRAINT ARCH_PK |
10 | PRIMARY KEY(ARCH_ID) |
11 | )' |
12 | ); |
13 | $con->exec( |
14 | 'CREATE TABLE IF NOT EXISTS PRODUCTO ( |
15 | PROD_ID INTEGER, |
16 | PROD_NOMBRE TEXT NOT NULL, |
17 | ARCH_ID INTEGER NOT NULL, |
18 | CONSTRAINT PROD_PK |
19 | PRIMARY KEY(PROD_ID), |
20 | CONSTRAINT PROD_NOM_UNQ |
21 | UNIQUE(PROD_NOMBRE) |
22 | CONSTRAINT PROD_ARCH_FK |
23 | FOREIGN KEY (ARCH_ID) REFERENCES ARCHIVO(ARCH_ID) |
24 | )' |
25 | ); |
26 | } |
27 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/bdCrea.php"; |
4 | |
5 | class Bd |
6 | { |
7 | |
8 | private static ?PDO $conexion = null; |
9 | |
10 | static function getConexion(): PDO |
11 | { |
12 | if (self::$conexion === null) { |
13 | |
14 | self::$conexion = new PDO( |
15 | // cadena de conexión |
16 | "sqlite:srvarchivos.db", |
17 | // usuario |
18 | null, |
19 | // contraseña |
20 | null, |
21 | // Opciones: conexiones persistentes y lanza excepciones. |
22 | [PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] |
23 | ); |
24 | |
25 | bdCrea(self::$conexion); |
26 | } |
27 | |
28 | return self::$conexion; |
29 | } |
30 | } |
31 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../modelo/Archivo.php"; |
4 | require_once __DIR__ . "/Bd.php"; |
5 | |
6 | function archivoAgrega(Archivo $modelo) |
7 | { |
8 | $modelo->valida(); |
9 | $con = Bd::getConexion(); |
10 | $stmt = $con->prepare( |
11 | "INSERT INTO ARCHIVO |
12 | (ARCH_BYTES) |
13 | VALUES |
14 | (:bytes)" |
15 | ); |
16 | $stmt->execute([ |
17 | ":bytes" => $modelo->bytes |
18 | ]); |
19 | /* Si usas una secuencia para generar el id, |
20 | * pasa como parámetro de lastInsertId el |
21 | * nombre de dicha secuencia, debes |
22 | * ejecutarlo antes del INSERT y pasarle el |
23 | * id generado al SQL. */ |
24 | $modelo->id = $con->lastInsertId(); |
25 | } |
26 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../modelo/Archivo.php"; |
4 | require_once __DIR__ . "/Bd.php"; |
5 | |
6 | function archivoBusca(int $id): false|Archivo |
7 | { |
8 | $con = Bd::getConexion(); |
9 | $stmt = $con->prepare( |
10 | "SELECT |
11 | ARCH_ID AS id, |
12 | ARCH_BYTES AS bytes |
13 | FROM ARCHIVO |
14 | WHERE ARCH_ID = :id" |
15 | ); |
16 | $stmt->execute([":id" => $id]); |
17 | $stmt->setFetchMode( |
18 | PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, |
19 | Archivo::class |
20 | ); |
21 | return $stmt->fetch(); |
22 | } |
23 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/Bd.php"; |
4 | |
5 | function archivoElimina(int $id) |
6 | { |
7 | $con = Bd::getConexion(); |
8 | $stmt = $con->prepare( |
9 | "DELETE FROM ARCHIVO |
10 | WHERE ARCH_ID = :id" |
11 | ); |
12 | $stmt->execute([":id" => $id]); |
13 | } |
14 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../modelo/Archivo.php"; |
4 | require_once __DIR__ . "/Bd.php"; |
5 | |
6 | function archivoModifica(Archivo $modelo) |
7 | { |
8 | $modelo->valida(); |
9 | $con = Bd::getConexion(); |
10 | $stmt = $con->prepare( |
11 | "UPDATE ARCHIVO |
12 | SET ARCH_BYTES = :bytes |
13 | WHERE ARCH_ID = :id" |
14 | ); |
15 | $stmt->execute([ |
16 | ":bytes" => $modelo->bytes, |
17 | ":id" => $modelo->id |
18 | ]); |
19 | } |
20 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../modelo/Archivo.php"; |
4 | require_once __DIR__ . "/Bd.php"; |
5 | require_once __DIR__ . "/archivoAgrega.php"; |
6 | |
7 | function productoAgrega(Producto $modelo) |
8 | { |
9 | $modelo->validaNuevo(); |
10 | $con = Bd::getConexion(); |
11 | $con->beginTransaction(); |
12 | archivoAgrega($modelo->archivo); |
13 | $stmt = $con->prepare( |
14 | "INSERT INTO PRODUCTO |
15 | (PROD_NOMBRE, ARCH_ID) |
16 | VALUES |
17 | (:nombre, :archId)" |
18 | ); |
19 | $stmt->execute([ |
20 | ":nombre" => $modelo->nombre, |
21 | ":archId" => $modelo->archivo->id |
22 | ]); |
23 | /* Si usas una secuencia para generar el id, |
24 | * pasa como parámetro de lastInsertId el |
25 | * nombre de dicha secuencia, debes |
26 | * ejecutarlo antes del INSERT y pasarle el |
27 | * id generado al SQL. */ |
28 | $modelo->id = $con->lastInsertId(); |
29 | $con->commit(); |
30 | } |
31 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../modelo/Archivo.php"; |
4 | require_once __DIR__ . "/../modelo/Producto.php"; |
5 | require_once __DIR__ . "/Bd.php"; |
6 | |
7 | function productoBusca(int $prodId) |
8 | { |
9 | $con = Bd::getConexion(); |
10 | $stmt = $con->prepare( |
11 | "SELECT |
12 | P.PROD_ID AS prodId, |
13 | P.PROD_NOMBRE AS prodNombre, |
14 | A.ARCH_ID AS archId |
15 | FROM PRODUCTO P |
16 | LEFT JOIN ARCHIVO A |
17 | ON P.ARCH_ID = A.ARCH_ID |
18 | WHERE P.PROD_ID = :prodId" |
19 | ); |
20 | $stmt->execute([ |
21 | ":prodId" => $prodId |
22 | ]); |
23 | $stmt->setFetchMode(PDO::FETCH_OBJ); |
24 | $obj = $stmt->fetch(); |
25 | if ($obj === false) { |
26 | return false; |
27 | } else { |
28 | $id = $obj->prodId; |
29 | $nombre = $obj->prodNombre; |
30 | $archId = $obj->archId; |
31 | $archivo = $archId === null ? null : new Archivo(id: $archId); |
32 | $producto = new Producto( |
33 | id: $id, |
34 | nombre: $nombre, |
35 | archivo: $archivo |
36 | ); |
37 | return $producto; |
38 | } |
39 | } |
40 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../../lib/php/recibeFetchAll.php"; |
4 | require_once __DIR__ . "/../modelo/Producto.php"; |
5 | require_once __DIR__ . "/Bd.php"; |
6 | |
7 | function productoConsulta() |
8 | { |
9 | $con = Bd::getConexion(); |
10 | $stmt = $con->query( |
11 | "SELECT |
12 | P.PROD_ID AS prodId, |
13 | P.PROD_NOMBRE AS prodNombre, |
14 | A.ARCH_ID AS archId |
15 | FROM PRODUCTO P |
16 | LEFT JOIN ARCHIVO A |
17 | ON P.ARCH_ID = A.ARCH_ID |
18 | ORDER BY P.PROD_NOMBRE" |
19 | ); |
20 | $resultado = $stmt->fetchAll(PDO::FETCH_OBJ); |
21 | return recibeFetchAll($resultado); |
22 | } |
23 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/Bd.php"; |
4 | require_once __DIR__ . "/archivoElimina.php"; |
5 | require_once __DIR__ . "/productoBusca.php"; |
6 | |
7 | function productoElimina(int $id) |
8 | { |
9 | $con = Bd::getConexion(); |
10 | $con->beginTransaction(); |
11 | $modelo = productoBusca($id); |
12 | if ($modelo === false) { |
13 | $con->rollBack(); |
14 | } else { |
15 | archivoElimina($modelo->archivo->id); |
16 | $stmt = $con->prepare( |
17 | "DELETE FROM PRODUCTO |
18 | WHERE PROD_ID = :id" |
19 | ); |
20 | $stmt->execute([":id" => $modelo->id]); |
21 | $con->commit(); |
22 | } |
23 | } |
24 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../modelo/Producto.php"; |
4 | require_once __DIR__ . "/Bd.php"; |
5 | require_once __DIR__ . "/productoBusca.php"; |
6 | require_once __DIR__ . "/archivoModifica.php"; |
7 | |
8 | function productoModifica(Producto $modelo) |
9 | { |
10 | $modelo->valida(); |
11 | $con = Bd::getConexion(); |
12 | $con->beginTransaction(); |
13 | $archivo = $modelo->archivo; |
14 | $anterior = productoBusca($modelo->id); |
15 | if ($anterior === false) { |
16 | throw new Exception("Producto no encontrado."); |
17 | } |
18 | if ($anterior->archivo === null) { |
19 | throw new Exception("Falta el archivo anterior."); |
20 | } |
21 | if ($archivo === null) { |
22 | $archivo = $anterior->archivo; |
23 | $modelo->archivo = $archivo; |
24 | } else { |
25 | $archivo->id = $anterior->archivo->id; |
26 | archivoModifica($archivo); |
27 | } |
28 | $stmt = $con->prepare( |
29 | "UPDATE PRODUCTO |
30 | SET |
31 | PROD_NOMBRE = :nombre, |
32 | ARCH_ID = :archId |
33 | WHERE PROD_ID = :id" |
34 | ); |
35 | $stmt->execute([ |
36 | ":id" => $modelo->id, |
37 | ":nombre" => $modelo->nombre, |
38 | ":archId" => $archivo->id |
39 | ]); |
40 | $con->commit(); |
41 | } |
42 |