Bonjour à tous,

Je suis en train d'essayer de mettre un avatar à mes membres dans le profil.
J'ai une table users auquel j'ai ajouté un champ avatar varchar(255).
J'ai crée un dossier avatars dans img.
Tous mes avatars seront donc dans /img/avatars/
dans view profil.ctp :

<?php echo $this->Form->create('User',array('type'=>'file')); ?>
    <?php echo $this->Form->input('avatar',array('label'=>false,'type'=>'file')); ?>
<?php echo $this->Form->end(array('label'=>'Modifier','class'=>'btn info')); ?>

dans mon model User.php

public $validate = array(
    'avatar' => array(
        'rule' => '/^.*\.(jpg|png|jpeg|gif)$/',
    'allowEmpty' => true,
    'message' => "Le fichier n'est pas une image valide"
    )
);

dans mon controller UsersController.php je voudrais ajouter dans ma fonction profil un save et un move_uploaded_file pour
ajouter le chemin de l'avatar dans ma base et mettre cette avatar dans mon dossier.

Mon soucis c'est que je n'y arrive pas, je pense que la view et le model sont bons mais je n'arrive pas à créer dans ma fonction
la sauvegarde et l'upload de l'avatar. J'essaye de suivre le tuto correspondant aux médias dans Cakephp 2, mais sans succès.

Pouvez vous m'aider à créer cette fonction.

Merci

13 réponses


Montre ce que tu as essayer de faire dans ta fonction :)

zenkiai
Auteur

Voici mon code.

if($this->request->is('put') || $this->request->is('post')){
            $d = $this->request->data; 
            $d'User']'id'] = $user_id; 
$data = $this->request->data'User'];
            $dir = IMAGES.'avatars';
            $f = explode('.',$data'avatar']'name']);
            $ext = '.'.end($f);
            $filename = Inflector::slug(implode('.',array_slice($f,0,-1)),'-');
            $success = $this->User->save(array(
                'avatar' => $dir.DS.$filename.$ext
            ));
            if($success){
                move_uploaded_file($data'avatar']'tmp_name'], $dir.DS.$filename.$ext);
            }else{
                $this->Session->setFlash("Vérifiez le format de votre fichier","notif",array('type'=>'error'));
            }
            if($this->User->save($d,true,array('firstname','lastname'))){
                $this->Session->setFlash("Votre profil a bien été modifié","notif");
            }else{
                $this->Session->setFlash("Impossible de sauvegarder votre profil","notif",array('type'=>'error'));
            }

je n'ai pas très bien compris comment l'upload de fichier avec cakephp se passe.
En faite, pour les médias j'ai compris du faite que c'est une table à part, avec un name, une url etcc..
Par contre quand il faut uploader le chemin d'un image dans la BDD et l'image dans le dossier du site, à partir d'une table existante
comme users, je n'y arrive pas.
Il faut à tout pris que je comprenne ce système là car je serais amener à mon servir partout, je veux par exemple dans mon back-office ajouter une image pour mes catégories et bien d'autres choses encore.

Merci de ton aide.

zenkiai
Auteur

J'ai un peu avancer sur ma fonction, j'ai changé le code sur mon post précédant pour vous montrer où j'en suis.
Cela fonctionne toujours pas, de plus j'ai pas d'INSERT INTO qui montre que mon fichier est bien envoyé.
Pourtant lorsque je fais

$avatar = $dir.DS.$filename.$ext;
            debug($avatar);

j'ai bien comme réponse :
app/Controller/UsersController.php (line 116)
/img/avatars/kiai.jpg

A priori je pense que c'est au niveau du save que cela fonctionne pas, mais je ne sais pourquoi.

zenkiai
Auteur

Bonjour,

function profil(){
        $user_id = $this->Auth->user('id');
        if(!$user_id){
            $this->redirect('/');
            die(); 
        }
        $this->User->id = $user_id; 
        $passError = false; 
        if($this->request->is('put') || $this->request->is('post')){
            $d = $this->request->data; 
            $d'User']'id'] = $user_id; 
            if(!empty($d'User']'pass1'])){
                if($d'User']'pass1']==$d'User']'pass2']){
                    $d'User']'password'] = Security::hash($d'User']'pass1'],null,true); 
                }else{
                    $passError = true;  
                }
            }

                $dir = IMAGES.'avatars';
                $f = explode('.',$d'User']'avatar']'name']);
                $ext = '.'.end($f);
                $filename = Inflector::slug(implode('.',array_slice($f,0,-1)),'-');
                $ok = $this->User->save($d,true,array(
                    'avatar' => $dir.DS.$filename.$ext
                )); 
                if($ok){
                    move_uploaded_file($d'User']'avatar']'tmp_name'], $dir.DS.$filename.$ext);
                }else{
                    $this->Session->setFlash("Vérifiez le format de votre fichier","notif",array('type'=>'error'));
                }

            if($this->User->save($d,true,array('firstname','lastname'))){
                $this->Session->setFlash("Votre profil a bien été modifié","notif");
            }else{
                $this->Session->setFlash("Impossible de sauvegarder votre profil","notif",array('type'=>'error'));
            }
            if($passError) $this->User->validationErrors'pass2'] = array('Les mots de passe ne correspondent pas');
        }else{
            $this->request->data = $this->User->read(); 
        }
        $this->request->data'User']'pass1'] = $this->request->data'User']'pass2'] = ''; 
    }

Avec ce code j'ai bien l'upload de mon fichier dans mon dossier avatar. Seulement du faite d'avoir mis true dans mon save, on peut uploader n'importe quel fichier (ce que je ne veux pas bien sûr). Lorsque j'enlève le true, la modification du profil ne fonctionne pas et donc l'upload non plus(pourtant mon validate me semble correct !). De plus, les données ne sont toujours pas sauvegardées dans ma BDD dans ces deux cas.

Est il possible de faire deux save dans une même fonction ? Là, je fais une save de l'avatar et ensuite des champs du profil lastname et firstname.
Avez vous une idée sur le faite qu'aucune donnée ne soit sauvegarder dans ma BDD.
Je tiens à signaler que mon champs avatar à une valeur par défaut, le chemin d'une image par défaut au cas ou un utilisateur n'en met pas. Mais je ne penses pas que cela pose soucis.

Je vous remercie de votre aide...

J'ai eu exactement le même problème mais pour X raison, cakePHP n'a pas l'air de bien aimé une règle de validation sur un input de type file ! Du coup j'utilise la méthode oldschool en comparant le type du fichier directement dans mon controller.

function edit(){
        $user_id = $this->Auth->user('id');
        if (!$user_id){
            $this->redirect('/');
            die();
        }
        $passError = false;
        $avatarError = false;
        $this->User->id = $user_id;
        if ($this->request->is('put') || $this->request->is('post')){
            $data = $this->request->data'User'];
            $data'id'] = $user_id;
            if (!empty($data'pass1'])){
                if ($data'pass1'] == $data'pass2']){
                    $data'password'] = Security::hash($data'User']'pass1'],null,true);
                } else {
                    $passError = true;
                }
            }
            if ($data'avatar']'name'] != ''){
                if ($data'avatar']'type'] == 'image/jpeg' || $data'avatar']'type'] == 'image/png') {
                    $dir = IMAGES.'avatar';
                    if (!file_exists($dir)) {
                        mkdir($dir,0777);
                    }
                    $f = explode('.',$data'avatar']'name']);
                    $ext = '.'.end($f);
                    $filename = strtolower($this->Auth->user('username'));
                    if ($this->User->save($data'avatar'],true,array('avatar' => 'avatar/'.$filename.'.jpg'))) {
                        move_uploaded_file($data'avatar']'tmp_name'],$dir.DS.$filename.'_o'.$ext);
                        $this->Img->crop($dir.DS.$filename.'_o'.$ext,$dir.DS.$filename.'.jpg',100,100);
                        unlink($dir.DS.$filename.'_o'.$ext);
                    }
                } else {
                    $avatarError = true;
                }
            }
            if ($passError){
                $this->User->validationErrors'pass2'] = array('Les mots de passes ne correspondent pas');
                $this->Session->setFlash("Merci de corriger vos erreurs",'notif',array('type'=>'error'));
            } elseif ($avatarError) {
                $this->User->validationErrors'avatar'] = array("Le fichier n'est pas une image valide");
                $this->Session->setFlash("Merci de corriger vos erreurs",'notif',array('type'=>'error'));
            } else { 
                if ($this->User->save($data,true,array('firstname','lastname','password','mail'))){
                    $this->Session->setFlash('Votre profil a bien été édité','notif');
                } else {
                    $this->Session->setFlash("Impossible d'éditer votre profil",'notif',array('type'=>'error'));
                }
            }
        } else {
            $this->request->data = $this->User->read();
        }
        $this->request->data'User']'pass1'] = $this->request->data'User']'pass2'] = $this->request->data'User']'avatar'] = '';
    }
zenkiai
Auteur

Je te remercie de m'avoir répondu Gyzmo.
Alors la validation fonctionne maintenant, mon image est cropé et uploader dans mon dossier avatar. C'est cool.
Par contre j'ai toujours mon soucis de mon save. Mon champ avatar de ma BDD ne se modifie pas. J'arrive pas à comprendre pourquoi?
Ce qui fait que j'ai bien le nouvel avatar mais du fait que dans la BDD le chemin de l'ancien avatar ne change pas, son avatar de profil reste le même.
As tu une idée d'où cela peut venir ?
Merci de ton aide

zenkiai
Auteur

Salut,
En essayant de comprendre d'où vient l'erreur, je me suis posé la question de savoir si cela ne venait pas de mon input de type file.
En effet lorsque je met un input normal et que j'y copie une adresse du style /avatars/monavatar.jpg et en enlevant le validate. cela fonctionne. Mon champ avatar de ma BDD est bien mise à jour. J'ai l'impression que l'option value de mon input ne se remplie pas.

Je vous envoie ma view complète pour voir si j'ai pas fait une erreur :

<?php echo $this->Form->create('User',array('type'=>'file')); ?>
        <?php echo '<div class="grid4 alpha">'; ?>
            <?php echo '<h3>Votre compte</h3>'; ?>
            <?php echo $this->Form->input('mail',array('label'=>"Email :",'class'=>'w270')); ?>
            <?php echo $this->Form->input('pass1',array('label'=>"Mot de passe :",'class'=>'w270')); ?>
            <?php echo $this->Form->input('pass2',array('label'=>"Confirmer le mot de passe :",'class'=>'w270')); ?>
            <?php echo '<h3>Votre avatar</h3>'; ?>
            <?php echo '<div class="profil-img-avatar">'; ?>
                   <?php echo '<img src="'.AuthComponent::user('avatar').'" height="150px" width="150px">'; ?>
            <?php echo '</div>'; ?>                     
            <?php echo $this->Form->input('avatar',array('label'=>false,'class'=>'grid_2','type'=>'file')); ?>
        <?php echo '</div>'; ?>
        <?php echo '<div class="grid4 profil-pad">'; ?>
            <?php echo $this->Form->input('lastname',array('label'=>"Nom :",'class'=>'w270')); ?>
            <?php echo $this->Form->input('firstname',array('label'=>"Prénom :",'class'=>'w270')); ?>
        <?php echo '</div>'; ?>
<?php echo $this->Form->end(array('label'=>'Modifier votre profil','class'=>'btn info')); ?>

Je ne perd pas espoir, merci

Bon en fait le code que j'ai filé est tout pourris mais à force de persévérer j'ai trouvé une méthode qui a plutôt l'air de fonctionner mais il faut faire pas mal de changements !

Dans le Controller :

function edit(){
        $user_id = $this->Auth->user('id');
        if (!$user_id){
            $this->redirect('/');
            die();
        }
        $passError = false;
        $this->User->id = $user_id;
        if ($this->request->is('put') || $this->request->is('post')){
            $data = $this->request->data'User'];
            $data'id'] = $user_id;
            if (!empty($data'pass1'])){
                if ($data'pass1'] == $data'pass2']){
                    $data'password'] = $data'User']'pass1'];
                } else {
                    $passError = true;
                }
            }
            if ($data'avatar']'name'] == '') {
                unset($data'avatar']);
            }
            if ($passError){
                $this->User->validationErrors'pass2'] = array('Les mots de passes ne correspondent pas');
                $this->Session->setFlash("Merci de corriger vos erreurs",'notif',array('type'=>'error'));
            } else { 
                if ($this->User->save($data,true,array('firstname','lastname','password','mail','avatar'))){
                    $this->Session->setFlash('Votre profil a bien été édité','notif');
                } else {
                    $this->Session->setFlash("Impossible d'éditer votre profil",'notif',array('type'=>'error'));
                }
            }
        } else {
            $this->request->data = $this->User->read();
        }
        $this->request->data'User']'pass1'] = $this->request->data'User']'pass2'] = $this->request->data'User']'avatar'] = '';
    }

Dans le Model au niveau du validate de avatar :

'avatar' => array(
            'rule' => array('extension', array(0 => 'jpeg', 1 => 'jpg', 2 => 'png')),
            'allowEmpty' => true,
            'message' => "Le fichier n'est pas une image valide"
        )

Enfin dans le Model au niveau du beforeSave :

function beforeSave($options = array()){
        if(!empty($this->data'User']'password'])){
            $this->data'User']'password'] = AuthComponent::password($this->data'User']'password']);
        }
        if(isset($this->data'User']'avatar'])){
            $dir = IMAGES.'avatar';
            if (!file_exists($dir)) {
                mkdir($dir,0777);
            }
            $f = explode('.',$this->data'User']'avatar']'name']);
            $ext = '.'.end($f);
            $filename = strtolower(AuthComponent::user('username'));
            move_uploaded_file($this->data'User']'avatar']'tmp_name'],$dir.DS.$filename.'_o'.$ext);
            ImgComponent::crop($dir.DS.$filename.'_o'.$ext,$dir.DS.$filename.'.jpg',100,100);
            unlink($dir.DS.$filename.'_o'.$ext);
            $this->data'User']'avatar'] = 'avatar/'.$filename.'.jpg';
        }
        return true;
    }
zenkiai
Auteur

Salut, je vais étudié ton code car c'est vrai que cette fois-ci, tout change.
C'est tout de même dommage puisque tout fonctionne sauf le faite que mon champ avatar ne se modifie pas dans la BDD.
Il doit tout de même avoir une raison à ce problème ? Si vous avez la réponse je suis preneur.
Je te tiens au courant Gyzmo en tout cas pour ce dernier code.

zenkiai
Auteur

Salut,
Ton code fonctionne en effet Gyzmo, j'ai dû mal à comprendre pourquoi faire cela dans un beforeSave du model au lieu du Controller, mais cela fonctionne.
Si je comprend bien ton code, on détruit la variable avatar dans le controller avec le unset et on l'a recrée dans le model avant le save ?
Est-ce propre ? ou plûtot est-ce conventionnel pour CakePHP de faire ainsi ?

PS: Sous firefox mon avatar se rafraîchit, se met à jour automatiquement. Par contre sous Chrome je doit revenir sur mon profil pour que le changement soit effectif. Est ce lié tout simplement à chrome ou y a t-il une solution pour remédié à cela ?

zenkiai
Auteur

Bon je crois que je vais abandonner cet idée d'avatar, maintenant que je peux modifié un avatar, je me suis rendu compte que tous les commentaires et les articles que j'avais écris ne sont pas mis à jour. De plus comme dans mon champ BDD j'ai mis par défaut un avatar pour ceux qui n'en mettent pas. Tous ceux qui veulent modifier leur avatar par défaut, leur nouveau avatar ne se modifie pas dans les articles et les commentaires d'avant ce changement. Je suppose que c'est dû à mes belongsTo et hasMany entre mes tables users posts et comments.
Je crois que je vais attendre et espérer que Grafikart fasse un jour un tutoriel dessus en même temps peut être qu'un compte plus.
Je laisse le sujet ouvert car si quelqu'un a envie de m'aider à poursuivre, je serais preneur.
Merci

Pour mon code, en fait on détruit la clé avatar dans le tableau $data uniquement si aucun fichier n'est envoyé ($data'avatar']'name'] == ''). Ca évite de faire la vérification sur le champ avatar. En revanche si un fichier est transmis, alors là on vérifie la valeur du champ avatar et si tout est OK on fait le traitement sur l'image. J'ai préféré faire cela dans le beforeSave parce que comme ça je peux réassigner la valeur de $data'avatar'] ($this->data'User']'avatar'] dans le beforeSave) avec l'url de l'avatar.

Pour ce qui est de ton problème avec les avatars qui se changent pas dans les commentaires et articles, c'est bizarre parce que pour ma part, ça fonctionne très bien, même avec une modification d'avatar !

voila pour ma part mes belongsTo et hasMany pour mes Model User, Post et Comment

Model User :

public $hasMany = array(
        'Comment'
    );

Model Comment :

public $belongsTo = array(
        'Post' => array(
            'counterCache' => array('comment_count' => array('online' => 1))
        ),
        'User'
    );

Model Post :

public $hasMany = array(
        'Media' => array(
            'dependent' => true
        ),
        'PostTag',
        'Comment'
    );

Enfin, dans mon PostController pour l'action show

function show($id = null, $slug = null){
        if ($this->request->is('post')) {
            $this->loadModel('Comment');
            if ($this->Comment->save($this->request->data)) {
                $this->Session->setFlash('Votre commentaire a bien été posté', 'notif');
            } else {
                $this->Session->setFlash('Merci de corriger vos erreurs', 'notif');
            }
        }
        if (!$id) {
            throw new NotFoundException('Aucune page ne correspond à cet ID');
        } 
        $post = $this->Post->find('first', array(
            'conditions' => array('Post.id' => $id),
            'contain' => array('Comment'=>array('User'=>array('fields'=>array('id','username','avatar'))),'Category'),
            'recursive' => 2
        ));
        if (empty($post)){
            throw new NotFoundException('Aucune page ne correspond à cet ID');
        }
        if ($slug != $post'Post']'slug']){
            $this->redirect($post'Post']'link'],301);
        }
        $data'post'] = $post;
        $this->set($data);
    }
zenkiai
Auteur

Bonjour et merci de tes réponses,
En effet j'ai compris d'où venait mon soucis. Au début, je n'avais pas mis comme nom d'avatar le pseudo de l'utilisateur. Ce qui fait qu'avec cette nouvelle façon de faire, dans user mon avatar devenait membre.jpg mais par contre dans mon user_avatar de comment il restait anciennom.jpg. J'ai donc modifié cela et maintenant ça fonctionne (cela fonctionne car le changement d'avatar reste toujours du même nom et non grâce à mes belongsTo et hasMany). La seule chose, c'est que lorsqu'un membre post des commentaires sans avatar et qu'il décide d'en mettre un, tout c'est ancien commentaire ne se mette pas à jour au niveau de l'avatar puisque que mon champ user_avatar de comment reste vide. Par contre s'il change d'avatar, après en avoir déjà mis un, vu que le nom reste le même ça fonctionne.
Pour tes models, j'ai exactement les mêmes, il n'y a que ton Postscontroller qui diffère du mien. je n'utilise pas la méthode containable. Ceci étant dit, je pense que ta façon est meilleure mais je ne suis pas très à l'aise encore avec cette méthode.

function show($id = null,$slug = null){
        if(!$id || !is_numeric($id)){
            throw new NotFoundException('Aucune article ne correspond à cet ID');
        }
        if(!empty($this->request->data)){
            $this->Post->Comment->set($this->request->data);
            if($this->Post->Comment->validates()){
                $this->Post->Comment->save($this->request->data);
                $this->Session->setFlash("Votre commentaire a bien été enregistré !", "notif"); 
            }else{
                $this->Session->setFlash("Votre commentaire n'a pas été enregistré !", "notif", array('type' => 'error'));
            }
        }
           $totpost = $this->Post->find('count',array(
            'conditions' => array('type'=>'post','online'=>1)
        ));
        $this->set('totpost',$totpost);
        $d'comments'] = $this->Post->Comment->find('all',array(
            'conditions' => array('Comment.post_id' => $id)
        ));
        $post = $this->Post->find('first',array(
            'conditions' => array('Post.id' => $id,'type'=>'post'),
            'recursive' => 0
        ));
        if(empty($post)){
            throw new NotFoundException('Aucune article ne correspond à cet ID');
        }
        if($slug != $post'Post']'slug']){
            $this->redirect($post'Post']'link'],301); 
        }
        $d'post'] = $post;
        $this->set($d);
    }

Il faudrait que j'optimise tout cela un jour.
Je vais maintenant essayé d'appliquer la même chose pour assigner un icône à mes catégories.

Merci Gyzmo