PHP-Astux.info

"Just Tux it."

Dernière màj : 01-01-2011

Un petit script de vote en AJAX et MySQL

Sommaire

  1. Explications et prérequis
  2. Le code source
  3. Une démo ?
  4. Améliorations et Sécurisation
  5. Divers

Explications et prérequis

Explications

Le but de cet article est de faire un vote ultra simple qui n'a que 2 choix possibles : "oui" et "non". Une barre de progression montrera l'état des votes en cours ...

Pour faire plus "actuel", on utilisera dans cet article de l'AJAX (Asynchronous Javascript And XML), c'est une sorte d'interface entre le PHP et le Javascript, via de l'XML. Pourquoi ?

PHP est côté serveur (et c'est PHP qui va lire/écrire dans la base de données) et HTML/Javascript sont du coté client : pour actualiser les données, il n'y a donc pas d'autre alternative, à première vue, que de recharger toute la page ... Sauf si, dans un coin de page, un module dynamique (Javascript est, après tout, le côté dynamique chez le "client") permet d'aller à la volée lire des informations et les mettre à jour ...

Ce script présente donc un inconvénient (soyons clairs) c'est que sans javascript, il ne fonctionnera pas. Pour cela, on peut prévoir une variante ("accessibilité" oblige) qui ne nécessite pas le Javascript (mais dans ce cas, il y a rechargement complet de la page).

Prérequis

On a besoin :

Le code source

Structure de la base de données

Commençons par la base de données. Je suppose ici qu'on veut évaluer des articles qui ont un identifiant (ID) : savoir si vos visiteurs ont apprécié cet article. Il y a donc une table 'articles' comme suit :

id            fichier                 titre
-----------------------------------------------------------------------------
1             article1.html           L'ornithorynque polyglotte
2             faq_newslettux.html     Questions des utilisateurs NewsletTux
3             Tuto_NewsletTux2.html   Installation de NewsletTux 2 de A à Z

Bien entendu, le principe peut être adapté à n'importe quoi... L'idée maintenant est de mettre une sorte de notation pour chaque ID de cette table. Par souci de simplicité (et de clarté) dans ce script, on ne bloque pas à 1 vote par visiteur et par jour.

Voici la table qu'il faut créer pour stocker les votes :

	CREATE TABLE votes_articles(
	  id              INT(5) NOT NULL auto_increment,
	  id_article      INT(5) NOT NULL, -- l'ID de l'article auquel ce vote se réfère
	  num_votes       INT(5) NOT NULL DEFAULT 0, -- le nombre de votants
	  votes_plus      INT(5) NOT NULL DEFAULT 0, -- ceux qui auront voté (+)
	  votes_moins     INT(5) NOT NULL DEFAULT 0, -- ceux qui auront voté (-)
	  pourcent_depart INT(3) NOT NULL DEFAULT 0, -- le % de la barre au départ

	  PRIMARY KEY(id)
	) Type = MyISAM;

A chaque vote, on rajoutera +1 au compteur correspondant (+ ou -) et on affichera, en fonction du nombre de départ, l'état des votes...

Code source du moteur de vote

On va maintenant créer les fonctions qui vont réaliser les votes. Ce seront des fonctions, parce qu'on pourra les appeler plus facilement. Ces fonctions sont à placer dans un fichier d'extension .php, ce fichier devra être inclus dans la page des votes via un include (ou un require). Nommons par exemple ce fichier "ajax_vote.php" :

<?php
	/*
	 * Cette fonction enregistre un nouveau vote pour l'article
	 * entrée : $id_article {int}
	 * entrée : $vote, ('plus' || 'moins') {string}
	 * entrée : $verbose, pour renvoyer le résultat {bool}
	 *
	 * sortie {string}
	 */
	function AddNewVote($id_article, $vote, $verbose = true)
	{
		require('mysql.php');

		$req_update_votes = "UPDATE
									vote_articles
								SET
									votes_{TYPE} = (votes_{TYPE} + 1),
									num_votes = (num_votes + 1)
								WHERE
									id_article = '".$id_article."';";
		// replace vote
		$req_update_votes = str_replace('{TYPE}', $vote, $req_update_votes);

		if (mysql_query($req_update_votes))
		{
			// read and return new status
			return ReadVoteStatus($id_article, $verbose);
		}
		else
		{
			die($req_update_votes.'<br />'.mysql_error());
		};
	};

	/*
	 * Cette fonction lit l'état d'un vote pour l'article
	 * entrée : $id_article {int}
	 * entrée : $verbose pour renvoyer le résultat {bool}
	 *
	 * sortie : {string}
	 */
	function ReadVoteStatus($id_article, $verbose)
	{
		require('mysql.php');

		$req_vote_status = "SELECT
								num_votes,
								votes_plus,
								votes_moins
							FROM
								vote_articles
							WHERE
								id_article = '".$id_article."';";
		$vote_status = mysql_query($req_vote_status) or die ($req_vote_status.'<br />'.mysql_error());

		$answer = '';
		if (mysql_num_rows($vote_status) == 1)
		{
			$status = mysql_fetch_array($vote_status);
			$answer = $status['num_votes'].'|'.$status['votes_plus'].'|'.$status['votes_moins'];
		};

		if ($verbose)
			return $answer;
	};

	// maintenant on appelle ces fonctions
	$dont_exec = (isset($dont_exec)) ? $dont_exec : false;
	if (!$dont_exec) // evite la double exécution sans Javascript
	{
		$id_article = (isset($_GET['id_article'])) ? abs(intval($_GET['id_article'])) : 0;
		$vote = (isset($_GET['vote'])) ? $_GET['vote'] : 'plus';
		if (($vote != 'plus') && ($vote != 'moins')) { $vote = 'plus'; }

		if ($id_article != 0)
		{
			echo AddNewVote($id_article, $vote, true); // répond par le statut du vote
		};
	};

?>

2 fonctions sont ici décortiquées (et nommées explicitement) : une fonction pour réaliser un vote (en plus ou en moins) et une fonction pour lire l'état des votes pour un article. Une fois qu'un vote est enregistré, on lit le nouvel état des votes de l'article : soit l'ID de l'article est complètement fantaisiste auquel cas on ne renvoie rien (une chaine vide, plus exactement), soit l'ID est bien réel et on renvoie 3 nombres, séparés par des pipes (AltGr + 6) qui sont respectivement le nouveau nombre de votes, le nouveau nombre de plus et le nouveau nombre de moins.

Il reste maintenant à faire 3 choses : créer les liens "plus" et "moins", créer les fonctions AJAX qui les activent (et les liens HTML en cas de Javascript indisponible) et afficher la barre de progression ;o)

Code source HTML : présentation des données

Cet exemple, simple, n'a pas la prétention de faire joli. On aura recours aux styles CSS pour embellir la présentation qui, je l'avoue, est très sommaire ici. Je suppose que par une requête on lit tous les articles, et pour chacun, on crée une ligne de paragraphe.

Ceci est la page articles.php, d'extension .php, qui présente les articles (et permet le vote d'ailleurs).

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
	<head>
	<title>PHP-Astux - Accueil du site</title>
	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-15">
	<script type="text/javascript" src="ajax_vote.js"></script>
</head>

<body>
	<h1>PHP-Astux, le site des scripts PHP !</h1>
	<p>Cette page est celle qui liste mes articles pour lesquels j'organise un vote.
	Il y a 3 articles (les mêmes que ceux de l'article) et le vote commence toujours à 50%.</p>
<?php
	require('mysql.php');

	// display article
	function DisplayArticle($article)
	{
		echo '<div>';
		echo '<strong>'.$article['titre'].'</strong>'; // le titre de l'article
		echo ' - [ <a href="http://monsite/'.$article['fichier'].'">Lien</a> ]'; // le lien vers l'article

		// et maintenant, le système de vote
		// Affichage du nombre total de votes
		echo ' Nombre de votes : <span id="numvotes_'.$article['id'].'">'.$article['num_votes'].'</span> '; // laisser l'espace final

		// les liens de vote en AJAX, par défaut masqués (et activés si Javascript est présent)
		echo '<span id="show_'.$article['id'].'" style="display:none;">';
			// vote +
		echo '<span id="vote_plus_'.$article['id'].'">(<a href="#" onclick="vote(\''.$article['id'].'\',\'plus\'); return false;" title="Voter (+)">+</a>)</span> '; // laisser l'espace final

			// vote -
		echo '<span id="vote_moins_'.$article['id'].'">(<a href="#" onclick="vote(\''.$article['id'].'\',\'moins\'); return false;" title="Voter (-)">-</a>)</span></span> '; // laisser l'espace final

		// les liens de vote "classiques" (sans javascript), masqués si Javascript actif
		echo '<span id="hide_'.$article['id'].'">';
			// vote +
		echo '(<a href="?act=vote&id_article='.$article['id'].'&vote=plus" title="Voter (+)">+</a>) ';
			// vote -
		echo '(<a href="?act=vote&id_article='.$article['id'].'&vote=moins" title="Voter (-)">-</a>)';
		echo '</span>';

		// barre de progression : un <div> de largeur fixée, avec une couleur de fond et une image de largeur variable
		//calcul du pourcentage (largeur de la barre rouge)

		// on part du chiffre initial ($article['pourcent_depart']) et on lui ajoute les votes + et - :
		// La note va de 0 à 100% et on part d'un nombre initial : X
		// depuis ce nombre, il y a eu A votes (+) et B votes (-)
		// on met tout le monde à la même échelle, et on recalcule la note...

		// au départ
		$total_votes_ini = 100;
		$total_votes_ini_plus = $article['pourcent_depart'];
		$total_votes_ini_moins = $total_votes_ini - $article['pourcent_depart'];

		// les votes des gens
		$total_votes_gens = $article['num_votes'];
		$total_votes_gens_plus = $article['votes_plus'];
		$total_votes_gens_moins = $article['votes_moins'];

		// calcul des sommes
		$total_final_votes = $total_votes_ini + $total_votes_gens;
		$total_final_plus = $total_votes_ini_plus + $total_votes_gens_plus;
		$total_final_moins = $total_votes_ini_moins + $total_votes_gens_moins;

		// recalcul des % (avec antibug pour la division par 0) : on a la taille de l'image ;o)
		$pourcent_votes_plus = ($total_final_votes == 0 ) ? 0 : intval(($total_final_plus / $total_final_votes) * 100);

		echo '<div style="width:100px; background-color:#090;"><img src="rouge.png" width="'.$pourcent_votes_plus.'" height="20" alt=""  style="border:0; margin:0; padding:0;"> </div>';

		// Si javascript inactig : les liens AJAX n'apparaissent pas, les liens classique le sont
		// sinon, on masque les liens classiques et on active les liens AJAX
		echo '<script type="text/javascript">document.getElementById(\'hide_'.$article['id'].'\').style.display="none"; document.getElementById(\'show_'.$article['id'].'\').style.display="";</script>';
		echo '</div>';
	};

	// récupération des articles depuis la base
	$array_articles = array(); $i = 0;

	$req_articles = "SELECT id, titre, fichier FROM articles;";
	$articles = mysql_query($req_articles) or die($req_articles.'<br />'.mysql_error());
	if (mysql_num_rows($articles) > 0)
	{
		while($art = mysql_fetch_array($articles))
		{
			$array_articles[$i]['id'] = $art['id'];
			$array_articles[$i]['titre'] = $art['titre'];
			$array_articles[$i]['fichier'] = $art['fichier'];

			// ces variables seront mises à jour s'il y a un vote
			$array_articles[$i]['num_votes'] = 0;
			$array_articles[$i]['votes_plus'] = 0;
			$array_articles[$i]['votes_moins'] = 0;
			$array_articles[$i]['pourcent_depart'] = 0;

			// on lit les votes pour cet article
			$req_vote = "SELECT
									num_votes,
									votes_plus,
									votes_moins,
									pourcent_depart
								FROM
									vote_articles
								WHERE
									id_article = '".$art['id']."';";
			$rs_vote = mysql_query($req_vote) or die ($req_vote.'<br />'.mysql_error());

			if (mysql_num_rows($rs_vote) == 1)
			{
				$vote = mysql_fetch_array($rs_vote);

				$array_articles[$i]['num_votes'] = $vote['num_votes'];
				$array_articles[$i]['votes_plus'] = $vote['votes_plus'];
				$array_articles[$i]['votes_moins'] = $vote['votes_moins'];
				$array_articles[$i]['pourcent_depart'] = $vote['pourcent_depart'];
			};
			$i++;
		};
	};

	// maintenant, on affiche les articles
	//print_r($array_articles);
	foreach($array_articles as $i => $article)
	{
		DisplayArticle($article);
	};

	// Vote sans javascript : même principe, mais "en local sur cette page".
	if ((isset($_GET['act'])) && ($_GET['act'] == 'vote'))
	{
		$dont_exec = true;
		require('ajax_vote.php'); // pour hériter des fonctions
		$id_article = (isset($_GET['id_article'])) ? abs(intval($_GET['id_article'])) : 0;
		$vote = (isset($_GET['vote'])) ? $_GET['vote'] : 'plus';
		if (($vote != 'plus') && ($vote != 'moins')) { $vote = 'plus'; }

		if ($id_article != 0)
		{
			if (AddNewVote($id_article, $vote, false) === NULL)
			{
				echo '<p>Votre vote a bien été pris en compte, merci ! <a href="articles.php">Cliquez sur ce lien pour revenir aux articles</a></p>';
			}
		};
	};

?>
</body>
</html>

Code source de l'AJAX

L'AJAX est du javascript : c'est donc une suite d'instructions que nous allons placer dans un fichier, par exemple ajax_vote.js d'extension .js et appelé entre <head> et </head> par la ligne :

	<script type="text/javascript" src="ajax_vote.js"></script>

Dans ce fichier, il y a une fonction qui crée un objet nommé XMLHTTPRequest. C'est l'objet XML qui va s'interposer entre le Javascript et le PHP. Il peut avoir 3 états : opération faite, opération en cours, opération finie. Les commentaires du fichier JS détaillent mieux tout cela.

function vote(id_article, type_vote)
{
	// l'URL appelée pour voter
	var url = 'ajax_vote.php';
	var httpRequest = false;

	if (window.XMLHttpRequest)
	{ // Mozilla, Safari,...
		httpRequest = new XMLHttpRequest();
		if (httpRequest.overrideMimeType)
		{
			httpRequest.overrideMimeType('text/xml');
		}
	}
	else if (window.ActiveXObject)
	{ // IE
		try
		{
			httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (e)
		{
			try
			{
				httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch (e) {}
		}
	}

	if (!httpRequest)
	{
		alert('Abandon :( Impossible de créer une instance XMLHTTP');
		return false;
	}

	httpRequest.onreadystatechange = function() { alertContents(httpRequest, id_article); };
	httpRequest.open('GET', url + '?id_article=' + id_article + '&vote=' + type_vote, true);
	httpRequest.send(null);
	return true;
}

function alertContents(httpRequest, id_article)
{
	if (httpRequest.readyState == 4)
	{
		if (httpRequest.status == 200)
		{
			// Edit page content
			str = httpRequest.responseText;
			var tab = str.split('|');

			// On lit (et on parse) la réponse : 1er nombre = nombre total de votes, puis Plus, puis Moins
			var total_votes = tab[0];
			var total_votes_plus  = tab[1];
			var total_votes_moins = tab[2];

			// on met à jour la page de présentation, maintenant
			document.getElementById('numvotes_' + id_article).innerHTML = total_votes;

			// on efface les liens de vote
			document.getElementById('vote_plus_' + id_article).innerHTML = '';
			document.getElementById('vote_moins_' + id_article).innerHTML = '';

		}
		else
		{
			alert('Un problème est survenu avec la requête.');
		}
	}
}

Une démo ?

Voir une démo de ce script

Point de vue sécurisation, il faut enlever (sur un serveur de production) tous les "die mysql_error" : on n'affiche pas les erreurs aux gens, on les traite... Je suppose pour cet article que vous avez bien entendu de quoi les masquer ou les traiter. Attention, il ne faut pas masquer une erreur pour le plaisir des yeux : il faut absolument la corriger...

Même si ce n'est pas explicitement écrit, dans les pages PHP vous avez fait un "require" sur un fichier contenant une connexion à MySQL : 4 identifiants, une ouverture de connexion.

Point de vue améliorations, on citera d'emblée (et sans hésiter) l'apparence. Oui, la démo est très sommaire mais il s'agit de montrer ce que ça donne en ligne, le reste c'est vous qui l'adaptez à vos besoins !