Todas las entradas de: admin

Guía 93 – Proteger los desarrollos PHPRunner

Este artículo es el resultado del estudio de este tema, solicitado por el desarrollador Rubén.

Rubén tiene un desarrollo con el que quiere vender servicios a otras empresas y por las características de las instalaciones necesita «proteger»  frente a posibles copias fraudulentas, su desarrollo.

Si consultáis en internet, veréis que se indica que como PHP no es un lenguaje compilado (es interpretado) no es posible protegerlo 100%, pero lo que sí se puede hacer es ponerlo un poco más difícil y eso es lo que hemos intentado.

En el supuesto que hemos utilizado es el siguiente:

1.- La aplicación (PHP) y la base de datos (MySQL), se instala en una máquina Windows del Cliente. Además de la protección del desarrollo, tenemos que dotar al sistema de copias de seguridad ejecutadas por el Cliente (usuario no técnico) y de la capacidad de actualización de tablas de parametrización en las que se basa la solución.

2.- La aplicación se instala en un hosting de la empresa suministradora del software (servicio llave en mano), también se le da soporte de copias de seguridad/restauración, del aplicativo. A cada empresa cliente se le debe ofrecer un dominio de acceso diferenciado y sus datos no pueden estar accesibles ni compartidos con otros clientes. En los backup, sólo estarán los datos de su Empresa.

Con el supuesto (1), también otros desarrolladores me preguntaron como hacer una instalación local con un número concreto de días de evaluación y después de esos días, que el aplicativo no funcionara.

Si estas interesado en este tema, sigue leyendo el artículo de este enlace.

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.

 

Guía 92 – Restore de Base de Datos

Pienso, que esta debería ser la Guía 1, porque así habría dotado a todos mis ejemplos de la posibilidad de RESTAURAR de un backup de Base de Datos y tanto vosotros, como yo, podríamos disfrutar del juego de datos suficiente para ver las posibilidades del ejemplo. Ahora, en la situación actual, la mayoría de las veces no me entero que los datos han sido destruidos y me cuesta más de un «click» el restaurar la situación.

Si tenéis la necesidad de un entorno de Demo, creo que esta alternativa es muy importante para que siempre esté operativo.

Realmente, la necesidad me la ha planteado un compañero que deseaba instalar en PC’s locales un desarrollo y deseaba tener la posibilidad de Backup y Restore, de la copia de seguridad, facilitando estas acciones al usuario.

Objetivo

Disponer en un desarrollo de PHPRunner la posibilidad de Backup de la Base de datos y del Restore de uno de los Backup.

DEMO:  https://fhumanes.com/videoclub2/

Usuario «admin»/»admin»

Las dos opciones de menú están integradas en la seguridad de PHPRunner, por lo que se le pueden asignar a cualquier perfil de usuario. Esto se ha hecho utilizando Vistas de una de las tablas del proyecto. Da igual la tabla que elijas porque de esas tablas no se utiliza nada salvo el contexto de la aplicación y la seguridad de esta. Las pantallas se crean con SNIPPET y botones de 3 estados.

Si estás interesado en el artículo, sigue leyendo en este enlace.

Guía 91 – Reducción de imágenes manteniendo proporciones

El problema de la deformación al reducir el tamaño de una imagen, especialmente cuando la imagen es vertical o se ha tomado en un móvil, es general en PHPRunner. Esto ha ocurrido en todas las versiones y yo, al menos, lo he reportado pero , que yo sepa, nunca se ha resuelto.

En mis desarrollos en React, me tuve que enfrentar al problema de reducir las imágenes que tomaba del móvil y consultando en internet vi cómo se resolvía y así lo apliqué en mi desarrollo.

Se ha publicado en el foro de Xlinesoft este mismo problema y visto que a otros muchos desarrolladores tienen el mismo problema,  me he animado a hacer este ejercicio.

Objetivo

Hacer transformaciones del tamaño de las imágenes conservando su orientación y proporción, para después utilizar las mismas en PHPRunner o cualquier otra solución.

DEMO: https://fhumanes.com/resize/

Si estás interesado sigue leyendo el artículo en este enlace.

Guía R-004 – React y FullCalendar

Esta biblioteca de JavaScript para definir la información de Calendario es un clásico y la hemos usado mucho en los desarrollos de PHPRunner.

En React, está toda ella disponible y sirve todo lo que hemos aprendido de su uso en PHPRunner y es, todavía, más sencillo explotar todas sus funcionalidades desde el entorno de React.

En este artículo facilito 2 ejemplos (de la propia biblioteca), pero adaptados a la última versión de React.

Aunque espero que ya conozcáis cómo se puede saber las librerías instaladas utilizando el fichero «package.json«, voy a explicar qué es lo que he instalado en cada caso.

Objetivo

Comprobar la integración de FullCalendar en el entorno de React.

Demo1:    https://fhumanes.com/fullcalendar-react/

Demo2: https://fhumanes.com/scheduler-react/

Si estás interesado en este tema, sigue leyendo el artículo.