Wordpress: analisi di un widget per Twitter

Wordpress: analisi di un widget per Twitter

Twitter Widget Pro è un plugin per Twitter che avevo installato per visualizzare i miei ultimi tweet in un'installazione di Wordpress. Si tratta di un plugin con molte opzioni avanzate, compresa la possibilità di visualizzare un messaggio alternativo quando Twitter è down o di regolare il tempo di attesa prima di una risposta di Twitter. Purtroppo il plugin funzionava molto peggio di quello usato dal mio tema, e quindi sono stato costretto a rimuoverlo. Tuttavia, questo plugin presenta delle porzioni di codice molto interessanti per chi volesse cimentarsi con Twitter in Wordpress. Vediamo quali.

Sostituzione dello username con un link al profilo Twitter


public function linkTwitterUsers($text) {
 $text = preg_replace_callback('/(^|\s)@(\w+)/i', array($this, '_linkTwitterUsersCallback'), $text);
 return $text;
}

private function _linkTwitterUsersCallback( $matches ) {
 $linkAttrs = array(
 'href'	=> 'http://twitter.com/' . urlencode( $matches[2] ),
 'class'	=> 'twitter-user'
  );
  return $matches[1] . $this->_buildLink( '@'.$matches[2], $linkAttrs );
}

Questo widget richiede PHP 5 per funzionare. In questo codice vengono usate le espressioni regolari per sostituire una stringa del tipo @gabromanato con http://twitter.com/gabromanato usando un link HTML con classe CSS twitter-user. Il metodo privato wpTwitterWidget::_buildLink viene definito come segue:


private function _buildLink( $text, $attributes = array(), $noFilter = false ) {
  $attributes = array_filter( wp_parse_args( $attributes ), array( $this, '_notEmpty' ) );
  $attributes = apply_filters( 'widget_twitter_link_attributes', $attributes );
  $attributes = wp_parse_args( $attributes );
  if ( strtolower( 'www' == substr( $attributes['href'], 0, 3 ) ) ) {
	$attributes['href'] = 'http://' . $attributes['href'];
  }
		$text = apply_filters( 'widget_twitter_link_text', $text );
		$link = '<a';
		foreach ( $attributes as $name => $value ) {
			$link .= ' ' . esc_attr( $name ) . '="' . esc_attr( $value ) . '"';
		}
		$link .= '>';
		if ( $noFilter ) {
			$link .= $text;
		} else {
			$link .= esc_html( $text );
		}
		$link .= '</a>';
		return $link;
}

Questo metodo lavora su una stringa ed un array di attributi che andranno a formare il nuovo link HTML. In realtà per lo scopo per il quale viene utilizzato, se ne potrebbe evitare l'uso e limitarsi alla semplice manipolazione delle stringhe, una soluzione che raccomando in termini di performance.

Gestire i tag di Twitter (hashtag)


public function linkHashtags($text) {
  $text = preg_replace_callback('/(^|\s)(#\w*)/i', array($this, '_linkHashtagsCallback'), $text);
  return $text;
}

private function _linkHashtagsCallback($matches) {
  $linkAttrs = array(
  'href'	=> 'http://search.twitter.com/search?q=' . urlencode( $matches[2] ),
  'class'	=> 'twitter-hashtag'
  );
  return $matches[1] . $this->_buildLink( $matches[2], $linkAttrs );
}

Questi due metodi funzionano in modo identico ai primi due visti, con la differenza che in questo caso sostituiscono la stringa #termine con http://search.twitter.com/search?q=termine all'interno di un link HTML con classe twitter-hashtag.

Trasformare gli URL testuali in link HTML


public function linkUrls($text) {
  $text = preg_replace_callback("/(^|\s)(([a-zA-Z]+:\/\/)([a-z][a-z0-9_\..-]*[a-z]{2,6})([a-zA-Z0-9~\/*-?&%]*))/i", array($this, '_linkUrlsCallback'), $text);
  $text = preg_replace_callback("/(^|\s)(www\.([a-z][a-z0-9_\..-]*[a-z]{2,6})([a-zA-Z0-9~\/*-?&%]*))/i", array($this, '_linkUrlsCallback'), $text);
  return $text;
}

La prima espressione regolare seleziona gli URL che cominciano con un protocollo, mentre la seconda quelli che cominciano senza un protocollo (senza ad esempio http o https). La funzione di callback è la seguente:


private function _linkUrlsCallback ($matches) {
  $linkAttrs = array(
	'href'	=> $matches[2]
   );
  return $matches[1] . $this->_buildLink( $matches[2], $linkAttrs );
}

Il link HTML restituito è privo di classi CSS.

Calcolare i tempi dei tweet


private function _timeSince( $startTimestamp, $max, $dateFormat ) {
		// array di periodi di tempo
		$chunks = array(
			'year'   => 60 * 60 * 24 * 365, // 31.536.000 secondi
			'month'  => 60 * 60 * 24 * 30,  // 2.592.000 secondi
			'week'   => 60 * 60 * 24 * 7,   // 604.800 secondi
			'day'    => 60 * 60 * 24,       // 86.400 secondi
			'hour'   => 60 * 60,            // 3600 secondi
			'minute' => 60,                 // 60 secondi
			'second' => 1                   // 1 secondo
		);

		$since = time() - $startTimestamp;

		if ( $max != '-1' && $since >= $max ) {
			return date_i18n( $dateFormat, $startTimestamp );
		}

		foreach ( $chunks as $key => $seconds ) {
			// trova il periodo di tempo maggiore
			if ( ( $count = floor( $since / $seconds ) ) != 0 ) {
				break;
			}
		}

		$messages = array(
			'year'   => _n( 'about %s year ago', 'about %s years ago', $count, $this->_slug ),
			'month'  => _n( 'about %s month ago', 'about %s months ago', $count, $this->_slug ),
			'week'   => _n( 'about %s week ago', 'about %s weeks ago', $count, $this->_slug ),
			'day'    => _n( 'about %s day ago', 'about %s days ago', $count, $this->_slug ),
			'hour'   => _n( 'about %s hour ago', 'about %s hours ago', $count, $this->_slug ),
			'minute' => _n( 'about %s minute ago', 'about %s minutes ago', $count, $this->_slug ),
			'second' => _n( 'about %s second ago', 'about %s seconds ago', $count, $this->_slug ),
		);

		return sprintf( $messages[$key], $count );
}

Il dato interessante è l'uso della funzione di Wordpress date_i18n(), la quale restituisce una data nel formato locale basandosi sul timestamp passato come secondo argomento.

Reperire i tweet

Parsing del feed di Twitter


private function _parseFeed( $widgetOptions ) {
		$feedUrl = $this->_getFeedUrl( $widgetOptions );
		$resp = wp_remote_request( $feedUrl, array( 'timeout' => $widgetOptions['fetchTimeOut'] ) );

		if ( !is_wp_error( $resp ) && $resp['response']['code'] >= 200 && $resp['response']['code'] < 300 ) {
			$decodedResponse = json_decode( $resp['body'] );
			if ( empty( $decodedResponse ) ) {
				if ( empty( $widgetOptions['errmsg'] ) ) {
					$widgetOptions['errmsg'] = __( 'Invalid Twitter Response.', $this->_slug );
				}
				throw new wpTwitterWidgetException( $widgetOptions['errmsg'] );
			} elseif( !empty( $decodedResponse->error ) ) {
				if ( empty( $widgetOptions['errmsg'] ) ) {
					$widgetOptions['errmsg'] = $decodedResponse->error;
				}
				throw new wpTwitterWidgetException( $widgetOptions['errmsg'] );
			} else {
				return $decodedResponse;
			}
		} else {
			// Impossibile aprire l'URL
			if ( empty( $widgetOptions['errmsg'] ) ) {
				$widgetOptions['errmsg'] = __( 'Could not connect to Twitter', $this->_slug );
			}
			throw new wpTwitterWidgetException( $widgetOptions['errmsg'] );
		}
}

Come opzione predefinita, questo plugin reperisce il feed JSON degli utenti e ne esegue il parsing. L'implementazione prevede che questo metodo restituisca un array di oggetti. Vengono usati un limite di tempo (deciso dall'utente) e analizzati gli header HTTP di risposta per stabilire se la richiesta è andata a buon fine. Infatti è da tenere presente che Twitter può essere offline o inservibile e quindi questa casistica va contemplata.

Il problema della cache

Il problema maggiore di questo plugin sta nel tempo intercorrente tra un parsing e l'altro del feed di Twitter. Infatti i tweet vengono messi in cache, se possibile, per migliorare la performance. La lacuna del plugin è che non consente all'utente di scegliere come gestire la cache. Per quegli utenti di Twitter che postano molti tweet in sequenza, questo può essere un problema. Ecco la parte del codice interessata:


private function _getTweets( $widgetOptions ) {
  $key = md5( $this->_getFeedUrl( $widgetOptions ) );
  if ( false === ( $tweets = get_site_transient( 'twp_' . $key ) ) ) {
    try {
	  $tweets = $this->_parseFeed( $widgetOptions );
	  set_site_transient( 'twp_' . $key, $tweets, 300 ); // in cache per  5 minuti
	} catch ( wpTwitterWidgetException $e ) {
	   throw $e;
	}
  }
  return $tweets;
}

Se volete usare la cache, vi consiglio di permettere all'utente di avere il controllo su come viene gestita.

Conclusione

Mi permetto di darvi alcuni consigli:

  1. date all'utente la possibilità di gestire l'intervallo tra un aggiornamento e l'altro
  2. se volete, potete mettere in cache i tweet, sempre permettendo però all'utente di gestirla
  3. usate, come fa il plugin, le API di Twitter per avere più controllo sul parsing remoto.
Torna su