PHP

Calendrier, Newsletter & PHP

Il y a quelques temps j’ai réalisé un calendrier dynamique pour des newsletters. Le but de la newsletter était d’inviter des clients à prendre rendez-vous dans des points de ventes pour tester un produit gratuitement.
Le principe global est assez simple: on vient placer l’adresse d’un script php dans l’attribut src d’une image dans notre newsletter. Notre script va alors renvoyer l’image attendue.
L’intérêt est de pouvoir créer des images personnalisées en passant des paramètres à notre script. Voici un exemple: ( un exemple visuel est dispo tout en bas de l’article. )

<img src="calendar.php?id=11123" alt="" width="600">

Les sources sont disponibles sur GitLab et inclues un petit bonus: un script pour importer un CSV dans la basse de données.

Mise en place

Avant de partir tête baissé dans le code, je dois vous expliquer un minimum le concept général.
Notre script va chercher dans une base de données toutes les entrées qui correspondent à l’ID puis la moulinette fait son oeuvre et affiche le calendrier.
Il faut donc avoir une base de donnée et pouvoir la lire. La base de donnée est simple; elle est constituée d’une table avec deux colonnes: jour, code_magasin ( notre id ). Chaque entrée représente donc un jour associé à un code_magasin ( id ). Voici un exemple rapide:

-- Structure de la table `magasins`
DROP TABLE IF EXISTS `magasins`;
CREATE TABLE IF NOT EXISTS `magasins` (
  `jour` varchar(255) DEFAULT NULL,
  `code_magasin` int(12) DEFAULT NULL,
  KEY `code_magasin` (`code_magasin`)
) 
-- Déchargement des données de la table `magasins`
INSERT INTO `magasins` (`jour`, `code_magasin`) VALUES
('01-06-2020', 32987),
('02-06-2020', 32987),
('08-06-2020', 32987),
('09-06-2020', 32987),
('15-06-2020', 32987),
('22-06-2020', 32987),
('23-06-2020', 32987),
('30-06-2020', 32987),

('01-07-2020', 11123),
('08-07-2020', 11123),
('09-07-2020', 11123),
('15-07-2020', 11123),
('16-07-2020', 11123),
('19-07-2020', 11123),
('26-07-2020', 11123),
('29-07-2020', 11123);
COMMIT;

Comme nous avons affaire avec des dates, nous allons aussi avoir besoin d’un ensemble de petites fonctions qui vont nous permettre de les manipuler.
Afin de mettre un peu d’ordre dans le code, j’ai séparé la partie PHP qui gère la connexion à la base de données et la manipulation des données dans une petite classe sobrement nommé FUNC. Cette classe sera incluse dans notre script chargé de rendre le calendrier.
La voici avec quelques commentaires pour vous aider a mieux la comprendre:

<?php
class Func {
	private $db;
    private $db_host = "localhost";
    private $db_name = "nl_calendar";
    private $db_user = "root";
    private $db_pass = "";


	public function __construct(){
		try{
			$this->db = new PDO('mysql:host='.$this->db_host.';dbname='.$this->db_name.'',$this->db_user,$this->db_pass, array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8') );
			$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		}catch(PDOException $e){
			//$this->msg_error = $e;
			echo '<br><br><strong>Probleme de connexion à la base de données.</strong>';
		}
	}
    /*
    retourne toutes les dates du magasin
    */
	public function get_dates($id){
		$sql = 'SELECT `jour` FROM `magasins` WHERE `code_magasin` = '.$id;
		try{
			$req = $this->db->query($sql);
			$tab= array();
			while($retour = $req->fetch(PDO::FETCH_BOTH)){
			// print_r($id);
				array_push($tab, $retour);
			}
            $tab = $this->setupDays($tab);
			return $tab;
		}catch(PDOException $e){
			echo $e;
		}
	}

    // retourne le format attendu par le script qui dessine le calendrier
    private function setupDays($tab){
        $retour = array();
        foreach ($tab as $key => $value) {
            //American 11/12/10 think "12 November, 2010". -> M/D/Y
            //Australian or European 11/12/10 think "11 December, 2010" -> D-M-Y
            //Sysadmin (ISO) 11/12/10 think "10th December 2011" -> Y.M.D
            $numday =  strftime("%d",strtotime($value['jour']));
            $retour[] = (int)$numday;
        }
        return $retour;
    }

    /*
    Retourne la valeur $format de strftime()
    $id: $id du magasin
    $format:string $m ou $Y
    http://php.net/manual/fr/function.strftime.php
    */
    public function get_month_year($id,$format){
        $sql = 'SELECT `jour` FROM `magasins` WHERE `code_magasin` = '.$id.' LIMIT 1';
		try{
			$req = $this->db->query($sql);
			$retour = $req->fetch(PDO::FETCH_BOTH);
			$num =  strftime("%$format",strtotime($retour['jour']));
			return $num;
		}catch(PDOException $e){
			echo $e;
		}
    }

    /*
    * days_in_month($month, $year)
    * Renvoie le nombre de jours d'un mois et d'une année donnés, en tenant compte des années bissextiles.
    * $month: numeric month (integers 1-12)
    * $year: numeric year (any integer)
    */
    public function days_in_month($month, $year)
    {
        return $month == 2 ? ($year % 4 ? 28 : ($year % 100 ? 29 : ($year % 400 ? 28 : 29))) : (($month - 1) % 7 % 2 ? 30 : 31);
    }

    // retourne le decalage à appliquer pour les jours, quand le 1er du moi n'est pas lundi
    // 0 = lundi 6 = dimanche
    public function first_day_month($month, $year)
    {
        // date_default_timezone_set('Europe/Paris');
        setlocale(LC_TIME, 'fr_FR.utf8','fra');
        // %A   Nom complet du jour de la semaine avec majuscule
        $ladate = date("$year-$month-01");
        $jour =  ucfirst(strftime("%A",strtotime($ladate)));
        $tab_jour = array('Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi','Dimanche');
        foreach ($tab_jour as $key => $cd) {
            if($cd == $jour){
                $decale_number = $key;
                break;
            }
        }
        return $decale_number;
    }

}

Dessiner le calendrier

Il reste maintenant à dessiner le calendrier. Je ne vais pas détailler chaque étape car le code est assez long mais j’ai mais des commentaires au fur et à mesure du code pour votre permettre de comprendre ce qui se passe. Au début du code j’ai mis toutes les variables, il y a notamment la variable $debug permet de ne pas afficher l’image l’ors de l’exécution du script.
Vous pourriez donc appeler le script directement comme ceci : calendar.php?id=32987
Enfin le script fait appel à des fonts pour fonctionner. Ne les oubliez pas !
Voici le code:

<?php

    // tous les fonctions et outil de la pratie front du projet
    require_once 'FUNC.php';
    $func = new FUNC();

    //////////////////////
    //Variables
    //////////////////////
    $debug = false; // permet de ne pas afficher l'image, et de débuger en cas de besion
    if(!isset($_GET['id']) || empty($_GET['id']) ){
        $idmag = 0; // si l'identifiant magasin n'existe pas, on le set à 0 pour que le script ne plante pas
    }else{
        $idmag = $_GET['id']; //<- identifiant point de vente
    }
    // largeur/hauteur de l'image ( en 2x pour les ecrans retina )
    $li = 1200; // 600
    $hi = 920; // 460
    // largeur/hauteur des colonnes
    $l = 160; // 80
    $h = 120; // 60
    // padding general
    $p = 40; // 20
    // nombre de ligne sous les intitulé des colonnes
    $nb_rows = 6;
    // creation de l'image
    $image = @imagecreate ($li, $hi);
    // Couleurs
    $fond = ImageColorAllocate ($image, 208, 216, 213);
    $black = ImageColorAllocate ($image, 0, 0, 0);
    $white = ImageColorAllocate ($image, 255, 255, 255);
    $bleu = ImageColorAllocate ($image, 60, 124, 168);
    $red = ImageColorAllocate ($image, 255, 124, 168);
    //Fonts
    $fregular = __DIR__."\Roboto-Regular.ttf";
    $fbold = __DIR__."\Roboto-Bold.ttf";
    $fontsize = 24;
    $fontsizesmall = 20;
    // intitulé des colonnes
    $tab_jour = array('L','M','M','J','V','S','D');

    //////////////////////
    //On commance par dessiner les cases et les numeros des jours en surbrillance
    //////////////////////
    // 2020 année bissextile
    $m = $func->get_month_year($idmag, 'm');
    $y = $func->get_month_year($idmag, 'Y');
    $tab_joursj = $func->get_dates($idmag);
    $nbj = $func->days_in_month($m,$y);
    $current_day = $func->first_day_month($m,$y); // retourne le numero du nom du 1er jour  du mois: 0 = Lundi, 6 = Dimanche
    $dimanche = 6;
    $current_row_num = 1;
    for ($i=0; $i < $nbj; $i++) {
        // Creation des cases blanches et ecriture du texte en rouge & bold si cest un jour choisie
        $iplus = $i + 1;
        if (in_array($iplus, $tab_joursj, true)) {
            /*
            // desine un rectangle dans la case du calendrié
            // calcule haut droite et bas gauche du rectangle
            $x1 = ($l * $current_day) + $p;
            $y1 = ($h * $current_row_num) + $p;
            $x2 = ($l * $current_day) + $p + $l;
            $y2 = ($h * $current_row_num) + ($p + $h);
            // dessine le cadre rouge qui indique la date
            imagefilledrectangle( $image,$x1,$y1,$x2,$y2, $white );
            */

            // desine un cercle autour du numero du jour
            $cx = ($l * $current_day) + $p + ($l / 2);
            $cy = ($h * $current_row_num) + $p + ($h / 2);
            $cw = $ch = 60;
            imagefilledellipse( $image,$cx,$cy,$cw,$ch, $white );
            ///////////////////////////
            // ecriture du texte
            //recupération des dimension du texte
            $text_box = imagettfbbox($fontsizesmall,0,$fbold,$iplus);
            // calcule la largeur et la hauteur du texte
            $text_width = $text_box[2]-$text_box[0];
            $text_height = $text_box[7]-$text_box[1];
            imagettftext(
                $image, $fontsizesmall,0,
                ($l * $current_day) + $p + ($l/2) - ($text_width / 2),
                ($h * $current_row_num) + ($p+$h) - ($h / 2) - ($text_height / 2),
                $red,$fbold,$iplus
            );

        }else{
            $text_box = imagettfbbox($fontsizesmall,0,$fbold,$iplus);
            $text_width = $text_box[2]-$text_box[0];
            $text_height = $text_box[7]-$text_box[1];
            imagettftext(
                $image, $fontsizesmall,0,
                ($l * $current_day) + $p + ($l/2) - ($text_width / 2),
                ($h * $current_row_num) + ($p+$h) - ($h / 2) - ($text_height / 2),
                $black,$fregular,$iplus
            );
        }

        // calcule pour passer à la ligne...
        if($current_day < $dimanche){
            $current_day++;
        }else{
            $current_day = 0;
            $current_row_num++;
        }
    }

    //////////////////////
    // Puis on dessine par dessus tous le reste
    // car sinon les cases viennent effacer les lignes en etant par dessus
    //////////////////////

    // rectangle vide, les bordures du tableau
    imagerectangle($image, $p, $p, $li - $p, $hi - $p, $black);

    //rectangle remplis, pour le titre des colonnes
    imagefilledrectangle($image, $p+1, $p+1, $li - $p - 1, $h+$p, $bleu);

    // creation des lignes vertical
    for ($i=1; $i <= $nb_rows; $i++) {
        imageline ($image, ($l * $i)+ $p, $p, ($l * $i)+ $p, $hi - $p, $black);
    }

    // creation des lignes horizontal
    for ($i=1; $i <= $nb_rows; $i++) {
        imageline ($image, $p, ($h * $i)+ $p, $li - $p, ($h * $i)+ $p, $black);
    }

    // creation des legendes jour, titre des colonnes

    foreach ($tab_jour as $key => $jour) {
        imagettftext($image, $fontsize, 0,
        ($l * $key) + $p + ($l/2) - ($fontsize / 2),
        ($p + $fontsize) + ($h / 2) - ($fontsize / 2),
        $white,$fregular,$jour);
    }


    if(!$debug){
        header("content-type:image/png");
        //expiration de l'image imédiate
        header( 'Expires: Sat, 26 Jul 1997 05:00:00 GMT' );
        header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
        header( 'Cache-Control: no-store, no-cache, must-revalidate' );
        header( 'Cache-Control: post-check=0, pre-check=0', false );
        header( 'Pragma: no-cache' );
        // Création de l'image
        imagepng ($image);
        // libération de la mémoire
        imagedestroy($image);
    }

    // petite fonction utile pour le debug...
    function pr($var){
        global $debug;
        if($debug){
            echo '<pre>'.print_r($var,true).'</pre><br>';
        }
    }
    function vd($var){
        global $debug;
        if($debug){
            var_dump($var);
        }
    }
?>

Et voici un exemple du résultât obtenu. Évidement vous pouvez modifier le code pour obtenir un résultat plus proche de vos besoins.

Articles similaires

Commencez à saisir votre recherche ci-dessus et pressez Entrée pour rechercher. ESC pour annuler.

Retour en haut