Obtener información de un usuario mediante la API de Twitter

Twitter ofrece una sencilla API a la que podemos acceder a través de un cliente Rest. El siguiente código obtiene información del usuario @iuttu utilizando el cliente Rest de Zend Framework:

$restClient = new Zend_Rest_Client('http://api.twitter.com/1');
$result = $restClient->restGet('/users/show.json', array('screen_name' => 'iuttu'));
 
if($result->isSuccessful()){
	$json = Zend_Json::decode($result->getBody());
	Zend_Debug::dump($json);
}

El código es muy sencillo:

  • Instanciamos un cliente Rest con la url de la API como base
  • Lanzamos una petición GET al path /users/show.json con un único parámetro screen_name correspondiente a iuttu (siguiendo lo descrito en la API)
  • Si la llamada ha sido correcta, pasamos el resultado JSON a array (/users/show.xxxx permite trabajar con JSON o XML)

Distancia entre dos puntos georeferenciados (PHP y MySQL)

Con el uso de APIs de mapas como Google Maps y la -cada día más- posibilidad de georeferenciación del contenido, es posible que en algún momento necesitemos calcular la distancia entre dos puntos georeferenciados.

Sin más explicaciones físicas, escribimos la función en PHP:

function distancia($punto1, $punto2){
    	$km = 111.302;
    	$coo1 = explode(',',$punto1);
    	$coo2 = explode(',',$punto2);
    	return floor(acos(sin((double)$coo1[0]) * sin((double)$coo2[0]) + (cos((double)$coo1[0]) * cos((double)$coo2[0]) * cos((double)$coo1[1] - (double)$coo2[1]))) * (double)$km);
    }

No vamos a entrar en demasiados detalles de la función, nos tenemos que creer la fórmula creada por nuestros amigos físicos, pero sí os explicaremos que hemos dado a la variable $km es la distancia en kilómetros que abarca un grado de circunferencia del planeta tierra (por tanto los habitantes de otros mundos tendréis que ajustar este valor).

Tambien és posible que guardemos en base de datos una lista de “elementos” georeferenciados (con latitud y longitud). En nuestro caso almacenamos la latitud y longitud en una misma columna de tipo varchar(255), pero se podía guardar en dos columnas diferentes o en una columna de tipo POINT.

Para nuestra llamada hemos creado una función strSplit que nos ayudará a dividir la cadena de latitud/longitud en cada valor:

CREATE
 
FUNCTION `strSplit`(x varchar(255), delim varchar(12), pos int)
  RETURNS varchar(255) CHARSET latin1
DETERMINISTIC
BEGIN
   RETURN replace(substring(substring_index(x, delim, pos),
      length(substring_index(x, delim, pos - 1)) + 1), delim, '');
 
-- end the stored function code block
END$$

La condición del WHERE de la consulta que devolvería todos los puntos a una distancia menor o igual que $distancia quedaría de la siguiente manera:

WHERE ACOS(
	SIN(strSplit(coordenadas,',',1)) * SIN($latitud) +
	COS(strSplit(coordenadas,',',1)) * COS($latitud) * COS(strSplit(coordenadas,',',2) - ($longitud))
      ) * 111.302 <= $distancia

Linkografía:
- En GeoNames se puede descargar varias bases de datos de información georeferenciada del planeta
- Fórmula Haversine del cálculo de distancias geográficas

WordPress: Uniendo the_content y the_excerpt

En los listados de entradas de un proyecto WordPress habitualmente se utilizan dos de estas funciones:

Si queremos tener un listado con un texto reducido, utilizaremos the_excerpt, que mostrará el resumen de una entrada. Si hemos escrito un resumen específico para una entrada, mostrará es resumen. Si no lo hemos hecho, WP creará un resumen automático mostrando los primeros N caracteres

Si por el contrario necesitamos un listado extendido, utilizaremos the_content. Siempre podremos mostrar sólo una parte del texto utilizando un comentario <!--more--> en nuestra entrada.

Vamos a mostrar como utilizar the_content y the_excerpt en la misma página para un único listado

Primero, crearemos una función que nos permita determinar si una entrada tiene o no un more:

function iuttu_has_more(){
	global $post;
 
	$result = false;
	if(!empty($post)) $result = strpos($post->post_content, '<!--more-->') !== false;
	return $result;
}

Utilizaremos el filtro the_excerpt para modificar el texto a mostrar. Si la entrada tiene un more, mostraremos ese texto. Si no lo tiene, mostraremos el resumen:

function iuttu_the_excerpt($content){
	// si la entrada tiene more
	if(iuttu_has_more())
		$content = str_replace(']]>', ']]&gt;', apply_filters('the_content', get_the_content()));
 
	return $content;
}
add_filter('the_excerpt', 'iuttu_the_excerpt');

El único problema es que por defecto, los enlaces para seguir leyendo no son iguales en las funciones the_excerpt y the_content, pero pueden serlo fácilmente si configuramos el enlace:

function iuttu_excerpt_more($more = null){
	return '<p class="leer_mas"><a href="' . get_permalink() . '" title="' . __('Leer más','iuttu') . '">'. __('Leer más','iuttu') . '</a></p>';
}
add_filter('excerpt_more', 'iuttu_excerpt_more');

La función get_the_content tiene como primer parámetro el enlace para seguir leyendo, así que actualizamos el filtro iuttu_the_excerpt:

function iuttu_the_excerpt($content){
	// si la entrada tiene more
	if(iuttu_has_more())
		$content = str_replace(']]>', ']]&gt;', apply_filters('the_content', get_the_content( iuttu_excerpt_more() )));
 
	return $content;
}

Por último, necesitamos tratar el caso de una entrada con resumen escrito manualmente. Por defecto en estas entradas no se añade el enlace a leer más al llamar a the_excerpt así que lo añadiremos nosotros:

function iuttu_get_the_excerpt($content){
	global $post;
 
	// la entrada tiene resumen escrito manualmente?
	if(!empty($post->post_excerpt))
		$content.= iuttu_excerpt_more();
 
	return $content;
}
add_filter('get_the_excerpt', 'iuttu_get_the_excerpt');

Si por cualquier motivo queremos reducir la longitud del texto de resumen automático, podemos utilizar el filtro excerpt_length:

function iuttu_excerpt_length($length){
	return 100;
}
add_filter('excerpt_length', 'iuttu_excerpt_length');

Forzar descarga de archivos en PHP (sin y con compresión)

En ocasiones necesitamos forzar que el navegador del usuario descargue un archivo. Un PDF, una imagen, un archivo de acceso privado… son casos en los que podemos necesitar descargar un archivo y que en usos normales, el navegador abriría él mismo. Para realizarlo, nos hemos preparado una función que hará nuestro trabajo:

function downloadFile($fileUrl){
	// get file size	
	if (substr($fileUrl,0,4)=='http') {
        $fileSize = array_change_key_case(get_headers($fileUrl, 1),CASE_LOWER);
        if ( strcasecmp($fileSize[0], 'HTTP/1.1 200 OK') != 0 ) { $fileSize = $fileSize['content-length'][1]; }
        else { $fileSize = $fileSize['content-length']; }
    } else { $fileSize = @filesize($fileUrl); }
 
	// download file
	$ctype="application/octet-stream";
	header("Pragma: public");
	header("Expires: 0");
	header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
	header("Cache-Control: private",false);
	header("Content-Type: $ctype");
 
	header("Content-Disposition: attachment; filename=\"".basename($fileUrl)."\";" );
	header("Content-Transfer-Encoding: binary");
	header("Content-Length: ".$fileSize);
	readfile("$fileUrl");
	exit();
 
}

No tiene mucho secreto, recogemos el tamaño del archivo a descargar, cambiamos las cabeceras que vamos a escribir para forzar la descarga del archivo y enviamos su contenido, provocando su descarga.

Y si necesitamos comprimir el archivo antes de enviarlo al usuario, lo podemos hacer así :

function downloadZipFile($fileUrl){
 
	$fileContent = file_get_contents($fileUrl);
	$compressed = gzencode($fileContent, 9);
	$fileSize = mb_strlen($compressed);
 
	// download file
	$ctype="application/octet-stream";
	header("Pragma: public");
	header("Expires: 0");
	header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
	header("Cache-Control: private",false);
	header("Content-Type: $ctype");
	header("Content-Disposition: attachment; filename=\"".basename($fileUrl).".gz\";" );
	header("Content-Transfer-Encoding: binary");
	header("Content-Length: ".$fileSize);
	echo $compressed;
	exit();
}

Hemos utilizado las funciones de compresión zlib de PHP, por lo que será necesario comprobar que tenemos disponibles las funciones antes de utilizarlo.

WordPress: Crear páginas de opciones

Habitualmente un plugin o tema de WordPress requiere de una página de opciones para su configuración. Desde la versión 2.7 de WordPress disponemos de una Settings API que facilita parcialmente el trabajo repetitivo. En el siguiente ejemplo crearemos una página de configuración con una sección y dos opciones.

Antes de empezar, necesitaremos una página:

if( !function_exists('iuttu_admin_menu_action') ){
 
  function iuttu_admin_menu_action(){
    // verificamos que el usuario pueda acceder a esta sección
    if(current_user_can('manage_options'))
      add_options_page(__('options_page_title','iuttu'), __('options_page_menu_item','iuttu'), 'manage_options', 'iuttu_options_page', 'iuttu_options_page_callback');
  }
 
}
 
// añadimos nuestra acción
add_action( 'admin_menu', 'iuttu_admin_menu_action' );

Antes de nada, verificamos que el usuario puede administrar opciones. El último parámetro corresponde a la función a la que se llamará para dibujar la página:

if( !function_exists('iuttu_options_page_callback')){
  function iuttu_options_page_callback(){
?&gt;
<div class="wrap">
    <!--?php screen_icon(); ?-->
<h2><!--?php _e('options_page_title','iuttu'); ?--></h2>
<form action="options.php" method="post">
<fieldset>
        <!--?php settings_fields('option_group'); ?-->
        <!--?php do_settings_sections('settings_page'); ?-->
 
<input class="button-primary" name="iuttu_save" type="submit" value="&lt;?php _e('Guardar','iuttu') ?&gt;" />
 
</fieldset>
</form></div>
<!--?php
  }
}</pre-->
 
Pintamos el formulario, pero en lugar de escribir los campos uno a uno, llamamos a <code>do_settings_sections('settings_page')</code>, que se encargará de escribirlos por nosotros. De la misma forma, la llamada <code>settings_fields('option_group')</code> se encarga de escribir el hidden de la acción, un nonce, etc...
 
Vamos ahora a crear el grupo de campos, la sección y los dos campos:
 
<pre lang="php">if( !function_exists('iuttu_admin_init_action') ){
 
  function iuttu_admin_init_action(){
    // creamos el grupo de campos
    register_setting(
      'option_group', // nombre del grupo
      'option_group' // nombre de la opción
    );
 
    // creamos la primera sección
    add_settings_section(
      'section_id', // identificador
      'section_title', // título
      'section_callback', // función que dibujará la sección
      'settings_page' // página a la que pertenece
    );
 
    // creamos los campos
    add_settings_field(
      'field1_id', // id
      'field1_title', // título (label)
      'field1_callback', // función que dibujará el campo
      'settings_page', // página a la que pertence
      'section_id' // sección dentro de la página
    );
 
    add_settings_field(
      'field2_id',
      'field2_title',
      'field2_callback',
      'settings_page',
      'section_id'
    );
  }
 
}
 
// añadimos nuestra acción
add_action( 'admin_init', 'iuttu_admin_init_action' );

Ahora sólo queda definir los callback para la sección y los dos campos:

if( !function_exists('section_callback')){
  function section_callback(){
    echo '
 
' . __('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla eget tellus vitae massa varius placerat. Fusce eu dolor quis tortor auctor convallis. In hac habitasse platea dictumst.','iuttu') . '
 
';
  }
}
 
if( !function_exists('field1_callback')){
  function field1_callback(){
    $options = get_option('option_group');
    echo '
<div class="form_field form_field_textbox">
<input id="field_id" name="option_group[field1_id]" type="text" value="' . $options['field1_id'] . '" /></div>
';
  }
}
 
if( !function_exists('field2_callback')){
  function field2_callback(){
    $options = get_option('option_group');
    echo '
<div class="form_field form_field_textarea"><textarea id="field2_id" name="option_group[field2_id]">' . $options['field2_id'] . '</textarea></div>
';
  }
}

El resultado será algo similar a esta imagen:

Consideraciones finales

  • La función register_setting admite como tercer parámetro el nombre de una función mediante la que podemos validar los valores de los campos.
  • No es necesario crear una página de opciones específica. Al definir la sección y el campo podemos indicar como página una ya existente (‘reading’, ‘writing’,…)
  • Tampoco es necesario definir todos los campos. En su lugar, podemos registrar el grupo mediante la llamada a register_setting y dibujar a mano el formulario en la página de opciones, con lo que nos “ahorramos” registrar las secciones, los campos y sus correspondientes callbacks
  • Según la documentación de WordPress, cada campo añadido debe registrarse antes, pero es bastante más práctico trabajar con arrays ;-)