Widget calendrier PHP

    Ici, l'idée est de concevoir un « widget » calendrier, non pas en Javascript mais en PHP. Le contenu est alimenté par le script ci-dessous et rafraichi localement en asynchrone (jQuery nécessaire).
    Pour l'exemple, le calendrier présent ici marque les jours de publication de photographies du site.
    Le premier chargement est assuré en Lazy Load.

    Le widget

    Source PHP

    <?php
    /*
        CONSTANTS

        define('ISO_DAYS', ['lundi' ,'mardi' ,'mercredi' ,'jeudi' ,'vendredi' ,'samedi' ,'dimanche']);
        define('MONTHS', ['janvier' ,'février' ,'mars' ,'avril' ,'mai', 'juin' ,'juillet' ,'août' ,'septembre' ,'octobre' ,'novembre' ,'décembre']);
        define('SHORT_MONTHS', ['janv.' ,'fév.' ,'mars' ,'avr.' ,'mai','juin' ,'juill.' ,'août' ,'sept.' ,'oct.' ,'nov.' ,'déc.']);
    */


        // Identifiant unique table ID + function jQuery
        
    $uniqid 'calendar_' uniqid();

        
    // plage d'années précédentes / suivantes
        
    $minyear date('Y') - 5;
        
    $maxyear date('Y') + 3;

        
    // récupération variables $_GET jour/mois/année ou current par défaut si vide ou non conforme
        
    $year     = (empty($_GET['y']) || (is_numeric($_GET['y']) && ($_GET['y'] < $minyear || $_GET['y'] > $maxyear)) || !is_numeric($_GET['y'])) ? date('Y') : $_GET['y'];
        
    $month     = (empty($_GET['m']) || (is_numeric($_GET['m']) && ($_GET['m'] < || $_GET['m'] > 12)) || !is_numeric($_GET['m'])) ? date('n') : $_GET['m'];
        
    $day     = (empty($_GET['d']) || (is_numeric($_GET['d']) && ($_GET['d'] < || $_GET['d'] > 31)) || !is_numeric($_GET['d'])) ? date('j') : $_GET['d'];

        
    $timestamp     mktime(0,0,0,$month$day$year);
        
    $today         mktime(0,0,0,date('n'), date('j'), date('Y'));

        
    // nombre jours dans le mois
        
    $nbdays date('t'$timestamp);

        
    // numero du premier jour du mois dans la semaine ISO (lun -> dim)
        
    $firstday date('N'mktime(000$month1$year)) - 1;

        
    // numero du dernier jour du mois dans la semaine ISO (lun -> dim)
        
    $lastday date('N'mktime(000$month$nbdays$year)) - 1;

        
    // nombre de semaines dans le mois
        
    $weeks = ($nbdays == 31 && $firstday 4) || ($nbdays == 30 && $firstday ==  6) ? : ( $nbdays 30 ? (date('L'$timestamp) || ($firstday && $nbdays == 28) ? 4) : );

        
    // nombre de jours avant le 1er du mois
        
    $daysbefore - ($firstday);

        
    // nombre de jours pour terminer l'affichage du mois
        
    $daysafter = (($weeks*7) - $nbdays) - $daysbefore;

        
    // mois/année précédents
        
    $prevmonth $month == 0  12 $month 1;
        
    $prevyear $month == 0  $year $year;

        
    // mois/année suivants
        
    $nextmonth $month 12 $month 1;
        
    $nextyear $month 12  $year $year;


        
    $is_current_month $month != date('n') || ($year != date('Y') && $month == date('n'));


        
    // récupération (ou pas) des valeurs présentes pour le mois sélectionné
        
    $widget_datas false;

        
    // code à développer selon l''environnement
        
    if(!empty($_GET['widget']) && file_exists(__DIR__ DIRECTORY_SEPARATOR $_GET['widget'] . '.php')) {
            
    $widget_datas __DIR__ DIRECTORY_SEPARATOR $_GET['widget'] . '.php';
            require_once(
    $widget_datas);

            
    // doit retourner une variable $GLOBALS['widget_values']

            /* exemple
            Array
            (
                [2018] => Array
                    (
                        [12] => Array
                            (
                                [23] => 6
                                [24] => 4
                                [25] => 4
                                [27] => 3
                                [28] => 4
                                [29] => 1
                                [30] => 2
                                [31] => 3
                            )

                    )

                [2019] => Array
                    (
                        [1] => Array
                            (
                                [1] => 1
                                [2] => 5
                                [3] => 6
                                [4] => 31
                                [5] => 3
                                [6] => 8
                            )

                    )

            );
            */

        
    }


    ?>

    <div class="calendar-wrapper">

        <table id="<?php echo $uniqid ?>" class="calendar-widget" data-request="<?php echo Kit::url('calendar')?>">
        <!-- data-request utilisé pour la requête ajax dans le script JS -->
            <caption>
                <?php echo MONTHS[$month 1] . ' ' $year ?>
            </caption>
            <thead>
                <tr>
                    <td>
                        <?php if($prevyear >= $minyear):?>
                            <button class="calendar-prev-month" data-values='{"m":"<?php echo $prevmonth?>", "y":"<?php echo $prevyear ?>"}'>
                                afficher <?php echo MONTHS[$prevmonth 1], ' '$prevyear ?>
                            </button>
                        <?php else : ?>
                            <button disabled aria-hidden="true" class="calendar-prev-month">mois précédent désactivé</button>
                        <?php endif ?>
                    </td>

                    <td colspan="2">
                        <label for="calendar-month" class="audible">sélectionner le mois</label>
                        <select id="calendar-month" class="calendar-month" data-post="m">
                        <?php foreach(MONTHS as $m => $value): ?>
                            <option value="<?php echo $m 1 ?>"<?php echo $m == $month ' selected="selected"' false ?>><?php echo ucfirst(SHORT_MONTHS[$m]) ?></option>
                        <?php endforeach ?>
                        </select>
                    </td>

                    <td>
                        <?php if($is_current_month): ?>
                            <img role="button" alt="" tabindex="0" decoding="async" crossorigin src="data:image/gif;base64,R0lGODlhIAAgAPAAAAAAACZFySH5BAEAAAEALAAAAAAgACAAAAIejI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC5sFADs=" data-values='{"m":"<?php echo date('n')?>", "y":"<?php echo date('Y'?>"}' title="<?php echo ucfirst(MONTHS[date('n')-1]) ?> <?php echo date('Y')?>" />
                        <?php endif ?>
                    </td>

                    <td colspan="2">
                        <label for="calendar-year" class="audible">sélectionner l'année</label>
                        <select id="calendar-year" class="calendar-year" data-post="y">
                        <?php for($i $minyear$i $maxyear+1$i++): ?>
                            <option value="<?php echo $i ?>"<?php echo $i == $year ' selected="selected"' false ?>><?php echo $i ?></option>
                        <?php endfor ?>
                        </select>
                    </td>

                    <td>
                        <?php if($nextyear <= $maxyear):?>
                            <button class="calendar-next-month" data-values='{"m":"<?php echo $nextmonth?>", "y":"<?php echo $nextyear ?>"}'>
                                afficher <?php echo MONTHS[$nextmonth 1], ' '$nextyear ?>
                            </button>
                        <?php else :?>
                            <button disabled aria-hidden="true" class="calendar-next-month">mois suivant désactivé</button>
                        <?php endif ?>
                    </td>
                </tr>

                <tr>
                    <?php foreach(ISO_DAYS as $isoday): ?>
                        <th scope="col" title="<?php echo $isoday ?>"><?php echo substr($isoday03?></th>
                    <?php endforeach ?>
                </tr>

            </thead>

            <tbody>

            <?php

            $start_a_week 
    0;

            if(
    $daysbefore != 0) {

                echo 
    PHP_EOL '<tr>' PHP_EOL;

                for(
    $i 0$i $daysbefore$i++) {

                    
    $date date('t'mktime(000$prevmonth1$prevyear) ) - ($daysbefore $i) + 1;

                    echo 
    '<td data-day="' $date '"'
                    
    . ($start_a_week%== ' data-week="' intval(date("W"mktime(000$month$i 1$year))) . '"' false)
                    . 
    ' data-disabled';
                    
    // si présence de datas pour le widget
                    
    if($widget_datas != false && !empty($GLOBALS['widget_values'][$prevyear][$prevmonth][$i-1])) {
                        echo 
    ' data-get>' PHP_EOL;
                    } else {
                        echo 
    '>' PHP_EOL;
                    }

                    echo 
    '</td>' PHP_EOL;

                    
    $start_a_week++;
                }
            }

            for(
    $i 0$i $nbdays$i++) {

                echo 
    $start_a_week%== PHP_EOL '<tr>' PHP_EOL PHP_EOL ;

                echo
                
    '<td title="'. ($i+1) . ' ' MONTHS[$month 1] . ' ' $year .'" data-day="' . ($i+1) .'"'
                
    . (date('N'mktime(000$month$i 1$year)) > ' data-off' false)
                . ( 
    mktime(000$month$i 1$year) == $today ' data-current' false )
                . (
    $start_a_week%== ' data-week="' intval(date("W"mktime(000$month$i 1$year))) .'"' false);


                
    // si présence de datas pour le widget
                
    if($widget_datas != false && !empty($GLOBALS['widget_values'][$year][$month][$i+1])) {
                    echo 
    ' data-get=\'{"d": "'.($i+1).'", "m": "'.$month.'", "y": "'.$year.'"}\'>' PHP_EOL;
                    
    // affichage des données si présente dans $GLOBALS['widget_content']
                    
    if( !empty($GLOBALS['widget_content'][$year][$month][$i+1]) ) {
                        echo 
    '<div>';
                        foreach(
    $GLOBALS['widget_content'][$year][$month][$i+1] as $item) {
                            echo 
    '<div>' $item '</div>';
                        }
                        echo 
    '</div>';
                    }
                } else {
                    echo 
    '>';
                }

                echo 
    '</td>' PHP_EOL;

                
    $start_a_week++;
                echo 
    $start_a_week%== PHP_EOL '</tr>' PHP_EOL PHP_EOL ;
            }

            if(
    $daysafter != 0) {

                for(
    $i 0$i $daysafter$i++) {

                    echo 
    $start_a_week%== PHP_EOL '<tr>' PHP_EOL PHP_EOL ;

                    echo
                    
    '<td data-day="'
                    
    . (date('j'mktime(000$nextmonth$i+1$nextyear) )) . '"'
                    
    . ($start_a_week%== ' data-week="' intval(date("W"mktime(000$month$i 1$year))) . '"' false)
                    .
    ' data-disabled';
                    
    // si présence de datas pour le widget
                    
    if($widget_datas != false && !empty($GLOBALS['widget_values'][$nextyear][$nextmonth][$i+1])) {
                        echo 
    ' data-get>' PHP_EOL;
                    } else {
                        echo 
    '>' PHP_EOL;
                    }

                    echo 
    '</td>' PHP_EOL;

                    
    $start_a_week++;

                    echo 
    $start_a_week%== PHP_EOL '</tr>' PHP_EOL :  PHP_EOL ;
                }
            }

            
    ?>
            </tbody>

        </table>

        <script>
        (($) => {
            let func_<?php echo $uniqid ?> = function() {

                let root = $('#<?php echo $uniqid ?>'),
                    calendar = root.parent(),
                    widget = <?php echo $widget_datas != false "'" $_GET['widget'] . "'" 'false' ?>,

                    update = function(parameters) {

                        if(widget !== false) {
                            parameters = parameters + '&widget=' + widget;
                        }

                        $.get(root.data('request') + '&' + parameters, response => {
                            calendar.replaceWith(response);
                        });
                    };

                    root.find('select').on('input', function() {
                        update('m=' + root.find('[data-post="m"]:first').val() + '&y=' + root.find('[data-post="y"]:first').val())
                    });

                    root.find('thead button').on('click', function() {
                        update($.param($(this).data('values')));
                    });

                    calendar.find('img[role=button]').on('click', function() {
                        update($.param($(this).data('values')));
                    });

            };
            func_<?php echo $uniqid ?>();

        })(jQuery);
        </script>
    </div>

    Source CSS

    .calendar-wrapper {
        overflow: hidden;
        background-color: var(--background-global-overlay);
        --size: 2rem;
        --icon-size: calc(var(--size) * .5);
    }

    .calendar-widget {
        table-layout: fixed;
        width: 100%;
        font-family: Arial, sans-serif;
    }

    .calendar-widget caption {
        line-height: calc(var(--size) * 1.5);
        font-size: var(--size);
        text-align: left;
        padding-left: 0.625rem;
        font-weight: 500;
        text-transform: capitalize;
        background-color: var(--box-shadow);
    }

    .calendar-widget select {
        width: 100%;
        height: var(--size);
    }

    .calendar-widget thead tr:first-child td {
        padding: 0.4375rem 0 0 0;
    }

    .calendar-widget thead tr:first-child td:nth-child(1) {
        padding-left: 1px;
        text-align: left;
    }

    .calendar-widget thead tr:first-child td:nth-child(3) {
        text-align: center;
    }

    .calendar-widget thead tr:first-child td:nth-child(5) {
        padding-right: 1px;
        text-align: right;
    }

    .calendar-widget thead button {
        background-position: center center;
        color: transparent;
        display: inline-block;
        line-height: var(--size);
        height: var(--size);
        overflow: hidden;
        vertical-align: middle;
        width: var(--size);
        background-size: var(--icon-size) var(--icon-size);
        background-color: transparent;
        background-image:url('/svg/spritesheet.svg#i-summary-open-green');
    }

    .calendar-widget thead .calendar-prev-month {
        -webkit-transform: scaleX(-1);
        -moz-transform: scaleX(-1);
        -ms-transform: scaleX(-1);
        transform: scaleX(-1);
    }

    .calendar-widget thead button[disabled] {
        opacity: .2;
        cursor: default;
    }

    .calendar-widget img[role=button] {
        width: var(--size);
        height: var(--size);
        background-position: center center;
        background-size: var(--icon-size) var(--icon-size);
        background-image:url('/svg/spritesheet.svg#i-ical-green');
        vertical-align: middle;
    }

    @media (any-hover: hover) {
        .calendar-widget img[role=button] {
            cursor: pointer;
        }
    }

    .calendar-widget th {
        padding-right: 0.25rem;
        text-align: right;
        font-weight: unset;
        font-size: 0.75rem;
        line-height: var(--size);
        text-transform: uppercase;
    }

    .calendar-widget tbody td {
        position: relative;
        padding-bottom: calc(100% / 7);
        width: calc(100% / 7);
    }

    .calendar-widget tbody td:before {
        position: absolute;
        right: 0.125rem;
        top: 0.125rem;
        display: inline-block;
          font-size: 0.8125rem;
          text-align: center;
        border-radius: 1.25rem;
        height: 1.25rem;
        width: 1.25rem;
        line-height: 1.25rem;
        content: attr(data-day);
    }

    .calendar-widget tbody [data-current]:before {
        color: #fff;
        background-color: var(--green);
        font-weight: 700;
    }

    .calendar-widget tbody [data-week]:after {
        content: attr(data-week);
        display: inline-block;
        left: 0.25rem;
        top: 50%;
        position: absolute;
        font-size: 0.75rem;
        line-height: 0.75rem;
        opacity: .4;
        -webkit-transform: translateY(-50%);
        -moz-transform: translateY(-50%);
        -ms-transform: translateY(-50%);
        transform: translateY(-50%);
    }

    .calendar-widget [data-get] {
        background-image:url('/svg/spritesheet.svg#i-dot-cta');
        background-size: calc(var(--icon-size) / 2) calc(var(--icon-size) / 2);
        background-position: 0.3125rem 0.4375rem;
    }

    .calendar-widget [data-get]:before {
        font-weight: 700;
    }

    .calendar-widget [data-get] > div {
        position: absolute;
        top: 1.5625rem;
        right: 0.3125rem;
        bottom: 0.3125rem;
        left: 0.3125rem;
        overflow-y: auto;
    }

    .calendar-widget [data-get][data-week] > div {
        left: 1.375rem;
    }

    .calendar-widget [data-get] > div div {
        max-width: 100%;
        text-overflow: ellipsis;
        white-space: nowrap;
        font-size: 0.625rem;
        line-height: 0.6875rem;
        overflow: hidden;
    }

    /* UNCOMMENT FOR STYLE "OFF" DAYS

    .calendar-widget [data-off]:not([data-disabled]) {
        opacity: .5;
    }
    */

    .calendar-widget td[data-off]:not([data-current]):before {
        opacity: .7;
    }

    .calendar-widget tbody td[data-disabled]:before {
        opacity: .2;
    }

    .calendar-widget thead {
        box-shadow: 0 0 0 1px var(--box-shadow) inset;
    }

    .calendar-widget tbody tr td:not(:first-child) {
        box-shadow: -1px 0 0 0 var(--box-shadow) inset, 0 -1px 0 0 var(--box-shadow) inset;
    }

    .calendar-widget tbody tr td:first-child {
        box-shadow: 1px 0 0 0 var(--box-shadow) inset, -1px 0 0 0 var(--box-shadow) inset, 0 -1px 0 0 var(--box-shadow) inset;
    }
    Si vous souhaitez participer, même modestement, au maintien du site et pour son usage, vous pouvez cliquer sur le joli bouton…
    Voir aussi…
    • Vérifier un numéro de carte bancaire Vérification de carte bancaire, script de validation de carte bancaire. Contrôle de format de numéro de carte bancaire, algorithme de Luhn carte de crédit Visa, Mastercard, Maestro, CB, Carte Bancaire, Discover Card, American Express, JCB Cards
    • Owl Carousel multiple Gérer plusieurs carrousels/sliders sur une seule page avec Owl Carousel 2. Manage multiple carousels/sliders on one page with Owl Carousel 2
    • Conversion SVG/CSS Encodage SVG pour CSS compatible Sass, SVG encoder, base64 SVG pour CSS. SVG to base64, Convert SVG to Data URI for css
    • Optimisation SVG Intégration SVG via xlink/use/symbol, factorisation de symbol SVG, optimisation SVG web performance
    • SVG Responsive Utilisation des media queries internes au SVG
    • Lazy Load Intersection Observer Lazy Load avec IntersectionObserver API pour images, picture, iframe, etc. Chargement lazy load de contenu, chargement différé de contenu de page web, lazy loading script
    • Design, flux et accessibilité jQuery Responsive layout logical DOM plugin
    • Lightbox Responsive et Accessible Lightbox images jQuery Responsive accessible a11y. Automatisation d'une Lightbox multipages. Lightbox image unique ou multiple. Lightbox responsive et accessible a11y wcag
    • Tooltip (infobulle) a11y Tooltip (infobulle) compatible a11y et SEO, naviguable au clavier, accessible par lecteur d'écran. Infobulle accessible, accessible tooltip
    • Zipcode patterns Contrôle automatisé de codes postaux internationaux, script zipcode, zipcode validator
    • Widget calendrier PHP Construction d'un widget « calendrier » en PHP et asynchrone
    • Responsive background-image jQuery responsive background-image plugin, plugin jQuery responsive background images
    • Drapeaux ISO 3366 SVG Drapeaux nationaux vectoriels normés ISO alpha 2, alpha 3 et numérotation Organisation des Nations Unies (ONU). Drapeaux SVG pour internationalisation de sites multilingues.
    • Layout rigolo Une autre approche pour définir un layout de page responsive.
    • Serveur d'images placeholders Serveur d'images placeholder pour le développement front-end
    • Générateur de noms API de génération de couples prénom/nom aléatoires.
    • Générateur images TTF API de génération de textes en images.
    2020-09-23