Tag: PHP

DataTables: Tried to bind parameter number 65536. SQL Server supports a maximum of 2100 parameters.

Podemos encontrar este error en el método complex() de la librería ssp.class.php de DataTables. Esta función en casos normales debería funcionar, pero resulta que si utilizas el filtro de repente nos salta este error.

«error»:»An SQL error occurred: SQLSTATE[IMSSP]: Tried to bind parameter number 65536. SQL Server supports a maximum of 2100 parameters.

¿Qué está pasando?

Vamos a empezar con entender que hace la función sql_exec. Esta función ejecuta la sql utilizando variables Bind para realizar la ejecución de los filtros Where. La función que realiza la sustitución es bindValue que se ejecuta siempre que $bindings sea un array()

// Bind parameters
if ( is_array( $bindings ) ) {
    for ( $i=0, $ien=count($bindings) ; $i<$ien ; $i++ ) {
        $binding = $bindings[$i];
        $stmt->bindValue( $binding['key'], 
        $binding['val'], $binding['type'] );
    }
}

En la ejecución del método complex() hay 3 llamadas al sql_exec:

  1. La prímera (main query) que nos devuelve los datos. 
  2. La segunda (Data set length after filtering) que nos devuelve el número total de registros filtrados.
  3. La tercera (Total data set length) que es el total de registros sin filtrar.

La que nos da el error es la tercera llamada. En esta llamada intenta utilizar la variable $bindings que sustituiría los valores del Where.  Pero la variable Where que se utiliza es $whereAllSql que NO TIENE NINGÚN VALOR A SUSTITUIR.

$whereAllSql es un parámetro que le podemos pasar al método complex() para realizar la consulta por defecto. Por ejemplo un listado de alumnos donde siempre queremos que aparezcan los de la sección A. Añadiríamos a ese parámetro el texto correspondiente a la cláusula where:

SSP::complex($_GET, $sql_details, $table, $primaryKey, $columns, null, "(section='A')")

SOLUCIÓN

Por lo que el error lo podemos controlar de forma sencilla. La solución  es enviar el parámetro $bindings vacío a la función sql_exec que nos da el error:

Código Original

// Total data set length
$resTotalLength = self::sql_exec( $db, 
$bindings,
"SELECT COUNT({$primaryKey})
FROM   $table 
$whereAllSql"
);

Código Nuevo

// Total data set length
$resTotalLength = self::sql_exec( $db, 
"",
"SELECT COUNT({$primaryKey})
FROM   $table 
$whereAllSql"
);

Moodle: $CFG variables

La variable global $CFG y sus propiedades se definen en lib/setup.php

// Directorio raíz
$CFG->dirroot

// URL raíz
$CFG->wwwroot

// Directorio de Librerías
$CFG->libdir

// Directorio Temporal
$CFG->tempdir

// Directorio Caché
$CFG->cachedir

Moodle Error: Output can not be buffered before instantiating table_dataformat_export_format

Lo que nos dice este error es que no debe haber ningún tipo de Output si vamos a realizar una exportación.

Aún dejando limpio el HTML, no era capaz de eliminar este error por lo que buscando en el código eliminé la excepción y lo sustituí por un ob_clean().

En lib/table_lib.php buscamos el constructor de la case table_dataformat_export_format y realizamos la operación:

if (ob_get_length()) {
            // EXCEPTION was not helping
            // Is this the solution? It solves problem when exporting
            ob_clean();
            // throw new coding_exception("Output can not be buffered before instantiating table_dataformat_export_format");
        }

Por el momento nos dejará realizar la exportación sin problema, pero no sé si esta sea la mejor solución.

MOODLE Mostrar Tabla de Datos HTML con flexible_table

Vamos a partir de la base en la que no sé nada sobre la API de Moodle. Buscando información, he encontrado que NO HAY CASI NADA DE INFORMACIÓN / DOCUMENTACIÓN / EJEMPLOS.

Por lo que voy a compartir una mini guía personal y algunos recursos que he utilizado para poder mostrar una tabla dinámica que incluye paginación, ordenación y exportación de datos.

Para ello vamos a crear nuestra propia clase partiendo de la clase table_sql la cual extiende la clase flexible_table.

// Esta clase se encuentra en 
// lib\tablelib.php
class table_sql extends flexible_table{
}


// Crearemos nuestra propia clase en nuestra carpeta
// personal o en la misma carpeta lib
class table_sql_pers extends table_sql {
}

La idea de crear otra clase a partir de la table_sql es añadir un nuevo parámetro al método set_sql y personalizar así nuestra MySql Query sin tener que modificar la clase original.

En el mismo archivo he creado la clase para la gestión de alumnos creando una query personalizada dividiendo las partes de SELECT FROM WHERE y GROUP BY en variables para poder añadir los datos que necesitemos:

<?php

require "$CFG->libdir/tablelib.php";

/**
 * Clase de gestión personalizada de Alumnos
 * Métodos:
 * 		LIstado
 * 		Importación
 */

class GestionAlumnos{

	/**
	 * Variable FIELDS para SELECT
	 *
	 * @var string
	 */
	private $selectAlumnosFields = "
		{user}.`id`,
		DATE_FORMAT(
			FROM_UNIXTIME({user}.`timecreated`),
			'%d/%m/%Y %H:%i:%s'
		) AS `timecreated`,
		DATE_FORMAT(
			FROM_UNIXTIME({user}.`timemodified`),
			'%d/%m/%Y %H:%i:%s'
		) AS `timemodified`,
		{user}.`username` AS `username`
	";

	/**
	 * Variable FROM con las tablas y JOINS
	 *
	 * @var string
	 */
	private $selectAlumnosFrom = "
	(
		(
			(
				(
					(
						(
							(
								(
									(
										{role_assignments}
										LEFT JOIN {user} ON(
											(
												{user}.`id` = {role_assignments}.`userid`
											)
										)
									)
									LEFT JOIN {context} ON(
										(
											{role_assignments}.`contextid` = {context}.`id`
										)
									)
								)
							)
							JOIN {user_info_data} ON(
								(
									{user}.`id` = {user_info_data}.`userid`
								)
							)
						)
						JOIN {user_enrolments} ON(
							(
								{user_enrolments}.`userid` = {user}.`id`
							)
						)
					)
					JOIN {enrol} ON(
						(
							{enrol}.`id` = {user_enrolments}.`enrolid`
						)
					)
				)
				JOIN {course} ON(
					(
						{course}.`id` = {enrol}.`courseid`
					)
				)
			)
		)
	)
	";

	/**
	 * Variable WHERE
	 *
	 * @var string
	 */
	private $selectAlumnosWhere = "
		(
			NOT(
				(
					{user}.`firstname` LIKE '%\\_%'
				)
			)
		)
		AND (
			{user_info_data}.`fieldid` = 1
		)
		AND (
			{role_assignments}.`roleid` = 5
		)
	";

	/**
	 * Variable GROUP BY
	 *
	 * @var string
	 */
	private $selectAlumnosGroupBy = "
		{user}.`id`,
		{user}.`timecreated`,
		{user}.`timemodified`,
		{user}.`username`
	";

	/**
	 * Variable con las columnas que vamos a mostrar de la query original
	 *
	 * @var array
	 */
	protected $columns = array(
		'id',
		'username',
		'timecreated',
		'timemodified'
	);

	/**
	 * Cabeceras para el listado de la tabla HTML
	 *
	 * @var array
	 */
	protected $headers = array(
		'ID',
		'Usuario',
		'F. Crea.',
		'F. Modif.'
	);

	protected $database;
	protected $baseUrl;

	/**
	 * Constructor al que pasamos el recurso de BBDD
	 * y la URL para mostrar los links de paginación,
	 * ordenación en la tabla de datos
	 *
	 * @param [type] $DB
	 * @param [type] $baseUrl
	 */
	public function __construct($DB, $baseUrl)
	{
		$this->database = $DB;
		$this->baseUrl = $baseUrl;
	}

	/**
	 * Setea las columnas para mostrar solamente las que necesitamos
	 *
	 * @param array $columns
	 * @return void
	 */
	public function setColumns($columns = array()){
		$this->columns = $columns;
	}

	/**
	 * Undocumented function
	 *
	 * @param array $headers
	 * @return void
	 */
	public function setHeaders($headers = array()){
		$this->headers = $headers;
	}

	/**
	 * Crea la tabla para mostrar el listado de alumnos
	 * Devuelve el recurso $table que se podrá utilizar 
	 * para renderizar la tabla con el método out()
	 * Ejemplo: $table->out(15, true); 
	 * 
	 * Lo hacemos así para poder verificar cuando se hace un renderizado en HTML
	 * o una exportación ya que a la hora de exportar no debemos mostrar más que
	 * los datos. En la página donde llamamos esta clase y sus métodos debemos
	 * utilizar la siguiente condición antes de mostrar los headers / footers o
	 * cualquier tipo de $OUTPUT:
	 * if (!$table->is_downloading()) {
	 * 		echo $OUTPUT->header();
	 * 		echo $OUTPUT->footer();
	 * }
	 *
	 * @return $table
	 */
	public function getListAlumnosHTML(){

		$table = new table_sql_alumnos('listado-alumnos');

		$download = optional_param('download', '', PARAM_ALPHA);
		$table->is_downloading($download, 'Listado de Alumnos', 'Listado');

		$table->define_columns($this->columns);

		$table->define_headers($this->headers);

		$table->set_sql(
			$this->selectAlumnosFields,
			$this->selectAlumnosFrom,
			$this->selectAlumnosWhere,
			array(),
			$this->selectAlumnosGroupBy
		);

		$table->define_baseurl($this->baseUrl);

		return $table;
	}
}


/**
 * Extendemos la clase para modificar el método set_sql y añadir un nuevo parámetro $groupBy
 */
class table_sql_alumnos extends table_sql {

	/**
     * Set the sql to query the db. Query will be :
     *      SELECT $fields FROM $from WHERE $where
     * Of course you can use sub-queries, JOINS etc. by putting them in the
     * appropriate clause of the query.
     */

	/**
	 * MODIFICADO del original para añadir el parámetro GROUP BY
	 */
    function set_sql($fields, $from, $where, array $params = array(), $groupBy) {
        $this->sql = new stdClass();
        $this->sql->fields = $fields;
        $this->sql->from = $from;
        $this->sql->where = $where;
        $this->sql->params = $params;
        $this->sql->groupBy = $groupBy;
    }

    /**
     * Query the db. Store results in the table object for use by build_table.
     *
     * @param int $pagesize size of page for paginated displayed table.
     * @param bool $useinitialsbar do you want to use the initials bar. Bar
     * will only be used if there is a fullname column defined for the table.
     */
    function query_db($pagesize, $useinitialsbar=true) {
        global $DB;
        if (!$this->is_downloading()) {
            if ($this->countsql === NULL) {
                $this->countsql = 'SELECT COUNT(1) FROM '.$this->sql->from.' WHERE '.$this->sql->where;
                $this->countparams = $this->sql->params;
            }
            $grandtotal = $DB->count_records_sql($this->countsql, $this->countparams);
            if ($useinitialsbar && !$this->is_downloading()) {
                $this->initialbars($grandtotal > $pagesize);
            }

            list($wsql, $wparams) = $this->get_sql_where();
            if ($wsql) {
                $this->countsql .= ' AND '.$wsql;
                $this->countparams = array_merge($this->countparams, $wparams);

                $this->sql->where .= ' AND '.$wsql;
                $this->sql->params = array_merge($this->sql->params, $wparams);

                $total  = $DB->count_records_sql($this->countsql, $this->countparams);
            } else {
                $total = $grandtotal;
            }

            $this->pagesize($pagesize, $total);
        }

        // Fetch the attempts
        $sort = $this->get_sql_sort();
        if ($sort) {
            $sort = "ORDER BY $sort";
		}
		
		/**
		 * MODIFICADO del original para añadir GROUP BY
		 */
        $sql = "SELECT
                {$this->sql->fields}
                FROM {$this->sql->from}
                WHERE {$this->sql->where}
				GROUP BY {$this->sql->groupBy}
				{$sort}";

        if (!$this->is_downloading()) {
            $this->rawdata = $DB->get_records_sql($sql, $this->sql->params, $this->get_page_start(), $this->get_page_size());
        } else {
            $this->rawdata = $DB->get_records_sql($sql, $this->sql->params);
        }
    }
}

Este bloque es el que utilizaremos para renderizar nuestro HTML:

<?php

/**
 * Contiene la clase para la gestión de Alumnos
 */
require_once('./lib/GestionAlumnos.php');

$title = "Gestión de Alumnos";

$download = optional_param('download', '', PARAM_ALPHA);
$baseUrl = $CFG->wwwroot."/gestion_alumnos.php";
$gestAlumnos = new GestionAlumnos($DB,$baseUrl);
$table = $gestAlumnos->getListAlumnosHTML();

// Si no se está exportando, mostramos el HTML
if (!$table->is_downloading()) {

    $PAGE->set_title($title);
    $PAGE->set_heading($title);
    $PAGE->set_cacheable(false);
    $PAGE->navbar->ignore_active();
    $PAGE->set_url('/gestion_alumnos.php');

    $PAGE->navbar->add($title, new moodle_url(substr($PAGE->url, 0, strpos($PAGE->url, '?'))));
    $PAGE->set_pagelayout('frametop');

    echo $OUTPUT->header();
    echo '<link rel="stylesheet" type="text/css" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">';

?>

<style>
    #tabs{
        min-height: 1150px;height:100%;margin-top:10px
    }
</style>

<h2><?= $title ?></h2>

<div id="tabs">
    <ul>
        <li><a href="#listado">Listado Alumnos</a></li>
	</ul>
	<div id="listado">
		<?php
			$table->out(15, true); 
		?>
    </div>
</div>

<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$(function() {
    $("#tabs").tabs();
});
</script>

<?php
    echo $OUTPUT->footer();
}else{
    $table->out(15, true); 
}

?>

RECURSOS

  • Moodle Docs: https://docs.moodle.org/dev/lib/tablelib.php
  • Moodle Forums (Entrar como Invitado): https://moodle.org/mod/forum/discuss.php?d=335786

Fatal error: Call to undefined function money_format()

Si estamos trabajando con WAMP o XAMPP puede que nos topemos con este error.

( ! ) Fatal error: Call to undefined function money_format()

Según la documentación de PHP La función money_format() sólo está definida si el sistema tiene capacidad strfmon. Por ejemplo, Windows no lo hace, así que money_format() no está definido en Windows.

Para solventarlo crearemos un archivo que adjuntaremos en nuestro php.ini

Abre el php.ini y busca la línea con la palabra «auto_prepend_file» y sustituye el valor vacío con

auto_prepend_file = "C:\wamp64\money_format.php"

Ahora crea el archivo en la misma ruta con la siguiente función de PHP

<?php

function money_format($formato, $valor)
{
    if (setlocale(LC_MONETARY, 0) == 'C') {
        return number_format($valor, 2);
    }

    $locale = localeconv();

    $regex = '/^' . // Inicio da Expressao
        '%' . // Caractere %
        '(?:' . // Inicio das Flags opcionais
        '\=([\w\040])' . // Flag =f
        '|' .
        '([\^])' . // Flag ^
        '|' .
        '(\+|\()' . // Flag + ou (
        '|' .
        '(!)' . // Flag !
        '|' .
        '(-)' . // Flag -
        ')*' . // Fim das flags opcionais
        '(?:([\d]+)?)' . // W Largura de campos
        '(?:#([\d]+))?' . // #n Precisao esquerda
        '(?:\.([\d]+))?' . // .p Precisao direita
        '([in%])' . // Caractere de conversao
        '$/'; // Fim da Expressao

    if (!preg_match($regex, $formato, $matches)) {
        trigger_error('Formato invalido: ' . $formato, E_USER_WARNING);
        return $valor;
    }

    $opcoes = array(
        'preenchimento' => ($matches[1] !== '') ? $matches[1] : ' ',
        'nao_agrupar' => ($matches[2] == '^'),
        'usar_sinal' => ($matches[3] == '+'),
        'usar_parenteses' => ($matches[3] == '('),
        'ignorar_simbolo' => ($matches[4] == '!'),
        'alinhamento_esq' => ($matches[5] == '-'),
        'largura_campo' => ($matches[6] !== '') ? (int) $matches[6] : 0,
        'precisao_esq' => ($matches[7] !== '') ? (int) $matches[7] : false,
        'precisao_dir' => ($matches[8] !== '') ? (int) $matches[8] : $locale['int_frac_digits'],
        'conversao' => $matches[9]
    );

    if ($opcoes['usar_sinal'] && $locale['n_sign_posn'] == 0) {
        $locale['n_sign_posn'] = 1;
    } elseif ($opcoes['usar_parenteses']) {
        $locale['n_sign_posn'] = 0;
    }
    if ($opcoes['precisao_dir']) {
        $locale['frac_digits'] = $opcoes['precisao_dir'];
    }
    if ($opcoes['nao_agrupar']) {
        $locale['mon_thousands_sep'] = '';
    }

    $tipo_sinal = $valor >= 0 ? 'p' : 'n';
    if ($opcoes['ignorar_simbolo']) {
        $simbolo = '';
    } else {
        $simbolo = $opcoes['conversao'] == 'n' ? $locale['currency_symbol']
            : $locale['int_curr_symbol'];
    }
    $numero = number_format(
        abs($valor),
        $locale['frac_digits'],
        $locale['mon_decimal_point'],
        $locale['mon_thousands_sep']
    );


    $sinal = $valor >= 0 ? $locale['positive_sign'] : $locale['negative_sign'];
    $simbolo_antes = $locale[$tipo_sinal . '_cs_precedes'];

    $espaco1 = $locale[$tipo_sinal . '_sep_by_space'] == 1 ? ' ' : '';

    $espaco2 = $locale[$tipo_sinal . '_sep_by_space'] == 2 ? ' ' : '';

    $formatado = '';
    switch ($locale[$tipo_sinal . '_sign_posn']) {
        case 0:
            if ($simbolo_antes) {
                $formatado = '(' . $simbolo . $espaco1 . $numero . ')';
            } else {
                $formatado = '(' . $numero . $espaco1 . $simbolo . ')';
            }
            break;
        case 1:
            if ($simbolo_antes) {
                $formatado = $sinal . $espaco2 . $simbolo . $espaco1 . $numero;
            } else {
                $formatado = $sinal . $numero . $espaco1 . $simbolo;
            }
            break;
        case 2:
            if ($simbolo_antes) {
                $formatado = $simbolo . $espaco1 . $numero . $sinal;
            } else {
                $formatado = $numero . $espaco1 . $simbolo . $espaco2 . $sinal;
            }
            break;
        case 3:
            if ($simbolo_antes) {
                $formatado = $sinal . $espaco2 . $simbolo . $espaco1 . $numero;
            } else {
                $formatado = $numero . $espaco1 . $sinal . $espaco2 . $simbolo;
            }
            break;
        case 4:
            if ($simbolo_antes) {
                $formatado = $simbolo . $espaco2 . $sinal . $espaco1 . $numero;
            } else {
                $formatado = $numero . $espaco1 . $simbolo . $espaco2 . $sinal;
            }
            break;
    }

    if ($opcoes['largura_campo'] > 0 && strlen($formatado) < $opcoes['largura_campo']) {
        $alinhamento = $opcoes['alinhamento_esq'] ? STR_PAD_RIGHT : STR_PAD_LEFT;
        $formatado = str_pad(
            $formatado,
            $opcoes['largura_campo'],
            $opcoes['preenchimento'],
            $alinhamento
        );
    }
    return $formatado;
}

Descargar PDF base64 con Symfony

Para hacer la recuperación de un archivo, en este caso un PDF, que está almacenado en nuestra base de datos como un BLOB para luego realizar el return añadiendo las cabeceras necesarias para realizar la acción de «Descargar» cuando llamemos a esta URL.

En este ejemplo sencillo, recuperamos el PDF utilizando una función ficticia en la que supuestamente recuperamos los datos del PDF en bruto (raw) y codificado en base64.

El segundo paso es decodificar ese raw utilizando la función base64_decode().

El último paso es crear la respuesta con los métodos que nos da la clase Response. A esta respuesta le vamos a añadir las cabeceras necesarias para poder decirle al navegador que es lo que tiene que hacer con este archivo.

En este caso lo que queremos es descargarlo. Por lo que añadiremos las cabeceras Content-Description y Content-Disposition:

$response->headers->set(‘Content-Description’, ‘File Transfer’);

$response->headers->set(‘Content-Disposition’, ‘attachment; filename=»Archivo.pdf»‘);
Con esto, al realizar una consulta a la ruta creada directamente nos descargará el PDF.
¡Importante! Si vas a realizar la consulta con un formulario, no te olvides de colocar el atributo enctype en tu etiqueta FORM.
<form id="pruebaPDF" method="post" enctype="multipart/form-data">
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
    /**
    * @Route("/show-pdf", name="show_pdf")
    */
    public function getPDFDocument()
    {
        $result = getPdfFromRaw();
        $pdf = base64_decode($result->PDF);
        $response = new Response($pdf);
        $response->headers->set('Content-Type', 'application/octet-stream');
        $response->headers->set('Content-Description', 'File Transfer');
        $response->headers->set('Content-Disposition', 'attachment; filename="Archivo.pdf"');
        // $response->headers->set('Expires', '0');
        // $response->headers->set('Content-Transfer-Encoding', 'binary');
        $response->headers->set('Content-length', strlen($pdf));
        $response->headers->set('Cache-Control', 'no-cache private');
        // $response->headers->set('Pragma', 'public');
        // Send headers before outputting anything
        $response->sendHeaders();
        return $response;
    }

Detectar dispositivo móvil Javascript / PHP

Estas dos funciones son la manera más simple para poder detectar si se está utilizando un dispositivo móvil en nuestra página web.

Existirán funciones más complejas, pero estas cumplen bien su cometido.

Si vemos, la versión de Javascript parece mucho más compleja, por lo que podríamos utilizar AJAX para realizar nuestras validaciones realizando la llamada a un archivo PHP utilizando la función en este lenguaje.

JAVASCRIPT

PHP

function isMobile() {
    return preg_match("/(android|avantgo|blackberry|bolt|boost|cricket|docomo|fone|hiptop|mini
|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i", $_SERVER["HTTP_USER_AGENT"]);
}

// Use the function
if(isMobile()){
    // Do something for only mobile users
}
else {
    // Do something for only desktop users
}

Utilizar Mailtrap como Inbox para Desarrollo

Hace poco hemos tenido un problema a la hora de gestionar los correos que utilizabamos desde nuestro entorno de desarrollo. Hemos descargado datos del entorno de producción y hemos utilizado correos reales para hacer pruebas sin querer queriendo. Nuestra intención no era enviarlos, habíamos creado unas condiciones pero nos olvidamos de añadir una condición más y se hizo el envío a unos cuantos. No fueron muchos pero, ¿y si se hubiera enviado el correo a todo el mundo? Estoy seguro que a alguno más le ha pasado esto mismo, ha utilizado su entorno de desarrollo para hacer pruebas de emailing y justo coincide que los correos de prueba son correos reales.

Pues bien, para evitar esto vamos a gestionar de ahora en adelante nuestros correos con un servicio gratuito y fácil de instalar en nuestro framework, sea el framework que sea.

Hablo de Mailtrap.io qué, como su misma página dice, es un entorno para el testeo de emailing seguro para desarrolladores: «SAFE email testing for dev teams«.

Es un servidor de SMTP creado por y para desarrolladores, para testear, ver, analizar y compartir emails enviados desde el entorno de desarrollo sin tener que «espamear» a emails reales. Podemos utilizar Mailtrap desde Localhost, Stage, QA…

Tendremos la posibilidad de analizar el diseño, datos estructurados y otras opciones relacionadas con el envío de correo.

Mailtrap - Serever Smpt

 

PRECIOS

Este servicio es gratuito pero si queremos más, obviamente tendremos que pagar. Según el plan que elijas podrás obtener menos o más beneficios.

Mailtrap - Pricing

Gestión de Resultados RaceChipAragon.com

Gestor de Resultados Online

RaceChipAragón.com es una empresa que cronometra tiempos de eventos deportivos (running, trail, btt), dando servicio como cronometradores certificados en España. Sus clientes directos son organizadores y los indirectos los atletas que participan en los eventos deportivos, los cuales son los interesados principales en ver los tiempos que realiza cada uno de ellos.

Para gestionar los tiempos he creado un sistema de gestión de clasificaciones utilizando el framework de LARAVEL para gestionar todo el backend de la sección de Resultados.

El usuario puede crear, editar y eliminar eventos. Por cada evento se pueden gestionar las clasificaciones utilizando una importación con EXCEL y/o con archivos PDF.

Cuando se suben los archivos Excel el nombre de cada columna se añade como título de las columnas que se muestran en el listado. Se pueden habilitar o deshabilitar columnas y por cada columna añadir un filtro para que los inscritos puedan utilizarlo.

La página es responsive gracias a Bootstrap 4 y además es capaz de redimensionarse automáticamente en modo iframe utilizando un plugin especifico para ello, evitando que el iframe se muestre con un alto estático.

Podemos verla dentro de la sección https://www.racechiparagon.com/resultados-de-cronometrajes

Cuando publicamos los resultados veremos un listado de los eventos que estén habilitados:

Listado Eventos - RacechipAragon.com

Por cada evento gestionado podemos generar clasificaciones tanto subiendo un Excel como utilizando archivos PDF:

Clasificaciones - RacechipAragon.com

Array limpio de valores vacíos en PHP

Para poder dejar un array limpio de valores vacíos, en PHP utilizaremos la función nativa array_filter. Esta función lo que hace es recorrer el array y en el caso de que exista valor devuelve un «true» junto con el valor. En caso de que devuelva «false», el indice y valor vacío es eliminado.

Al eliminar estos indices con valor vacío, lo que obtendremos al final es un array con saltos de indice. Si necesitamos reinicar estos indices, utilizaremos otra función nativa de PHP: array_values la cual recorrerá todo el array de nuevo y volverá a indexarlo numéricamente.

$newArray = array_values(array_filter($OldArray))