Вы находитесь на странице: 1из 14

Tutoriel pour apprendre à

créer une application Laravel

Le contact

Par Maurice Chavelli

Date de publication : 28 novembre 2017

Ce cours fait partie d'une série de tutoriels qui se charge de vous apprendre à créer une
application avec Laravel. À travers ce cours, vous allez apprendre à gérer le formulaire de
contact.

Commentez
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

I - Introduction..............................................................................................................................................................3
II - Le statut................................................................................................................................................................. 3
III - Le formulaire......................................................................................................................................................... 5
III-A - Affichage.......................................................................................................................................................5
III-B - Traitement.....................................................................................................................................................6
IV - L'administration..................................................................................................................................................... 8
IV-A - Le tableau de bord...................................................................................................................................... 8
IV-B - Affichage des messages........................................................................................................................... 10
IV-C - Marquer le message..................................................................................................................................12
IV-D - Supprimer le message.............................................................................................................................. 13
V - Remerciements.................................................................................................................................................... 14

-2-
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

I - Introduction

Pour comprendre comment est organisée l'application je vais prendre quelque chose de simple : le formulaire de
contact auquel le visiteur accède à partir du menu :

Nous allons suivre le cheminement de la requête à partir du clic sur l'option du menu, jusqu'à l'enregistrement du
message et nous verrons ensuite la gestion de ce message par l'administrateur. Cela donnera une vision globale
de l'application.

II - Le statut

Le menu s'adapte automatiquement selon que l'on a un simple visiteur, un utilisateur connecté, un rédacteur ou
un administrateur. On ne va évidemment pas proposer un formulaire de contact à ces deux dernières catégories.
Comment cela est-il géré ? Regardons le code de cet item dans la vue resources/views/front/template.blade.php :

1. @if(session('statut') == 'visitor' || session('statut') == 'user')


2. <li {!! Request::is('contact/create') ? 'class="active"' : '' !!}>
3. {!! link_to('contact/create', trans('front/site.contact')) !!}
4. </li>
5. @endif

On a dans la session une clé statut qui informe sur le statut. Ici, on teste si on a un simple visiteur ou un utilisateur de
base. Dans les deux cas on fait apparaître l'option. D'autre part on lui ajoute la classe active si la requête correspond
à cet item, pour le distinguer dans le menu.

Pour gérer le statut on a un service (app/Services/Statut.php) :

1. <?php namespace App\Services;


2.
3. use Auth;
4.
5. class Statut {
6.
7. /**
8. * Set the login user statut

-3-
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

9. *
10. * @param App\Models\User $user
11. * @return void
12. */
13. public function setLoginStatut($user)
14. {
15. session()->put('statut', $user->role->slug);
16. }
17.
18. /**
19. * Set the visitor user statut
20. *
21. * @return void
22. */
23. public function setVisitorStatut()
24. {
25. session()->put('statut', 'visitor');
26. }
27.
28. /**
29. * Set the statut
30. *
31. * @return void
32. */
33. public function setStatut()
34. {
35. if(!session()->has('statut'))
36. {
37. session()->put('statut', Auth::check() ? Auth::user()->role->slug : 'visitor');
38. }
39. }
40.
41. }

Lorsqu'une requête arrive le middleware App est activé. On y trouve le déclenchement d'un événement :

event('user.access');

Or la méthode setStatut du service écoute cet événement :

1. protected $listen = [
2. ...
3. 'user.access' => ['App\Services\Statut@setStatut']
4. ];

On définit alors le statut dans cette méthode.

On a aussi besoin de définir le statut lorsque quelqu'un se connecte, ce qui est réalisé dans la méthode
setLoginStatut.

Et finalement on doit aussi fixer le statut de visiteur en cas de déconnexion, ce qui est réalisé par la méthode
setVisitorStatut.

Ces deux méthodes écoutent les événements correspondants :

1. protected $listen = [
2. 'auth.login' => ['App\Services\Statut@setLoginStatut'],
3. 'auth.logout' => ['App\Services\Statut@setVisitorStatut'],
4. ...
5. ];

-4-
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

III - Le formulaire

III-A - Affichage

La route contact/create aboutit à la fonction create du contrôleur ContactController :

1. /**
2. * Show the form for creating a new resource.
3. *
4. * @return Response
5. */
6. public function create()
7. {
8. return view('front.contact');
9. }

Ici on se contente de retourner la vue du formulaire. Notez qu'aucun middleware ne protège cette fonction, ce qui
serait vraiment superflu.

La vue est bien rangée dans le dossier du front-end :

Avec ce code :

1. @extends('front.template')
2.
3. @section('main')
4. <div class="row">
5. <div class="box">
6. <div class="col-lg-12">
7. <hr>
8. <h2 class="intro-text text-center">{{ trans('front/contact.title') }}</h2>
9. <hr>
10. <p>{{ trans('front/contact.text') }}</p>
11.
12. {!!
Form::open(['url' => 'contact', 'method' => 'post', 'role' => 'form']) !!}
13.
14. <div class="row">
15.
16. {!! Form::control('text', 6, 'name', $errors,
trans('front/contact.name')) !!}
17. {!! Form::control('email', 6, 'email', $errors,
trans('front/contact.email')) !!}

-5-
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

18. {!! Form::control('textarea', 12, 'message', $errors,


trans('front/contact.message')) !!}
19. {!! Form::text('address', '', ['class' => 'hpet']) !!}
20.
21. {!! Form::submit(trans('front/form.send'), ['col-lg-12']) !!}
22.
23. </div>
24.
25. {!! Form::close() !!}
26.
27. </div>
28. </div>
29. </div>
30. @stop

Le formulaire est simplifié grâce à l'utilisation de quelques extensions du FormBuilder (ce que nous verrons dans
un article ultérieur).

Notez la présence d'un champ qui peut vous sembler inutile :

{!! Form::text('address', '', ['class' => 'hpet']) !!}

C'est en fait un pot de miel masqué par la classe hpet pour piéger les robots. En effet ceux-ci ont tendance à remplir
tous les champs. On va donc vérifier à la soumission si ce champ a été complété. Si c'est le cas, on ne va pas aller
plus loin dans le traitement du formulaire. Ceci s'effectue dans la classe de base App\Http\Requests\Request :

1. <?php namespace App\Http\Requests;


2.
3. use Illuminate\Foundation\Http\FormRequest;
4.
5. abstract class Request extends FormRequest {
6.
7. public function authorize()
8. {
9. // Honeypot
10. return $this->input('address') == '';
11. }
12.
13. }

Comme toutes les requêtes de formulaire héritent de cette classe on va donc appliquer le pot de miel à l'ensemble
du site.

III-B - Traitement

Lorsque le formulaire est soumis on aboutit à la méthode store du contrôleur ContactController :

1. /**
2. * Store a newly created resource in storage.
3. *
4. * @param App\Repositories\ContactRepository $contact_gestion
5. * @param ContactRequest $request
6. * @return Response
7. */
8. public function store(
9. ContactRepository $contact_gestion,
10. ContactRequest $request)
11. {
12. $contact_gestion->store($request->all());
13.
14. return redirect('/')->with('ok', trans('front/contact.ok'));
15. }

-6-
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

On voit que l'on injecte une requête de formulaire pour la validation (App\Http\Requests\ContactRequest). Voici
le code de cette requête :

1. <?php namespace App\Http\Requests;


2.
3. class ContactRequest extends Request {
4.
5. /**
6. * Get the validation rules that apply to the request.
7. *
8. * @return array
9. */
10. public function rules()
11. {
12. return [
13. 'name' => 'required|max:100',
14. 'email' => 'required|email',
15. 'message' => 'required|max:1000'
16. ];
17. }
18.
19. }

On réclame tous les champs et on impose quelques contraintes.

On injecte aussi dans la méthode un repository (App\Repositories\ContactRepository). C'est la méthode store qui
est chargée d'enregistrer le contact dans la base :

1. /**
2. * Store a contact.
3. *
4. * @param array $inputs
5. * @return void
6. */
7. public function store($inputs)
8. {
9. $contact = new $this->model;
10.
11. $contact->name = $inputs['name'];
12. $contact->email = $inputs['email'];
13. $contact->text = $inputs['message'];
14.
15. $contact->save();
16. }

J'aurais pu utiliser la méthode create pour alléger le code, mais j'aime bien détailler les entrées.

Quand le message a été mémorisé, le contrôleur renvoie sur la page d'accueil avec un message flashé dans la
session :

return redirect('/')->with('ok', trans('front/contact.ok'));

Ainsi le visiteur a une confirmation que son message est bien arrivé à destination :

-7-
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

IV - L'administration

IV-A - Le tableau de bord

Lorsqu'un administrateur va se connecter sur le tableau de bord il va voir qu'un nouveau message est arrivé :

Comment cela fonctionne-t-il ?

C'est la méthode admin du contrôleur AdminController qui est chargée de gérer le tableau de bord :

1. /**
2. * Show the admin panel.
3. *
4. * @param App\Repositories\ContactRepository $contact_gestion
5. * @param App\Repositories\BlogRepository $blog_gestion
6. * @param App\Repositories\CommentRepository $comment_gestion
7. * @return Response
8. */
9. public function admin(
10. ContactRepository $contact_gestion,
11. BlogRepository $blog_gestion,
12. CommentRepository $comment_gestion)
13. {
14. $nbrMessages = $contact_gestion->getNumber();
15. $nbrUsers = $this->user_gestion->getNumber();
16. $nbrPosts = $blog_gestion->getNumber();
17. $nbrComments = $comment_gestion->getNumber();
18.
19. return view('back.index', compact('nbrMessages', 'nbrUsers', 'nbrPosts', 'nbrComments'));
20. }

Évidemment l'accès est réservé aux administrateurs :

1. Route::get('admin', [
2. 'uses' => 'AdminController@admin',
3. 'as' => 'admin',
4. 'middleware' => 'admin'
5. ]);

On voit que plusieurs repositories sont injectés dans la méthode. En effet, il faut aller vérifier le nombre de nouveaux
articles et commentaires en plus des messages. D'autre part, on affiche aussi le nombre total de chacune des
catégories. Dans tous les cas c'est la méthode getNumber des repositories qui est appelée.

Comme la méthode est commune à plusieurs repositories elle est placée dans la classe mère (App\Repositories
\BaseRepository) :

1. /**
2. * Get number of records.
3. *
4. * @return array

-8-
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

5. */
6. public function getNumber()
7. {
8. $total = $this->model->count();
9.
10. $new = $this->model->whereSeen(0)->count();
11.
12. return compact('total', 'new');
13. }

On va chercher ici le nombre total et les nouveautés (repérés par le champ seen à 0) et on les retourne au contrôleur
qui envoie tout dans la vue :

return view('back.index', compact('nbrMessages', 'nbrUsers', 'nbrPosts', 'nbrComments'));

Cette vue est rangée dans le dossier du back-end :

Voici la partie qui concerne les contacts :

@include('back/partials/pannel', ['color' => 'red', 'icone' => 'comment', 'nbr' => $nbrComments, 'name' =>
trans('back/admin.new-comments'), 'url' => 'comment', 'total' => trans('back/admin.comments')])

Comme le code est commun à toutes les catégories, on fait appel à une vue partielle :

avec ce code :

1. <div class="col-lg-4 col-md-6">


2. <div class="panel panel-{{ $color }}">
3. <div class="panel-heading">
4. <div class="row">

-9-
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

5. <div class="col-xs-3">
6. <span class="fa fa-{{ $icone }} fa-5x"></span>
7. </div>
8. <div class="col-xs-9 text-right">
9. <div class="huge">{{ $nbr['new'] }}</div>
10. <div>{{ $name }}</div>
11. </div>
12. </div>
13. </div>
14. <a href="{{ $url }}">
15. <div class="panel-footer">
16. <span class="pull-left">{{ $nbr['total'] . ' ' . $total }}</span>
17. <span class="pull-right fa fa-arrow-circle-right"></span>
18. <div class="clearfix"></div>
19. </div>
20. </a>
21. </div>
22. </div>

IV-B - Affichage des messages

L'administrateur peut accéder à la gestion des messages :

Là, il trouve le nouveau message avec un fond jauni et la case à cocher « Vu » non activée.

C'est la méthode index du contrôleur ContactController qui est chargée d'afficher et renseigner cette vue :

1. /**
2. * Display a listing of the resource.
3. *
4. * @param ContactRepository $contact_gestion
5. * @return Response
6. */
7. public function index(
8. ContactRepository $contact_gestion)
9. {
10. $messages = $contact_gestion->index();
11.
12. return view('back.messages.index', compact('messages'));
13. }

On retrouve un appel au repository (ContactRepository), cette fois la méthode index :

1. /**

- 10 -
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

2. * Get contacts collection.


3. *
4. * @return Illuminate\Support\Collection
5. */
6. public function index()
7. {
8. return $this->model
9. ->oldest('seen')
10. ->latest()
11. ->get();
12. }

On va chercher tous les messages, classés prioritairement avec ceux qui n'ont pas été vus et ensuite en partant des
plus récents. Puis le contrôleur génère la vue :

Les messages sont générés dans une boucle :

1. @foreach ($messages as $message)


2. <div class="panel {!! $message->seen? 'panel-default' : 'panel-warning' !!}">
3. <div class="panel-heading">
4. <table class="table">
5. <thead>
6. <tr>
7. <th class="col-lg-1">{{ trans('back/messages.name') }}</th>
8. <th class="col-lg-1">{{ trans('back/messages.email') }}</th>
9. <th class="col-lg-1">{{ trans('back/messages.date') }}</th>
10. <th class="col-lg-1">{{ trans('back/messages.seen') }}</th>
11. <th class="col-lg-1"></th>
12. </tr>
13. </thead>
14. <tbody>
15. <tr>
16. <td class="text-primary"><strong>{{ $message->name }}</strong></td>
17. <td>{!! HTML::mailto($message->email, $message->email) !!}</a></td>
18. <td>{{ $message->created_at }}</td>
19. <td>{!! Form::checkbox('vu', $message->id, $message->seen) !!}</td>
20. <td>
21. {!!
Form::open(['method' => 'DELETE', 'route' => ['contact.destroy', $message->id]]) !!}
22. {!! Form::destroy(trans('back/messages.destroy'),
trans('back/messages.destroy-warning'), 'btn-xs') !!}
23. {!! Form::close() !!}
24. </td>
25. </tr>
26. </tbody>
27. </table>
28. </div>
29. <div class="panel-body">
30. {{ $message->text }}
31. </div>
32. </div>
33. @endforeach

On retrouve en particulier le dernier message :

- 11 -
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

Tous les renseignements figurent et les deux actions possibles sont :

• marquer le message comme vu en cochant la case ;


• supprimer le message en utilisant le bouton.

IV-C - Marquer le message

Le marquage du message se fait en Ajax pour éviter de régénérer toute la vue. Cela est effectué en JavaScript sur
la page avec l'utilisation de jQuery pour faciliter le codage :

1. $(function() {
2. $(':checkbox').change(function() {
3. $(this).parents('.panel').toggleClass('panel-warning').toggleClass('panel-default');
4. $(this).hide().parent().append('<i class="fa fa-refresh fa-spin"></i>');
5. var token = $('input[name="_token"]').val();
6. $.ajax({
7. url: 'contact/' + this.value,
8. type: 'PUT',
9. data: "seen=" + this.checked + "&_token=" + token
10. })
11. .done(function() {
12. $('.fa-spin').remove();
13. $('input[type="checkbox"]:hidden').show();
14. })
15. .fail(function() {
16. $('.fa-spin').remove();
17. var chk = $('input[type="checkbox"]:hidden');
18. chk.parents('.panel').toggleClass('panel-warning').toggleClass('panel-default');
19. chk.show().prop('checked', chk.is(':checked') ? null:'checked');
20. alert('{{ trans('back/messages.fail') }}');
21. });
22. });
23. });

Comme la procédure peut prendre un petit moment on fait apparaître une petite animation à la place de la case à
cocher :

La requête arrive à la méthode update du contrôleur ContactController :

1. /**
2. * Update the specified resource in storage.
3. *
4. * @param App\Repositories\ContactRepository $contact_gestion
5. * @param Illuminate\Http\Request $request
6. * @param int $id

- 12 -
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

7. * @return Response
8. */
9. public function update(
10. ContactRepository $contact_gestion,
11. Request $request,
12. $id)
13. {
14. $contact_gestion->update($request->input('seen'), $id);
15.
16. return response()->json(['statut' => 'ok']);
17. }

On applique le middleware ajax pour cette méthode au niveau du constructeur :

$this->middleware('ajax', ['only' => 'update']);

La mise à jour s'effectue dans la méthode update du repository (ContactRepository) :

1. /**
2. * Update a contact.
3. *
4. * @param bool $vu
5. * @param int $id
6. * @return void
7. */
8. public function update($seen, $id)
9. {
10. $contact = $this->getById($id);
11.
12. $contact->seen = $seen == 'true';
13.
14. $contact->save();
15. }

Ensuite on envoie une réponse JSON au navigateur. Si la procédure a réussi on réaffiche la case en changeant son
aspect. En cas d'échec on affiche un message.

IV-D - Supprimer le message

Pour supprimer le message on clique sur le bouton « Supprimer » et on affiche une fenêtre de confirmation :

En cas de réponse positive on arrive à la méthode destroy du contrôleur (ContactController) :

1. /**
2. * Remove the specified resource from storage.
3. *
4. * @param App\Repositories\ContactRepository $contact_gestion
5. * @param int $id
6. * @return Response
7. */
8. public function destroy(
9. ContactRepository $contact_gestion,

- 13 -
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/
Tutoriel pour apprendre à créer une application Laravel par Maurice Chavelli

10. $id)
11. {
12. $contact_gestion->destroy($id);
13.
14. return redirect('contact');
15. }

Cette méthode est protégée par le middleware admin :

$this->middleware('admin', ['except' => ['create', 'store']]);

Elle fait appel à la méthode destroy du repository (ContactRepository). Comme cette méthode est commune aux
repositories, on la trouve dans le repository de base (Baserepository) :

1. /**
2. * Destroy a model.
3. *
4. * @param int $id
5. * @return void
6. */
7. public function destroy($id)
8. {
9. $this->getById($id)->delete();
10. }
11.
12. /**
13. * Get Model by id.
14. *
15. * @param int $id
16. * @return App\Models\Model
17. */
18. public function getById($id)
19. {
20. return $this->model->findOrFail($id);
21. }

Remarquez que l'on utilise la méthode getById qui est, elle aussi, commune.

Lorsque le message a été supprimé le contrôleur renvoie la vue d'affichage des messages actualisée.

On a ainsi fait un peu le tour de l'application avec cette gestion des messages !

V - Remerciements

Nous remercions Maurice Chavelli qui nous autorise à publier ce tutoriel.

Nous tenons également à remercier Winjerome pour la gabarisation et Jacques_jean pour la relecture
orthographique.

- 14 -
Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de
l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
http://maurice-chavelli.developpez.com/tutoriels/creer-application/le-contact/

Вам также может понравиться