J. Carpeta « srv »

Versión para imprimir.

A. srv / archivo.php

1<?php
2
3require_once __DIR__ . "/../lib/php/NOT_FOUND.php";
4require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
5require_once __DIR__ . "/../lib/php/recuperaIdEntero.php";
6require_once __DIR__ . "/../lib/php/ProblemDetails.php";
7require_once __DIR__ . "/../lib/php/selectFirst.php";
8require_once __DIR__ . "/Bd.php";
9require_once __DIR__ . "/TABLA_ARCHIVO.php";
10
11ejecutaServicio(function () {
12
13 // Evita que la imagen se cargue en el caché del navegador.
14 header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
15 header("Cache-Control: post-check=0, pre-check=0", false);
16 header("Pragma: no-cache");
17
18 $archId = recuperaIdEntero("id");
19
20 $archivo =
21 selectFirst(pdo: Bd::pdo(), from: ARCHIVO, where: [ARCH_ID => $archId]);
22
23 if ($archivo === false) {
24 $idHtml = htmlentities($archId);
25 throw new ProblemDetails(
26 status: NOT_FOUND,
27 title: "Archivo no encontrado.",
28 type: "/error/archivonoencontrado.html",
29 detail: "No se encontró ningún archivo con el id $idHtml.",
30 );
31 }
32
33 $bytes = $archivo[ARCH_BYTES];
34 $contentType = (new finfo(FILEINFO_MIME_TYPE))->buffer($bytes);
35 header("Content-Type: $contentType");
36 echo $bytes;
37});
38

B. srv / Bd.php

1<?php
2
3class Bd
4{
5 private static ?PDO $pdo = null;
6
7 static function pdo(): PDO
8 {
9 if (self::$pdo === null) {
10
11 self::$pdo = new PDO(
12 // cadena de conexión
13 "sqlite:srvarchivos.db",
14 // usuario
15 null,
16 // contraseña
17 null,
18 // Opciones: pdos no persistentes y lanza excepciones.
19 [PDO::ATTR_PERSISTENT => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
20 );
21
22 self::$pdo->exec(
23 'CREATE TABLE IF NOT EXISTS ARCHIVO (
24 ARCH_ID INTEGER,
25 ARCH_BYTES BLOB NOT NULL,
26 CONSTRAINT ARCH_PK
27 PRIMARY KEY(ARCH_ID)
28 )'
29 );
30 self::$pdo->exec(
31 'CREATE TABLE IF NOT EXISTS PRODUCTO (
32 PROD_ID INTEGER,
33 PROD_NOMBRE TEXT NOT NULL,
34 ARCH_ID INTEGER NOT NULL,
35 CONSTRAINT PROD_PK
36 PRIMARY KEY(PROD_ID),
37 CONSTRAINT PROD_NOM_UNQ
38 UNIQUE(PROD_NOMBRE),
39 CONSTRAINT PROD_NOM_NV
40 CHECK(LENGTH(PROD_NOMBRE) > 0),
41 CONSTRAINT PROD_ARCH_FK
42 FOREIGN KEY (ARCH_ID) REFERENCES ARCHIVO(ARCH_ID)
43 )'
44 );
45 }
46
47 return self::$pdo;
48 }
49}
50

C. srv / producto-agrega.php

1<?php
2
3require_once __DIR__ . "/../lib/php/BAD_REQUEST.php";
4require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
5require_once __DIR__ . "/../lib/php/recuperaBytes.php";
6require_once __DIR__ . "/../lib/php/recuperaTexto.php";
7require_once __DIR__ . "/../lib/php/validaNombre.php";
8require_once __DIR__ . "/../lib/php/ProblemDetails.php";
9require_once __DIR__ . "/../lib/php/insert.php";
10require_once __DIR__ . "/../lib/php/devuelveCreated.php";
11require_once __DIR__ . "/Bd.php";
12require_once __DIR__ . "/TABLA_PRODUCTO.php";
13require_once __DIR__ . "/TABLA_ARCHIVO.php";
14require_once __DIR__ . "/validaImagen.php";
15
16ejecutaServicio(function () {
17
18 $nombre = recuperaTexto("nombre");
19 $bytes = recuperaBytes("imagen");
20
21 $nombre = validaNombre($nombre);
22 $bytes = validaImagen($bytes);
23
24 if ($bytes === "") {
25 throw new ProblemDetails(
26 status: BAD_REQUEST,
27 title: "Imagen vacía.",
28 type: "/error/imagenvacia.html",
29 detail: "Selecciona un archivo que no esté vacío."
30 );
31 }
32
33 $pdo = Bd::pdo();
34 $pdo->beginTransaction();
35
36 insert(pdo: $pdo, into: ARCHIVO, values: [ARCH_BYTES => $bytes]);
37 $archId = $pdo->lastInsertId();
38
39 insert(
40 pdo: $pdo,
41 into: PRODUCTO,
42 values: [PROD_NOMBRE => $nombre, ARCH_ID => $archId]
43 );
44 $id = $pdo->lastInsertId();
45
46 $pdo->commit();
47
48 $encodeId = urlencode($id);
49 $encodeArchId = urlencode($archId);
50 $htmlEncodeArchId = htmlentities($encodeArchId);
51 // Los bytes se descargan con "archivo.php"; no desde aquí.
52 devuelveCreated("/srv/producto.php?id=$encodeId", [
53 "id" => ["value" => $id],
54 "nombre" => ["value" => $nombre],
55 "imagen" => ["data-file" => "srv/archivo.php?id=$htmlEncodeArchId"]
56 ]);
57});
58

D. srv / producto-elimina.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
4require_once __DIR__ . "/../lib/php/recuperaIdEntero.php";
5require_once __DIR__ . "/../lib/php/selectFirst.php";
6require_once __DIR__ . "/../lib/php/delete.php";
7require_once __DIR__ . "/../lib/php/devuelveNoContent.php";
8require_once __DIR__ . "/Bd.php";
9require_once __DIR__ . "/TABLA_PRODUCTO.php";
10require_once __DIR__ . "/TABLA_ARCHIVO.php";
11
12ejecutaServicio(function () {
13
14 $prodId = recuperaIdEntero("id");
15
16 $pdo = Bd::pdo();
17 $pdo->beginTransaction();
18
19 $producto =
20 selectFirst(pdo: $pdo, from: PRODUCTO, where: [PROD_ID => $prodId]);
21 if ($producto !== false) {
22 delete(pdo: $pdo, from: PRODUCTO, where: [PROD_ID => $prodId]);
23 if ($producto[ARCH_ID] !== null) {
24 delete(pdo: $pdo, from: ARCHIVO, where: [ARCH_ID => $producto[ARCH_ID]]);
25 }
26 }
27
28 $pdo->commit();
29
30 devuelveNoContent();
31});
32

E. srv / producto-modifica.php

1<?php
2
3require_once __DIR__ . "/../lib/php/NOT_FOUND.php";
4require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
5require_once __DIR__ . "/../lib/php/recuperaIdEntero.php";
6require_once __DIR__ . "/../lib/php/recuperaTexto.php";
7require_once __DIR__ . "/../lib/php/recuperaBytes.php";
8require_once __DIR__ . "/../lib/php/validaNombre.php";
9require_once __DIR__ . "/../lib/php/selectFirst.php";
10require_once __DIR__ . "/../lib/php/ProblemDetails.php";
11require_once __DIR__ . "/../lib/php/insert.php";
12require_once __DIR__ . "/../lib/php/update.php";
13require_once __DIR__ . "/../lib/php/devuelveJson.php";
14require_once __DIR__ . "/Bd.php";
15require_once __DIR__ . "/TABLA_PRODUCTO.php";
16require_once __DIR__ . "/TABLA_ARCHIVO.php";
17require_once __DIR__ . "/validaImagen.php";
18
19ejecutaServicio(function () {
20
21 $prodId = recuperaIdEntero("id");
22 $nombre = recuperaTexto("nombre");
23 $bytes = recuperaBytes("imagen");
24
25 $nombre = validaNombre($nombre);
26 $bytes = validaImagen($bytes);
27
28 $pdo = Bd::pdo();
29 $pdo->beginTransaction();
30
31 $producto =
32 selectFirst(pdo: $pdo, from: PRODUCTO, where: [PROD_ID => $prodId]);
33
34 if ($producto === false) {
35 $prodIdHtml = htmlentities($prodId);
36 throw new ProblemDetails(
37 status: NOT_FOUND,
38 title: "Producto no encontrado.",
39 type: "/error/productonoencontrado.html",
40 detail: "No se encontró ningún producto con el id $prodIdHtml.",
41 );
42 }
43
44 $archId = $producto[ARCH_ID];
45
46 if ($bytes !== "") {
47 if ($archId === null) {
48 insert(pdo: $pdo, into: ARCHIVO, values: [ARCH_BYTES => $bytes]);
49 $archId = $pdo->lastInsertId();
50 } else {
51 update(
52 pdo: $pdo,
53 table: ARCHIVO,
54 set: [ARCH_BYTES => $bytes],
55 where: [ARCH_ID => $archId]
56 );
57 }
58 }
59
60 update(
61 pdo: $pdo,
62 table: PRODUCTO,
63 set: [PROD_NOMBRE => $nombre, ARCH_ID => $archId],
64 where: [PROD_ID => $prodId]
65 );
66
67 $pdo->commit();
68
69 $encodeArchId = $archId === null ? "" : urlencode($archId);
70 $htmlEncodeArchId = htmlentities($encodeArchId);
71 // Los bytes se descargan con "archivo.php"; no desde aquí.
72 devuelveJson([
73 "id" => ["value" => $prodId],
74 "nombre" => ["value" => $nombre],
75 "imagen" => [
76 "data-file" => $htmlEncodeArchId === ""
77 ? ""
78 : "srv/archivo.php?id=$htmlEncodeArchId"
79 ]
80 ]);
81});
82

F. srv / producto.php

1<?php
2
3require_once __DIR__ . "/../lib/php/NOT_FOUND.php";
4require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
5require_once __DIR__ . "/../lib/php/recuperaIdEntero.php";
6require_once __DIR__ . "/../lib/php/selectFirst.php";
7require_once __DIR__ . "/../lib/php/ProblemDetails.php";
8require_once __DIR__ . "/../lib/php/devuelveJson.php";
9require_once __DIR__ . "/Bd.php";
10require_once __DIR__ . "/TABLA_PRODUCTO.php";
11require_once __DIR__ . "/TABLA_ARCHIVO.php";
12
13ejecutaServicio(function () {
14
15 $prodId = recuperaIdEntero("id");
16
17 $modelo =
18 selectFirst(pdo: Bd::pdo(), from: PRODUCTO, where: [PROD_ID => $prodId]);
19
20 if ($modelo === false) {
21 $prodIdHtml = htmlentities($prodId);
22 throw new ProblemDetails(
23 status: NOT_FOUND,
24 title: "Producto no encontrado.",
25 type: "/error/productonoencontrado.html",
26 detail: "No se encontró ningún producto con el id $prodIdHtml.",
27 );
28 }
29
30 $encodeArchId = $modelo[ARCH_ID] === null ? "" : urlencode($modelo[ARCH_ID]);
31 $htmlEncodeArchId = htmlentities($encodeArchId);
32 devuelveJson([
33 "id" => ["value" => $prodId],
34 "nombre" => ["value" => $modelo[PROD_NOMBRE]],
35 "imagen" => [
36 "data-file" => $htmlEncodeArchId === ""
37 ? ""
38 : "srv/archivo.php?id=$htmlEncodeArchId"
39 ]
40 ]);
41});
42

G. srv / productos.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
4require_once __DIR__ . "/../lib/php/select.php";
5require_once __DIR__ . "/../lib/php/devuelveJson.php";
6require_once __DIR__ . "/Bd.php";
7require_once __DIR__ . "/TABLA_PRODUCTO.php";
8require_once __DIR__ . "/TABLA_ARCHIVO.php";
9
10ejecutaServicio(function () {
11
12 $lista = select(pdo: Bd::pdo(), from: PRODUCTO, orderBy: PROD_NOMBRE);
13
14 $render = "";
15 foreach ($lista as $modelo) {
16 $prodId = htmlentities($modelo[PROD_ID]);
17 $prodNombre = htmlentities($modelo[PROD_NOMBRE]);
18 $encodeArchId = $modelo[ARCH_ID] === null ? "" : urlencode($modelo[ARCH_ID]);
19 $archId = $encodeArchId === "" ? "" : htmlentities($encodeArchId);
20 $src = $archId === "" ? "" : "srv/archivo.php?id=$archId";
21 $render .=
22 "<div style='display: flex; flex-direction: row-reverse;
23 align-items: center; gap: 0.5rem'>
24 <dt style='flex: 1 1 0'>
25 <a href='modifica.html?id=$prodId'>$prodNombre</a>
26 </dt>
27 <dd style='flex: 1 1 0; margin: 0'>
28 <a href='modifica.html?id=$prodId'><img
29 style='width: 100%; aspect-ratio:16/9; object-fit: cover'
30 alt='Imagen del producto' src='$src'></a>
31 </dd>
32 </div>";
33 }
34
35 devuelveJson(["lista" => ["innerHTML" => $render]]);
36});
37

H. srv / TABLA_ARCHIVO.php

1<?php
2
3const ARCHIVO = "ARCHIVO";
4const ARCH_ID = "ARCH_ID";
5const ARCH_BYTES = "ARCH_BYTES";
6

I. srv / TABLA_PRODUCTO.php

1<?php
2
3const PRODUCTO = "PRODUCTO";
4const PROD_ID = "PROD_ID";
5const PROD_NOMBRE = "PROD_NOMBRE";
6

J. srv / validaImagen.php

1<?php
2
3require_once __DIR__ . "/../lib/php/BAD_REQUEST.php";
4require_once __DIR__ . "/../lib/php/ProblemDetails.php";
5
6function validaImagen(false|string $bytes)
7{
8
9 if ($bytes === false) {
10 throw new ProblemDetails(
11 status: BAD_REQUEST,
12 title: "Falta la imagen.",
13 type: "/error/faltaimagen.html",
14 detail: "La solicitud no tiene el valor de imagen."
15 );
16 }
17
18 return $bytes;
19}
20