Usando 7zip para hacer una sencilla copia de seguridad

Si tienes instalado 7zip [1] puedes usarlo para hacerte una sencilla copia de seguridad. Este es un ejemplo de cómo hacerlo.

@echo off

:: URL: http://en.wikibooks.org/wiki/Windows_Programming/Programming_CMD
:: Variables

SET zip_path="C:\Program Files\7-Zip\7z.exe"
SET base_folder="C:\carpeta-de-origen\*.*"
SET out_folder=D:\carpeta-de-destino\
SET zip_name=copia-seguridad.zip

::echo %zip_path%

:: Programa
IF EXIST %out_folder% (
CLS
SET passw=
echo.
echo.Iniciamos el proceso de copia.
del %out_folder%%zip_name%
%zip_path% a -tzip -scsDOS -p%passw% %out_folder%%zip_name% -r %base_folder%
echo.Proceso finalizado.
set passw=
pause
) ELSE (
echo.ERROR: "No se encuentra la carpeta de destino, detenemos el proceso."
)
ECHO ON

[1] http://www.7-zip.org/

Actualizar la versión de PHP que usamos en Wamp

Aquellos que usamos Wamp, para desarrollar aplicaciones PHP en Windows, hay veces que nos vemos limitados a trabajar con las versiones que este nos instala (en mi caso estaba con Apache 2.4, PHP 5.4.12 y mysql 5.6). Desde hace tiempo vengo pensando en cambiar esta situación y hoy me he puesto a ello. Voy a mostraros cómo actualizar la versión de PHP en nuestro WAMP, pasando de la 5.4.12 a la 5.6.14. Os lo dejo en sencillos pasos:

  1. Descargar la nueva versión de PHP (en mi caso http://windows.php.net/downloads/releases/php-5.6.14-Win32-VC11-x64.zip)
  2. Descomprimirla en wamp/bin/php con el nombre de la versión (php5.6.14)
  3. Eliminar ficheros de la carpeta wamp/bin/apache/Apache2.4.4/bin que ya no usaremos por estar con PHP 5.6 en Apache 2.4: php5isapi.dll y php5ts.dll
  4. Copiamos a la carpeta wamp/bin/apache/Apache2.4.4/bin el fichero php5apache2_4.dll, que viene en el fichero descargado con la nueva versión de PHP (php-5.6.14-Win32-VC11-x64.zip)
  5. Movemos el fichero wamp/bin/apache/Apache2.4.4/bin/php.ini a wamp/bin/php/php5.6.14/phpForApache.ini
  6. Eliminamos la carpeta wamp/bin/php/php5.4.12 o la renombramos de modo que, ordenando el contenido de la carpeta wamp/bin/php/ alfabéticamente, esta quede después de la nueva php5.6.14.
  7. Editamos los ficheros wamp/wampmanager.conf, wamp/wampmanager.ini y wamp/bin/apache/apache2.4.9/conf/httpd.conf en los que sustituimos la cadena “5.4.12” (la versión actual de PHP) por “5.6.14” (la nueva).
  8. Editamos los ficheros php.ini (en la carpeta de php y en la bin de apache) para corregir las rutas de la carpeta de extensiones de php.

Tras estos pasos, si inicias Wamp y accedes a la página http://localhost/?phpinfo debieras ver que la versión de PHP es la nueva 5.6.14

Wamp server con PHP actualizado a 5.6.14
Wamp server con PHP actualizado a 5.6.14

Nota: debemos dejar dos ficheros php.ini, uno en la carpeta bin de php (para la ejecución de este desde la consola) y otro en el bin de apache (para cuando PHP se ejecuta dentro de Apache). El por qué lo puedes leer aquí.

WordPress security: plugins de seguridad en WordPress

A la hora de dotar de más seguridad a un WordPress, además de la que trae de serie, nos encontramos con una selva de opciones. Podemos querer dotar a nuestra web de control de accesos (vigilancia del formulario de login para evitar ataques de fuerza bruta), o bloquear  peticiones sospechosas (XSS, SqlInjection). Quizás también queramos vigilar los ficheros que nuestros usuarios suben a la web (exploits, shells, virus, etc), querer interactuar con la base de datos (copia de seguridad de la misma, consultas, etc) o controlar las versiones de nuestros plugins.

Si buscamos en el directorio de plugins de WordPress  con los términos “wordpress firewall” o “wordpress security” nos salen bastantes candidatos (algunos repetidos en ambas búsquedas), todos ellos luchando por ser la solución definitiva para la Seguridad de WordPress. Personalmente, suelo huir de las soluciones “todo en uno” porque, como dice un refrán español, “el que mucho abarca, poco aprieta”. Pero dejémonos de charlas y vamos a lo que interesa.

Análisis

Vamos a realizar el análisis de algunos de los plugins más descargados, o de los que aparecen en los primeros puestos de las búsquedas, en los apartados de firewall o security. De ellos, vamos a intentar averiguar cómo se comportan respecto al análisis y control de las peticiones que llegan desde el usuario. Es decir, nos centraremos en ver cómo se compartan actuando como Firewall de la aplicación o como IDS (Intrusion Detection System).

Para el test, realizaremos estas sencillas peticiones, tanto GET como POST, para ver cómo se comportan:

  1. GET – http://www.wordpress.exa/?p=91+order+by+23;
  2. GET – http://www.wordpress.exa/?p=91+union+all+select+1;
  3. GET – http://www.wordpress.exa/?p=91+or+(1=(select+count(*)+from+mysql.user));
  4. GET – http://www.wordpress.exa/?p=91+or+(1=(select+count(*)+from+information_schema.tables));
  5. GET – http://www.wordpress.exa/?p=91“><img src=”” onerror=”alert(1)”>;
  6. POST – http://www.wordpress.exa/ – data = {txtBuscar : post”><!—}
  7. GET http://www.wordpress.exa/?p=91+or+(1%3D(select%2Bcount(*)+from+information_schema.tables))%3B
  8. GET http://www.wordpress.exa/?p=91%20%6F%72%20%28%31%3D%28%73%65%6C%65%63%74%2B%63%6F%75%6E%74%28%2A%29%20%66%72%6F%6D%20%69%6E%66%6F%72%6D%61%74%69%6F%6E%5F%73%63%68%65%6D%61%2E%74%61%62%6C%65%73%29%29%3B

Los plugins escogidos, y que instalaremos y configuraremos su apartado de Firewall, son los siguientes.

Tras proceder a realizar todas las peticiones para cada uno de ellos, obtenemos la siguiente lista en la que  se indica si el plugin fue capaz de detectar el ataque (alucinad como yo).

Plugin Petición
(1) (2) (3) (4) (5) (6) (7) (8)
Simple Security Firewall NO SI NO NO NO NO NO NO
BulletProof Security NO SI SI SI SI NO SI NO
Block Bad Queries NO NO NO NO NO NO NO NO
Wordfence Security MA MA MA MA MA MA MA MA
Sucuri Security ? ? ? ? ? ? ? ?
All in One WP Security & Firewall NO NO NO NO SI NO NO NO
Mute Screamer NO NO SI SI SI SI SI SI

 

Resumen

Ninguno de los plugins analizados, salvo NinjaFirewall (quizás Sucuri) y Mute Screamer, analizan las variables que no vengan por GET (que son muchas). Muchos optan por usar el fichero .htaccess como elemento de bloqueo, que si bien detiene el ataque en las primeras fases de la petición (sólo entra en juego Apache, no el motor de base de datos) no tiene en cuenta la posible codificación de las variables ni aquellas enviadas por POST. Esta dependencia del fichero .htaccess hace que no funcionen en servidores que no sean Apache (si esto no es cierto, y alguno lo sabe, agradecería que me lo comentase). Por otro lado, los que no usan .htaccess implementan sus propios códigos de control pero siguen limitándose a las variables GET.

La sorpresa que me he llevado no ha sido pequeña, pensaba que el código que protege a más de 400.000 de instalaciones (no cuento a Wordfence, con sus 800.000, pero debiera hacerlo porque el bloqueo de IPs es manual y no hay un trabajo real de IDS) estaba mejor hecho. Si yo que nada sé sobre seguridad he visto esto, qué habrán encontrado los profesionales del tema.

Si tuviera que poner nota a los plugins en cuanto a la detección y bloqueo de ataques podría ser así:

  • Mute Screamer: notable
  • WP_Expose: notable
  • Wordfence Security: suspenso
  • BulletProof Security: suspenso
  • All in One WP Security & Firewall: suspenso
  • Block Bad Queries: suspenso
  • Simple Security Firewall: suspenso

A la vista de estos resultados, tened cuidado con qué instaláis en vuestro WordPress y cuanto confiáis en ello.

Algo de código

Antes de acabar veamos algo de las tripas de alguno de los plugin. En el caso de NinjaFirewall trabaja usando la directiva PHP de auto_prepend_file, lo que le da un acceso temprano a las variables, y permite analizar tanto GET como POST, REQUEST, COOKIE, HTTP_USER_AGENT, HTTP_REFERER, PATH_INFO, PATH_TRANSLATED y PHP_SELF. Pero el cómo lo hace podría decirse que es un poco ligero porque sólo se centra en escapar caracteres como ( “,’, <, >, %00,`  ) mediante real_escape_string() o con expresiones regulares.

if (is_string($str) ) {
	if ($how == 1) {
		$str2 = $nfw_['mysqli']->real_escape_string($str);
		$str2 = str_replace('`', '\`', $str2);
	} elseif ($how == 2) {
		$str2 = str_replace(	array('\\', "'", '"', "\x0d", "\x0a", "\x00", "\x1a", '`', '<', '>'),
			array('\\\\', "\\'", '\\"', 'X', 'X', 'X', 'X', '\\`', '\\<', '\\>'),	$str);
	} else {
		$str2 = str_replace(	array('\\', "'", "\x00", "\x1a", '`'), array('\\\\', "\\'", 'X', 'X', '\\`'),	$str);
	}
}
else {

	if ($how == 3) {
		$key2 = str_replace(	array('\\', "'", "\x00", "\x1a", '`', '<', '>'),
			array('\\\\', "\\'", 'X', 'X', '\\`', '<', '>'),	$key, $occ);
	} else {
		// We sanitise variables **name** using :
		// -str_replace to escape [\], ['] and ["]
		// -str_replace to replace [\n], [\r], [\x1a] and [\x00] with [X]
		//	-str_replace to replace [`], [<] and [>] with their HTML entities (` < >)
		$key2 = str_replace(	array('\\', "'", '"', "\x0d", "\x0a", "\x00", "\x1a", '`', '<', '>'),
			array('\\\\', "\\'", '\\"', 'X', 'X', 'X', 'X', '`', '<', '>'),	$key, $occ);
	}
}

BulletProof Security trabaja con .htaccess lo que imposibilita la detección de ataques codificados vía GET y no gestiona los que son enviados por POST.

Sucuri Security:  trabaja, creo, enviando la petición a los servidores de Sucuri lo que, si es cierto, me hace pensar en la posible penalización en el rendimiento.

Block Bad Queries: Usa código propio para bloquear las peticiones pero es tan pobre que casi es como si no lo tuviera. Además, olvida que los caracteres que llegan pueden estar codificados y hace una comparación literal.

$request_uri_array  = apply_filters('request_uri_items',  array('eval\(', 'UNION\+SELECT', '\(null\)', 'base64_', '\/localhost', '\%2Flocalhost', '\/pingserver', '\/config\.', '\/wwwroot', '\/makefile', 'crossdomain\.', 'proc\/self\/environ', 'etc\/passwd', '\/https\:', '\/http\:', '\/ftp\:', '\/cgi\/', '\.cgi', '\.exe', '\.sql', '\.ini', '\.dll', '\.asp', '\.jsp', '\/\.bash', '\/\.git', '\/\.svn', '\/\.tar', ' ', '\<', '\>', '\/\=', '\.\.\.', '\+\+\+', '\:\/\/', '\/&&', '\/Nt\.', '\;Nt\.', '\=Nt\.', '\,Nt\.', '\.exec\(', '\)\.html\(', '\{x\.html\(', '\(function\('));
	$query_string_array = apply_filters('query_string_items', array('\.\.\/', '127\.0\.0\.1', 'localhost', 'loopback', '\%0A', '\%0D', '\%00', '\%2e\%2e', 'input_file', 'execute', 'mosconfig', 'path\=\.', 'mod\=\.'));
	$user_agent_array   = apply_filters('user_agent_items',   array('binlar', 'casper', 'cmswor', 'diavol', 'dotbot', 'finder', 'flicky', 'nutch', 'planet', 'purebot', 'pycurl', 'skygrid', 'sucker', 'turnit', 'vikspi', 'zmeu'));
// more code
preg_match( '/' . implode( '|', $request_uri_array )  . '/i', $request_uri_string )

Simple Security Firewall: con código de la aplicación, del estilo de BBQ, que ni comentaré. Este es el código de src/processors/firewall.php que cómo controla las peticiones…miedo que da!

protected function doPassCheckBlockSqlQueries() {
	$aTerms = array(
		'/concat\s*\(/i',
		'/group_concat/i',
		'/union.*select/i'
	);
	$fPass = $this->doPassCheck( $this->getParamsToCheck(), $aTerms, true );
	if ( !$fPass ) {
		$sAuditMessage = sprintf( _wpsf__('Firewall Trigger: %s.'), _wpsf__('SQL Queries') );
		$this->addToAuditEntry( $sAuditMessage, 3, 'firewall_block' );
		$this->doStatIncrement( 'firewall.blocked.sqlqueries' );
	}
	return $fPass;
}
private function doPassCheck( $aParamValues, $aMatchTerms, $fRegex = false ) {

	$fFAIL = false;
	// code
	if ( $fRegex && preg_match( $sTerm, $mValue ) ) { //dodgy term pattern found in a parameter value
		$fFAIL = true;
	}
	else if ( strpos( $mValue, $sTerm ) !== false ) { //dodgy term found in a parameter value
		$fFAIL = true;
	}
	// code

	return true;
}

Wordfence Security: No tiene un Firewall como tal, tiene un monitor en tiempo real de las peticiones sobre el que permite bannear IP’s. Es un trabajo manual con lo que lo voy a considerar como IDS.

All in One WP Security & Firewall trabaja con .htaccess, al igual que Bulletproof Secutiry pero con menos controles que este, e ignora todo lo que venga por POST.

Instalar WordPress 3.5 en Internet Information Services (IIS) sobre SQL Server 2008

Desde hace algún tiempo llevaba con la idea de intentar montar WordPress en IIS sobre SQL Server, aprovechando las mejoras que están haciendo los chicos de Microsoft para ejecutar PHP en su servidor web ( [1], [2] y [3]). Hoy lo he conseguido así que os cuento cómo hacerlo vosotros (mis pruebas fueron sobre un windows7 con IIS 7, SQL server 2008 y PHP 5.4.12RC1-Win32-VC9-x86).

Dando por sentado que tenemos instalado PHP en nuestro IIS, hay que instalar las extensiones de PHP para SQL Server. Tras descargar el fichero, descomprimirlo, llevar las librerías adecuadas a la carpeta ‘ext’ de nuestra instalación de PHP ya podemos conectarnos a SQL Server con PHP. ¿Que qué librerías son las adecuadas? Al descomprimir el fichero con los drivers encontraremos que los tenemos para php 5.3 y 5.4.Además, en cada una de ellas, los tenemos en su version “thread safe” ( busca “_ts” en nombre del archivo) y “non thread safe” ( busca “_nts” en nombre del archivo). En mi caso, como estoy trabajando con php 5.4.12 en Windows 7 utizaré “php_pdo_sqlsrv_54_ts.dll” y “php_sqlsrv_54_ts.dll“.

¿Cómo compruebo que los drivers están correctamente instalados?

Desde una consola de ms-dos ejecuta:

php.exe -m

que debe generate una lista con los módulos disponibles, en donde deben aparecen los que acabamos de instalar.

Lo siguiente es descargamos la última versión de WP desde la página oficial ( http://wordpress.org/ ), o su versión en español ( http://es.wordpress.org/ ), que hoy es la 3.5.1. Para poder conectarlo con SQL server, en lugar de mySQL, hacemos uso de WordPress Database Abstraction, a día de hoy en su versión 1.1.4.

Lo descargamos, descrompimimos y seguimos las instrucciones que nos proporciona el documento README.txt, que viene dentro del plugin:

1. Upload wp-db-abstraction.php and the wp-db-abstraction directory to wp-content/mu-plugins. 
This should be parallel to your regular plugins directory.  If the mu-plugins directory does not exist, you must create it.
2. Put the db.php file from inside the wp-db-abstraction.php directory to wp-content/db.php
3. Visit $your_wordpress_url/wp-content/mu-plugins/wp-db-abstraction/setup-config.php to generate your wp-config.php file
4. Install WordPress normally
5. Visit $your_wordpress_url/wp-content/mu-plugins/wp-db-abstraction/setup-config.php to generate your wp-config.php file

Lo importante es que, para ejecutar el instalador, en lugar de ir a

http://instalacion.de.wordpress/wp-admin/setup-config.php

vayamos a:

http://instalacion.de.wordpress/wp-content/mu-plugins/wp-db-abstraction/setup-config.php

ya que la primera dirección corresponde con el asistente para instalar sobre mySQL y el segundo es para hacerlo sobre SQLServer (lo que nosotros queremos). Ahora sólo nos queda seguir el asistente e instalar nuestro WP, a lo que debo añadir que, a la hora de seleccionar el tipo de base de datos, tuve que elegir “PDO_sqlsrv” porque con la otra opción, “SQL Server using MS PHP driver”, no instalaba correctamente (probadlo y me decís).

Escogiendo tipo de base de datos en la instalación de wordpress

si habéis seguido los pasos correctamente debiérais tener vustra instalación de WordPress corriendo en IIS  sobre SQL Server. Como nota final, y que me tuvo un rato largo resolverlo (por no buscar en Internet, orgulloso que es uno), si al acceder al blog no véis ningún post o si añadís alguno y tampoco salen daros una vuelta por este hilo del foro de WordPress Database Abstraction: Not showing posts.

Referencias.

Mapa de Google señalando varios puntos mediante jQuery y/o Prototype

Hace unos días que estoy para escribir este artículo pero por una cosa u otra se me queda en el tintero. Pero de hoy no pasa así que aquí va.

A raiz de intentar incluir el módulo GMap de Drupal, en un proyecto reciente, me vi en la necesidad de generar una nueva clave para incluir un mapa de google. Cual fué mi sorpresa al ver que esto ya no era posible desde la publicación de la versión 3 de la API de Google Maps. Tras dar alguna que otra vuelta por la documentación de esta conseguí crear el ejemplo básico de un mapa de Google usando API v3  ( lo cierto es que es bastante sencillo de crear y nos evitan el tener que crear la clave para cada dominio donde alojemos el mapa).

Pero me quedó el gusanillo de poder tener un mapa con varios puntos así que me puse a ello: teniendo un documento XML con los puntos y sus localizaciones:

<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<!-- http://geoco.org/espana-es.html -->
<puntos>
	<punto>
		<titulo><![CDATA[Santander]]></titulo>
		<desc><![CDATA[Santander, home sweet home]]></desc>
		<url>http://www.ayto-santander.es</url>
		<latitud>43.464</latitud>
		<longitud>-3.8</longitud>
	</punto>	
	<punto>
		<titulo><![CDATA[Barcelona]]></titulo>
		<desc><![CDATA[Barcelona, capital de Cataluña]]></desc>
		<url>http://www.bcn.es/</url>
		<latitud>41.38</latitud>
		<longitud>2.18</longitud>
	</punto>	
	<punto>
		<titulo><![CDATA[Mérida]]></titulo>
		<desc><![CDATA[Cuidad monumental y arqueológica]]></desc>
		<url>http://www.merida.es/</url>
		<latitud>38.92</latitud>
		<longitud>-6.33</longitud>
	</punto>		
</puntos>

los cargaría con javascript ( la primera opción, no os mentiré, fue hacerlo con Prototype -porque tenía un ejemplo de lectura de un XML hecho con él, y luego con jQuery) y los mostraría en un sencillo documento HTML.

Carga de los datos con Prototype.

Si véis el contenido del fichero javascrpipt que realiza el proceso, xml.js , veréis algo como esto:

function obtenerPuntos( )
{
	var url = 'js/puntos.xml';
	var myAjax = new Ajax.Request( url, { method: 'get', onComplete: renderResultsPuntos });
}

Que no es otra cosa que la lectura del fichero XML, con los datos, mediante Ajax y la asignación de esta lectura al objeto myAjax que se utiliza en la función que pinta el mapa, renderResultsPuntos().

function renderResultsPuntos(responseXMLSec)
{
	var myZona = new Array();
	var marker = new Array();
	var contentString = new Array();
	var infowindow = new Array();
	var xml  = responseXMLSec.responseXML.documentElement;
	var miDiv = document.getElementById('puntos-place'); 
	
	var ptoMadrid = new google.maps.LatLng(40, -3.5); // Centramos el mapa en Madrid.
	var myOptions = {
		zoom: 6,
		center: ptoMadrid,
		mapTypeId: google.maps.MapTypeId.ROADMAP
	}
	var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
	for (i = 0; i < xml.getElementsByTagName('punto').length; i++)
	{
		var item = xml.getElementsByTagName('punto')[i]; // Accedemos al objeto XML seccion
		var urlDir = item.getElementsByTagName('url')[0].firstChild.data;
		var titulo = item.getElementsByTagName('titulo')[0].firstChild.data;
		var texto = limpiaStr(item.getElementsByTagName('desc')[0].firstChild.data);
		
		myZona[i] = new google.maps.LatLng(
item.getElementsByTagName('latitud')[0].firstChild.data,
item.getElementsByTagName('longitud')[0].firstChild.data); 
		contentString[i] = '<div class="google-content">'+
			'<h2>' + titulo + '</h2>'+
			'<div class="bodyContent">'+
			'<p>' +  texto + '</p>'+
			'<p>URL: <a href="'+urlDir+'">' +  urlDir + '</a></p>'+
			'</div>'+
			'</div>';
		infowindow[i] = new google.maps.InfoWindow({ content: contentString[i] });
		marker[i] = new google.maps.Marker({ position: myZona[i], map: map, title: titulo });
		eval ( "google.maps.event.addListener( marker[" + i + "], 'click', function() { infowindow["+i+"].open( map, marker["+i+"] ); })");
	}
}

Con lo que la creación de un mapa de Goole con Prototype queda completa.

Carga de los datos con jQuery.

En el caso de crear el mapa de Google con jQuery, ejecutamos el proceso dentro de la función, pintaMapa(), que realiza la petición Ajax al fichero de datos y, cuando esta se haya completado, pinta el mapa, ver fichero jquery.xml.

var myZona = new Array();
var marker = new Array();
var contentString = new Array();
var infowindow = new Array();

$(document).ready(function() {  
	pintaMapa("map_canvas");
});

// general options for all ajax request
$.ajaxSetup({
	type: "GET",
	statusCode: {
		404: function() {
			alert("page not found");
		},
		500: function() { 
			alert("Server error");
		}
	}
});
function pintaMapa(destID) {
	
	if ( $('#' + destID).length = 0 ) { return;}
	$.ajax({
		dataType: 'text html',
		url: 'js/puntos.xml'
	})
	.done(function(data){
		xmlDoc = $.parseXML( data );
		$xml = $( xmlDoc );
		$puntos = $xml.find( "punto" );
		if  ( $puntos.length > 0 ) {
			var ptoCentro = new google.maps.LatLng(40, -3.5); // Madrid, aprox!!!
			var myOptions = {
				zoom: 6,
				center: ptoCentro,
				mapTypeId: google.maps.MapTypeId.ROADMAP
			}
			var map = new google.maps.Map(document.getElementById(destID), myOptions);
			var i = 0;
			$.each ( $puntos, function() {
				var titulo = $(this).find("titulo").text();
				var url = $(this).find("url").text();
				myZona[i] = new google.maps.LatLng($(this).find("latitud").text(), $(this).find("longitud").text()); 
				contentString[i] = '<div class="google-content">'+
					'<h2>' + titulo + '</h2>'+
					'<div class="bodyContent">'+
					'<p>' +  $(this).find("desc").text() + '</p>'+
					'<p>URL: <a href="'+url+'">' +  url + '</a></p>'+
					'</div>'+
					'</div>';
				infowindow[i] = new google.maps.InfoWindow({ content: contentString[i] });
				marker[i] = new google.maps.Marker({ position: myZona[i], map: map, title: titulo });
				eval ( "google.maps.event.addListener( marker[" + i + "], 'click', function() { infowindow["+i+"].open( map, marker["+i+"] ); })");
				i++;
			});
		}
	});
}

Una de las diferencias entre generar el mapa con Prototype o con jQuery es que en este segundo caso las variables donde se almacenan los puntos deben ser globales a todos el script. Antes de acabar deciros que  seguro que el codigo fuente es muy mejorable pero por lo menos funciona.

Imprimir en Opera con javascript

Hoy me he enterado, dicen que más vale tarde que nunca, que la estándar de imprimir con javascript:

window.print();

no funciona siempre en Opera. Para que en lo haga hay que tener en cuenta que la página debe estar completamente cargada. Si esto se cumple, el siguiente ejemplo funciona:

<input type="button" value="Print this page" onClick="javascript:window.print();" ID="Button1" NAME="Button1">

El problema viene cuando antes de imprimir necesitamos hacer algo sobre el HTML ( abrimos una nueva ventana, modificarlo, etc). Tomemos este ejemplo, en el que en la función ocultar() debe hacer cosas previas a la impresión.

function imprimePagina(){
    ocultar(); // funcion que necesita ocultar información que no debe ser impresa.
    window.print();
    // resto de código;
}

Este ejemplo funciona en IE, Firefox, Chrome pero no en Opera. Para solventarlo podríamos probar con:

function imprimePagina() {
    ocultar(); // funcion que necesita ocultar información que no debe ser impresa.
    window.setTimeout(function () {
        window.print();
    }, 5);
    // resto de código;
}

Que, si no he escrito mal el ejemplo, debiera funcionar. En el caso de que abriéramos una ventana hemos de recordar lo que comentamos al principio: la página, la nueva, debe estar completamente cargada antes de intentar imprimirla en Opera. Como no controlamos en qué momento sucede esto, podemos confiar en el evento load de la ventana para imprimirla.

window.onload = function () {
    window.setTimeout(function () {
        window.print();
    }, 500);
}

Espero que a alguno le valga de algo 🙂

Referencia: http://stackoverflow.com/questions/3482428/window-print-not-working-with-opera-browser

Columnas de igual altura usando Javascript con jQuery

En el desarrollo de la Web de mi hermano Tomás me he visto obligado a establecer la misma altura a dos columnas. Como no fuí capaz de hacerlo mediante CSS me decidí por usar el método que ya os dejé hace tiempo, en el artículo Columnas de igual altura usando Javascript que si bien no es un lujo de programación resuelve algún que otro problema.

Como recientemente he estado enredado algo con jQuery me dije, ¿seré capaz de rehacer el código javascript usado en aquel entonces para que haga uso de jQuery? Pués dicho y hecho, aquí tenéis el nuevo código.

  var boxes = {  
    init : function(){},  
    alturaJQuery : function (){  
      al = new Array(); max = 0;  
      try  
      {  
        jQuery.each(arguments, function(c, v){  
          al[c] = $('#' + v).height();  
        })  
        max = boxes.mayorJQuery(al);  
        if(max > 0) {  
          for(i=0; i<arguments.length;i++){
            $("#" + arguments[i]).css('height', max + 'px');
          }
        };
      }
      catch (exc)
      { 
        alert('se ha producido un error en los ajustes'); 
        throw exc;
      }
    }
    , mayorJQuery : function(datos)
    {
      var salida =0;
      jQuery.each(datos, function(c, v){ v = parseInt(v); if(v> salida) salida = v; } )  
      return salida;  
    }
  }
$(document).ready(function(){ boxes.alturaJQuery('caja_uno', 'caja_dos', 'caja_tres'); } );

Menu horizontal basado en el de Roger Johansson

Nace una idea

Hace algunas semanas, por la petición de un compañero de trabajo, me surgió la necesidad de hacer una menú horizontal con doble nivel, es decir, un menú horizontal con un submenú para cada uno de sus elementos. Sé que hay muchos modos de solventar el problema pero me acordé de la propuesta de Roger Johansson, que mi amigo Marco tiene colocada en su Web, y me piqué con la idea de adaptarlo para este caso.

La idea que me surgió para el menú fué la de uno que se desplegase dentro de dos áreas horizontales contigüas verticalmente y de altura fija, o lo que es lo mismo, dos bandas de color de la misma altura y una encima de otra, tal y como se muestra en el siguiente esquema.

---------------------------------------------------------------------------------------------
| Zona I: zona de visualización de los elementos del menú superior                           |
---------------------------------------------------------------------------------------------
| Zona II: zona de visualización del submenú de cada uno de los elementos del nivel superior.|
---------------------------------------------------------------------------------------------

O como se ve en la siguiente imagen

Menu horizontal

Analizando la propuesta de Johansson

Roger Johansson utiliza únicamente tres elementos para crear su menú: un código HTML correcto, una hoja de estilos y un fichero JavaScript sencillo de entender y muy efectivo (no pocos lo hubieran hecho con jQuery o Mootools pero en ellos queda hacer la pertinente versión). Los submenús, de un elemento del menú padre, son desplegados y replegados mediante los consiguientes clicks del ratón: uno para desplegar y otro para replegar. Es decir, es necesaria la interacción del usuario para replegar los submenús ya desplegados.

Este comportamiento, que para muchas situaciones es válido, en nuestro caso (el doble menú horizontal en dos bandas, o zonas), no lo es. Necesitamos que estando desplegado un submenú, ocupando toda la Zona II, seleccionemos otro elemento del menú padre, de la Zona I, aquel se repliegue y se despliegue el submenú del elemento seleccionado. Buff! no sé si me estoy explicando u os estoy liando. Vayamos más despacio: si tenemos desplegado el submenú del elemento_1, llamado submenu_1, y queremos ver el submenú del elemento_2, llamado submenu_2 tendremos que:

  1. pinchar en elemento_2
  2. replegar submenu_1
  3. desplegar submenu_2

Es decir: antes de desplegar un nuevo submenú hemos de replegar el que esté desplegado o, lo que es lo mismo, antes de desplegar un nuevo submenú hemos de replegarlos todo.

El código JavaScript

Manos a la obra

Llevando esta idea al código JavaScript, lo que vamos a hacer es modificar el método toggle(), de la clase toggleMenuH() (llamada así para diferenciarla de la original toggleMenu()), para que sea capaz de replegar todos los submenús.

Código original:

toggle : function(el, sHiddenClass) {
  var oRegExp = new RegExp("(^|\\s)" + sHiddenClass + "(\\s|$)");
  el.className = (oRegExp.test(el.className)) ? el.className.replace(oRegExp, '') : el.className + ' ' + sHiddenClass;
}

Código modificado:

toggle : function(el, sHiddenClass, bHide) {
  if(bHide == true) this.hideAll(el, sHiddenClass)
  var oRegExp = new RegExp("(^|\\s)" + sHiddenClass + "(\\s|$)");
  el.className = (oRegExp.test(el.className)) ? el.className.replace(oRegExp, '') : el.className + ' ' + sHiddenClass;
}

Lo único que hemos hecho es incluir una variable para saber si hemos de ocultar todos los elementos del menú (la presencia de esta nueva variable obligará a modificar todas las llamadas que se hagan a esta función para tenerla en cuenta), y crear la función, llamada hideAll(), que se encargará de ocultar todos los submenús cuando sea necesario.

hideAll : function(el, sHiddenClass){
  for(i = 0; i < arrMenus.length; i++){
    arrSubMenus = arrMenus[i].getElementsByTagName('ul');
    for (var j = 0; j < arrSubMenus.length; j++) {
      oSubMenu = arrSubMenus[j];
      if(el !== oSubMenu){
        var oRegExp = new RegExp("(^|\\s)" + sHiddenClass + "(\\s|$)");
        if(!oRegExp.test(oSubMenu.className)) {oSubMenu.className = oSubMenu.className + ' ' + sHiddenClass;}
      }
    }
  }
}
Vamos un poco más allá

En cuanto le envié el nuevo menú a mi gurú del CSS, el Sr. Marco Giacomuzzi, me retó a darle una vuelta de tuerca más y dotarle de alguna nueva funcionalidad. De las que comentaba las más destacables fueron:

  • Encerrar cada uno de los enlaces dentro de una etiqueta HTML, lo que permitiría un mejor control sobre el elemento a la hora de maquetar el menú.
  • Resaltar la opción del menú superior asociada al submenú desplegado.

La implementación de estas dos nuevas cualidades provocaron una serie de cambios en el código JavaScript, que a continuación veremos. La introducción de un elemento HTML para que contenga a los enlaces provoca una cambio en la ruta de acceso, vía DOM, desde el nodo del enlace hasta el elemento de la lista que lo contiene. Es decir, que en la función toggleMenuH.ini() pasamos de tener:

oLink.onclick = function(){
  toggleMenuH.toggle(this.parentNode.getElementsByTagName('ul')[0], sHiddenClass, sTag, sOverParent, true);
  return false;
}

a tener esto otro (fijaros en la palabra en negrita).

oLink.onclick = function(){
  toggleMenuH.toggle(this.parentNode.parentNode.getElementsByTagName('ul')[0], sHiddenClass, sTag, sOverParent, true);
  return false;
}

Sencillo, ¿no? Veamos ahora qué necesitamos para poder resaltar la opción de menú asociada al submenú desplegado. Intuitivamente vemos que lo que necesitamos es cambiar el estilo del elemento del menú del primer nivel cada vez pinchemos sobre él. O lo que es lo mismo, necesitamos cambiar el estilo del elemento del menú del primer nivel cada vez que que despleguemos o repleguemos su submenú asociado. Es decir, que hemos de modificar el método, o función, que ejecuta esta última idea, que no es otro que el método toggleMenuH.toggle(), para localizar el elemento padre y aplicarle, o quitarle, el estilo que se encargue del resalte (estilo que debemos pasar a la clase toggleMenuH y que debe ser incorporado a todo los métodos que hagan uso de él). Al implementarlo esta idea en el código se pasa de tener:

toggle : function(el, sHiddenClass, bHide) {
  if(bHide == true) this.hideAll(el, sHiddenClass)
  var oRegExp = new RegExp("(^|\\s)" + sHiddenClass + "(\\s|$)");
  el.className = (oRegExp.test(el.className)) ? el.className.replace(oRegExp, '') : el.className + ' ' + sHiddenClass;
 }

A tener:

toggle : function(el, sHiddenClass, sTag, sOverParent, bHide) {
  if(bHide == true) this.hideAll(el, sHiddenClass, sOverParent)
  var oRegExp = new RegExp("(^|\\s)" + sHiddenClass + "(\\s|$)");
  var oRegExpParent = new RegExp("(^|\\s)" + sOverParent + "(\\s|$)");
  eval("t = el.parentNode.getElementsByTagName('"+sTag+"')[0].getElementsByTagName('a')[0]");
  if(oRegExp.test(el.className)){
    el.className = el.className.replace(oRegExp, '');
    t.className = t.className + ' ' + sOverParent;
  }
  else{
    el.className = el.className + ' ' + sHiddenClass;
    t.className = t.className.replace(oRegExpParent, '');
  }
}

Fijaros en las líneas en negrita, son las encargadas de hacer lo que antes comentábamos sobre identificar al padre y modificarlo con un nuevo estilo.

El código CSS

El modo de presentar los elementos en la página depende ámpliamente de la persona que lo haga; en nuestro caso la hoja de estilos intenta ceñirse a nuestro planteamiento inicial (un menú horizontal de dos niveles con los elementos del primer nivel en la Zona I y los del segundo nivel en la Zona II) así que veréis que la hoja de estilos está muy adaptada a las necesidades del problema y que no es fácilmente utilizable en otras situaciones.

Ya casi acabamos

Como no podía ser menos, en cuanto aplicamos la hoja de estilos y probamos todo junto nos damos cuenta que nuestro amigo Internet Explorer 6 tenía problemas para interpretar nuestro código y no ajusta correctamente el tamaño del submenú desplegado (en lugar de ajustarlo al 100%, como fija la clase second_menu de la hoja de estilos, .lo ajustaba al ancho del mayor de los textos incluidos en alguno de los elementos del submenú). A la vista del problema, y ya que vía CSS no pude solventarlo, decidí usar JavaScript para hacerlo.

La idea de lo que se tiene que hacer era clara: hay que que asignar al submenú la anchura del menú principal. Para ello creamos un nuevo método para la clase toggleMenuH llamado equalWidth que realiza lo establecido: busca el primero de los elementos, del tipo dado por la variable oElm, que tengan la clase strClassFather y establece su anchura de diseño, dada por su propiedad offsetWidth, como la anchura de su submenú asociado.

equalWidth : function (oElm, strClassFather, strClassChild){
  arrMenus = this.getElementsByClassName(document, oElm, strClassFather);
  if(arrMenus[0]){
    var dWidth = parseInt(arrMenus[0].offsetWidth);
    arrSubMenus = this.getElementsByClassName(document, oElm, strClassChild);
    for(var j=0; j<arrSubMenus.length;j++){
      oSubMenu = arrSubMenus[j];
      oSubMenu.style.width = dWidth + 'px';
    }
  }
}

Para que esta corrección de dimensiones tenga en cuenta todas las posibilidades de actuación del usuario debe ser incluida en los eventos onload y resize de la página.

Resultado final

Las modificaciones que han sido incorporadas provocan que sean necesarios más datos a la hora de iniciar el proceso (recordad que se hace con la última línea del documento JavaScript hmenu.js). El significado de las siguientes líneas de código es establecer que en el momento de finalización de la carga de la página se pongan en marcha las funciones de preparación del menú y de corrección de las anchuras (la misma que se pone en marcha también en el momento de redimensionar la página).

toggleMenuH.addEvent(window, 'load', function(){
  toggleMenuH.init('first_menu', 'hidden', 'span', 'select');
  toggleMenuH.equalWidth('ul','first_menu', 'second_menu');
})
toggleMenuH.addEvent(window, 'resize', function(){
  toggleMenuH.equalWidth('ul','first_menu', 'second_menu');
})

Los argumentos que se le deben pasar a la función toggleMenuH.init, tal y como vemos en el ejemplo, son:

  • first_menu: el nombre de la clase que se aplicará a la lista, etiqueta <ul>, de elementos del primer nivel del menú.
  • hidden: el nombre de la clase que se aplicará a cada uno de los submenús, o listas del segundo nivel, para ocultarlos.
  • span: la etiqueta HTML que será usada para encerrar cada uno de los enlaces del menú y dar así mayor versatilidad a la hora de maquetar el menú.
  • select: el nombre de la clase que se aplicará al elemento del primer nivel que tenga su submenú desplegado, es decir, el elemento activo.

Así mismo, los argumentos para la función toggleMenuH.equalWidth, tal y como vemos en el ejemplo, son:

  • ul: el elemento HTML sobre el que buscaremos aquellos que tengan las clases dadas por los demás parámetros.
  • first_menu: el nombre de la clase que se ha establecido en el elemento HTML, <ul>, del menú principal.
  • second_menu: el nombre de la clase que se ha establecido en el elemento HTML, <ul>, del menú secundario.

No sé si la explicación ha quedado clara o no, así que mejor será que os proporcione una demostración de lo que os he estado contando. Los ficheros que necesitas para reproducir el ejemplo son el código JavaScript y el código CSS

A petición popular

Como varios de vosotros me habéis preguntado por el modo de conseguir que al cargar la página una de las opciones del menú superior ya esté desplegada, he modificado el código JavaScript del menú para que lo tenga en cuenta. Lo único que he hecho es incluir un parámetro más en toggleMenuH.init() para indicar qué elemento del menú superior queremos desplegar. Así, pasamos de tener

init : function(sContainerClass, sHiddenClass, sTag, sOverParent)

a tener

init : function(sContainerClass, sHiddenClass, sTag, sOverParent, iViewIndex)

donde, si consideramos las opciones del menú superior como los elementos de un array, iviewIndex indica el elemento del array de opciones de menú superior que queremos desplegar.

Si ahora volvéis al ejemplo del doble menú horizontal veréis que automáticamente se muestra la opción 2 (Seires de televisión), teniendo en cuenta que los indices de los arrays en Javascript comienzan en 0

Avisos y consideraciones

No podemos finalizar el texto sin poner de manifiesto que tanto la programación en JavaScript como la hoja de estilos están adaptadas para un menú horizontal de doble nivel. En el caso de menús de mayor profundidad u horientación sería necesario readaptarlo todo a las nuevas situaciones. Este menú nunca pretendió ser la respuesta a todos los menús horizontales, esa es otra batalla, pero esperamos que a alguien le pueda ser útil. En lo referente a la compatibilidad con navegadores, el menú responde a las espectativas de correcta visualización con IE6, IE7, Firefox 2.0.* y Opera 9.* (en WINDOWS XP Prof. SP2).

La elegancia del lenguaje

En cientos de documentos web se habla sobre la creación de código correcto y las posibles razones para hacerlo. En ellos se intenta dar razones sociales, véase como ejemplo los beneficiones para la accesibilidad, o razones comerciales, como el efecto positivo para la indexación por los motores de búsqueda. Pero hoy, viendo el código de un sencillo documento web, me ha surgido otra posible razón: el ego del programador

Muchas veces, debido a causas ajenas (como pueda ser el apretado tiempo de desarrollo, a los contínuos cambios que se suelen producir una vez comenzado el proceso de desarrollo, etc) o a las propias del programador (vagancia, poco interés, etc) los proyectos que inicialmente cumplían ciertas espectativas acaban siendo 'una merienda de negros' (que nadie malinterprete esta frase hecha).

Una medida para intentar que esto no suceda es hacer ver al programador/maquetador que el código HTML generado habla sobre su personalidad. Cualquiera que haya programado sabe lo que siente al ver el código escrito por otra persona, el experimentar esa sensación de profesor corrigiendo exámenes al revisarlo. Ninguno podemos negar el haber calificado a otro programador o maquetador al ver la calidad de su código (esta es una realidad que he visto en todos los trabajos por los que ido pasando).

Así que desde aquí apelo a ese ser vanidoso que todo programador esconde para que intentemos hacer las cosas mejor, porque se tarda lo mismo en hacerlo mal que en hacerlo bien. Veámoslo con el código que ha motivado este artículo.

El documento en cuestión es un sencillo buscador al que se quiere añadir un pie con más opciones de búsqueda. Este es el código original, usado en el documento, para generar el pie:

<table border="0" width="100%" id="table2" bgcolor="#E3AE64">
  <tr>
	<td colspan="3"><b><font face="Verdana" size="2">Otras búsquedas</font></b></td>
  </tr>
  <tr>
  <td valign="top" width="33%">
	<b><font face="Verdana, Arial, Helvetica, sans-serif" size="1">
	<a class="p" target="_top" xhref="http://www.sitioweb.com/compras/"
mce_href="http://www.sitioweb.com/compras/">
	<span style="background-color: #E3AE64">Compras:<br>
	Coches, Motos, Perros, </span></a></font></b><br>
  </td>
  <td valign="top" width="33%">
	<font face="Verdana, Arial, Helvetica, sans-serif" size="1">
	<b><a class="D" target="_top" 
xhref="http://www.sitioweb.com/directorio" mce_href="http://www.sitioweb.com/directorio">
	<span style="background-color: #E3AE64">
	Buscador de personas:Teléfonos y Direcciones</span>
	</a>
       </b></font><br>
  </td>

  <td valign="top" width="33%">
	<b>	<font face="Verdana, Arial, Helvetica, sans-serif" size="1">
	<a class="D" target="_top" 
xhref="http://www.sitioweb.com" mce_href="http://www.sitioweb.com">
	<span style="background-color: #E3AE64">Catálogo de Libros</span></a></font></b>
  </td>
</tr>
</table>

Este código, en mi modesta opinión, duele con solo mirarlo. A simple vista se aprecia

  • código HTML obsoleto, lo que pudiera indicar esa misma cualidad en el creador
  • falta de eficiencia; imaginad el coste de tiempo y esfuerzo en el caso de tener esa misma tabla con 5 veces más opciones (15 filas) y que se desea cambiar el tipo de fuente en todas ellas.
  • falta de respeto por la estructuración lógica del documento y poco control sobre las diferentes entidades HTML y sus funciones

Alternativas a este código hay tantas como programadores, todas ellas dependiendo contexto donde se enmarque este fragmento de código, de los conocimientos de HTML y CSS. Esta es una de las mias en la que, básicamente, me ocupo del apartado estético:

<style type='text/css'>
#table2{width:100%;background-color:#E3AE64;border:0;}
#table2 td{font-family:Arial,Helvetica,sans-serif;font-size:0.7em;
        font-weight:bold;vertical-align:top; width:33%;}
.tdTit{font-family:Verdana, sans-serif;size:1em;}
.fondo{background-color: #E3AE64}
</style>
<table id="table2">
  <th>
    <td class='tdTit' colspan="3">Otras búsquedas</td>
  </th>
  <tr>
    <td>
 	  <a class="p" target="_top" xhref="http://www.sitioweb.com/compras/">
	  <span class='fondo'>Compras:<br>Coches, Motos, Perros, </span></a>
    </td>
    <td>
      <a class="D" target="_top" xhref="http://www.sitioweb.com/directorio">
      <span class='fondo'>Buscador de personas:Teléfonos y Direcciones</span></a>
    </td>
    <td>
      <a class="D" target="_top" xhref="http://www.sitioweb.com">
      <span class='fondo'>Catálogo de Libros</span></a>
    </td>
  </tr>
</table>

Si a parte de retocar el apartado estético, metemos mano a la estructura lógica del documento, podríamos hacer:

<style type='text/css'>
#table2{width:100%;background-color:#E3AE64;border:0;}
#table2 li{font-family:Arial,Helvetica,sans-serif;
font-size:0.7em;font-weight:bold;vertical-align:top;width:33%;}
#table2 li a{background-color: #E3AE64}
.tdTit{font-family:Verdana, sans-serif;size:1em;}
</style>
<p class='tdTit'>Otras búsquedas</p>
<ul id="table2">
  <li>
    <a class="p" target="_top" 
xhref="http://www.sitioweb.com/compras/">Compras: Coches, Motos, Perros</a>
  </li>
  <li>
    <a class="D" target="_top"
xhref="http://www.sitioweb.com/directorio">
	Buscador de personas: Teléfonos y Direcciones
	</a> 
  </li>

  <li>
    <a class="D" target="_top" 
xhref="http://www.sitioweb.com">Catálogo de Libros</a>
  </li>
</ul>

Como véis, hay tantas posibilidades como maquetadores, sólo he querido mostrar que el hacer bien las cosas o hacerlas mal lleva el mismo tiempo.