Bonjour,
j'ai fait un formulaire d'inscription sur mon site pour une épreuve sportive. Les participants doivent notamment sélectionner leur parcours et leur taille vestimentaire via 3 listes de sélections :
Celui-ci fonctionne bien dans 99% des cas, mais bizarrement, certaines personnes terminent leur inscription en n'ayant pas sélectionné leur parcours, ni la taille du haut du corps. En revanche, la taille du bas qui est sélectionnée est toujours en taille "L".
Je précise aussi que mon formulaire se fait en plusieurs étapes, et que j'utilise la version 2.6.11 de CakePHP. Pour faire mon formulaire sur plusieurs étapes, j'ai repris ce code : http://bakery.cakephp.org/2012/09/09/Multistep-forms.html
(il commence à dater effectivement...)
Si quelqu'un saurait ce qui ne va pas dans mon formulaire, mon modèle ou mon contrôleur, merci d'avance !
Voici ma vue pour le formulaire en question (étape 2) :
<div class="row">
<div class="col-sm-12">
<div class="panel panel-primary">
<div class="panel-heading"><h2><?= __('Inscription - Etape 2: Choix du parcours et options'); ?></h2></div>
<div class="panel-body">
<?php echo $this->Form->create('Participant', array(
'inputDefaults' => array(
'div' => array('class' => 'form-group'),
'label' => array('class' => 'col-lg-4 control-label'),
'between' => '<div class="col-lg-8">',
'seperator' => '</div>',
'after' => '</div>',
'class' => 'form-control',
'error' => array('attributes' => array('wrap' => 'div', 'class' => 'col-lg-8 pull-right'))
),
'class' => 'form-horizontal',
'role' => 'form')); ?>
<?= $this->Form->input('parcours_id', array(
'label' => array('text'=>__('Parcours'), 'class'=>'col-lg-4 control-label'),
'type' => 'select',
'empty'=> false,
'options' => $parcours )); ?>
<!-- PRISE DE REPAS OPTIONNELLE -->
<?php if ($config['repas_statut'] == 1) { ?>
<?= $this->Form->input('repas', array(
'label' => array('text'=>__('Nombre de repas'), 'class'=>'col-lg-4 control-label'),
'type' => 'select',
'empty'=> false,
'options' => array(0,1,2,3,4,5,6,7,8) )); ?>
<?php } ?>
<!-- PRISE DE REPAS DESACTIVEE -->
<?php if ($config['repas_statut'] == 0) { ?>
<?= $this->Form->input('repas', array(
'type' => 'hidden',
'value' => 0
)); ?>
<?php } ?>
<!-- PRISE DE REPAS INCLUSE DANS LE PRIX -->
<?php if ($config['repas_statut'] == 2) { ?>
<?= $this->Form->input('repas', array(
'type' => 'hidden',
'value' => 1
)); ?>
<?php } ?>
<!-- REPAS INCLUS & PRISE DE REPAS SUPPLEMENTAIRE OPTIONNELLE -->
<?php if ($config['repas_statut'] == 3) { ?>
<div class="form-group">
<div class="col-lg-4 control-label pull-left"><?= $this->Form->label('repasnormal', __('Repas participant')); ?></div>
<div class="col-lg-8 control-label pull-left"><?= $this->Form->label('nbrepasnormal',__('Inclus dans le prix')); ?></div>
</div>
<?= $this->Form->input('repas', array(
'label' => array('text'=>__('Repas supplémentaire').' ('.$config['tarif_repas_supp'].'€)', 'class'=>'col-lg-4 control-label'),
'type' => 'select',
'empty'=> false,
'options' => array(1=>'0',2=>'1',3=>'2',4=>'3',5=>'4',6=>'5') )); ?>
<?php } ?>
<?= $this->Form->input('taille_tshirt', array(
'label' => array('text'=>__('Taille de maillot'), 'class'=>'col-lg-4 control-label'),
'type' => 'select',
'empty'=> false,
'options' => $tailles )); ?>
<?= $this->Form->input('taille_bas', array(
'label' => array('text'=>__('Taille de cuissard'), 'class'=>'col-lg-4 control-label'),
'type' => 'select',
'empty'=> false,
'options' => $tailles )); ?>
<?php foreach ($options as $key => $option) { ?>
<?= $this->Form->input('Participant.Option.', array(
'label' => array('text'=>$option, 'class'=>'col-lg-4 control-label'),
'type' => 'select',
'empty'=> false,
'options' => array('0' => __('non'), $key => __('oui')))); ?>
<?php } ?>
<!-- CATEGORIE HIM & HER -->
<?php if ($config['himher'] == 1) { ?>
<?= $this->Form->input('himher', array(
'label' => array('text'=>__('Participer en catégorie Him&Her'), 'class'=>'col-lg-4 control-label'),
'type' => 'select',
'options' => array('0' => __('non'), '1' => __('oui')))); ?>
<?= $this->Form->input('himher_binome', array('label' => array('text'=>__('Pour les "Him&Her", indiquez le nom de votre binôme'), 'class'=>'col-lg-4 control-label') )); ?>
<?php } ?>
<?= $this->Form->input('accept_info', array(
'label' => array('text'=>__("J'accepte de recevoir des email de la part de l'organisateur"), 'class'=>'col-lg-4 control-label'),
'type' => 'select',
'empty'=> false,
'options' => array('0' => __('non'), '1' => __('oui')))); ?>
<?= $this->Form->input('accept_info_partenaires', array(
'label' => array('text'=>__("J'accepte de recevoir des email de la part de partenaires"), 'class'=>'col-lg-4 control-label'),
'type' => 'select',
'empty'=> false,
'options' => array('0' => __('non'), '1' => __('oui')))); ?>
</div>
<div class="panel-footer">
<?= $this->Html->link(__('<< Retour'),
array('action' => 'msf_step', $params['currentStep'] -1),
array('class' => 'btn btn-primary')
); ?>
<?= $this->Form->end(array(
'label' => __('Suite >>'),
'class' => 'btn btn-primary pull-right',
'div' => false
)); ?>
</div>
</div>
</div>
</div>
Voici le controleur associé :
public function msf_index() {
$this->Session->delete('form');
}
/**
* this method is executed before starting the form and retrieves one important parameter:
* the form steps number
* you can hardcode it, but in this example we are getting it by counting the number of files that start with msf_step_
*/
public function msf_setup() {
App::uses('Folder', 'Utility');
$usersViewFolder = new Folder(APP.'View'.DS.'Participants');
$steps = count($usersViewFolder->find('msf_step_.*\.ctp'));
$this->Session->write('form.params.steps', $steps);
$this->Session->write('form.params.maxProgress', 0);
$this->redirect(array('action' => 'msf_step', 1));
}
/**
* this is the core step handling method
* it gets passed the desired step number, performs some checks to prevent smart users skipping steps
* checks fields validation, and when succeding, it saves the array in a session, merging with previous results
* if we are at last step, data is saved
* when no form data is submitted (not a POST request) it sets this->request->data to the values stored in session
*/
public function msf_step($stepNumber) {
$this->loadModel('Config');
$config = $this->Config->find('list', array(
'fields' => array('nom', 'valeur')
));
$this->set('config', $config);
if ($config['inscriptions_statut'] == 0) {
$this->redirect(array('controller'=>'participants', 'action'=>'indisponible'));
}
if ($stepNumber == 1) {
$this->loadModel('Departement');
$this->set('departements', $this->Departement->find('list', array(
'fields' => array('id', 'nom')
)));
$this->loadModel('Pays');
$this->set('pays', $this->Pays->find('list', array(
'fields' => array('id', 'nom')
)));
$this->loadModel('Federation');
$this->set('federations', $this->Federation->find('list', array(
'fields' => array('id', 'nom'),
'recursive' => 0
)));
}
if ($stepNumber == 2) {
$this->loadModel('Parcours');
$this->set('parcours', $this->Parcours->find('list', array(
'fields' => array('id', 'nom'),
'conditions'=> array('statut' => 1)
)));
$this->loadModel('Option');
$this->set('options', $this->Option->find('list', array(
'fields' => array('id', 'label'),
'recursive' => 0,
'conditions'=> array('statut' => 1))));
$this->Session->delete('form.data.Participant.Option');
$this->set('tailles', array('XXL'=>'XXL','XL'=>'XL','L'=>'L','M'=>'M','S'=>'S','XS'=>'XS'));
//Si le participant a choisi un pays différent de la France, alors son département devient "Etranger"
if ($this->Session->read('form.data.Participant.pays_id') != 1) {
$this->Session->write('form.data.Participant.departement_id', 103);
}
//VERIFICATION DU CODE PROMO ET CALCUL DE LA REMISE
//Le calcul de la remise se fait dans le formulaire précédent afin de revenir dans le formulaire en cours, et non être dirigé sur la suite de l'inscription.
if (isset($this->request->data['Participant']['code_promo'])) {
$this->loadModel('Codepromo');
$code_promo = $this->Codepromo->find('first', array(
'conditions' => array(
'nom' =>$this->request->data['Participant']['code_promo'],
'fin >='=>date('Y-m-d') )
));
if(!empty($code_promo)) {
$remise = $code_promo['Codepromo']['remise'];
} else {
$remise = 0;
}
// La valeur de la remise est stockée en variable de session
$this->Session->write('form.data.Participant.remise', $remise);
}
}
if ($stepNumber == 3) {
//Modèles utilisés pour l'étape 3
$this->loadModel('Parcours');
$parcoursSelected = $this->Parcours->find('first', array(
'conditions' => array('Parcours.id' => $this->Session->read('form.data.Participant.parcours_id'))
));
$this->set('parcoursSelected', $parcoursSelected);
$this->loadModel('Option');
$this->set('options', $this->Option->find('all', array(
'fields' => array('id', 'nom', 'tarif'),
'recursive' => 0,
'conditions'=> array('statut' => 1,
'id' => $this->Session->read('form.data.Participant.Option')
))));
$prixOptions = 0;
$prixOptions = $this->Option->find('first', array(
'fields' => array('SUM(tarif)'),
'recursive' => 0,
'conditions'=> array('statut' => 1,
'id' => $this->Session->read('form.data.Participant.Option')
)));
$prixOptions = (is_null($prixOptions[0]['SUM(tarif)']))?0:$prixOptions[0]['SUM(tarif)'];
$this->set('prixOptions', $prixOptions);
//Calcul du tarif actuel
if ( date('Y-m-d') >= $config['date_promo'])
{ $prixParcours = $parcoursSelected['Parcours']['tarif_normal']; }
else
{ $prixParcours = $parcoursSelected['Parcours']['tarif_promo']; }
$this->set('prixParcours', $prixParcours);
//Calcul du tarif des repas
$repasNormal = $this->Session->read('form.data.Participant.repas') >= 1 ? 1 : 0;
$repasSupp = $repasNormal == 1 ? $this->Session->read('form.data.Participant.repas') - 1 : 0;
$this->set('repasNormal', $repasNormal);
$this->set('repasSupp', $repasSupp);
//Calcul de la remise ou majoration pour la licence sélectionnée
$this->loadModel('Federation');
$prixFederation = $this->Federation->find('first', array(
'fields' => array('modif_tarif'),
'recursive' => 0,
'conditions'=> array('id' => $this->Session->read('form.data.Participant.federation_id')
)));
$this->set('remise_majoration', $prixFederation['Federation']['modif_tarif']-$this->Session->read('form.data.Participant.remise'));
if ($this->Session->read('form.data.Participant.remise')!= 0) {
$this->Session->setFlash(__('Code promotionnel valide !'), 'default', array(), 'message_code_promo');
//$this->Flash->set('Code promotionnel valide!');
} else {
if ($this->Session->read('form.data.Participant.code_promo')!= '') {
$this->Session->setFlash(__('Code promotionnel invalide !'), 'default', array(), 'message_code_promo');
}
}
$this->loadModel('Codepromo');
$this->set('codepromo', $this->Codepromo->find('count', array(
'conditions' => array('fin >='=>date('Y-m-d'))
)));
//Calcul du tarif total
//$prixTotal = $prixParcours+$repasNormal+$repasSupp+$prixOptions[0]['SUM(tarif)'];
//$this->set('prixTotal', $prixTotal);
}
if ($stepNumber == 4) {
$this->loadModel('Config');
/* Ne sert à rien finalement
$config = $this->Config->find('first');
$this->set('config', $config);
*/
/*
$this->loadModel('Paypal');
$paypal = $this->Paypal->find('first');
$this->set('paypal', $paypal);
*/
$paypal = $this->Config->find('list', array(
'fields' => array('nom', 'valeur'),
'conditions' => array('categorie' => 'paypal'),
));
$this->set('paypal', $paypal);
}
if (!file_exists(APP.'View'.DS.'Participants'.DS.'msf_step_'.$stepNumber.'.ctp')) {
$this->redirect('/participants/msf_index');
}
$maxAllowed = $this->Session->read('form.params.maxProgress') + 1;
if ($stepNumber > $maxAllowed) {
$this->redirect('/participants/msf_step/'.$maxAllowed);
} else {
$this->Session->write('form.params.currentStep', $stepNumber);
}
if ($this->request->is('post')) {
$this->Participant->set($this->request->data);
if ($this->Participant->validates()) {
$prevSessionData = $this->Session->read('form.data');
$currentSessionData = Hash::merge( (array) $prevSessionData, $this->request->data);
/**
* if this is not the last step we replace session data with the new merged array
* update the max progress value and redirect to the next step
*/
if ($stepNumber < $this->Session->read('form.params.steps')) {
$this->Session->write('form.data', $currentSessionData);
$this->Session->write('form.params.maxProgress', $stepNumber);
$this->redirect(array('action' => 'msf_step', $stepNumber+1));
} else {
//On recherche les participants ayant les mêmes informations d'inscription et n'ayant pas encore payé pour les supprimer (les doublons)
$this->Participant->deleteAll(array(
'Participant.nom' => $currentSessionData['Participant']['nom'],
'Participant.prenom' => $currentSessionData['Participant']['prenom'],
'Participant.date_naissance' => $currentSessionData['Participant']['date_naissance']['year'].'-'.$currentSessionData['Participant']['date_naissance']['month'].'-'.$currentSessionData['Participant']['date_naissance']['day'],
'Participant.paiement' => 0));
//Sauvegarde du nouveau participant
$this->Participant->save($currentSessionData);
//Récupération des options sélectionnées
$this->loadModel('Option');
$options = $this->Option->find('all', array(
'fields' => array('id', 'nom', 'tarif'),
'recursive' => 0,
'conditions'=> array('statut' => 1,
'id' => $this->Session->read('form.data.Participant.Option')
)));
//Sauvegarde des options sélectionnées avec les données
$this->loadModel('OptionParticipant');
foreach ($options as $option) {
$this->Participant->OptionParticipant->create();
$this->Participant->OptionParticipant->set('option_id', $option['Option']['id']);
$this->Participant->OptionParticipant->set('participant_id', $this->Participant->id);
$this->Participant->OptionParticipant->set('nom', $option['Option']['nom']);
$this->Participant->OptionParticipant->set('prix', $option['Option']['tarif']);
$this->Participant->OptionParticipant->save();
}
//Si la personne choisi le paiement par Paypal
if (isset($this->request->data['Participant']['type_paiement']))
{
//PAIEMENT PAR PAYPAL
if ($this->request->data['Participant']['type_paiement'] == 1)
{
$this->loadModel('Transaction');
$prixTotal = $this->Session->read('form.data.Participant.prix');
$vars = "&SHIPTONAME=".urlencode($this->Session->read('form.data.Participant.prenom')." ".$this->Session->read('form.data.Participant.nom')).
"&SHIPTOSTREET=".urlencode($this->Session->read('form.data.Participant.adresse')).
"&SHIPTOCITY=".urlencode($this->Session->read('form.data.Participant.ville')).
"&SHIPTOZIP=".urlencode($this->Session->read('form.data.Participant.code_postal')).
"&EMAIL=".urlencode($this->Session->read('form.data.Participant.mail'));
$url = $this->Transaction->requestPaypal($prixTotal, "Inscription", 'action=subscribe',$vars, $this->Participant->id, $paypal);
if($url){
$this->redirect($url);
}
}
//PAIEMENT PAR CHEQUE
if ($this->request->data['Participant']['type_paiement'] == 2)
{
$this->redirect(array('controller'=>'Participants', 'action'=>'pdf', 'ext' => 'pdf', $this->Participant->id));
}
}
//$this->redirect('/participants/msf_index');
}
}
} else {
$this->request->data = $this->Session->read('form.data');
}
/**
* here we load the proper view file, depending on the stepNumber variable passed via GET
*/
$this->render('msf_step_'.$stepNumber);
}
Le modèle participant :
<?php
class Participant extends AppModel {
public $validate = array(
'nom' => array(
'rule' => "/^([a-zA-Z'àâéèêôùûçÀÂÉÈÔÙÛÇ[:blank:]-]{1,30})$/", // ou bien : array('nomRegle', 'parametre1', 'parametre2' ...)
'allowEmpty' => false,
'message' => "Le nom utilisé n'est pas valide."
),
'prenom' => array(
'rule' => "/^([a-zA-Z'àâéèêôùûçÀÂÉÈÔÙÛÇ[:blank:]-]{1,30})$/", // ou bien : array('nomRegle', 'parametre1', 'parametre2' ...)
'allowEmpty' => false,
'message' => "Le prénom utilisé n'est pas valide."
),
'mail' => array(
'rule' => 'email',
'message' => "L'adresse e-mail indiquée n'est pas valide."
),
'parcours_id' => array(
'rule' => 'notEmpty',
'allowEmpty' => false
),
'ville' => array(
'rule' => 'notEmpty',
'allowEmpty' => false
),
'type_paiement' => array(
'rule' => 'notEmpty'
),
'repas' => array(
'rule' => 'notEmpty'
),
'accept_reglement' => array(
'rule' => '[1]',
'message' => 'Vous devez accepter le règlement pour continuer votre inscription.'
),
'accept_certificat' => array(
'rule' => '[1]',
'message' => "Vous devez accepter cette clause pour continuer votre inscription."
)
);
public $hasMany = array(
'OptionParticipant'
);
public $belongsTo = array(
'Federation' => array('className' => 'Federation',
'foreignKey' => 'federation_id'
),
'Parcours' => array('className' => 'Parcours',
'foreignKey' => 'parcours_id'
),
'Pays' => array('className' => 'Pays',
'foreignKey' => 'pays_id'
),
'Departement' => array('className' => 'Departement',
'foreignKey' => 'departement_id'
)
);
var $virtualFields = array(
'age' => "YEAR(NOW())-YEAR(Participant.date_naissance)"
);
}
?>
PS: J'ai envoyé des messages aux personnes en question, mais j'attends toujours les réponses. Je vous dirai si j'en reçois.
As-tu penser à passer tes champs en required ?
Ca devrait ressembler à ceci :
<?= $this->Form->input('champ', array('label' => 'Mon Champ', 'required' => true); ?>
Ainsi, il devrait absolument sélectionner un élément.
Bonjour Romram,
déjà les 'Loïc' et les 'Laëtitia' ne passent pas la regex ; dommage pour eux
sinon plus globalement (j'ai pas trop cherché), as-tu vérifié l'encodage du script par rapport à celui de la page html ?
Bien vu Huggy ! Ce sera déjà un bug de moins. Sinon, je ne pense pas que ça vienne de l'encodage, car le problème devrait surtout être pour les champs de texte dans ce cas. Or, toutes autres informations sont bien enregistrées, à part ces 3 champs. Je me demande si ce n'est pas une erreur due à certains navigateurs, qui permettraient de ne rien sélectionner dans les select. Pourtant, j'imagine que CakePHP devrait s'en occuper. A moins que j'ai mal écrit mon modèle ou mes input...
Deux personnes qui ont rencontré ce problème se sont ré-inscrites sans soucis ce matin. Finalement, ca rend l'erreur encore plus insoluble. A moins qu'ils aient changé d'ordinateur ou qu'il y ait eu une panne sur le serveur au moment de leur inscription.
Bonne idée Kareylo, j'avais mis 'empty'=> false. Mais j'imagine que ça n'est pas bon lorsqu'il s'agit d'un select.
Je vais faire ça et vérifier.
Merci à Huggy et Kareylo. Je vais marquer le sujet comme résolu, bien que je ne puisse pas vraiment tester si c'est bien le cas. En attendant, ça fonctionne bien sur mes ordis et sur les 3 principaux navigateurs, donc ça devrait être bon!