diff --git a/www/images/user_photo.jpg b/www/images/user_photo.jpg new file mode 100644 index 0000000..af73630 Binary files /dev/null and b/www/images/user_photo.jpg differ diff --git a/www/protected/controllers/UsuarioController.php b/www/protected/controllers/UsuarioController.php index 42f8dc1..7762917 100644 --- a/www/protected/controllers/UsuarioController.php +++ b/www/protected/controllers/UsuarioController.php @@ -39,21 +39,30 @@ class UsuarioController extends Controller { if ($id != Yii::app()->user->id) throw new CHttpException(404, Yii::t('profind', 'La página solicitada no existe.')); - $model = $this->loadModel($id); - + $usuario = $this->loadModel($id); + // Uncomment the following line if AJAX validation is needed - // $this->performAjaxValidation($model); + // $this->performAjaxValidation($usuario); if (isset($_POST['Usuario'])) { - $model->attributes = $_POST['Usuario']; - if ($model->save()) { + $usuario->attributes = $_POST['Usuario']; + $ficheroFotografia = CUploadedFile::getInstance($usuario, 'ficheroFotografia'); + $quitarFotografia = Yii::app()->request->getParam('quitar_fotografia', '0'); + + if ($usuario->save()) { + if (($quitarFotografia == '1') && ($usuario->fotografia->tieneFotografia())) + $usuario->fotografia->eliminarFotografia(); + + if ($ficheroFotografia) + $usuario->fotografia->guardarFotografia($ficheroFotografia); + Yii::app()->user->setFlash('success', Yii::t('profind', 'Se ha actualizado el perfil')); - $this->redirect(array('modificar', 'id' => $model->id)); + $this->redirect(array('modificar', 'id' => $usuario->id)); } } $this->render('modificar', array( - 'model' => $model, + 'model' => $usuario, )); } @@ -66,6 +75,7 @@ class UsuarioController extends Controller { $model = Usuario::model()->findByPk($id); if ($model === null) throw new CHttpException(404, Yii::t('profind', 'La página solicitada no existe.')); + return $model; } diff --git a/www/protected/helpers/recursive_remove_directory.php b/www/protected/helpers/recursive_remove_directory.php new file mode 100644 index 0000000..daa4410 --- /dev/null +++ b/www/protected/helpers/recursive_remove_directory.php @@ -0,0 +1,46 @@ + \ No newline at end of file diff --git a/www/protected/models/Usuario.php b/www/protected/models/Usuario.php index 7a80d50..5820e0b 100644 --- a/www/protected/models/Usuario.php +++ b/www/protected/models/Usuario.php @@ -1,9 +1,9 @@ estado == self::ESTADO_ACTIVO); } - + /** * Returns the static model of the specified AR class. * @param string $className active record class name. @@ -43,7 +45,7 @@ class Usuario extends CActiveRecord { public static function model($className = __CLASS__) { return parent::model($className); } - + /** * @return string the associated database table name */ @@ -54,22 +56,27 @@ class Usuario extends CActiveRecord { /** * @return array validation rules for model attributes. */ - public function rules() - { + public function rules() { // NOTE: you should only define rules for those attributes that // will receive user inputs. return array( array('email, password', 'required'), - array('estado', 'length', 'max'=>1), + array('estado', 'length', 'max' => 1), array('email', 'email'), array('email', 'unique'), array('descripcion', 'safe'), - array('email, nombre, apellidos, password, tipo, titulo, localidad, telefono', 'length', 'max'=>255), - - array('id, id_empresa, estado, email, nombre, apellidos, tipo, titulo, localidad, telefono, descripcion', 'safe', 'on'=>'search'), + array('email, nombre, apellidos, password, tipo, titulo, localidad, telefono', 'length', 'max' => 255), + array('ficheroFotografia', 'file', + 'types' => 'jpg', + 'maxSize' => 1024 * 1024 * 1, // 1MB como máximo + 'tooLarge' => Yii::t('profind', 'La imagen es demasiado pesada. Elija otra fotografía más pequeña.'), + 'wrongType' => Yii::t('profind', 'Sólo se permiten imágenes en formato JPG.'), + 'allowEmpty' => 'true', + ), + array('id, id_empresa, estado, email, nombre, apellidos, tipo, titulo, localidad, telefono, descripcion', 'safe', 'on' => 'search'), ); - } - + } + /** * @return array relational rules. */ @@ -77,16 +84,17 @@ class Usuario extends CActiveRecord { // NOTE: you may need to adjust the relation name and the related // class name for the relations automatically generated below. return array( - 'empresa' => array(self::HAS_ONE, 'Empresa', 'id'), + 'empresa' => array(self::HAS_ONE, 'Empresa', 'id'), ); } - + /** * @return array customized attribute labels (name=>label) */ public function attributeLabels() { return array( 'id' => 'ID', + 'ficheroFotografia' => 'Fotografía', 'estado' => 'Estado', 'email' => 'Email', 'nombre' => 'Nombre', @@ -97,37 +105,98 @@ class Usuario extends CActiveRecord { 'localidad' => 'Localidad', 'telefono' => 'Teléfono', 'descripcion' => 'Descripción', - ); + ); } /** * Retrieves a list of models based on the current search/filter conditions. * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions. */ - public function search() - { - $criteria=new CDbCriteria; + public function search() { + $criteria = new CDbCriteria; - $criteria->compare('id',$this->id); - $criteria->compare('id_empresa',$this->id_empresa); - $criteria->compare('estado',$this->estado,true); - $criteria->compare('email',$this->email,true); - $criteria->compare('nombre',$this->nombre,true); - $criteria->compare('apellidos',$this->apellidos,true); - $criteria->compare('tipo',$this->tipo,true); - $criteria->compare('titulo',$this->titulo,true); - $criteria->compare('localidad',$this->localidad,true); - $criteria->compare('telefono',$this->telefono,true); - $criteria->compare('last_login_time',$this->last_login_time,true); - $criteria->compare('descripcion',$this->descripcion,true); + $criteria->compare('id', $this->id); + $criteria->compare('id_empresa', $this->id_empresa); + $criteria->compare('estado', $this->estado, true); + $criteria->compare('email', $this->email, true); + $criteria->compare('nombre', $this->nombre, true); + $criteria->compare('apellidos', $this->apellidos, true); + $criteria->compare('tipo', $this->tipo, true); + $criteria->compare('titulo', $this->titulo, true); + $criteria->compare('localidad', $this->localidad, true); + $criteria->compare('telefono', $this->telefono, true); + $criteria->compare('last_login_time', $this->last_login_time, true); + $criteria->compare('descripcion', $this->descripcion, true); return new CActiveDataProvider($this, array( - 'criteria'=>$criteria, - )); + 'criteria' => $criteria, + )); + } + + protected function afterFind() { + parent::afterFind(); + $this->fotografia = new UsuarioFotografia(); + $this->fotografia->usuario = $this; } + protected function afterConstruct() { + parent::afterConstruct(); + $this->fotografia = new UsuarioFotografia(); + $this->fotografia->usuario = $this; + } + + protected function afterSave() { + parent::afterSave(); + if ($this->isNewRecord) + $this->createUploadDir(); + } + + protected function afterDelete() { + parent::afterDelete(); + $this->deleteUploadDir(); + } + + /* + * Devuelve la ruta con los ficheros del usuario. + * Incluye el separador de directorios al final de la ruta. + * @return string ruta + */ + public function getUploadPath() { + return Yii::getPathOfAlias('application.uploads') . DIRECTORY_SEPARATOR . $this->id . DIRECTORY_SEPARATOR; + } + + /* + * Crea un directorio para almacenar ficheros del usuario + * @return boolean + */ + private function createUploadDir() { + $upload = $this->getUploadPath(); + + if(!is_dir($upload)) { + return mkdir($upload); + } + else return false; + } + + /* + * Elimina el directorio del usuario y todos sus ficheros + * @return boolean + */ + private function deleteUploadDir() { + $upload = $this->getUploadPath(); + + if(is_dir($upload)) { + require_once( Yii::getPathOfAlias('application.helpers') . DIRECTORY_SEPARATOR . 'recursive_remove_directory.php'); + return recursive_remove_directory($upload); + } + else return false; + } + + /* + * Función para cifrar la contraseña del usuario (temporal) + * @return string contraseña cifrada + */ public function encrypt($value) { return md5($value); } - } diff --git a/www/protected/models/UsuarioFotografia.php b/www/protected/models/UsuarioFotografia.php new file mode 100644 index 0000000..fdee73e --- /dev/null +++ b/www/protected/models/UsuarioFotografia.php @@ -0,0 +1,97 @@ +usuario) + throw new CException(Yii::t('profind', 'Usuario no asignado.')); + + if ($this->tieneFotografia()) { + $fichero = $this->getRutaCompletaFicheroFotografia(); + } else { + $fichero = Yii::getPathOfAlias('webroot') . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR; + $fichero .= 'user_photo.jpg'; + } + $imgdata = base64_encode(file_get_contents($fichero)); + return 'data:image/jpeg;base64,' . $imgdata; + } + + /* + * Comprueba si el usuario ha subido su fotografía + * @return boolean + */ + public function tieneFotografia() { + if (!$this->usuario) + throw new CException(Yii::t('profind', 'Usuario no asignado.')); + + $fichero = $this->getRutaCompletaFicheroFotografia(); + return file_exists($fichero); + } + + /* + * Devuelve la ruta completa (ruta y nombre) del fichero con la fotografía del usuario. + * @return string ruta completa del fichero + */ + private function getRutaCompletaFicheroFotografia() { + if (!$this->usuario) + throw new CException(Yii::t('profind', 'Usuario no asignado.')); + + $upload = $this->usuario->getUploadPath(); + $nombre = $this->getNombreFicheroFotografia(); + + return $upload . $nombre; + } + + /* + * Devuelve el nombre del fichero con la fotografía del usuario. + * @return string nombre del fichero + */ + private function getNombreFicheroFotografia() { + if (!$this->usuario) + throw new CException(Yii::t('profind', 'Usuario no asignado.')); + + return $this->usuario->id . '.jpg'; + } + + /* + * Guarda una fotografía subida por el usuario + * return CUploadedFile fichero subido + */ + + public function guardarFotografia($fichero) { + if (!$this->usuario) + throw new CException(Yii::t('profind', 'Usuario no asignado.')); + + if ($fichero) { + $nombre = $this->getRutaCompletaFicheroFotografia(); + return $fichero->saveAs($nombre); + } + else + return false; + } + + /* + * Elimina la fotografía del usuario + * return bool + */ + + public function eliminarFotografia() { + if (!$this->usuario) + throw new CException(Yii::t('profind', 'Usuario no asignado.')); + + $fichero = $this->getRutaCompletaFicheroFotografia(); + return unlink($fichero); + } +} + +?> diff --git a/www/themes/profind/js/bootstrap-fileupload.js b/www/themes/profind/js/bootstrap-fileupload.js new file mode 100644 index 0000000..17cbc3c --- /dev/null +++ b/www/themes/profind/js/bootstrap-fileupload.js @@ -0,0 +1,126 @@ +/* =========================================================== + * bootstrap-fileupload.js j1a + * http://jasny.github.com/bootstrap/javascript.html#fileupload + * =========================================================== + * Copyright 2012 Jasny BV, Netherlands. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + +!function ($) { + + "use strict"; // jshint ;_ + + /* INPUTMASK PUBLIC CLASS DEFINITION + * ================================= */ + + var Fileupload = function (element, options) { + this.$element = $(element) + this.type = this.$element.data('uploadtype') || (this.$element.find('.thumbnail').length > 0 ? "image" : "file") + + this.$input = this.$element.find(':file') + if (this.$input.length === 0) return + + this.name = this.$input.attr('name') || options.name + + this.$hidden = this.$element.find(':hidden[name="'+this.name+'"]') + if (this.$hidden.length === 0) { + this.$hidden = $('') + this.$element.prepend(this.$hidden) + } + + this.$preview = this.$element.find('.fileupload-preview') + var height = this.$preview.css('height') + if (this.$preview.css('display') != 'inline' && height != '0px' && height != 'none') this.$preview.css('line-height', height) + + this.$remove = this.$element.find('[data-dismiss="fileupload"]') + + this.listen() + } + + Fileupload.prototype = { + + listen: function() { + this.$input.on('change.fileupload', $.proxy(this.change, this)) + if (this.$remove) this.$remove.on('click.fileupload', $.proxy(this.clear, this)) + }, + + change: function(e, invoked) { + var file = e.target.files !== undefined ? e.target.files[0] : { name: e.target.value.replace(/^.+\\/, '') } + if (!file || invoked === 'clear') return + + this.$hidden.val('') + this.$hidden.attr('name', '') + this.$input.attr('name', this.name) + + if (this.type === "image" && this.$preview.length > 0 && (typeof file.type !== "undefined" ? file.type.match('image.*') : file.name.match('\\.(gif|png|jpe?g)$')) && typeof FileReader !== "undefined") { + var reader = new FileReader() + var preview = this.$preview + var element = this.$element + + reader.onload = function(e) { + preview.html('') + element.addClass('fileupload-exists').removeClass('fileupload-new') + } + + reader.readAsDataURL(file) + } else { + this.$preview.text(file.name) + this.$element.addClass('fileupload-exists').removeClass('fileupload-new') + } + }, + + clear: function(e) { + this.$hidden.val('') + this.$hidden.attr('name', this.name) + this.$input.attr('name', '') + + this.$preview.html('') + this.$element.addClass('fileupload-new').removeClass('fileupload-exists') + + this.$input.trigger('change', [ 'clear' ]) + + e.preventDefault() + return false + } + } + + + /* INPUTMASK PLUGIN DEFINITION + * =========================== */ + + $.fn.fileupload = function (options) { + return this.each(function () { + var $this = $(this) + , data = $this.data('fileupload') + if (!data) $this.data('fileupload', (data = new Fileupload(this, options))) + }) + } + + $.fn.fileupload.Constructor = Fileupload + + + /* INPUTMASK DATA-API + * ================== */ + + $(function () { + $('body').on('click.fileupload.data-api', '[data-provides="fileupload"]', function (e) { + var $this = $(this) + if ($this.data('fileupload')) return + $this.fileupload($this.data()) + + if ($(e.target).data('dismiss') == 'fileupload') $(e.target).trigger('click.fileupload') + }) + }) + +}(window.jQuery) diff --git a/www/themes/profind/views/layouts/main.php b/www/themes/profind/views/layouts/main.php index 91c8689..3d4b419 100644 --- a/www/themes/profind/views/layouts/main.php +++ b/www/themes/profind/views/layouts/main.php @@ -35,7 +35,7 @@ name); ?>