12
Riepilogo post dicembre 2009

DOM Tree parsing con PHP: phpQuery

Attenzione! Questo contenuto è vecchioQuesto articolo risale al 2009, quindi i contenuti e le operazioni qui consigliate potrebbero essere diventate obsolete nel corso del tempo.

Quanti di voi non conoscono la libreria javascript jQuery? Penso pochi, se non nessuno. La potenza espressiva di questa libreria javascript permette una vastità quasi infinita di metodi di accesso agli elementi del DOM. La parte più interessante di questa libreria è senza dubbio la potenza del motore di selezione degli elementi, il Sizzle CSS Selector Engine.

Ora, quanti di voi programmatori PHP si saranno chiesti se questa potenza dei selettori sia applicabile anche a script PHP server-side che fungono da data crawler o data mining?
Penso parecchi.

Navigando in internet, ovviamente, la soluzione è già stata trovata ed il suo nome+link è PHPQuery Ecco un rapidissimo esempio di quello che questa classe può offrire: scarichiamo la index di google e troviamo i suoi link nel footer (Pubblicità, Soluzioni Aziendali, Tutto su Google, Google.com in English)

<?php
    require_once "class.phpquery.php";
    $document = phpQuery::newDocumentHTML( file_get_contents('http://www.google.it') );
    phpQuery::selectDocument($document);
    foreach ( pq ( 'span#footer a' ) as $link_footer_google ) {
        if ( pq( $link_footer_google ) -> html() != 'Privacy' ) {
            echo 'Link trovato nel footer della homepage di Google. Href=' . pq( $link_footer_google ) -> attr('href') . '; Text=' . pq( $link_footer_google ) -> html() . chr(10);
        }
    }
?>

Non mi dinlugherò in altri esempi più complessi per mostrarvi la potenza della classe: la pagina ufficiale di PHPquery offre già un ottimo manuale con le spiegazioni dei "metodi" sfruttabili.

Buon Lavoro!
Attenzione! Questo contenuto è vecchioQuesto articolo risale al 2009, quindi i contenuti e le operazioni qui consigliate potrebbero essere diventate obsolete nel corso del tempo.

Il problema della sicurezza è cruciale quando si sviluppa una applicazione in PHP. Se non vengono prese determinate precauzioni, un semplice script php come questo che sto per mostrarvi (una risposta xml, magari chiamata tramite ajax) può trasformarsi in un pericoloso buco per il vostro sito, e rendere possibile più o meno tutto a chi sta tentando di "lavorare" sopra lo script:

<?php
    // Connessione al db e roba varia
    $sql = "SELECT * FROM tabella WHERE id='" . $_GET['id_passato_da_get'] . "'";
    $rs = mysql_query($sql);
    $rw = mysql_fetch_assoc($rs);
    header('Content-Type:text/xml');
    echo '<?xml version="1.0" encoding="ISO-8859-1"?><root><dati>' . $rw['prova'] .'</dati></root>';
    exit();
?>

I problemi? Pessima programmazione: nessuna validazione dei dati, nessun controllo sul funzionamento della query, e in generale mancanza di occhio critico riguardo la sicurezza di uno script simile. Sarebbe aperto ad ogni tipologia di attacco.

Allora, come ovviamo a questo problema? In primis, programmando meglio. In secundis, facendoci aiutare da qualche classe che pulirà un pò i dati che vengono passati tramite GET e POST e che controlli un pò le impostazioni di sicurezza dell'environment (vedere register_globals per farsi un'idea)

Classe "Security PHP Class"

<?php
/**
* Provides static functions to help protect against cross site scripting
* attacks and helps clean up the php environment upon initializing.
*
* Based upon Security library by http://kohanaphp.com/
*
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/

class Security
{
    // Instance of the security class.
    protected static $instance;
    protected $magic_quotes_gpc = FALSE;
    
    /**
     * Gets the instance of the Security class.
     *
     * @return object Instance of Security
     */
    public static function instance()
    {
        if(self::$instance === NULL)
        {
            return new Security;
        }
        
        return self::$instance;
    }
    
    
    /**
     * Constructor. Sanitizes global data GET, POST and COOKIE data.
     * Also makes sure those pesty magic quotes and register globals
     * don't bother us. This is protected because it really only needs
     * to be run once.
     *
     * @return void
     */
    protected function __construct()
    {
        if(self::$instance === NULL)
        {
            // Check for magic quotes
            if(get_magic_quotes_runtime())
            {
                // Dear lord!! This is bad and deprected. Sort it out ;)
                set_magic_quotes_runtime(0);
            }
            
            if(get_magic_quotes_gpc())
            {
                // This is also bad and deprected. See http://php.net/magic_quotes for more information.
                $this->magic_quotes_gpc = TRUE;
            }
            
            // Check for register globals and prevent security issues from arising.
            if(ini_get('register_globals'))
            {
                if(isset($_REQUEST['GLOBALS']))
                {
                    // No no no.. just kill the script here and now
                    exit('Illegal attack on global variable.');
                }
                
                // Get rid of REQUEST
                $_REQUEST = array();
                
                // The following globals are standard and shouldn't really be removed
                $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION');
                
                // Same effect as disabling register_globals
                foreach($GLOBALS as $key => $value)
                {
                    if( ! in_array($key, $preserve))
                    {
                        global $$key;
                        $$key = NULL;
                        
                        unset($GLOBALS[$key], $$key);
                    }
                }
            }
            
            // Sanitize global data
            
            if(is_array($_POST))
            {
                foreach($_POST as $key => $value)
                {
                    $_POST[$this->clean_input_keys($key)] = $this->clean_input_data($value);
                }
            }
            else
            {
                $_POST = array();
            }
            
            if(is_array($_GET))
            {
                foreach($_GET as $key => $value)
                {
                    $_GET[$this->clean_input_keys($key)] = $this->clean_input_data($value);
                }
            }
            else
            {
                $_GET = array();
            }
            
            if(is_array($_COOKIE))
            {
                foreach($_COOKIE as $key => $value)
                {
                    $_COOKIE[$this->clean_input_keys($key)] = $this->clean_input_data($value);
                }
            }
            else
            {
                $_COOKIE = array();
            }
            
            // Just make REQUEST a merge of POST and GET. Who really wants cookies in it anyway?
            $_REQUEST = array_merge($_GET, $_POST);
            
            self::$instance = $this;
        }
    }
    
    /**
     * Cross site filtering (XSS). Recursive.
     *
     * @param  string Data to be cleaned
     * @return mixed
     */
    public function xss_clean($data)
    {
        // If its empty there is no point cleaning it :\
        if(empty($data))
            return $data;
            
        // Recursive loop for arrays
        if(is_array($data))
        {
            foreach($data as $key => $value)
            {
                $data[$key] = $this->xss_clean($data);
            }
            
            return $data;
        }
        
        // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
        // +----------------------------------------------------------------------+
        // | Copyright (c) 2001-2006 Bitflux GmbH                                 |
        // +----------------------------------------------------------------------+
        // | Licensed under the Apache License, Version 2.0 (the "License");      |
        // | you may not use this file except in compliance with the License.     |
        // | You may obtain a copy of the License at                              |
        // | http://www.apache.org/licenses/LICENSE-2.0                           |
        // | Unless required by applicable law or agreed to in writing, software  |
        // | distributed under the License is distributed on an "AS IS" BASIS,    |
        // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or      |
        // | implied. See the License for the specific language governing         |
        // | permissions and limitations under the License.                       |
        // +----------------------------------------------------------------------+
        // | Author: Christian Stocker <chregu@bitflux.ch>                        |
        // +----------------------------------------------------------------------+
        
        // Fix &entity\n;
        $data = str_replace(array('&','<','>'), array('&amp;','&lt;','&gt;'), $data);
        $data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
        $data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
        $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');

        // Remove any attribute starting with "on" or xmlns
        $data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);

        // Remove javascript: and vbscript: protocols
        $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
        $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
        $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);

        // Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
        $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
        $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
        $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);

        // Remove namespaced elements (we do not need them)
        $data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);

        do
        {
            // Remove really unwanted tags
            $old_data = $data;
            $data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)  ?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:c  ript|tyle)|title|xml)[^>]*+>#i', '', $data);
        }
        while ($old_data !== $data);
        
        return $data;
    }
    
    /**
     * Enforces W3C specifications to prevent malicious exploitation.
     *
     * @param  string Key to clean
     * @return string
     */
    protected function clean_input_keys($data)
    {
        $chars = PCRE_UNICODE_PROPERTIES ? '\pL' : 'a-zA-Z';
        
        if ( ! preg_match('#^[' . $chars . '0-9:_.-]++$#uD', $data))
        {
            exit('Illegal key characters in global data');
        }
        
        return $data;
    }
    
    /**
     * Escapes data.
     *
     * @param  mixed Data to clean
     * @return mixed
     */
    protected function clean_input_data($data)
    {
        if(is_array($data))
        {
            $new_array = array();
            foreach($data as $key => $value)
            {
                $new_array[$this->clean_input_keys($key)] = $this->clean_input_data($value);
            }
            
            return $new_array;
        }
        
        if($this->magic_quotes_gpc === TRUE)
        {
            // Get rid of those pesky magic quotes!
            $data = stripslashes($data);
        }
        
        $data = $this->xss_clean($data);
        
        return $data;
    }
}
?>

Classe vi validazione Server-Side "PHP Validation Class"

<?php
define("vSTRING", 1);		// any string that doesn't have control characters (ASCII 0 - 31) but spaces are allowed
define("vALPHA", 2);		// only letters a-z and A-Z
define("vDIGIT", 3);		// only numbers 0-9
define("vALNUM", 4);		// letters and numbers
define("vINTEGER", 5);		// only numbers 0-9 and an optional - (minus) sign (in the beginning only)
define("vFILENAME", 6);		// a valid file name (including dots but no slashes and other forbidden characters)
define("vBOOL", 7);			// a boolean (TRUE is either a case-insensitive "true" or "1". Everything else is FALSE)
define("vVARIABLE", 8);		// a valid variable name (letters, digits, underscore)
define("vPASSWORD", 9);		// a valid password (alphanumberic + some other characters but no spaces: ASCII 33 - 126)
define("vURL", 10);			// a valid URL (http connection is used to check if url exists!)
define("vEMAIL", 11);		// a valid email address (only checks for valid format: xxx@xxx.xxx)
define("vTEXT", 12);		// *NEW* like vSTRING, but newline characters are allowed

class Validator
{
	var $placeHolder;
	
	function Validator()
	{
		$this -> placeHolder = 0;
	}

	function get_valid($param, $type, $def)
	{
		$value = $param;
		if(!isset($value)) return $def;
		// strip slashes if they've been added by magic_quotes_gpc
		if(get_magic_quotes_gpc())
			$value = stripslashes($value);
		if($value === "") return $def;

		$is_valid = FALSE;
		switch($type)
		{
			case vSTRING:
				if(preg_match("/^[^\x-\x1F]+$/", $value)) $is_valid = TRUE;
				break;
			case vALPHA:
				if(preg_match("/^[a-z]+$/i", $value)) $is_valid = TRUE;
				break;
			case vDIGIT:
				if(preg_match("/^[0-9]+$/", $value))
				{
					$is_valid = TRUE;
					settype($value,"integer");
				}
				break;
			case vALNUM:
				if(preg_match("/^[a-z0-9]+$/i", $value)) $is_valid = TRUE;
				break;
			case vINTEGER:
				if(preg_match("/^-?[0-9]+$/", $value))
				{
					$is_valid = TRUE;
					settype($value, "integer");
				}
				break;
			case vFILENAME:
				if(preg_match("{^[^\\/\*\?\:\,]+$}", $value)) $is_valid = TRUE;
				break;
			case vBOOL:
				if(preg_match("/^true$|^1$/i", $value))
				{
					$is_valid = TRUE;
					$value = TRUE;
				}
				break;
			case vVARIABLE:
				if(preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*$/i", $value)) $is_valid = TRUE;
				if($value == "_") $is_valid = FALSE;
				break;
			case vPASSWORD:
				if(preg_match("/^[\41-\176]+$/", $value)) $is_valid = TRUE;
				break;
			case vURL:
				$href = $value;
				if(!preg_match("/^[a-z]+:/i", $href))
					$href = "http://".$href;
				if(preg_match("/^http:\/\//", $href))
					if($fp = @fopen($href, "r"))
					{
						$is_valid = TRUE;
						$value = $href;
						@fclose($fp);
					}
				break;
			case vEMAIL:
				if(preg_match("/^[a-z0-9_\.-]+@([a-z0-9]+([\-]+[a-z0-9]+)*\.)+[a-z]{2,7}$/i", $value)) $is_valid = TRUE;
				break;
			case vTEXT:
				if(preg_match("/^([^\x-\x1F]|[\r\n])+$/", $value)) $is_valid = TRUE;
				break;
		}
		
		if(!$is_valid) return false;
		else return true;
	}
}
?>

Non mi dilungherò molto nella spiegazione dell'utilizzo delle due classi, prendiamo sempre come esempio lo script errato che c'era ad inizio post e correggiamolo assieme, integrando queste due classi, e facendo un pò più attenzione :)

<?php
    // Connessione al db e inclusioni delle classi (sicurezza e validazione)
    include 'security.class.php';
    include 'validator.class.php';

    // Faccio partire i primi controlli (veloci, senza error handling)
    $security = Security::instance();
    $__GET = $security -> xss_clean($_GET); // -> assegnato nuovo nome a $_GET
    $v = new Validator();
    if ( !($v -> get_valid($__GET['id_passato_da_get'], vDIGIT, "")) ) exit();
    
    $sql = "SELECT * FROM tabella WHERE id='" . $__GET['id_passato_da_get'] . "'";
    $rs = mysql_query($sql);
    if ( $rs && mysql_num_rows($rs) == 1 ) {
        $rw = mysql_fetch_assoc($rs);
        header('Content-Type:text/xml');
        echo '<?xml version="1.0" encoding="ISO-8859-1"?><root><dati>' . $rw['prova'] .'</dati></root>';
    }
    exit();
?>

In questo esempio abbiamo istanziato la classe di sicurezza, abbiamo pulito la variabile superglobale $_GET per prevenire XSS, e abbiamo fatto una validazione in modo che l'unico parametro accettato sia un numero. Tutto il resto infatti può essere considerato un tentativo per bucare lo script :)

Attenzione! Questo contenuto è vecchioQuesto articolo risale al 2009, quindi i contenuti e le operazioni qui consigliate potrebbero essere diventate obsolete nel corso del tempo.

Quello che vedete sulla destra di questo sito è un calendario particolare, nel senso che è progettato per il funzionamento asincrono tramite ajax. In questa maniera si può facilmente integrare un modulo calendario in qualsiasi sito con pochissime modifiche e separando nettamente la parte "intelligente" del programma, cioè la creazione dei giorni a partire da un dato mese/anno, dalla parte "meno intelligente", quella di output verso il browser.

Ecco quindi spiegato il funzionamento, separato in:

  • Script PHP Server-Side che crea l'xml con i dati del mese/anno
  • Script client-side in Javascript per iniettare il markup del calendario verso il browser
  • Un esempio di markup HTML per rendere funzionante il tutto

Codice PHP server-side (Creazione dell'xml della coppia mese/anno richiesta)

<?php
	$mese = $_GET['month'];
	$anno = $_GET['year'];
	
	$primoGiornoMese = date( "w", mktime (0, 0, 0, $mese, 1, $anno) );
	
	if( $primoGiornoMese != 1 ) {
		if( $primoGiornoMese == 0 ) $counter = 6;
		else $counter = $primoGiornoMese - 1;
		
		if( $mese == 1 ) { $vecchioMese = 12; $vecchioAnno = $anno - 1; } else { $vecchioMese = $mese -1; $vecchioAnno = $anno; }
		
		$giorniMesePrecedente = date( "t", mktime(0, 0, 0, $vecchioMese, 1, $vecchioAnno) );
		for($i = $counter - 1; $i >= 0; $i--) {
			$curr = mktime(0, 0, 0, $vecchioMese, $giorniMesePrecedente - $i, $vecchioAnno);
			$tArr = Array('mod' => 'oldmonth', 'day' => $giorniMesePrecedente - $i, 'changeRow' => false);
			$calendario[] = $tArr;
		}
	}
	
	$curr = mktime(0, 0, 0, $mese, 1, $anno);
	$continua = true;
	$k = 0;
	while( $continua ) {
		$flagStile = $cambiaRiga = false;
		
		$giornoCorrente = date( "w", $curr );
		$numeroCorrente = date( "d", $curr );
		if( $curr == mktime(0, 0, 0, date("m"), date("d"), date("Y")) ) $flagStile = 'currentDay';
		else {
			if( $giornoCorrente != 6 && $giornoCorrente != 0 ) $flagStile = 'normalDay';
			else $flagStile = 'weekendDay';
		}
		
		if( $giornoCorrente == 0 ) $cambiaRiga = true;
		
		$tArr = Array('mod' => $flagStile, 'day' => $numeroCorrente, 'changeRow' => $cambiaRiga);
		$calendario[] = $tArr;
		
		$curr = mktime(0, 0, 0, $mese, date("d", $curr) + 1, $anno);
		
		if( date("m", $curr) > $mese || ( date("m", $curr) == 1 && $mese == 12 ) || $k >= 50) $continua = false;
		$k++;
	}
	$giorniCodestoMese = date("t", mktime(0, 0, 0, $mese, 1, $anno));
	if( ( $giornoFinaleMese = date("w", mktime(0, 0, 0, $mese, $giorniCodestoMese, $anno)) ) != 0 ) {
		for($i = 1; $i <= (7 - $giornoFinaleMese); $i++) {
			$tArr = Array('mod' => 'oldmonth', 'day' => $i);
			$calendario[] = $tArr;
		}
	}
	
	header('Content-Type:text/xml');
	echo '<?xml version="1.0" encoding="UTF-8" ?>' . chr(10);
	echo '<root>' . chr(10);
	echo '<monthInfo><month>' . substr( ucfirst(itdate("F", mktime(0, 0, 0, $mese, 1, $anno))), 0, 3 ) . '</month><year>' . $anno . '</year></monthInfo>' . chr(10);
	
	foreach( $calendario as $giorno => $valoriGiorno ) {
		$mod = $valoriGiorno['mod'];
		$day = number_format($valoriGiorno['day'], 0, "", "");
		$alt = $valoriGiorno['alt'];
		$title = $valoriGiorno['title'];
		$link = $valoriGiorno['link'];
		$changeRow = ( $valoriGiorno['changeRow'] == false ) ? 0 : 1;
		
		echo '<giorno mod="' . $valoriGiorno['mod'] . '" day="' . number_format($valoriGiorno['day'], 0, "", "") . '" changeRow="' . $changeRow . '"></giorno>' . chr(10);
	}
	echo '</root>' . chr(10);
	
	function itdate ($date_format="l j F Y - H:i:s", $time=0) {//, $date_format="l j F Y - H:i:s", $lang=it 

		$hourdiff = "0";
		$timeadjust = ($hourdiff * 60 * 60);

		if (! $time) $time = time()+$timeadjust;
		if (! $lang) $lang = "It";
		if (! $date_format) 
		{ 
			$date_format = "l j F Y - H:i:s"; 
		}

		$week_days_long = array('Sunday' => 'Domenica', 'Monday' => 'Lunedì ', 'Tuesday' => 'Martedì',
			'Wednesday' => 'Mercoledì', 'Thursday' => 'Giovedì', 'Friday' => 'Venerdì',
			'Saturday' => 'Sabato');
		$months_long = array('January' => 'Gennaio', 'February' => 'Febbraio',
			'March' => 'Marzo', 'April' => 'Aprile','May' => 'Maggio', 'June' => 'Giugno',
			'July' => 'Luglio', 'August' => 'Agosto', 'September' => 'Settembre',
			'October' => 'Ottobre', 'November' => 'Novembre', 'December' => 'Dicembre');
		$week_days_short = array('Sun' => 'Dom', 'Mon' => 'Lun', 'Tue' => 'Mar', 'Wed'=>'Mer',
			'Thu' => 'Gio', 'Fri' => 'Ven', 'Sat' => 'Sab');
		$months_short = array('Jan' => 'Gen', 'Feb' => 'Feb', 'Mar' => 'Mar', 'Apr' => 'Apr',
			'May' => 'Mag', 'Jun' => 'Giu', 'Jul' => 'Lug', 'Aug' => 'Ago',
			'Sep' => 'Set', 'Oct' => 'Ott', 'Nov' => 'Nov', 'Dec' => 'Dic');

		$clock = date($date_format, $time);
		
		if(preg_match("/F/", $date_format) && ($lang != "En")) {
			$model = date("F", $time);
			$replace = $months_long[date("F", $time)];
			$clock = preg_replace("/$model/", $replace, $clock);
		}
		if(preg_match("/l/", $date_format) && ($lang != "En")) {
			$model = date("l", $time);
			$replace = $week_days_long[date("l", $time)];
			$clock = preg_replace("/$model/", $replace, $clock);
		}
		if(preg_match("/M/", $date_format) && ($lang != "En")) {
			$model = date("M", $time);
			$replace = $months_short[date("M", $time)];
			$clock = preg_replace("/$model/", $replace, $clock);
		}
		if(preg_match("/D/", $date_format) && ($lang != "En")) {
			$model = date("D", $time);
			$replace = $week_days_short[date("D", $time)];
			$clock = preg_replace("/$model/", $replace, $clock);
		}
		
		return strtolower($clock);
	}
?>

Codice Javascript per l'iniettamento del markup del calendario verso il browser

/* Calendar Handler JS client-side script v1.1 */

/* 

CONFIGURAZIONE:

Cambiare i valori delle variabili di configurazione, sapendo che:
_id_contenitore_calendario = ID del contenitore che andrà a contenere il calendario. E' un div che conterrà solo una tabella. SPECIFICARE IL # prima dell'id.
_id_contenitore_header_mese_anno = ID del contenitore che conterrà la coppia mese-anno sopra la tabella del mese.
_classe_cella_mesenoncorrente = classe css da applicare alle celle che NON sono del mese corrente, ma che mandiamo in output per completezza
_classe_cella_mesecorrente = classe css da applicare alle celle che sono del mese corrente
_classe_cella_weekend = classe css da applicare alle celle che sono sabato o domenica del mese corrente
_classe_cella_giornocorrente = classe css da applicare nel caso in cui una cella-giorno del calendario corrisponda al giorno corrente

ESEMPIO MARKUP HTML PER RENDERE FUNZIONANTE IL MODULO CALENDARIO: 
<div id="contenitore_tabella_calendario">
	<p id="header_tabella_calendario"></p>
	<table>
		<tbody></tbody>
	</table>
</div>

*/
	// Inizializzazione variabili
	_id_contenitore_calendario = '#contenitore_tabella_calendario';
	_id_contenitore_header_mese_anno = '#header_tabella_calendario';
	_classe_cella_mesenoncorrente = 'oldmonth';
	_classe_cella_mesecorrente = 'normalmonth';
	_classe_cella_weekend = 'weekendday';
	_classe_cella_giornocorrente = 'currentday';
	
	//Gestione Data Odierna
	var today = new Date();
	var mese = today.getMonth() + 1;
	var anno =  today.getFullYear();
	
	// Trigger delle funzioni da far partire all'avvio
	$(document).ready(function() {
		$.ajax({
			type: "GET",
			url: "xml_calendar_creator.php",
			data: "month=" + mese + "&year=" + anno,
			dataType: 'xml', 
			success: parseXmlCalendario, 
			error: function (a, b, e) {
				throw(e);
			}
		});
	});
	
	parseXmlCalendario = function(xml) {
		// Variabile della classe delle celle per la tabella del calendario
		var cellClass = "";
		
		// Iniettamento del titolo del calendario (Mese e Anno)
		$(_id_contenitore_header_mese_anno).html( $(xml).find('monthInfo month').text() + " " + $(xml).find('monthInfo year').text());
		
		// Iniettamento del codice html completo per il calendario
		
		var htmlCalendario = '<tr>';
		$(xml).find('giorno').each(function() {
			var mod = $(this).attr('mod');
			var giorno = $(this).attr('day');
			var cambiaRiga = $(this).attr('changeRow');
			
			switch ( mod ) {
				case 'oldmonth':
					cellClass = _classe_cella_mesenoncorrente;
					break;
				case 'normalDay':
					cellClass = _classe_cella_mesecorrente;
					break;
				case 'currentDay':
					cellClass = _classe_cella_giornocorrente;
					break;
				case 'weekendDay':
					cellClass = _classe_cella_weekend;
					break;
				default: break;
			}
			
			htmlCalendario +=  '<td class="' + cellClass + '" align="center">' + giorno + '</td>';
			if ( cambiaRiga == 1 ) htmlCalendario += '</tr><tr>';
		});
		htmlCalendario += '</tr>';
		
		$(_id_contenitore_calendario + ' tbody').html(htmlCalendario);
	}

Esempio di markup HTML per rendere il tutto funzionante

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
	<title>Prova</title>
	<script type="text/javascript" src="jquery.js"></script>
	<script type="text/javascript" src="calendar_js.js"></script>
</head>
<body>
	<div id="contenitore_tabella_calendario">
		<p id="header_tabella_calendario"></p>
		<table>
			<tbody></tbody>
		</table>
	</div>
</body>
</html>
Mischiare il tutto

Il codice PHP può essere salvato con un nome qualsiasi, solo bisogna ricordarsi di andare a modificare il file javascript e, sotto la parte di creazione della richiesta ajax, andare a modificare il nome della variabile che contiene le impostazioni della richiesta.

Ricordo che questo sistema implica forzatamente l'utilizzo della libreria jQuery per poter funzionare. E' chiaramente visibile nel terzo snippet di codice HTML, dove è specificato il richiamo del file jquery.js

Acquisto di una PSP nuova e modificabile al 100%

Attenzione! Questo contenuto è vecchioQuesto articolo risale al 2009, quindi i contenuti e le operazioni qui consigliate potrebbero essere diventate obsolete nel corso del tempo.

Siamo quasi sotto Natale 2009, e la PSP è un aggeggino tecnologico che potrebbe interessare molti, sia per regararlo ai cari sia per regalarselo da soli :)

Il problema è però quello della scelta di una PSP che sia modificabile al 100% e che sia una di quelle "nuove", la versione slim brite, cioè tutte quelle PSP che sono della generazione 3000.

Molti di voi lo sapranno, ma per chi non lo sapesse c'è una informazione assolutamente vitale che dovete conoscere prima di acquistare una PSP nuova: tutte le PSP generazione 3000 con firmware maggiore del 5.03 non sono per il momento modificabili.

Adesso il problema è: quale PSP devo acquistare per averne la certezza?.

Allora, andiamo con ordine. Partiamo con la semi-certezza che le nuove PSP in bundle (ovvero con gioco compreso) con l'ultimo Need For Speed e Fifa 2010 sono quelle PSP che sono state prodotte e inscatolate da poco. Quindi queste ultime sono quelle che hanno più probabilità di essere quelle con un firmware di versione maggiore alla 5.03. Ricordo infatti che l'aggiornamento ufficiale del firmware 5.03 per psp risale al febbraio del 2009. Quindi tutti i bundle inscatolati e usciti DOPO il febbraio/marzo del 2009 sono a mio avviso da ritenere potenzialmente pericolosi per l'acquisto.

In generale, il mio consiglio, ed è esattamente come ho personalmente fatto e provato, è andare su bundle che sono più vecchi, e anche di molto.
Ho girato 3 grossi centri commerciali: La Coop, Saturn e MediaWorld. Ecco i risultati di quello che ho personalmente sperimentato:

  • Saturn: in tutto il centro commerciale erano presenti solo PSP in bundle con Need For Speed e Fifa 2010, a 169,90 €. Guardando negli scaffali più nascosti relativi alle psp ho trovato qualche psp NON in bundle che però non mi convincevano perchè erano scatole troppo nuove.
  • MediaWorld: stessa identica storia come per Saturn. Non mi sono fidato, e ho preferito andare più sul sicuro.
  • Coop: strano posto dove tentare l'acquisto di una PSP :) ma in ogni caso, è stata la mossa vincente. Nella vetrina espositiva delle PSP c'erano molte cosette interessanti: tutti i nuovi bundle con Need For Speed e Fifa 2010, un paio di PSP serie 2000 (ricordo che sono assolutamente modificabili), e 3 psp versione 3000 in bundle con giochi molto vecchi. I giochi erano Ratchet & Clank e SBK 2008.
Entrambi i giochi, SBK 2009 e Ratchet & Clank erano ASSOLUTAMENTE antecedenti al febbraio del 2009. Quindi, da buon consumatore, sono andato sulla scelta più vecchia e sicuramente funzionante, acquistando (a prezzo anche MAGGIORE rispetto agli ultimi bundle con i giochi più recenti, a 189,90 €) la PSP versione 3000 in bundle con SBK 2008

Accesa, et voilà! Firmware versione 4.20, aggiornabile tramite aggiornamento ufficiale alla versione 5.03, e quindi modificabile con le svariate guide disponibili in internet (e che presto scriverò anche io mettendo tutte le accortezze che bisogna ricordare per avere una modifica che funzioni e che non faccia impazzire per la complessità).

In ultimo, un piccolo riassunto di quello che bisogna cercare e quello che bisogna tenere presente nell'acquisto oculato di una PSP versione 3000 modificabile:

  • Ricordare che le PSP con firmware MAGGIORE del 5.03 non permettono il downgrade del firmware (downgrade = passare ad un firmware più recente) e quindi non sono modificabili
  • Ricordare che il firmware ufficiale 5.03 Sony è uscito nel febbraio del 2009, quindi è necessario acquistare qualcosa di antecedente al febbraio del 2009. Se possibile, fate come me, e cercate la PSP in bundle con SBK 2008.
  • Versioni di PSP con firmware minori della 5.03 possono essere portate alla versione 5.03 con un semplice aggiornamento, e poi modificate senza nessun problema

Per chiarimenti o domande, lasciatemi un commento qui sotto! Grazie per la lettura!