6.3. Estructura General de Gekko

6.3.1. Directorios y Archivos

6.3.1.1. ./data

Debe ser escribible por el Servidor Web, contiene archivos que posiblemente sean accesados por el usuario, como por ejemplo imágenes.

Cada módulo tiene libertad de utilizar ./data a su mayor conveniencia, sin embargo, es necesario que cree su propia subcarpeta ( dentro de data) para este fin.

6.3.1.2. ./install

Archivos que son llamados durante la instalación o actualización de Gekko. Una vez que el proceso ha sido completado ya no son necesarios.

6.3.1.3. ./lang

Contiene los Archivos de Idioma organizados en carpetas

6.3.1.4. ./media

Archivos Multimedia que son accesados (solo lectura) por los módulos.

6.3.1.5. ./temp

Almacén temporal de ficheros que contienen información no elemental para los módulos, pero que no necesitan ser accesados desde el exterior. Debe ser escribible.

6.3.1.6. ./templates

Fue creado para contener plantillas y estilos.

6.3.1.7. ./cgdb.php

Contiene la lista de módulos oficiales de Gekko y su ID de Grupo.

6.3.1.8. ./conf.php

Script maestro llamado por todos los demás. Su función es iniciar variables comunes, verificar el acceso en base a la lista negra y conectar a la base de datos.

6.3.1.9. ./dbconf.php

Un pequeño almacen de información que sólo debe contener datos sobre la conexión y tipo de base de datos a usar.

6.3.1.10. ./index.php

Incluye los scripts necesarios para mostrar en pantalla la salida de Gekko. Su función principal es relegar el control a un módulo llamado por el usuario si este último tiene acceso.

6.3.1.11. ./install.php

Herramienta para instalar o actualizar Gekko.

6.3.1.12. ./robots.txt

Fichero que algunos bots de buscadores deben leer para evitar incluir en sus búsquedas datos no importantes.

6.3.1.13. ./runscript.php

Llama al script indicado que debe estar contenido en la carpeta run dentro de su módulo padre.

6.3.2. Conceptos básicos

A continuación definimos algunas partes elementales de Gekko para su posterior estudio.

Para adentrarse más en el funcionamiento de alguna de las partes refiérase a la sección específica del manual que le corresponde.

6.3.2.1. Módulo

Un modulo es un conjunto de scripts que realizan una funcion especifica. La diferencia con las otras partes de Gekko es que presentan una interfaz para realizar una acción específica, como por ejemplo interactuar con la base de datos.

6.3.2.2. Bloques

Son partes pequeñas cuya única función es mostrar información al usuario o proporcionar una interfaz para interactuar con un módulo. No son elementales.

Los bloques en Gekko pueden ser estáticos y dinámicos.

6.3.2.2.1. Bloques estáticos

Son totalmente definidos por el usuario y muestran la misma información todo el tiempo.

6.3.2.2.2. Bloques dinámicos

Son scripts cargados por un bloque que pueden trabajar con las variables del entorno de Gekko (o variables específicas para cada bloque) y mostrar información diferente en base al usuario al que se muestra.

6.3.3. Autenticación de usuario

La autenticación se lleva a cabo por la simple inclusión del archivo ./lib/auth.inc.php despues de conectar con la base de datos. Por ejemplo:

./test.php

<?php
	define("IN-GEKKO", true); // si no es definido, el script simplemente falla.

	require_once "conf.php"; // necesario, realiza la conexión.
	require_once "lib/auth.inc.php";

	header("Content-Type: text/plain"); // Solo para mostrarlo sin HTML

	if (defined("AUTH")) {
		echo "Autenticado.\n";
		echo "Contenido de la variable \$User:\n";
		print_r($User);
	} else {
		echo "No autenticado.";
	}
?>

El script ./lib/auth.inc.php realiza las siguientes funciones:

6.3.3.1. Función gauth()

La funcion gauth(grupos requeridos, [grupos de usuario]); es definida en la libreria ./lib/core.lib.php y la utilizamos para comparar los grupos de un usuario contra los grupos que se necesitan para llevar a cabo una acción.

En el argumento "grupos requeridos" podemos incluir una lista de modulos, ID de modulos o su código (Tabla 6-1) separados por comas (,), resulta lo mismo llamar a gauth("admin") que a gauth("1"), ya que el ID "1" pertenece al grupo "admin". El segundo parámetro se refiere a los grupos que el usuario posee, por ejemplo gauth("1,2", "3,4") devuelve false ya que el usuario posee los grupos "3" y "4" y la función require a "1" y a "2".

Para hacer la vida mas cómoda se han incluído códigos que engloban ciertos grupos para sustituir el primer argumento (o incluso utilizarlo como un grupo de grupos).

Tabla 6-1. Códigos de Grupo

CódigoSignificado
* Es true para todo nivel. Devuelve false cuando el usuario no posee grupos.
@ Devuelve true para grupos del rango 1~200 (reservados para modulos oficiales de Gekko). Da false en caso contrario.
# Devuelve true para grupos del rango 1~400 (reservados para modulos). false para casos contrarios.
$ Solo es true para usuarios registrados, esto es, grupos del rango 1~500 (los grupos 401 a 499 son reservados para grupos definidos por el usuario y tambien son incluídos). Al momento de registrarse, el grupo de usuario (500) le es agregado automáticamente al nuevo miembro.
? Solo es true para usuarios sin login o que no poseen grupos. Tambien para el grupo de Anónimos (501)

Ahora veamos un ejemplo para entender mejor el tema.

<?php
	define("IN-GEKKO", true);

	require_once "conf.php";
	require_once "lib/auth.inc.php";

	header("Content-Type: text/plain");

	if (defined("AUTH")) {
		if (gauth("admin")) {
			echo "Eres administrador total.\n";
			if (gauth("?")) {
				echo "Esto es raro, ser administrador total significa poseer
				todos los privilegios (incluso el nulo) al mismo tiempo.\n";
			}
		}
		if (gauth("#")) {
			echo "Tienes privilegios de administrador en algun módulo.\n";
		}
		if (gauth("users,admin")) {
			echo "Puedes administrar usuarios y bloques.\n";
		}
	} else {
		if (gauth("$")) {
			echo "Esto es imposible puesto que no tienes privilegios.\n";
		}
		if (gauth("?")) {
			echo "No tienes privilegios. No sabemos quien eres.\n";
		}
	}
?>

6.3.4. El Parser de Plantillas (gtEngine)

Esta herramienta se creó para separar código PHP del código HTML dandole libertad de edición a ambos. Por ejemplo, la posibilidad de editar una plantilla sin involucrar código PHP. Tambien hace que las plantillas sean reutilizables y que se puedan usar variables dentro de la plantilla, asímismo incluye un parser de bloques y opciones condicionales para mayor flexibilidad. Ambos, el parser de bloques y las opciones condicionales pueden contener a su vez (Gekko >= 0.5.7) estructuras anidadas.

Las plantillas, por defecto, se guardan en el directorio ./templates/default y poseen extensión .tpl

6.3.4.1. Estructura general de una plantilla

Para nuestro ejemplo trabajaremos con ./test.tpl

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
	<head>
		<title>{V_PAGE_TITLE}</title>
	</head>
	<!--
		Este es un comentario normal.
	-->
	<body>
		<h1>{L_GTENGINE_EXAMPLE}</h1>

		<!-- Constante de idioma -->
		\{L_MY_LANGUAGE_CONSTANT\} = {L_MY_LANGUAGE_CONSTANT}<br />
		<!-- Variable -->
		\{V_MY_VARIABLE\} = {V_MY_VARIABLE}<br />

		<!-- Estructura condicional, prueba cuantos estas loggeado y cuando no -->
		<!--if ($V_IS_USER)-->
			<b>Eres un usuario autenticado</b><br />
			<!-- podemos usar estructuras anidadas -->
			<!--if ($V_IS_ADMIN)-->
				<b>Eres también un administrador.</b><br />
			<!--endif-->
		<!--endif-->
		<hr />
		<table>
			<tr>
				<td colspan="2">{V_TABLE_TITLE}</td>
			</tr>
			<tr>
				<td>{L_ID}</td>
				<td>{L_RANDOM}</td>
			</tr>
			<!-- Asi se inicia un bloque -->
			<!--{bgn: RANDOM_VALUE}-->
			<tr>
				<td>{V_ID}</td>
				<td>
					{V_RANDOM}
					<!--
						Ten en cuenta que para evaluar las condicionantes
						debes pasar una parámetro explícitamente
						a la función parse()
					-->
					<!--if ($V_ID >= 5)-->
						(Esta fila tiene ID mayor o igual a 5)
						<!--if ($V_ID == 5)-->
						(Esta fila tiene ID 5)
						<!--endif-->
					<!--endif-->
				</td>
			</tr>
			<!--{end: RANDOM_VALUE}-->
		</table>
	</body>
</html>

Y por supuesto, con ./test.php

<?php

	define("IN-GEKKO", true);

	require_once "conf.php";
	require_once "lib/template.lib.php";
	require_once "lib/auth.inc.php";

	/*
		Especificando constantes de idioma, en la plantilla las
		identificamos porque comienzan con L_
	*/
	Lang::Set("L_GTENGINE_EXAMPLE", "Ejemplo de gtEngine");
	Lang::Set("L_MY_LANGUAGE_CONSTANT", "Mi constante de Idioma");
	Lang::Set("L_RANDOM", "Numero Aleatorio");

	/*
		Son las variables que seran pasadas a la funcion parse() para
		evaluarlas contra de la plantilla.
	*/
	$my_vars = Array (
		"V_PAGE_TITLE" => "Título de página",
		"V_TABLE_TITLE" => "Título de tabla",
		"V_MY_VARIABLE" => "Mi variable",
		"V_IS_ADMIN" => gauth("#"),
		"V_IS_USER" => gauth("$")
	);

	$tpl = new gtEngine();

	/*
		Al llamar a load con estos argumentos le estamos indicando que
		carge la plantilla ./test.tpl y que aplique el idioma
		correspondiente.
	*/
	$buff = $tpl->load(GEKKO_ROOT."test.tpl", true);

	/*
		La función parse() regresa una copia evaluada de $buff
		contra el array $my_vars. Observa la relacion entre los
		índices de $my_vars y los nombres de las variables en
		test.tpl
	*/
	$buff = $tpl->parse($buff, $my_vars);

	/*
		Ahora veremos como utilizar un bloque, en este ejemplo llenaremos
		la tabla con numeros aleatorios.
	*/

	// un almacén que contendrá las salidas de parse()
	$block_buff = "";
	// este ciclo llenará la tabla con datos
	for ($i = 0; $i < 10; $i++) {
		// será la variable que pasaremos al bloque
		$block_vars = Array (
			"V_ID" => $i,
			"V_RANDOM" => rand(1000, 9999)
		);
		/*
			Utilizando parse() en el bloque, observa que se llama
			al buffer con el indice ["CORE"].
			Nota tambien que ahora agregamos otro parámetro a la
			función parse() que indica que el bloque debe interpretar
			las condicionales. Prueba poniendolo como false y mirando
			que pasa con el código fuente.
		*/
		$block_buff .= $tpl->parse($buff["RANDOM_VALUE"]["CORE"], $block_vars, true);
	}
	// al final, pasamos el buffer de bloque a ser el bloque mismo.
	$buff["RANDOM_VALUE"]["CORE"] = $block_buff;

	/*
		Finalmente hacemos la salida al navegador. En todo esto no
		mezclamos código HTML con código PHP :).
	*/
	$tpl->output($buff);
?>

Guarda esos archivos y ejecútalos, podras observar una salida de página normal aparentemente sin problemas, sin embargo contiene errores lógicos, mira el código fuente, verás que los condicionales <!--if ()--> no fueron evaluados. La razón de esto es que a parse() no se le indico que lo hiciera. Hagámozlo:

$buff = $tpl->parse($buff, $my_vars, true);

Si ejecutamos de nuevo la solución daña los siguientes condicionales, por lo que es mejor evaluar los bloques en orden inverso a su profundidad. Esto es, evaluar antes el bloque que la platilla principal.

Al final nuestro ./test.php queda así

<?php

	define("IN-GEKKO", true);

	require_once "conf.php";
	require_once "lib/template.lib.php";
	require_once "lib/auth.inc.php";

	Lang::Set("L_GTENGINE_EXAMPLE", "Ejemplo de gtEngine");
	Lang::Set("L_MY_LANGUAGE_CONSTANT", "Mi constante de Idioma");
	Lang::Set("L_RANDOM", "Numero Aleatorio");

	$my_vars = Array (
		"V_PAGE_TITLE" => "Título de página",
		"V_TABLE_TITLE" => "Título de tabla",
		"V_MY_VARIABLE" => "Mi variable",
		"V_IS_ADMIN" => gauth("#"),
		"V_IS_USER" => gauth("$")
	);

	$tpl = new gtEngine();

	$buff = $tpl->load(GEKKO_ROOT."test.tpl", true);

	// aqui estaba el primer parse()

	$block_buff = "";
	for ($i = 0; $i < 10; $i++) {
		$block_vars = Array (
			"V_ID" => $i,
			"V_RANDOM" => rand(1000, 9999)
		);
		$block_buff .= $tpl->parse($buff["RANDOM_VALUE"]["CORE"], $block_vars, true);
	}

	$buff["RANDOM_VALUE"]["CORE"] = $block_buff;

	// al moverlo aquí cambiamos el orden de evaluación
	$buff = $tpl->parse($buff, $my_vars, true);

	$tpl->output($buff);
?>

El código es demasiado expícito, solo require un poco de observación. Lo que se debe recordar es la forma de pasarle variables al la funcion parse() y como es que los indices del array $my_vars se relacionan con las variables usadas en la plantilla, además de entender el correcto orden de avaluación que debe asignarse a parse().

Hemos visto la forma "cruda" de manejar plantillas, sin embargo, para manejo de bloques puede resultar en un extenso código con buffers por todas partes. Sí, Gekko tambien tiene una solución para esto.

Experimentemos con un nuevo ./test.php

<?php
	define("IN-GEKKO", true);

	require_once "conf.php";
	require_once "lib/template.lib.php";
	require_once "lib/auth.inc.php";

	// variables para la plantilla principal
	$my_vars = Array (
		"V_SCRIPT_EXEC_TIME" => "0",
		"V_DB_QUERIES" => "0"
	);

	$tpl = new gtEngine();

	/*
		Cargamos el tema, observa que esta vez no damos una
		ruta completa. Por lo tanto Gekko toma el archivo
		relativo a la plantilla configurada.
	*/
	$buff = $tpl->load("main/index.tpl", true);

	// iniciamos un nuevo bloque de contenido
	$mblock = new ContentBlock();

	// variables asociadas a este bloque
	$b_vars = Array();
	// nota que no tienes que usar V_VARIABLE
	$b_vars["BLOCK_ICON"] = mkIcon("blocks.png", 16); // icono asignado
	$b_vars["BLOCK_CLASS"] = "tbox1"; // atributo class para el bloque
	$b_vars["BLOCK_TITLE"] = "Título del bloque";
	$b_vars["BLOCK_CONTENT"] = "Contenido del bloque: Hola ".(gauth("$") ? $User["Realname"] : "Anónimo")."";

	// le pasamos las variables al bloque
	$mblock->set_arr ($b_vars);

	// le asignamos el valor del bloque compilado el indice M_BLOCKC. Observa index.tpl
	$my_vars["M_BLOCKC"] = $mblock->make(); // <-- el bloque se compila antes de asignarse

	// al final evaluamos la platilla principal
	$buff = $tpl->parse($buff, $my_vars, true);

	// mostramos la salida
	$tpl->output($buff);
?>

Lo primero que cambia es que no le pasamos una ruta completa a load(), le pasamos una ruta relativa, y Gekko cargará el archivo relativo a la carpeta ./templates/el_tema_elegido/. Debes observar la nueva clase que llamamos, ContentBlock() y la forma en la que le pasamos variables y la compilamos. Cuando lo compilamos, el contenido mismo pasa a ser una variable.

Usando lo que hemos aprendido hasta ahora, la version mejorada del ejemplo donde llenamos una tabla de datos aleatorios puede quedar de esta forma:

Con ./test.tpl

<table>
	<tr>
		<td colspan="2">{V_TABLE_TITLE}</td>
	</tr>
	<tr>
		<td>{L_ID}</td>
		<td>{L_RANDOM}</td>
	</tr>
	<!--{bgn: RANDOM_VALUE}-->
	<tr>
		<td>{V_ID}</td>
		<td>
			{V_RANDOM}
			<!--if ($V_ID>=5)-->
				(Esta fila tiene ID mayor o igual a 5)
				<!--if ($V_ID == 5)-->
				(Esta fila tiene ID 5)
				<!--endif-->
			<!--endif-->
		</td>
	</tr>
	<!--{end: RANDOM_VALUE}-->
</table>

Con ./test.php

<?php
	define("IN-GEKKO", true);

	require_once "conf.php";
	require_once "lib/template.lib.php";
	require_once "lib/auth.inc.php";

	Lang::Set("L_RANDOM", "Numero Aleatorio");


	$my_vars = Array (
		"V_SCRIPT_EXEC_TIME" => "0",
		"V_DB_QUERIES" => "0"
	);

	$tpl = new gtEngine();

	$buff = $tpl->load("main/index.tpl", true);

	$mblock = new ContentBlock();
	// insertamos ./test.tpl en el contenido del bloque, le pasamos la ruta completa
	$mblock->insert("BLOCK_CONTENT", GEKKO_ROOT."test.tpl");

	// variables de bloque
	$b_vars = Array (
		"TABLE_TITLE" => "Título de tabla",
		"BLOCK_TITLE" => "Mira mi tabla",
		"BLOCK_ICON" => mkIcon("blocks.png", 16)
	);

	$mblock->set_arr ($b_vars);

	// el mismo ciclo para poner las variables aleatorias
	for ($i = 0; $i < 10; $i++) {
		// será un array temporal solo válido para este bloque
		$t_vars = Array (
			"ID" => $i,
			"RANDOM" => rand(1000,9999)
		);
		// le pasamos el array $t_vars al bloque RANDOM_VALUE
		$mblock->set_arr($t_vars, "RANDOM_VALUE");
		// salvamos el compilado en el buffer con solo indicar el nombre del bloque
		$mblock->saveBuff("RANDOM_VALUE");
	}

	// compilamos el bloque y todos sus bloques hijos con una llamada
	$my_vars["M_BLOCKC"] = $mblock->make();

	$buff = $tpl->parse($buff, $my_vars, true);

	$tpl->output($buff);
?>

6.3.5. Control de peticiones y Tabla de Acceso

Se creó para prevenir que molestos bots jugaran con un sitio basado en Gekko, hacer un control de este tipo puede prevenir ataques flood automatizados ya que controla la cantidad de peticiones que pueden hacerse en determinado tiempo. La Clase PHP necesaria para esto esta en accesscontrol.lib.php y es cargada por el midmo conf.php

Para entender mejor como trabaja esta herramienta, carga el script siguiente y trata de refrescar la pagina mas de 10 veces en 30 segundos. Así, un ataque automatizado queda reducido a aplicar bien las reglas de la tabla de acceso :).

Ejemplo: ./test.php

<?php
	define("IN-GEKKO", true);

	require_once "conf.php";

	/*
		conf.php carga la Clase en la variable global $AccessTable y es
		recomendable siempre usarla con el mismo nombre.
	*/

	// set_rule(regla, tiempo de chequeo, peticiones maximas por chequeo, persistente);
	$AccessTable->set_rule("my-rule", 30, 10, true);

	// carga la regla, si no ha sido respetada muestra el mensaje de error
	$AccessTable->load_rule("my-rule", "Haz hecho mas de 10
	peticiones en 30 segundos. Espera 30 segundos para volver
	a intentar");

	// si la regla es respetada continua la carga normal
	echo "Hola, estas viendo el contenido de una pagina protegida.";
?>