Archives de l’auteur : dwmjf

Photos en base de données

Type de champs à utiliser

On devra disposer au minimum du type de photo : JPEG, GIF, PNG, BMP ... et du champ contenant les données binaires de la photo.

Dans une base MySql, pour les données binaire, on dispose des types suivants :

  • tinyblob : 255 octets
  • blob : 64 Ko
  • mediumblob : 16 Mo
  • longblob : 4 Go

Création de la table

CREATE TABLE `photos` (
    `id` int NOT NULL,
    `phototype` varchar(25) NOT NULL DEFAULT 'JPEG',
    `photodata` blob NOT NULL
);
ALTER TABLE `photos`
  ADD PRIMARY KEY (`id`);

Afin de lier la photo à une fiche d'un individu, d'un objet ou d'un paysage ... contenue dans une table datas, on aura intérêt à déclarer une foreign key

ALTER TABLE `datas`
  ADD CONSTRAINT `photos_ibfk_1` FOREIGN KEY (`id`) REFERENCES `datas` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

Formulaire d'envoi d'une photo

Le formulaire doit être celui d'un envoi de fichier :

<form name="formphoto" enctype="multipart/form-data" method="post">
    <input name="id" type="hidden" value="">
    <label>Choisissez le fichier image (JPEG, GIF ou PNG)
    <input name="filephoto" type="file"></label>
    <input name="submit" type="submit" value="Envoyer la photo">
</form>

Réception coté serveur

Ne pas oublier que les variables se récupèreront dans la variable globale $__POST sauf le fichier photo qui sera dans la variable globale $__FILES.

$ret =is_uploaded_file($__FILES['filephoto']['tmp_name'];
if (!$ret) {
    throw new Exception('Problème de transfert.');
}
$filesize = $__FILES['filephoto']['size'];
if ($filesize > SIZE_MAX) {
    throw new Exception('Fichier photo trop gros');
}
$phototype = $__FILES['filephoto']['type'];
$photodata = file_get_contents($__FILES['filephoto']['tmp_name']);
$id = $__POST['id'];
// si on travaille en https, il faut ajouter le context dans file_get_contents()
$photodata = addslashes($photodata); // indispensable pour enregistrer dans MySql
// procédure classique d'enregistrement des données $id, $phototype, $photodata

Affichage de la photo dans une page

Comme il n'y a pas de fichier, on doit envoyer les données binaires de la photo, encodées base64. On pourra utiliser la fonction suivante :

function base64_encode_image($imgdata, $imgtype) {
    return 'data:image/' . $imgtype .';base64,' . stripslashes($imgdata);
}

et on affectera ce résultat à la propriété src de la balise <img> comme ceci :

<img src="<?= base64_encode_image($photodata, $phototype);?>">

School bus manager


Le logiciel full web de gestion des transports scolaires développé par le cabinet d'études DAFAP informatique, publié sous licence CeCILL, licence française de logiciels libres.

  • Gestion du réseau de transport avec cartographie interactive
  • Inscription en ligne et paiement en ligne pour les parents qui le souhaitent
  • Inscription par fiche papier et paiement par chèque ou espèces, gestion des encaissements
  • Intégration des barêmes de tarifs et des règles de droits au transport
  • Édition des cartes de transport, éditions d'états personnalisés, statistiques
  • Gestion de pièces jointes aux formulaires d'inscription
  • Échange d'informations entre les organisateurs, les parents, les transporteurs et les établissements scolaires

Accéder au site démo.

En 2019, 29 ans d'expérience dans le domaine de la gestion des transports scolaires.

ZF3 – Changer le module par défaut

Dans Zendframework3, dans l'exemple fourni (Skeleton Application), le nom du module par défaut est `Application`. Comment en changer ?

  1. Dans le fichier `/composer.json` remplacer `Appliciation` par le nom de votre module dans les sections `autoload` et `autoload-dev`, puis lancer `composer update` pour mettre à jour les fichiers `/vendor/composer/autoload_...`
  2. Dans le fichier `/config/modules.config.php` remplacer `Application` par le nom de votre module. Ne pas oublier de laisser dans la liste des modules à charger, avant vos modules, le module `Zend\Router` qui sera nécessaire.
  3. Dans le dossier de votre module, écrire le fichier `config/module.config.php` sur le modèle de `application/config/module.config.php` en remplaçant `application` par le nom de votre module (en particulier dans le tableau `template_map`).
  4. Dans votre module par défaut, vous devez définir les dossiers `view/error` et `view/layout`. Ces dossiers doivent contenir respectivement les fichiers
    • `index.phtml` et `404.phtml`
    • `layout.phtml`

Windows 10 et la couleur sur la console de commande

console-couleur-texte-fondDans l'invite de commande de Windows 10, la documentation précise comment modifier la couleur du texte ou de l'arrière plan.

Mais comment interpréter la couleur adressée par séquences ANSI lors de l'exécution de certaines commandes comme par exemple :

 

php composer.phar self-update
console-couleur-exemple

Une solution consiste à utiliser l'utilitaire ansicon que l'on peut trouver avec sa documentation à l'adresse https://github.com/adoxa/ansicon

Un lien permet d'accéder au téléchargement du binaire (http://adoxa.altervista.org/ansicon/)

SSL certificate problem: unable to get local issuer certificate

Erreur fréquemment obtenue lorsqu'on utilise CURL pour interroger un service par son API depuis son serveur local de développement.

Solution 1 : modifier le paramétrage du fichier php.ini

Dans le fichier php.ini, il suffit d'indiquer ce qui suit

[curl]
 ; A default value for the CURLOPT_CAINFO option. This is required to be an
 ; absolute path.
 curl.cainfo = "C:\Users\admin\cer\cacert.pem"

et de placer dans le dossier un fichier cacert.pem à l'emplacement indiqué. Le chemin doit être absolu. Voici un fichier cacert compressé au format zip.

Solution 2 : indiquer à curl l'emplacement du fichier cacert.pem

Lors de l'utilisation, au moment du paramétrage de CURL, indiquer la ligne :

curl_setopt($ch, CURLOPT_CAINFO,  getcwd().'/cert/cacert.pem');

Attention, le chemin doit être absolu et le fichier doit être présent.

La mauvaise solution

Eviter de désactiver le contrôle des certificats. L'option  CURLOPT_SSL_VERIFYPEER permet d'arrêter la vérification mais provoque une faille de sécurité lorsque le code est installé sur un serveur de production.

Avertissement des transporteurs par mail

Fonctionnalité

Lorsque la campagne d'inscription est terminée, les transporteurs récupèrent les listes d'élèves par circuit depuis le portail des transporteurs.

Durant l'année scolaire, il va y avoir quelques modifications d'inscriptions dues à de nouveaux arrivants, des départs ou des déménagements. Le service de transport enregistre ces modifications.

Afin d'attirer l'attention des transporteurs, un mail leur est envoyé à la fin de la journée si des modifications d'inscription les concernant ont eu lieu. Si les transporteurs concernés ont autorisé des utilisateurs (administrateurs, chauffeurs, responsables ...) à accéder au portail des transporteurs, ces utilisateurs recevront également le mail.

alerte-transporteur

Cliquez sur l'image pour voir le modèle de mail.

Installation technique

Pour mettre en place cet envoi, une tâche cron est planifiée sur le serveur :

cron-transporteur

Cliquez sur l'image

Cette tâche s'exécute une fois par jour, à 18h. Elle envoie un compte-rendu sur la boite de contrôle sbm de dafap.

 

Voici ce que contiennent ces comptes-rendus :

S'il n'y a pas eu de modification d'inscription :

Durant les dernières 24 heures, il n'y a pas eu de modification d'inscription. Par conséquent, aucun mail n'a été envoyé.

S'il y a eu des modifications :

Suite aux modifications d'inscription qui ont eu lieu durant les dernières 24 heures les transporteurs suivants ont reçu un email d'information.
 - TUB
 - SAUTEREL

Eclipse ne répond pas ?

Je travaille sous Windows 7. Suite au chargement d'un fichier trop gros et à un traitement (recherche dans les fichiers), Eclipse s'est planté.
En réalité, il semble que le problème vienne de Java SE (message: Java Platform SE binary ne répond pas).
La solution consiste à aller dans le panneau de configuration, Java, onglet Général, bouton Paramètres puis bouton Supprimer les fichiers.

Zend\Form\Element\Time

Présentation de l'élément

Zend\Form\Element\Time permet de créer grâce à l'aide de vue Zend\Form\View\Helper\FormTime une balise <input type="time">  en HTML5. Cet élément ajoute les filtres et validateurs conformes aux spécifications HTML5 de ce type.

Utilisation

L'exemple de la documentation de ZF2 ne fonctionne pas. En effet, il faut rendre compatible les formats des données avec le pas spécifié dans l'attribut step. En particulier, si step = 60, le contrôle de saisie dans un navigateur ne présentera que les heures et les minutes. Aussi, il faut que le format indiqué soit 'H:i' et que les attributs min et max soient aussi de même format.

Voici donc quelques exemples qui doivent fonctionner :

Pour step=60

use Zend\Form\Form;

$form = new Form('my-form');
$form->add(array(
     'type' => 'Zend\Form\Element\Time',
     'name' => 'time',
     'options' => array(
             'label' => 'Time',
             'format' => 'H:i'
     ),
     'attributes' => array(
             'min' => '00:00',
             'max' => '23:59',
             'step' => '60', // pas de 1 minute ; on ne voit pas les secondes.
     )

Pour step=30

use Zend\Form\Form;

$form = new Form('my-form');
$form->add(array(
     'type' => 'Zend\Form\Element\Time',
     'name' => 'time',
     'options' => array(
             'label' => 'Time',
             'format' => 'H:i:s'
     ),
     'attributes' => array(
             'min' => '00:00:00',
             'max' => '23:59:00',
             'step' => '30', // pas de 30 secondes ; on voit les secondes
     )

Création d’un état avec niveau de regroupement

Dans un rapport Microsoft Access, il est possible de créer des niveaux de regroupement sur des champs d'une table ou d'une requête. Comment réaliser ces mêmes états sous ZF2 / MySql avec TcPdf ?

Exemple concret

Soit 2 tables, `eleves` et `tarifs` définies de la manière suivante :

Table `eleves` :

CREATE TABLE IF NOT EXISTS `t_eleves` (
 `eleveId` int(11) NOT NULL AUTO_INCREMENT,
 `tarifId` int(11) NOT NULL DEFAULT "0"',
 `nom` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
 `prenom` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
 `adresseL1` varchar(38) COLLATE utf8_unicode_ci NOT NULL,
 `adresseL2` varchar(38) COLLATE utf8_unicode_ci NOT NULL,
 `codePostal` varchar(5) COLLATE utf8_unicode_ci NOT NULL,
 `commune` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
 `dateN` date NOT NULL,
 `dateCreation` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `dateModification` datetime NOT NULL DEFAULT '1900-01-01 00:00:00',
 `dateInscription` datetime NOT NULL DEFAULT '1900-01-01 00:00:00',
 `inscrit` tinyint(1) unsigned NOT NULL DEFAULT '1',
 `selection` tinyint(1) unsigned NOT NULL DEFAULT '0',
 PRIMARY KEY (`eleveId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Table `tarifs` :

CREATE TABLE IF NOT EXISTS `t_tarifs` (
 `tarifId` int(11) NOT NULL AUTO_INCREMENT,
 `montant` decimal(10,2) NOT NULL DEFAULT '0.00',
 `nom` varchar(48) COLLATE utf8_unicode_ci NOT NULL,
 `rythme` int(4) NOT NULL DEFAULT '1',
 `grille` int(4) NOT NULL DEFAULT '1',
 PRIMARY KEY (`tarifId`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

On souhaite créer un état qui affiche la liste des élèves par tarif, avec un sous-total à la fin de chaque groupe de tarifs, et le total général à la fin de l'état.

 Création de la requête

On a 3 parties dans la requête :

  • le détail de chaque groupe contenant les élèves
  • le pied de chaque groupe contenant le nombre d'élèves du groupe et le montant total du groupe
  • la marge du bas contenant le nombre total d'élèves et le total général

On codera 'd'  pour 'détail', 'p' pour 'pied', 'g' pour 'groupe', 'm' pour 'marge'. On remarquera que le codage respecte l'ordre d'affichage :

  • 'g' avant 'm'
  • 'd' avant 'p'

On obtient un résultat convenable par :

SELECT nom, prenom, montant
FROM (
 SELECT 'g' c1, t.tarifId c2, 'd' c3, e.nom, e.prenom, t.montant 
 FROM t_eleves e INNER JOIN t_tarifs t ON e.tarifId=t.tarifId
UNION
 SELECT 'g' c1, t.tarifId c2, 'p' c3, concat('Pour ', t.nom), count(eleveId), sum(t.montant) 
 FROM t_eleves e INNER JOIN t_tarifs t ON e.tarifId=t.tarifId GROUP BY t.tarifId
UNION
 SELECT 'm' c1, '' c2, '' c3, 'Total général', count(eleveId), sum(t.montant) 
 FROM t_eleves e INNER JOIN t_tarifs t ON e.tarifId=t.tarifId
) u 
ORDER BY c1,c2,c3,1,2

Pour la mise en page, on aura sans doute intérêt à afficher les colonnes c1, c2 et c3.

etat-niveau-regroup1

avec PhpMyAdmin

 

ZF2 et utilisation de Tcpdf

Pour utiliser Tcpdf sous ZF2, j'ai créé un module basé sur le principe suivant :

  • La méthode qui souhaitera créer un pdf lancera un événement contenant tous les paramètres et toutes les données permettant de créer le pdf.
  • un écouteur (listener) sera chargé de surveillé la présence de ces événements et déclenchera la création

Voici le schéma d'organisation de mon module :

DafapTcpdf

Schéma d'organisation du module

La configuration de Tcpdf est basée sur des constantes. Elle se fera dans le fichier config/autoload/tcpdf-config.global.php de l'application.

Voici ci-dessous un exemple d'utilisation de mon module dans un controller. Il s'agit de créer un listing à partir d'une sélection dans une table de tarifs. La sélection est définie dans l'action tarifListeAction() à partir d'un formulaire criteres_form et ses paramètres sont conservés en session.

/**
* envoie un evenement contenant les paramètres de création d'un document pdf
* (le listener DafapTcpdf\Listener\PdfListener lancera la création du pdf)
* Il n'y a pas de vue associée à cette action puisque la response html est créée par \TCPDF
*/
public function tarifPdfAction()
{
  $currentPage = $this->params('page', 1);
  $criteres_form = new CriteresForm('tarifs');
  $criteres_obj = new ObjectDataCriteres($criteres_form->getElementNames());
  $session = new SessionContainer(str_replace('pdf', 'liste', $this->getSessionNamespace()));
  if (isset($session->criteres)) {
      $criteres_obj->exchangeArray($session->criteres);
  }
  $tableTarifs = $this->getServiceLocator()->get('Sbm\Db\Table\Tarifs');
  $data = array();
  foreach ($tableTarifs->fetchAll($criteres_obj->getWhere(), 'nom') as $row) {
    $data[] = array(
        $row->nom,
        $row->montant,
        $row->rythme,
        $row->grille,
        $row->mode
    );
  }
  $call_pdf = $this->getServiceLocator()->get('RenderPdfService');
  $call_pdf->setData($data)
  ->setHead(array('Nom', 'Montant', 'Rytme', 'Grille', 'Mode'))
  ->setPdfConfig( array(
      'title' => 'Liste des tarifs',
      'header' => array(
      'title' => 'Liste des tarifs',
      'string' => 'éditée par School Bus Manager le ' . date('d/m/Y à H:i')
  )))
  ->setTableConfig(array(
      'tbody' => array(
          'cell' => array(
              'txt_precision' => array(0, 2, 0, 0, 0)
      )),
      'column_widths' => array(64, 30, 30, 20, 36)
  ))
  ->renderPdf();
  $this->flashMessenger()->addSuccessMessage("Création d'un pdf.");
}

DafapTcpdf-2

 

Cliquez sur cette image pour voir le résultat.

 

 

Ce module est disponible sur github/dafap.