9. Carpeta « php »

Versión para imprimir.

A. php / Bd.php

1
<?php
2
3
class Bd
4
{
5
6
 private static ?PDO $pdo = null;
7
8
 static function pdo(): PDO
9
 {
10
  if (self::$pdo === null) {
11
12
   self::$pdo = new PDO(
13
    // cadena de conexión
14
    "sqlite:" . __DIR__ . "/notipush.db",
15
    // usuario
16
    null,
17
    // contraseña
18
    null,
19
    // Opciones: pdos no persistentes y lanza excepciones.
20
    [PDO::ATTR_PERSISTENT => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
21
   );
22
23
   self::$pdo->exec(
24
    'CREATE TABLE IF NOT EXISTS SUSCRIPCION (
25
       SUS_ENDPOINT TEXT NOT NULL,
26
       SUS_PUB_KEY TEXT NOT NULL,
27
       SUS_AUT_TOK TEXT NOT NULL,
28
       SUS_CONT_ENCOD TEXT NOT NULL,
29
      CONSTRAINT SUS_PK
30
       PRIMARY KEY(SUS_ENDPOINT),
31
      CONSTRAINT SUS_ENDPNT_NV
32
       CHECK(LENGTH(SUS_ENDPOINT) > 0)
33
     )'
34
   );
35
  }
36
37
  return self::$pdo;
38
 }
39
}
40

B. php / genera-llaves.php

1
<!DOCTYPE html>
2
<html lang="es">
3
4
<head>
5
6
 <meta charset="UTF-8">
7
 <meta name="viewport" content="width=device-width">
8
9
 <title>Llaves VAPID</title>
10
11
</head>
12
13
<body>
14
15
 <h1>Llaves VAPID</h1>
16
17
 <pre>
18
 <?php
19
20
 require __DIR__ . "/../vendor/autoload.php";
21
22
 use Minishlink\WebPush\VAPID;
23
24
 var_dump(VAPID::createVapidKeys());
25
26
 ?>
27
 </pre>
28
29
</body>
30
31
</html>

C. php / notifica.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/../vendor/autoload.php";
5
require_once  __DIR__ . "/lib/devuelveJson.php";
6
require_once  __DIR__ . "/Bd.php";
7
require_once __DIR__ . "/Suscripcion.php";
8
require_once __DIR__ . "/suscripcionElimina.php";
9
10
use Minishlink\WebPush\WebPush;
11
12
const AUTH = [
13
 "VAPID" => [
14
  "subject" => "https://notipush.rf.gd/",
15
  "publicKey" => "BMBlr6YznhYMX3NgcWIDRxZXs0sh7tCv7_YCsWcww0ZCv9WGg-tRCXfMEHTiBPCksSqeve1twlbmVAZFv7GSuj0",
16
  "privateKey" => "vplfkITvu0cwHqzK9Kj-DYStbCH_9AhGx9LqMyaeI6w"
17
 ]
18
];
19
20
$webPush = new WebPush(AUTH);
21
$mensaje = "Hola! 👋";
22
23
// Envia el mensaje a todas las suscripciones.
24
25
$bd = Bd::pdo();
26
$stmt = $bd->query("SELECT * FROM SUSCRIPCION");
27
$suscripciones =
28
 $stmt->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, Suscripcion::class);
29
30
foreach ($suscripciones as $suscripcion) {
31
 $webPush->queueNotification($suscripcion, $mensaje);
32
}
33
$reportes = $webPush->flush();
34
35
// Genera el reporte de envio a cada suscripcion.
36
$reporteDeEnvios = "";
37
foreach ($reportes as $reporte) {
38
 $endpoint = $reporte->getRequest()->getUri();
39
 $htmlEndpoint = htmlentities($endpoint);
40
 if ($reporte->isSuccess()) {
41
  // Reporte de éxito.
42
  $reporteDeEnvios .= "<dt>$htmlEndpoint</dt><dd>Éxito</dd>";
43
 } else {
44
  if ($reporte->isSubscriptionExpired()) {
45
   suscripcionElimina($bd, $endpoint);
46
  }
47
  // Reporte de fallo.
48
  $explicacion = htmlentities($reporte->getReason());
49
  $reporteDeEnvios .= "<dt>$endpoint</dt><dd>Fallo: $explicacion</dd>";
50
 }
51
}
52
53
devuelveJson(["reporte" => ["innerHTML" => $reporteDeEnvios]]);
54

D. php / recibeSuscripcion.php

1
<?php
2
3
require_once __DIR__ . "/lib/BAD_REQUEST.php";
4
require_once __DIR__ . "/lib/recibeJson.php";
5
require_once __DIR__ . "/lib/ProblemDetailsException.php";
6
7
function recibeSuscripcion()
8
{
9
10
 $objeto = recibeJson();
11
12
 if (
13
  !isset($objeto->authToken)
14
  || !is_string($objeto->authToken)
15
  || $objeto->authToken === ""
16
 )
17
  throw new ProblemDetailsException([
18
   "status" => BAD_REQUEST,
19
   "title" => "El authToken debe ser texto que no esté en blanco.",
20
   "type" => "/errors/authtokenincorrecto.html",
21
  ]);
22
23
 if (
24
  !isset($objeto->contentEncoding)
25
  || !is_string($objeto->contentEncoding)
26
  || $objeto->contentEncoding === ""
27
 )
28
  throw new ProblemDetailsException([
29
   "status" => BAD_REQUEST,
30
   "title" => "La contentEncoding debe ser texto que no esté en blanco.",
31
   "type" => "/errors/contentencodingincorrecta.html",
32
  ]);
33
34
 if (
35
  !isset($objeto->endpoint)
36
  || !is_string($objeto->endpoint)
37
  || $objeto->endpoint === ""
38
 )
39
  throw new ProblemDetailsException([
40
   "status" => BAD_REQUEST,
41
   "title" => "El endpoint debe ser texto que no esté en blanco.",
42
   "type" => "/errors/endpointincorrecto.html",
43
  ]);
44
45
 if (
46
  !isset($objeto->publicKey)
47
  || !is_string($objeto->publicKey)
48
  || $objeto->publicKey === ""
49
 )
50
  throw new ProblemDetailsException([
51
   "status" => BAD_REQUEST,
52
   "title" => "La publicKey debe ser texto que no esté en blanco.",
53
   "type" => "/errors/publickeyincorrecta.html",
54
  ]);
55
56
 return [
57
  "SUS_AUT_TOK" => $objeto->authToken,
58
  "SUS_CONT_ENCOD" => $objeto->contentEncoding,
59
  "SUS_ENDPOINT" => $objeto->endpoint,
60
  "SUS_PUB_KEY" => $objeto->publicKey,
61
 ];
62
}
63

E. php / suscripcion-elimina.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/devuelveNoContent.php";
5
require_once  __DIR__ . "/Bd.php";
6
require_once  __DIR__ . "/recibeSuscripcion.php";
7
require_once  __DIR__ . "/suscripcionElimina.php";
8
9
$modelo = recibeSuscripcion();
10
suscripcionElimina(Bd::pdo(), $modelo["SUS_ENDPOINT"]);
11
devuelveNoContent();
12

F. php / suscripcion-modifica.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/devuelveCreated.php";
5
require_once __DIR__ . "/lib/devuelveJson.php";
6
require_once  __DIR__ . "/Bd.php";
7
require_once  __DIR__ . "/recibeSuscripcion.php";
8
9
$modelo = recibeSuscripcion();
10
11
$bd = Bd::pdo();
12
13
$stmt =
14
 $bd->prepare("SELECT * FROM SUSCRIPCION WHERE SUS_ENDPOINT = :SUS_ENDPOINT");
15
$stmt->execute([":SUS_ENDPOINT" => $modelo["SUS_ENDPOINT"]]);
16
$anterior = $stmt->fetch(PDO::FETCH_ASSOC);
17
18
if ($anterior === false) {
19
20
 $stmt = $bd->prepare(
21
  "INSERT INTO SUSCRIPCION (
22
    SUS_ENDPOINT, SUS_PUB_KEY, SUS_AUT_TOK, SUS_CONT_ENCOD
23
   ) values (
24
    :SUS_ENDPOINT, :SUS_PUB_KEY, :SUS_AUT_TOK, :SUS_CONT_ENCOD
25
   )"
26
 );
27
 $stmt->execute([
28
  ":SUS_ENDPOINT" => $modelo["SUS_ENDPOINT"],
29
  ":SUS_PUB_KEY" => $modelo["SUS_PUB_KEY"],
30
  ":SUS_AUT_TOK" => $modelo["SUS_AUT_TOK"],
31
  ":SUS_CONT_ENCOD" => $modelo["SUS_CONT_ENCOD"],
32
 ]);
33
34
 devuelveCreated("", $modelo);
35
} else {
36
37
 $stmt = $bd->prepare(
38
  "UPDATE SUSCRIPCION
39
   SET
40
    SUS_PUB_KEY = :SUS_PUB_KEY,
41
    SUS_AUT_TOK = :SUS_AUT_TOK,
42
    SUS_CONT_ENCOD = :SUS_CONT_ENCOD
43
   WHERE
44
    SUS_ENDPOINT = :SUS_ENDPOINT"
45
 );
46
 $stmt->execute([
47
  ":SUS_PUB_KEY" => $modelo["SUS_PUB_KEY"],
48
  ":SUS_AUT_TOK" => $modelo["SUS_AUT_TOK"],
49
  ":SUS_CONT_ENCOD" => $modelo["SUS_CONT_ENCOD"],
50
  ":SUS_ENDPOINT" => $modelo["SUS_ENDPOINT"],
51
 ]);
52
53
 devuelveJson($modelo);
54
}
55

G. php / Suscripcion.php

1
<?php
2
3
require_once __DIR__ . "/../vendor/autoload.php";
4
5
use Minishlink\WebPush\SubscriptionInterface;
6
7
class Suscripcion implements SubscriptionInterface
8
{
9
10
 public string $SUS_ENDPOINT;
11
 public string $SUS_PUB_KEY;
12
 public string $SUS_AUT_TOK;
13
 public string $SUS_CONT_ENCOD;
14
15
 public function __construct(
16
  string $SUS_ENDPOINT = "",
17
  string $SUS_PUB_KEY = "",
18
  string $SUS_AUT_TOK = "",
19
  string $SUS_CONT_ENCOD = ""
20
 ) {
21
  $this->SUS_ENDPOINT = $SUS_ENDPOINT;
22
  $this->SUS_PUB_KEY = $SUS_PUB_KEY;
23
  $this->SUS_AUT_TOK = $SUS_AUT_TOK;
24
  $this->SUS_CONT_ENCOD = $SUS_CONT_ENCOD;
25
 }
26
27
 public function getEndpoint(): string
28
 {
29
  return $this->SUS_ENDPOINT;
30
 }
31
32
 public function getPublicKey(): ?string
33
 {
34
  return $this->SUS_PUB_KEY;
35
 }
36
37
 public function getAuthToken(): ?string
38
 {
39
  return $this->SUS_AUT_TOK;
40
 }
41
42
 public function getContentEncoding(): ?string
43
 {
44
  return $this->SUS_CONT_ENCOD;
45
 }
46
}
47

H. php / suscripcionElimina.php

1
<?php
2
3
function suscripcionElimina(\PDO $bd, string $endpoint)
4
{
5
 $stmt =
6
  $bd->prepare("DELETE FROM SUSCRIPCION WHERE SUS_ENDPOINT = :SUS_ENDPOINT");
7
 $stmt->execute([":SUS_ENDPOINT" => $endpoint]);
8
}
9

I. Carpeta « php / lib »

Versión para imprimir.

1. php / lib / BAD_REQUEST.php

1
<?php
2
3
const BAD_REQUEST = 400;
4

2. php / lib / devuelveCreated.php

1
<?php
2
3
require_once __DIR__ . "/devuelveResultadoNoJson.php";
4
5
function devuelveCreated($urlDelNuevo, $resultado)
6
{
7
 $json = json_encode($resultado);
8
 if ($json === false) {
9
  devuelveResultadoNoJson();
10
 } else {
11
  http_response_code(201);
12
  header("Location: $urlDelNuevo");
13
  header("Content-Type: application/json; charset=utf-8");
14
  echo $json;
15
 }
16
}
17

3. php / lib / devuelveJson.php

1
<?php
2
3
require_once __DIR__ . "/devuelveResultadoNoJson.php";
4
5
function devuelveJson($resultado)
6
{
7
 $json = json_encode($resultado);
8
 if ($json === false) {
9
  devuelveResultadoNoJson();
10
 } else {
11
  header("Content-Type: application/json; charset=utf-8");
12
  echo $json;
13
 }
14
 exit();
15
}
16

4. php / lib / devuelveNoContent.php

1
<?php
2
3
function devuelveNoContent()
4
{
5
 http_response_code(204);
6
}
7

5. php / lib / devuelveResultadoNoJson.php

1
<?php
2
3
require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4
5
function devuelveResultadoNoJson()
6
{
7
 http_response_code(INTERNAL_SERVER_ERROR);
8
 header("Content-Type: application/problem+json; charset=utf-8");
9
10
 echo '{' .
11
  "status: " . INTERNAL_SERVER_ERROR .
12
  '"title": "El resultado no puede representarse como JSON."' .
13
  '"type": "/errors/resultadonojson.html"' .
14
  '}';
15
}
16

6. php / lib / INTERNAL_SERVER_ERROR.php

1
<?php
2
3
const INTERNAL_SERVER_ERROR = 500;

7. php / lib / manejaErrores.php

1
<?php
2
3
require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4
require_once __DIR__ . "/ProblemDetailsException.php";
5
6
// Hace que se lance una excepción automáticamente cuando se genere un error.
7
set_error_handler(function ($severity, $message, $file, $line) {
8
 throw new ErrorException($message, 0, $severity, $file, $line);
9
});
10
11
// Código cuando una excepción no es atrapada.
12
set_exception_handler(function (Throwable $excepcion) {
13
 if ($excepcion instanceof ProblemDetailsException) {
14
  devuelveProblemDetails($excepcion->problemDetails);
15
 } else {
16
  devuelveProblemDetails([
17
   "status" => INTERNAL_SERVER_ERROR,
18
   "title" => "Error interno del servidor",
19
   "detail" => $excepcion->getMessage(),
20
   "type" => "/errors/errorinterno.html",
21
  ]);
22
 }
23
 exit();
24
});
25
26
function devuelveProblemDetails(array $array)
27
{
28
 $json = json_encode($array);
29
 if ($json === false) {
30
  devuelveResultadoNoJson();
31
 } else {
32
  http_response_code(isset($array["status"]) ? $array["status"] : 500);
33
  header("Content-Type: application/problem+json; charset=utf-8");
34
  echo $json;
35
 }
36
}
37

8. php / lib / ProblemDetailsException.php

1
<?php
2
3
require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4
5
/**
6
 * Detalle de los errores devueltos por un servicio.
7
 */
8
class ProblemDetailsException extends Exception
9
{
10
11
 public array $problemDetails;
12
13
 public function __construct(
14
  array $problemDetails,
15
 ) {
16
  
17
  parent::__construct(
18
   isset($problemDetails["detail"])
19
    ? $problemDetails["detail"]
20
    : (isset($problemDetails["title"])
21
     ? $problemDetails["title"]
22
     : "Error"),
23
   $problemDetails["status"]
24
    ? $problemDetails["status"]
25
    : INTERNAL_SERVER_ERROR
26
  );
27
28
  $this->problemDetails = $problemDetails;
29
 }
30
}
31

9. php / lib / recibeJson.php

1
<?php
2
3
require_once __DIR__ . "/BAD_REQUEST.php";
4
5
function recibeJson()
6
{
7
 $json = json_decode(file_get_contents("php://input"));
8
9
 if ($json === null) {
10
11
  http_response_code(BAD_REQUEST);
12
  header("Content-Type: application/problem+json; charset=utf-8");
13
14
  echo '{' .
15
   "status: " . BAD_REQUEST .
16
   '"title": "Los datos recibidos no están en formato JSON."' .
17
   '"type": "/errors/datosnojson.html"' .
18
   '}';
19
20
  exit();
21
 }
22
23
 return $json;
24
}
25