Convertir documento MS Office a PDF – Actualización 14/02/2025

La codificación del la parte del SERVER, limitaba la concurrencia del proceso de conversión a 1 único caso, dejando en cola el resto de peticiones hasta completarse la que se estaba produciendo.

He hecho esta modificación para que se defina el número de procesos concurrentes de conversión (dependiendo del hardware, ya que es un proceso muy pesado). En el código que adjunto está a 5, pero es fácilmente ajustable, línea 106.

index.php
<?php

/**
 *
 * @About:      API Interface
 * @File:       index.php
 * @Date:       $Date:$ Agosot0 -2022
 * @Version:    $Rev:$ 1.0
 * @Developer:  Federico Guzman || Modificado por Fernando Humanes para PHP 8.1
 **/

/* Los headers permiten acceso desde otro dominio (CORS) a nuestro REST API o desde un cliente remoto via HTTP
 * Removiendo las lineas header() limitamos el acceso a nuestro RESTfull API a el mismo dominio
 * Nótese los métodos permitidos en Access-Control-Allow-Methods. Esto nos permite limitar los métodos de consulta a nuestro RESTfull API
 * Mas información: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
 **/
header("Access-Control-Allow-Origin: *");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS');
header("Access-Control-Allow-Headers: X-Requested-With");
header('Content-Type: text/html; charset=utf-8');
header('P3P: CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA"');

include_once '../include/Config.php';

require_once("../../include/dbcommon.php"); // DataBase PHPRunner

$debugCode = true;      // for Debug
custom_error(1,"Se inicia el Proceso de Conversión"); // To debug

$post = $_POST;
unset($post['file']); // Quitamos este dato porque es muy grande
custom_error(2,"Variable $_POST: ".json_encode($post)); // To debug

// use App\Models\Db;  // Utilizamos la conexión de PHPRunner
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Selective\BasePath\BasePathMiddleware;
use Slim\Factory\AppFactory;

require_once __DIR__ . '/../include/slim_4.11.0/autoload.php';



$app = AppFactory::create();


$app->addRoutingMiddleware();
// $app->add(new BasePathMiddleware($app)); // No usar si se ejecuta en subdirectorio
$app->addErrorMiddleware(true, true, true);
$app->addBodyParsingMiddleware();

$app->setBasePath(SCRIPTS_DIR);

// --------------------------------------------------------------------------------------

/* Usando POST para convertir fichero */

$app->post('/document', function(Request $request, Response $response) {

    $startConvert = date("Y-m-d H:i:s");;                                  // marcar fecha de inicio

    // Verify Token Security
    $verify = authenticate($request, $response);
    if (is_array($verify)) { // Si es una array, es que hay error
        $response->getBody()->write(json_encode($verify));
        return $response
            ->withHeader('content-type', 'application/json')
            ->withStatus(400);
    }
    // check for required params
    $verify = verifyRequiredParams(array('name', 'file','option'), $request, $response);
    if (is_array($verify)) { // Si es una array, es que hay error
        $response->getBody()->write(json_encode($verify));
        return $response
            ->withHeader('content-type', 'application/json')
            ->withStatus(400);
    }
    $data = $request->getParsedBody();

    $BlobInput=base64_decode($data['file']);  // Convert Base64
   
    $part_file = explode(".", $data['name']); // name file
    $ExtensionFile = $part_file[(count($part_file)-1)];     // Tipo file
    $new_file = substr($data['name'],0,(strlen($data['name'])-strlen($ExtensionFile)-1)).".pdf";

    $option = $data['option'];   // Option convert Opffice_to_PDF
    
  // -------------------- for create temporaly file --------------------------
    $temp_file = tempnam(sys_get_temp_dir(), 'temp');
    $part_path = pathinfo($temp_file);
    $temp_file1 = $part_path['dirname'].'/'.$part_path['filename'].'.'.$ExtensionFile;

  $phpLog = $part_path['dirname'].'/phplog.txt';      // Trace of Debug code
        
    $fp = fopen($temp_file1, 'w');
    $fwrite = fwrite($fp, $BlobInput);
    fclose($fp);
    
    $sizeFile = filesize($temp_file1);
  
    $temp_file2 = $part_path['dirname'].'/'.$part_path['filename'].'.pdf';
    
    do { // Para dotar de mútiples bloqueos
        $action = false; // Indica que falta la conversión
        $limint_block = 5; 
        for ($i=0;$i< $limint_block ; $i++) { // El Límite fija el número de procesos concurrentes
            $fileLock = $part_path['dirname'].'/lockConvert_'.$i.'.txt';
            if (!file_exists($fileLock)) { // Si no existe, se crea
                    $fpLock = fopen("$fileLock", "w");
                    fclose($fpLock);
            }
            custom_error(3,"Fichero de bloque: ".$fileLock); // To debug
            $fp = fopen($fileLock, "r+");		// Fichero de bloqueos para que sólo haya una única ejecución
            // do { // Bucle de conversión copn control de que sólo un proceso puede estar en ejecución

                    if (flock($fp, LOCK_EX)) {  // adquirir un bloqueo exclusivo

                            // Convert to PDF
                            custom_error(4,"Iniciamos conversión de : ".$temp_file1." con bloqueo. ".$fileLock); // To debug
                            // $result = shell_exec("..\\Office_to_PDF\\OfficeToPDF.exe $temp_file1 $temp_file2");
                                $status = exec("..\\Office_to_PDF\\OfficeToPDF.exe $option $temp_file1 $temp_file2", $outputCommand, $result);

                            if ($result <> 0) {
                                    // LOG
                                    $a = "Resultado de conversion: ".$result."\n".var_export($outputCommand, true)." \n";
                                    file_put_contents($phpLog, $a, FILE_APPEND | LOCK_EX);
                            }
                            flock($fp, LOCK_UN);    // libera el bloqueo
                            custom_error(5,"Termina conversión de : ".$temp_file1." con bloqueo. ".$fileLock); // To debug
                            $action = true;         // Se ha hecho la conersión
                            break;
                    } 
            // } while (true);
            fclose($fp);
        }
        if ($action) { break; } // Se termina. Se ha hecho conversión
        custom_error(6,"Espera en cola fichero: ".$temp_file1); // To debug
        sleep(1);
    } while (true);
    // ------------------ Operation with file result -------------------------------------------
    $document = file_get_contents($temp_file2);
    unlink($temp_file);   // delete file tmp
    unlink($temp_file1);  // delete file tmp
    unlink($temp_file2);  // delete file tmp

    $responseBody = array();
    //capturamos los parametros recibidos y los almacxenamos como un nuevo array
    $param['name']  = $new_file;
    $param['file'] = base64_encode($document);

    // Write LOG
    $data = array();
    $data["nameFile"] = $new_file;
    $data["sizeFile"]  = $sizeFile;
    $data["startConvert"] = $startConvert;
    $data["endConvert"] = date("Y-m-d H:i:s");
    DB::Insert("server_pdf_log", $data );


    if ( is_array($param) ) {
        $responseBody["error"] = false;
        $responseBody["message"] = "Documento convertido satisfactoriamente!";
        $responseBody["document"] = $param;
        $response->getBody()->write(json_encode($responseBody));
        return $response
            ->withHeader('content-type', 'application/json')
            ->withStatus(200);
    } else {
        $responseBody["error"] = true;
        $responseBody["message"] = "Error al crear auto. Por favor intenta nuevamente.";
        $response->getBody()->write(json_encode($responseBody));
        return $response
            ->withHeader('content-type', 'application/json')
            ->withStatus(400);
    }
   
    

});


/* corremos la aplicación */
$app->run();

/*********************** USEFULL FUNCTIONS **************************************/

/**
 * Verificando los parametros requeridos en el metodo
 */
function verifyRequiredParams($required_fields, Request  $request, Response $response)
{
    $error = false;
    $error_fields = "";

    $request_params = $request->getParsedBody();

    foreach ($required_fields as $field) {
        if (!isset($request_params[$field]) || strlen(trim($request_params[$field])) <= 0) {
            $error = true;
            $error_fields .= $field . ', ';
        }
    }

    if ($error) {
        // Required field(s) are missing or empty
        // echo error json and stop the app
        $responseBody = array();
        $responseBody["error"] = true;
        $responseBody["message"] = 'Required field(s) ' . substr($error_fields, 0, -2) . ' is missing or empty';

        return $responseBody;
    }
    return true;
}

/**
 * Validando parametro email si necesario; un Extra ;)
 */
function validateEmailRest($email, Request $request, Response $response)
{
    $responseBody = array();
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $responseBody["error"] = true;
        $responseBody["message"] = 'Email address is not valid';
        return $responseBody;
    }
    return true;
}



/**
 * Revisa si la consulta contiene un Header "Authorization" para validar
 */
function authenticate(Request $request, Response $response)
{
    // Getting request headers
    $headers = $request->getHeaders();
    // Verifying Authorization Header
    if (isset($headers['Authorization'])) {
        // get the api key
        $token = $headers['Authorization'];

        // validating api key
        if (!($token[0] == API_KEY)) { //API_KEY declarada en Config.php

            // api key is not present in users table
            $responseBody["error"] = true;
            $responseBody["message"] = "Acceso denegado. Token inválido";
            // Error 401
            return $responseBody;
        } else {
            //procede utilizar el recurso o metodo del llamado
            return true;
        }
    } else {
        // api key is missing in header
        $responseBody["error"] = true;
        $responseBody["message"] = "Falta token de autorización";
        // error 400
        return $responseBody;
    }
}

?>

Si te interesa este artículo, sigue leyendo accediendo a este enlace.

 

Crear informes con phpSpreadsheet (actualización 24/07/2024)

En esta actualización se ha intentado mejorar la seguridad y la información que se facilita al usuario para indicar que el informe se está elaborando.

En concreto, se ha puesto un gráfico de progreso para indicar que el informe se está elaborando o se está descargando.

El botón de 3 estado queda codificado:

  • Client (before):
Swal.fire({
    // icon: 'info',
   title: 'El Informe se ha inciado y se está ejecutando',
   text: '',
   imageUrl: "MyCode/images/calculated.gif",
   imageHeight: 200,
   imageAlt: "Estamos trabajando",
   timer: 60000,
   timerProgressBar: true,
   toast: true,
   showConfirmButton: false,
   position:  'center', // "top-start",
   footer: ''
  })
  • Server:
$_SESSION['report_fase'] = 1;
$_SESSION['report_file'] = '';

include "MyCode/Report001/report.php";

$_SESSION['report_fase'] = 2;
$result['report_file'] = $_SESSION['report_file']
  • Cient (after):
window.open("MyCode/Report001/report.php","_blank" ); 

Swal.fire({
    // icon: 'info',
   title: 'El Informe se ha descargado',
   text: '',
   imageUrl: "MyCode/images/download.gif",
   imageHeight: 200,
    imageAlt: "Estamos trabajando",
   timer: 4000,
   timerProgressBar: true,
   toast: true,
   showConfirmButton: false,
   position:  'center', // "top-start",
   footer: ''
  })

 

Con este sistema el proceso de elaboración del informe se fracciona en 2 fases (la obtención y la descarga). A nivel de seguridad, no se puede ejecutar el informe aunque se conozca la URL del mismo y al usuario se le facilita el gráfico indicando que se está elaborando el informe.

DEMO: https://fhumanes.com/reports/

Si te interesa este tema o deseas disponer del ejemplo, sigue este enlace.

PHP vs. Java: ¿Cuál es el adecuado para su aplicación?

PHP vs Java Leí este artículo porque he trabajado muchas veces en Java y en PHP y deseaba confrontar si mi opinión al respecto coincidía con el artículo.

En general, salvo alguna apreciación sobre el rendimiento, donde se ha valorado un excelente rendimiento a PHP, con todo me sentía totalmente identificado.

No tengo que olvidar que llevo unos cuantos años «alejado» de Java y por ocio, «cerca» de PHP. También, no se me olvida que el artículo es de una autor que trabaja en Zend (mantenedora de la actualizaciones de PHP), por lo que seguro está un poco sesgado.

Artículo de Zend

Es corto, directo e interesante.

Traducción un apartado del artículo:

PHP vs. Java: diferencias clave

Existen varias diferencias importantes al comparar PHP con Java. Son tan importantes que me pregunto si existe una comparación justa: si bien ambas son herramientas muy poderosas, es algo así como comparar un taladro eléctrico con una sierra de mesa. Aún así, ambos ofrecen excelentes resultados para aplicaciones web modernas y los desarrolladores a menudo se encuentran atrapados entre los dos lenguajes.

Seguir leyendo PHP vs. Java: ¿Cuál es el adecuado para su aplicación?

Crear una APP Web (Actualización 11/04/2024)

Se ha actualizado este ejemplo que partió de una versión PHPRunner 9.8 con las características y potenciación de la versión de PHPRunner 10.91.

Desde mi punto de vista, aunque todavía podría mejorarse más (por ejemplo, pudiendo editar las páginas para móvil desde el Diseñador de páginas), la solución actual es muy potente y se pueden hacer APP muy interesantes y potentes.

Se ha utilizado las opciones de páginas para móviles, que son muy potentes para diferenciar páginas de APP de escritorio y de móvil y personalización de interfaz de las aplicaciones.

DEMO: https://fhumanes.com/compra

Puedes utilizar el usuario «user1″/»user1» o darte de alta en la APP.

Te aconsejo que utilices un dispositivo móvil, para que observes cómo se puede instalar y lo agradable que se ve en dicha plataforma.

Hay muchos aspectos de JavaScript y CSS, que modifican características generales que creo pueden ser de mucho interés para quienes deseen construir una APP de este tipo.

Para acceder a toda la información y los fuentes del ejemplo, sigue este enlace.