Inici > Programació, Web > Subida de ficheros con Uploadify y validación Ajax en CakePHP

Subida de ficheros con Uploadify y validación Ajax en CakePHP

dimarts, 27 d'octubre de 2009 Imprimir

Ya hacía tiempo que tenía ganas de escribir una entrada “de estas” 😀 (de estas quiero decir de programación y con muuuucho muuuucho código, como a mí me gustan :P).

Hace unos meses conté cómo podíais utilizar el plugin Uploadify (de jQuery) para subir ficheros a vuestro portal hecho con CakePHP. Hoy iremos un poco más allá y crearemos un upload de imágenes con validación de campos con Ajax.

Antes de empezar estaría bien que hubierais leído el anterior tutorial —e incluso haberlo probado— para tener algo de práctica en el asunto. Este tutorial será (bastante) de ampliación del anterior. Quiero decir que habrá cosas en las que no profundizaré porque ya lo hice en el anterior, así como que en este hay mejoras, como la gestión de la respuesta de uploadify con JSON en lugar de con texto plano.

Si queréis podéis ver el resultado del tutorial que voy a explicar en este enlace:


Pongámonos a ello. Primero de todo, como siempre, ¿qué necesitamos?

(las versiones que he puesto entre paréntesis son las que he utilizado yo para el tutorial)

¿Y qué queremos hacer?

  • Mostraremos una vista con el botón de carga de ficheros (el de uploadify) y un botón desactivado para enviar el formulario.
  • El usuario seleccionará las imágenes deseadas y uploadify empezará a hacer la carga de imágenes
  • A medida que las imágenes vayan llegando al servidor generaremos dos miniaturas de la imagen (una para su futuro uso como miniatura y la otra sólo para mostrársela al usuario en el formulario de envío de imágenes) y guardaremos la original.
  • Si todas las imágenes se han guardado correctamente, mostraremos al usuario la imagen con todos los campos que pueda rellenar sobre la imagen. Si no se hubieran guardado correctamente se le mostrará al usuario un mensaje de error.
  • Una vez subidas todas las imágenes activamos el botón (eliminamos el atributo “disabled”) del formulario.
  • Cuando el usuario envíe el formulario pasaremos a hacer la validación Ajax.
  • Si todos los datos son correctos los guardamos y eliminamos la miniatura que no utilizaremos.

Y este es un resultado aproximado de cómo os podría quedar (paso por paso):

upload_imatges1upload_imatges2upload_imatges3

upload_imatges4upload_imatges5upload_imatges6

Como la otra vez, descargamos todo lo necesario y lo ponemos en nuestro proyecto. Así es como he organizado los ficheros en mi proyecto:

  • El componente de subida de ficheros en la carpeta /app/controllers/components/
  • jQuery en la carpeta /app/webroot/js/
  • Y uploadify…
    • Los JavaScript jquery.uploadify.js y swfobject en la carpeta /app/webroot/js/
    • El fichero uploadify.swf en la carpeta /app/webroot/flash/
    • La imagen cancel.png en la carpeta /app/webroot/img/
    • Y finalmente, el fichero uploadify.css en la carpeta /app/webroot/css/

A diferencia de otros plugins de jquery, uploadify no especifica la imagen “cancel.png” mediante CSS; lo hace mediante JavaScript y gracias a ello no tenemos que hacer ninguna modificación al CSS.

Hay una cosa que no he mencionado en los pasos de “¿qué queremos hacer?” y que encuentro que es bastante importante. Pensad un momento en el procedimiento de subir imágenes con este sistema…

Una vez el usuario ha seleccionado las imágenes estas empiezan a subir. Cuando han subido todas generamos ficheros (miniaturas, así como el archivo original) que guardamos en el servidor y que, si el usuario no diera al botón de “Guardar” del formulario, quedarían en nuestro servidor ocupando espacio.

Mi solución para este problema ha sido crear una tabla llamada “tempfiles” donde introduzco la ruta completa del fichero. Así después podré eliminar todos los ficheros temporales con un simple clic desde mi panel de administración o, si me gustara el contenido de estos ficheros temporales, podría añadirlos fácilmente a la base de datos sin tener que volver a subir los ficheros.

A diferencia del anterior tutorial, en este os explicaré desde la creación de las tablas SQL (cosa que no hice en el anterior).

Creemos entonces una tabla tempfiles con dos campos (id y location) y una tabla images con los campos que creamos necesarios:

model_tempfilesmodel_images

CREATE  TABLE IF NOT EXISTS `images` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL ,
  `description` VARCHAR(255) NULL ,
  `tags` TEXT NULL ,
  `file` VARCHAR(100) NULL ,
  `created` DATETIME NULL ,
  `modified` DATETIME NULL ,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB;

CREATE  TABLE IF NOT EXISTS `tempfiles` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `location` VARCHAR(350) NULL ,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB;

Y sus modelos correspondientes, image.php y tempfile.php:

// /app/models/tempfile.php
<?php
class Tempfile extends AppModel
{
	var $name = 'Tempfile';
}
// /app/models/image.php
<?php
class Image extends AppModel {
	var $name = 'Image';
	var $validate = array(
		'name'=>array(
			'length'=>array(
				'rule'=>array('between',3,45),
				'message'=>"El nombre debe contener entre 3 y 45 caracteres")),
		'description'=>array(
			'length'=>array(
				'rule'=>array('maxLength',100),
				'message'=>"La descripción no puede tener más de 255 caracteres")));
}

Necesitaremos tres carpetas para guardar las imágenes. Una para las imágenes a tamaño completo, otra para las miniaturas y otra para los ficheros temporales (las miniaturas que mostraremos al usuario). Creadlas en la carpeta img y dadles permiso de escritura:

  • /app/webroot/img/upload/full/
  • /app/webroot/img/upload/thumb/
  • /app/webroot/img/upload/tmp/

Ahora pasamos a la vista para añadir imágenes:

// /app/views/images/add.ctp
<?php
$javascript->codeBlock('var webroot="'.$this->webroot.'";var sessionId = "' . $session->id() .'";',array('inline'=>false));
$javascript->link(
	array(
		'jquery-1.3.2.min',
		'swfobject',
		'jquery.uploadify.min',
		'page_specific/images'
	), false);
?>
<div id="add-images" class="add-info">
	<? __('Subir imágenes') ?>
	<div class="contenido">
			<?= $form->create() ?>
			<div class="input upload">
				<div id="imageFile"><?php __("Necesitas JavaScript y Flash para poder subir ficheros") ?></div>
			</div>
			<div id="ajaxLoad" style="display:none;"><?= $html->image('ajax_load.gif', array('alt' =>__('Carregant...',true))) ?></div>
			<?= $form->end(array('label'=>__("Guardar",true),'disabled'=>'disabled')) ?>
	</div>
</div>

La primera línea es para iniciar dos variables de JavaScript, una de ellas con la ruta webroot y la otra con la id de la sesión de PHP. Quizás os interese poner esta porción de código en vuestro layout para aprovecharlo desde cualquier controlador/vista del proyecto.

Como véis hemos cargado jQuery, swfobject (de uploadify, tiene que cargarse antes de uploadify siempre), uploadify y un fichero que crearemos más adelante llamado “images.js” y situado en la carpeta /app/webroot/js/page_specific/.

También hemos creado una capa oculta llamada “ajaxLoad” que contiene una imagen para cuando hagamos la validación con Ajax.

Ahora que tenemos la vista empezaremos su fichero JavaScript images.js. Digo empezaremos porque iremos por partes, primero haremos la subida de ficheros y luego la validación con Ajax.

// /app/webroot/js/page_specific/images.js
$(function(){
	/**
	 * Validació amb Ajax
	 */
	var _loadingDiv = $("#ajaxLoad");

	// Muestra la capa #flashMessage encima de la capa con clase .add-info
	function flashMessage(message,classe){
		$(document.createElement('div'))
			.css('display', 'none')
			.attr('id','flashMessage')
			.addClass(classe)
			.html(message)
			.insertBefore($(".add-info")).fadeIn();
	}

// Sólo para Auth
//	function onTimeOut(data){
//		flashMessage(data.message,'error');
//	    window.setTimeout(function() {
//	        window.location.href = webroot + 'users/login';
//	    }, 2500);
//	}
// Fin Auth

	// Contador de IDs
	var item = 0;
	$('#imageFile').uploadify({
		'uploader' : webroot + 'flash/uploadify.swf',
		'script' : webroot +'images/upload/'+sessionId,
		'buttonText' : 'Buscar imágenes',
		'cancelImg' : webroot + 'img/botons/cancel.png',
		'auto' : 'true',
		'multi' : 'true',
		'simUploadLimit' : 3,
		'queueSizeLimit' : 10,
		'sizeLimit' : 800*1024,
		'fileExt' : '*.jpg;*.png;*.jpeg;*.gif',
		'fileDesc' : 'Imágenes',
		'onComplete' : function(evt, queueId, fileObj, response, data){
			// Interpretamos la respuesta JSON como un objeto
			var imageObj = eval('(' + response + ')');
			if (imageObj.success){
				$(".input.upload").append(
					// Creamos una capa que contenga la imagen resultante de fondo
					$("<div></div>").css({
						'background': 'left center no-repeat url(' + webroot + 'img/upload/tmp/' + imageObj.success.data + ')',
						'width': '385px'
					}).attr('id','imatge'+item)
						// Creamos una capa con los inputs
						.append($("<div></div>").css({
							'margin-left': '105px'
							// Afegim missatge d'èxit i inputs
						}).append(imageObj.success.message + '<br/>' +
							'<label for="ImageName'+item+'">Nombre de la imagen<em>*</em></label>'+
							'<input maxlength="45" type="text" name="data[Image][name]['+item+']" value="' +
							imageObj.success.data.replace(/\.([a-zA-Z]){3,4}$/,'') + '" id="ImageName'+item+'" />' +
							'<label for="ImageDescription'+item+'">Descripción (255 caracteres máximo)</label>' +
							'<input maxlength="255" type="text" name="data[Image][description]['+item+']" id="ImageDescription'+item+'" />' +
							'<label for="ImageTags'+item+'">Etiquetas (separadas por comas)</label>' +
							'<textarea name="data[Image][tags]['+item+']" id="ImageTags'+item+'"></textarea>' +
							'<input type="hidden" name="data[Image][file]['+item+']" value="' + imageObj.success.data + '" />' +
							'<div style="clear:both"></div>'
						)
					)
				);
				// Incrementamos contador de ids
				item++;
			}else if (imageObj.errors) {
				// En caso de error mostramos flashmessage
				flashMessage(imageObj.errors.message,'error');
			}
// Sólo para Auth
//			else if (imageObj.sessionTimeOut){
//				onTimeOut(imageObj.sessionTimeOut);
//			}
// Fin auth
			},
		'onAllComplete' : function(evt, data){
			flashMessage('Se han subido todas las imágenes. Recuerda enviar el formulario','info');
			$("#imageFile, #imageFileUploader").fadeOut('fast');
			$(":submit").removeAttr('disabled');
		}
	});
});

La función flashMessage sirve para generar una capa “flashMessage” dinámica justo encima de la capa con clase “.add-info”.

La función onTimeOut sólo es para los que utilicéis el componente Auth. En caso de terminarse la sesión muestra un mensaje al usuario y lo redirige hacia la página de login pasado un rato.

Nota: Esta no es la vía correcta para mostrar un error conforme la sesión del usuario ha expirado. Podéis ver la explicación que hago al respecto aquí.

Finalmente, en la función de carga de uploadify indicamos los parámetros que nos interesen y generamos una función OnComplete a nuestro gusto. En este ejemplo lo que he hecho es generar una capa (con id imatgeX, donde X es el número de ID actual, según el contador de IDs) con una miniatura de fondo que he generado exclusivamente para mostrársela al usuario en este formulario. Dentro de esta capa hay otra capa con las etiquetas y los inputs que necesito.

Es importante que todos elementos del formulario que queráis meter dinámicamente aquí lleven una ID única con un número al final. Esto nos servirá más adelante para la validación con Ajax.

Cuando todos los ficheros han terminado de subir mostramos un mensaje (con clase “.info”), eliminamos el botón de uploadify y eliminamos el atributo disabled del botón submit (y por tanto lo activamos).

Pasemos al controlador. Primero la construcción de éste, su función beforeRender (importante, lo expliqué en el anterior tutorial), el método add (vacío) y el método upload:

// /app/controllers/images_controller.php
<?php
class ImagesController extends AppController
{
	var $name = 'Images';
	// ¡Importante cargar RequestHandler!!
	var $components = array('Upload','RequestHandler');
	// Helpers necesarios
	var $helpers = array('Html','Form','Javascript');
	// Modelos a utilizar
	var $uses = array('Image','Tempfile');

	function beforeFilter()
	{
		if($this->action == 'upload'){
			if (isset($this->params['pass'][0])){
				$this->Session->id($this->params['pass'][0]);
				$this->Session->start();
			}else{
				$this->redirect('/');
			}
		}
		// Si utilizamos Auth debemos dar permiso a todo el mundo a las acciones 'upload' y 'ajaxAdd'
		//$this->Auth->allowedActions = array('upload','ajaxAdd');
		parent::beforeFilter();
	}

	function add()
	{
		$this->pageTitle = __("Añadir imágenes");
	}

	function upload()
	{
		// Desactivamos el debug (necesario siempre que trabajamos con Ajax)
		Configure::write('debug', 0);
		//header("Content-type: text/x-json");
		$this->autoRender = false;
		$this->layout = 'ajax';
		if (isset($this->params['form']['Filedata'])){
// Si utilizáis Auth eliminad estos comentarios
//			$user = $this->Auth->user();
//			if (!empty($user)){
// Fin Auth
				// Creamos la primera miniatura
				$thumb = $this->Upload->upload(
					$this->params['form']['Filedata'],'img/upload/thumb/', null,
					array(
						'type' => 'resizecrop',
						'size' => array(400,250),
						'output' => 'jpg',
						'quality'=>80),
					array('jpg','jpeg','png','gif'));
				// Si no se crea correctamente gestionamos los errores
				if (!empty($this->Upload->errors)){
					// Error que mostraremos al usuario
					$message = __("Error subiendo el fichero",true);
					// Guardamos el nombre original del fichero
					$data = $this->params['form']['Filedata']['name'];
					$this->set('errors', compact('message','data'));
					// Creamos un log con el auténtico error
					$this->log("Error creando la miniatura: " .
						implode(" | ",$this->Upload->errors),'upload/images');
				} else {
					// Si se ha guardado correctamente guardamos el 'fichero temporal' en la BD
					$file = $this->Upload->result;
					$location = realpath(WWW_ROOT . 'img/upload/thumb/' . $file);
					$tempfile['location'] = $location;
					$this->Tempfile->save($tempfile,false);
					// Generamos una miniatura temporal (la que mostraremos al usuario al guardar las imágenes)
					$tempThumb = $this->Upload->upload(
						$this->params['form']['Filedata'],'img/upload/tmp/', null,
						array(
							'type' => 'resizecrop',
							'size' => array(100,150),
							'output' => 'jpg',
							'quality'=>80),
						array('jpg','jpeg','png','gif'));
					if(!empty($this->Upload->errors)){
						// Si tiene errores lo guardamos en un log. A mi parecer, aquí no nos interesa mostrar error al usuario (ya que en realidad es una imagen temporal que más adelante borraremos)
						$this->log("Error creando la miniatura temporal: " .
							implode(" | ",$this->Upload->errors),'upload/images');
					} else {
						// Si no hay errores guardamos el 'fichero temporal'
						$location = realpath(WWW_ROOT . 'img/upload/tmp/' . $this->Upload->result);
						$tempfile['location'] = $location;
						// Es importante hacer el 'create' a partir de la segunda vez
						$this->Tempfile->create($tempfile);
						$this->Tempfile->save();
					}
					// Si se ha creado la primera miniatura subimos la original a la carpeta deseada
					$result = $this->Upload->upload($this->params['form']['Filedata'], 'img/upload/full/', $this->Upload->result);
					if (!empty($this->Upload->errors)){
						// Si no se guarda generamos log
						$this->log("Error subiendo la imagen: " .
							implode(" | ",$this->Upload->errors),'upload/images');
						// Y mostramos mensaje de error al usuario
						$message = __("Error subiendo el fichero",true);
						$data = $this->params['form']['Filedata']['name'];
						$this->set('errors', compact('message','data'));
					}else{
						// Guardamos 'fichero temporal'
						$data = $this->Upload->result;
						$location = realpath(WWW_ROOT . 'img/upload/full/' . $data);
						$tempfile['location'] = $location;
						$this->Tempfile->create($tempfile);
						$this->Tempfile->save();
						// Mostramos mensaje de éxito al usuario
						$message = sprintf(__("%s subido correctamente.",true),"<b>" . $this->params['form']['Filedata']['name'] . "</b>");
						$this->set('success',compact('data','message'));
					}
				}
// Si utilizáis Auth eliminad estos comentarios
//			}else{
//    			$message = "<b>" . __("Error",true) . ":</b> " . __("Tu sesión ha expirado. Vuelve a iniciarla por favor",true);
//				$this->set('sessionTimeOut',compact('message'));
//			}
// Fin Auth
			// Renderizamos la vista (/views/ajax/upload.ctp)
			$this->render('/ajax/upload');
		}
	}
}

A parte de los comentarios y el código (que hablan por sí solos.. 😉 ) quiero comentar un par de cosillas…

La cabecera que está comentada (Content-Type: text/x-json) la puse en su momento porque en Internet Explorer 8 y Opera 10 me daba algunos problemas si no ponía esta cabecera. Ahora sin ella me funciona correctamente (realmente había más cosas que interferían en su funcionamiento con IE y Opera), así que la he comentado por si la tuvierais que utilizar en algún momento.

Respecto a los comentarios sobre el componente de autenticación (Auth)… en caso de que en vuestra web queráis restringir las subidas a usuarios registrados deberéis descomentar las líneas comentadas para que, en caso de que al usuario le expire la sesión mientras está subiendo imágenes, se le muestre un mensaje de error (sin esto, en caso de expirar la sesión, no se mostraría nada al usuario).

Nota: Esta no es la vía correcta para mostrar un error conforme la sesión del usuario ha expirado. Podéis ver la explicación que hago al respecto aquí.

En cuanto al render de la vista… ahora pasaremos a la creación de la vista upload.ctp y veréis que el fichero es muy genérico. Quiero decir que con este mismo fichero podéis gestionar cualquier subida de ficheros que hagáis con Ajax, así que, tener que generar una vista idéntica para cada sección en que tengáis subida de ficheros, es algo absurdo.

Digo esto porque lo que hago yo es meter todos los ficheros relacionados con ajax en la carpeta /app/views/ajax/ y así los utilizo desde cualquier controlador (de ahí el “$this->render(‘/ajax/upload’)”.

Vamos a por el fichero upload.ctp:

// /app/views/ajax/upload.ctp
<?php
$output = array();
if(isset($errors)) {
	$output = Set::insert($output, 'errors',
		array(
			'message' => $errors['message'],
			'data' => $errors['data']
		));
} elseif (isset($success)) {
	$output = Set::insert($output, 'success',
		array(
			'message' => $success['message'],
			'data' => $success['data']
		));
}
// Sólo para Auth
//elseif (isset($sessionTimeOut)){
//	$output = Set::insert($output, 'sessionTimeOut', array('message' => $sessionTimeOut['message']));
//}
// Fin Auth
echo $javascript->object($output);

Este fichero es el encargado de convertir el array que le enviemos desde el controlador con la información (ya sea un error o un mensaje de éxito) a JSON. La salida que generará será algo así:

 // En caso de éxito
{
	"success":
	{
		"message":"<b>nombre_de_fichero.jpg<\/b> subido correctamente.",
		"data":"nombre_de_fichero.jpg"
	}
}
// En caso de error
{
	"errors":
	{
		"message":"Error subiendo el fichero",
		"data":"nombre_de_fichero.jpg"
	}
}

El campo “data” no lo utilizaremos en este caso, pero está ahí para que veáis que se pueden enviar tantos datos de respuesta como queráis.

Bien, con esto hemos terminado lo que sería la carga de ficheros. Nuestro método upload nos gestiona el fichero subido guardándolo en el servidor y devolviéndonos un mensaje como respuesta (tanto si la subida ha dado error como si no). Además nuestra función JavaScript se encarga de generarnos un formulario dinámicamente a medida que va recibiendo los nombres de imagen.

Ahora nos faltaría hacer la validación con Ajax de nuestro formulario.

Empezaremos por acabar de completar el fichero JavaScript. Al fichero images.js añadidle las siguientes funciones:

// Continuación del fichero /app/webroot/js/page_specific/images.js
// Convierte una_frase a unaFrase
function camelize(string) {
	var a = string.split('_'), i;
	s = [];
	for (i=0; i<a.length; i++){
		s.push(a[i].charAt(0).toUpperCase() + a[i].substring(1));
	}
	s = s.join('');
	return s;
}

// Decide qué función ejecutar según los datos recibidos (si es "error" o "success")
function afterValidate(data, status){
	console.log(data);
	console.log(webroot + 'images/ajaxAdd');
	$(".error-message, #flashMessage").remove();
	if (data.errors || data.saved) {
		if(data.saved){
			onSaved(data.saved);
		}
		onError(data.errors);
	} else if (data.success) {
		onSuccess(data.success);
	}
// Auth
//	 else if (data.sessionTimeOut){
//		onTimeOut(data.sessionTimeOut);
//	}
// fin Auth
}

// Esta función sirve para eliminar imágenes de la pantalla
// del usuario cuando estas han sido guardadas correctamente
function onSaved(data){
	$.each(data, function(id, item){
		$("#imatge"+id).slideUp('slow',function(){
			$(this).css({'background': 'none'})
				.html('<b class="ok">'+item.message+'</b>').slideDown('slow');
		});
	});
}

// En caso de error hacemos un bucle entre los errores y
// los mostramos cada uno en su input correspondiente
function onError(data){
	flashMessage(data.message,'error');
	$(".add-info :submit").removeAttr('disabled');
	$("#ajaxLoad").fadeOut();
	$.each(data.data, function(key){
		$.each(data.data[key], function(model, errors){
			for (fieldName in this) {
				var element = $("#" + camelize(model + '_' + fieldName) + key);
				var _insert = $(document.createElement('div')).insertAfter(element).hide()
				.addClass('error-message').text(this[fieldName]).slideDown();
			}
		});
	});
};

// En caso de guardarse todo correctamente mostramos mensaje
// y redirigimos al usuario donde queramos
function onSuccess(data){
	$("#ImageAddForm").slideUp('slow');
	flashMessage(data.message,'info');
	$("#ajaxLoad").fadeOut();
	window.setTimeout(function() {
		window.location.href = webroot + 'images/index';
	}, 1500);
};

// Envío del formulario mediante Ajax
$('#ImageAddForm').submit(function(){
	// Desactivamos el botón de submit
	$(".add-info :submit").attr('disabled','disabled');
	// Mostramos imagen de carga
	$("#ajaxLoad").fadeIn();
	// Eliminamos (si hubiera) mensajes de error
	$("#flashMessage").fadeOut();
	$(".error-message").slideUp();
	$.post(webroot + 'images/ajaxAdd',
		$(this).serializeArray(),
		afterValidate,
		"json"
	);
	return false;
});

La función camelize es la encargada de convertir las cadenas como_esta a cadenas comoEsta. Esto nos sirve para encontrar la id del textbox al que está vinculado el error a partir de los errores retornados en JSON.

La función afterValidate es la que decide qué función se ejecutará según la respuesta que recibamos (success, error o sessionTimeOut).

La función onSaved es la encargada de, en caso de que haya imágenes con errores y otras no (y al volver a enviar el usuario el formulario), eliminar del formulario las imágenes guardadas correctamente.

onError se encarga de mostrar un flashMessage mostrando el mensaje de error general y cada uno de los errores de validación.

Si todas las imágenes se guardan correctamente se ejecuta onSuccess que se encarga de ocultar todo el formulario haciendo un efecto slideUp, mostrar un flashMessage, ocultar la imágen de carga de Ajax y finalmente redirige al usuario a la página deseada (en el ejemplo lo redirijo a la misma página).

El último método es el encargado de enviar el formulario mediante Ajax.

De la vista ya no tenemos que tocar nada más así que pasemos al método ajaxAdd del controlador.

 // /app/controllers/images_controller.php
function ajaxAdd()
{
	Configure::write('debug', 0);
	$this->autoRender = false;
	$this->layout = 'ajax';
	if ($this->RequestHandler->isAjax()){
		if (!empty($this->data)){
			// Si utilizáis Auth eliminad estos comentarios
			//$user = $this->Auth->user();
			//if (!empty($user)){
				// Inicializamos las variables que contendrán errores y demás información
				$data = $dataOk = array();
				$error = false;
				// Iniciamos un bucle con todas las imágenes que recibamos
				foreach($this->data['Image']['name'] as $key=>$name){
					// Nombre de fichero
					$imageFile = $this->data['Image']['file'][$key];
					// Datos a guardar de la imagen
					$imageData = array(
						'Image'=>array(
							'name'		=> $name,
							'tags'		=> $this->data['Image']['tags'][$key],
							'description'=>$this->data['Image']['description'][$key],
							'file'		=> $imageFile));
					// Inicializamos el modelo (importante ya que estamos haciendo un bucle)
					$this->Image->create($imageData);
					// Validamos los campos
					if ($this->Image->validates()){
						// Guardamos la imagen en la base de datos
						$image = $this->Image->save($imageData);
						if (!empty($image)){
							// Eliminamos los 'ficheros temporales'
							$location = array('tmp','full','thumb');
							foreach ($location as $dir){
								$loc = realpath(WWW_ROOT . 'img/upload/' . $dir . '/' . $imageFile);
								// Si la carpeta es "tmp" eliminamos la imagen del servidor
								if($dir == 'tmp'){
									if (!unlink($loc)) $this->log('Error eliminando miniatura temporal ' . $imageFile);
									else $this->Tempfile->deleteAll(array('Tempfile.location'=>$loc));
								}else $this->Tempfile->deleteAll(array('Tempfile.location'=>$loc));
							}
							// Mensaje a mostrar cuando una sola imagen es guardada
							$message = sprintf(__("Imagen %s guardada correctamente",true),$imageFile);
							$dataOk[$key] = array('message'=>$message,'data'=>$imageFile);
						}
					}else {
						// Errores
						$error = true;
						$Image = $this->Image->invalidFields();
						$data[$key] = compact('Image');
					}
				}
				// Si no tenemos errores..
				if(!$error){
					$message = "<b>" . __("Todas las imágenes han sido guardadas correctamente", true) . "</b>";
					$data = $this->data;
					$this->set('success',compact('message','data'));
				} else {
					$message = "<b>" . __("Error",true) . ":</b> " . __("Hay campos que no son válidos, compruébalos por favor.",true);
					$set = compact('message','data');
					// Si tenemos algunas imágenes guardadas y otras no guardamos la variable dataOk
					if(!empty($dataOk)) $set = array_merge($set,compact('dataOk'));
					$this->set('errors',$set);
				}
// Auth
//        		}else{
//        			$message = "<b>" . __("Error",true) . ":</b> " . __("Tu sesión ha expirado. Vuelve a iniciarla por favor",true);
//					$data = $this->data;
//					$this->set('sessionTimeOut',compact('message','data'));
//        		}
// fin Auth
		}
		$this->render('/ajax/form_validation_array');
	}else $this->redirect('/');
}

Este es un poco más complicado que el de upload por una simple razón: tenemos un array de datos en lugar de un único dato.

Si os fijáis en el código veréis que dentro del foreach he utilizado la variable $key para definir la clave del elemento actual. Es importante utilizarla para que mantenga el orden de las imágenes. No nos interesa para nada que nos guarde la tercera imagen del formulario y nos diga que la que se ha guardado correctamente es la primera..

A parte de eso y como a menudo digo… el código habla por sí solo y además está bien comentado, así que si os lo miráis bien lo entenderéis sin problemas.

Por último nos falta crear la vista “form_validation_array” que, del mismo modo que en el método upload, se encargará de convertir el array resultante a JSON:

// /app/views/ajax/form_validation_array.ctp
<?php
$output = array();
if(isset($errors)) {
	// Si hay errores
	$output = Set::insert($output, 'errors', array('message' => $errors['message']));
	foreach ($errors['data'] as $key => $item) {
		foreach($item as $model => $errs){
			foreach ($errs as $field => $message) {
				$output['errors']['data'][$key][$model][$field] = $message;
			}
		}
	}
	// En caso de haberse guardado alguna imagen
	if(!empty($errors['dataOk'])){
		foreach($errors['dataOk'] as $key => $item){
			foreach($item as $field => $message){
				$output['saved'][$key][$field] = $message;
			}
		}
	}
// Si todas se han guardado correctamente...
}elseif (isset($success)) {
	$output = Set::insert($output, 'success', array('message' => $success['message']));
}
// Sólo para Auth
//elseif (!isset($auth)){
//	$output = Set::insert($output, 'sessionTimeOut', array(
//        'message' => $sessionTimeOut['message'],
//        'data' => $sessionTimeOut['data']
//	));
//}
// fin Auth
echo $javascript->object($output);

Y su salida aproximada:

// Si ha habido algún error (fijaros que una de las imágenes se ha guardado correctamente)
{"errors":{
	"message":"<b>Error:<\/b> Hay campos que no son v\u00e1lidos, compru\u00e9balos por favor.",
	"data":{
		"0":{
			"Image":{
				"name":"El nombre debe contener entre 3 y 45 caracteres"
				}
		},"2":{
			"Image":{
				"name":"El nombre debe contener entre 3 y 45 caracteres"
				}
			}
		}
	},"saved":{
		"1":{
			"message":"Imagen 1165589284_f0.jpg guardada correctamente",
			"data":"1165589284_f0.jpg"
		}
	}
}

// Si todo ha ido bien
{"success":{
	"message":"<b>Todas las im\u00e1genes han sido guardadas correctamente<\/b>"
}}

Pues ya está! Si habéis seguido todos los pasos correctamente deberíais tener vuestro upload funcionando.

A continuación os dejo el ejemplo que he ido haciendo a medida que hacía el tutorial así como un fichero .zip con todos los ficheros del proyecto.

Nada más, como siempre.. espero que le sirva a alguien :) y si tenéis cualquier duda podéis postearla en los comentarios que trataré de contestarla cuanto antes.

Páginas de referencia:

Categories: Programació, Web Etiquetes:, , , , ,
  1. Alexandro
    diumenge, 1 de novembre de 2009 a les 19:53 | #1

    Excelente tutorial, lo que andaba buscando y ademas con un ejemplo descargable, gracias por compartir

    Saludos

    • dilluns, 2 de novembre de 2009 a les 17:06 | #2

      Gracias a ti por contestar, al menos ya sé que a alguien sí le sirvió ^^

  2. dimecres, 4 de novembre de 2009 a les 10:46 | #3

    He actualizado el ejemplo porque tenía un pequeño error. Me había olvidado de eliminar / comentar una línea donde había un console.log (comando para mostrar código en la consola de firebug). Por culpa del console.log la mayoría de navegadores daban error y tras subir las fotos no se podía enviar el formulario.

    Lo dicho, ya está actualizado, ¡pero sólo del ejemplo! ¡Recordad eliminar el console.log del fichero zip si lo descargáis!

  3. Alexandro
    dijous, 5 de novembre de 2009 a les 18:09 | #4

    Hola, estube tratando de implementar algo parecido en un proyecto pero he estado teniendo un problema. Al parecer uploadify no me esta ejecutando el action del controller que le especifico en el parametro script, ya probe pasandole el id de la sesion como parametro al action y aun no me funciona, alguna idea de que podra ser?

    • diumenge, 8 de novembre de 2009 a les 12:30 | #5

      ¿La acción a la que intentas acceder es del mismo controlador? Porque me parece recordar que sólo funciona entre el mismo controlador.. Es decir, si quieres hacer un upload para el controlador “images”, la acción “add” también tendrá que estar en éste mismo controlador.

      Pero si intentas acceder desde la acción “add” de álbums a la acción “upload” de images no funcionará.

      Espero que te sirva

  4. Alexandro
    dimarts, 10 de novembre de 2009 a les 20:11 | #6

    Hola de nuevo, una disculpa por contestar tan tarde, ya logre solucionarlo tenia mal implementada la funcion beforefilter del controller, ya todo funciona excelente. Maravilloso tutorial.

    Saludos gracias.

  5. javier
    dimecres, 18 de novembre de 2009 a les 03:11 | #7

    Hola soy novato en cakePhp, muy buen tutorial pero no he podido hacer que me funcione lo de buscar la imagen, que puede ser?, ademas queria preguntarte si puedo utilizar esto para subir archivos.

    gracias

    • dimecres, 18 de novembre de 2009 a les 13:12 | #8

      ¿A qué te refieres con que no has podido hacer que te funcione lo de hacer buscar la imagen? ¿No logras que el botón de flash haga nada?

      Por otro lado.. si dices que hace poco que has empezado con Cake quizás este tutorial sea un poco complicado para tu nivel. Mejor si empiezas con algo más simple como la subida de ficheros sin validación Ajax.

      Salu2

  6. javier
    dijous, 19 de novembre de 2009 a les 03:10 | #9

    hola
    el boton de flash no aparece me sale lo siguiente “Necesitas JavaScript y Flash para poder subir ficheros ” pero tengo instalado el plugin de flash y tengo habilitado el javascript, utilice el internet explorer y me sale lo mismo, quiero implementarlo para un proyecto de la Universidad donde en el sitio web podes subir imagenes, archivos flash y audio, me gusto mucho el trabajo que hiciste te agradeceria si me puedes ayudar.

  7. javier
    dijous, 19 de novembre de 2009 a les 04:29 | #10

    hola: ya logre que me saliera como tu ejemplo aunque no me esta guardando las imagenes, me sale el mensaje que se ha guardado pero reviso la tabla y las carpetas y no hay nada, que puede ser?.
    ademas tengo otra pregunta puedo utilizar esto para poder subir otro tipo de archivos? o solo imagenes?
    gracias.

  8. dijous, 19 de novembre de 2009 a les 11:30 | #11

    Debes mirar que en todo momento te esté guardando la imagen. Para ello utiliza el método “log”, así averiguarás qué nombre tiene el fichero en cada momento y si te lo guarda en el sistema.

    Por ejemplo, fíjate en el método upload, en la línea 102 (de mi blog), verás lo siguiente:

    $data = $this->Upload->result;
    $location = realpath(WWW_ROOT . 'img/upload/full/' . $data);

    Sustitúyelo por…

    $data = $this->Upload->result;
    $this->log("Nombre del fichero: ".$data,'upload');
    $location = realpath(WWW_ROOT . 'img/upload/full/' . $data);
    $this->log("Ruta del fichero: ".$location,'upload');

    Luego, en la línea 19 del método AjaxAdd añade también:

    $this->log("Nombre del fichero recibido: ".$imageFile,'upload');

    Con esto lo que hacemos es generar un log en /app/tmp/logs/upload.log (de ahí el segundo parámetro “upload”). Mírate este log y dónde veas que no te aparece el nombre de imagen es que algo está fallando.

    Lamento no poder ayudarte más pero aparentemente esto es un error tuyo y deberás ser tú quien lo solucione finalmente.

    Si tienes cualquier otro problema más házmelo saber.

    Salu2

    Edito: No estarás utilizando i18n con su tabla en la base de datos y todo, ¿no?

  9. javier
    divendres, 20 de novembre de 2009 a les 04:04 | #12

    hola: gracias por tu ayuda, la ruta esta bien y el nombre aparece lo que veo es que cuando le doy guardar se borra la imagen de las carpetas y de la tabla tempfiles pero no se envia la informacion a la tabla images y se borra la imagen de la carpeta full.

  10. javier
    divendres, 20 de novembre de 2009 a les 05:12 | #13

    que pena yo de nuevo pero ya logre que me guardara en la BD era borrar la sgte linea en la function add lo siguiente : $this->Image->delete($image[‘Image’][‘id’]); ,lo que no he podido es que se me guarde la imagen en la carpeta full ya que cuando le doy guardar se borra de las 3 carpetas (full, temp, thumb). Como haces para que se borren? ahi debe estar la falla.

  11. divendres, 20 de novembre de 2009 a les 09:26 | #14

    Aaaaaaahhhmiiiiigooo… que estás utilizando el código que subí al zip.. si sigues todo el tutorial verás que en el método add sólo hace falta tener el título de la pàgina:

    function add()
    {
    $this->pageTitle = "Carga de ficheros con Uploadify y CakePHP";
    }

    Borra todo lo demás ya que lo que estoy haciendo es eliminar todos los ficheros guardados en la base de datos (está así en el ejemplo para evitar llenar el servidor de mierda).

    Salud

  12. javier
    dimarts, 24 de novembre de 2009 a les 04:38 | #15

    muchas gracias, perdon pense que el zip tenia lo que estabas explicando, tengo una duda puedo utilizar el sgte codigo para cambiar el tamaño de las imagenes es que necesito mostrar las que se han subido como si fuera una galeria de fotos :
    $this->params[‘form’][‘Filedata’],’img/upload/tmp/’, null,
    array(
    ‘type’ => ‘resizecrop’,
    ‘size’ => array(100,150),
    ‘output’ => ‘jpg’,
    ‘quality’=>80),
    array(‘jpg’,’jpeg’,’png’,’gif’));

    o tenes algun ejemplo que haga lo que necesito?
    gracias por la ayuda que me puedas prestar

  13. dimarts, 24 de novembre de 2009 a les 12:06 | #16

    ¿Quieres que se te muestre una galería directamente creada con javascript a partir del resultado? Si no te refieres a esto es porque no acabo de entender muy bien la pregunta.. para qué has puesto ese código ahí? :s

    Edit: Por cierto, respecto a la pregunta de hace unos días de si podías subir otras cosas con este método… sí, claro que puedes.. pero como imaginarás no podrás hacer una miniatura de un mp3…

  14. javier
    dimecres, 25 de novembre de 2009 a les 03:36 | #17

    hola: lo que pasa es que debo mostrar las imagenes que se han subido al servidor entonces me gustaria crear una galeria donde las muestre en miniatura y la que se seleccione se descarga al pc.
    lo otro para poder subir cualquier tipo de archivo debo de cambiar la sgte linea : ‘fileExt’ : ‘*.jpg;*.png;*.jpeg;*.gif’ o cual debo de cambiar?
    gracias por tu ayuda

  15. dimecres, 25 de novembre de 2009 a les 12:07 | #18

    No entiendo qué complicación puedes tener haciendo la galería..

    Para poder subir otros ficheros debes eliminar todo el primer array y en el segundo tienes que poner la extensión que quieras permitir.

    De todos modos yo no soy quien ha creado este componente. Como ya he dicho anteriormente deberías ir a su página web si tienes más dudas http://labs.iamkoa.net/2007/10/23/image-upload-component-cakephp/

    Salud

  16. Carlos
    divendres, 22 de gener de 2010 a les 15:47 | #19

    Hola muy buenas. He encontrado tu ejemplo buscando por internet alguna implementación de como cargar imágenes y además que usara el framework de cakephp (que yo también estoy utilizando). De antemano te felicito por el trabajo, y por compartir tus conocimientos con los demás.

    He seguido el tutorial y he conseguido subir las imagenes al servidor. Pero me han salido 2 problemas:

    1. No me copia nada en la carpeta full (en el resto si copia las imagenes), he estado revisando el código siguiente:

    $this->log(“Nombre del fichero: “.$data,’upload’);
    $location = realpath(WWW_ROOT . ‘img/upload/full/’ . $data);
    $this->log(“Ruta del fichero: “.$location,’upload’);

    y me muestra el nombre del fichero, pero no así la ruta (repito que las carpetas thumb y tmp, si se almacenan las imágenes, así que supongo que para full debería ser igual).

    2. No me almacena nada en la tabla images (en tempfiles, almacena la información correcta): la acción de guardar en la tabla la realiza en la función ajaxAdd, pero parece que no está entrando en dicha función (ya que pongo un log y no muestra nada). ¿alguna idea de por qué puede ser?

    Gracias de antemano, y un saludo!

  17. Carlos
    divendres, 22 de gener de 2010 a les 15:58 | #20

    Buenas de nuevo. He solucionado el primero de mis errores.

    En el controlador Images, en la línea 92, tenías puesto lo siguiente:
    $result = $this->Upload->upload($this->params[‘form’][‘Filedata’], ‘img/’, $this->Upload->result);

    donde indicas la ruta: ‘img/’, debería ser: ‘img/upload/full/’ que es la ruta donde quieres guardar la imagen original.

    Voy a seguir investigando a ver si logro almacenar en la tabla images.

    Saludos!

  18. divendres, 22 de gener de 2010 a les 17:25 | #21

    Hola Carlos, primero de todo agradecerte a ti que te pases por aquí y más aún que me hayas mencionado el error (que por cierto, ya he solucionado).

    Respecto al segundo punto… teniendo en cuenta que no puedes entrar en el método lo primero que me viene a la cabeza es el ‘RequestHandler’. Pero si dices que has seguido el tutorial imagino que lo tendrás cargado.

    Otra cosa que se me ocurre es que en lugar de haber seguido el tutorial hayas descargado el zip y ahí, en el método add elimino todas las fotos (dado que cogí los ficheros del ejemplo y sin modificarlos ni nada los comprimí y subí). Aunque imagino que este último no es tu caso ya que has mencionado que haces un log nada más entrar en ajaxAdd y que éste no te muestra nada.

    Otra cosa que podría fayar podría ser el JavaScript… aunque imagino que si fuera esto lo que fallara te hubuieras dado cuenta rápido ya que no te cargaría con Ajax 😛

    … Y no se me ocurren muchas cosas más :s … Revisa tu código que seguro que algo está fallando (el típico error que cuando encuentras dices pero que toooooooontooo [tú igual no lo dices, pero a mi esto me pasa casi cada día.. xD])

    En fin, me sabe mal no poder ayudarte un poco más, pero sin el código es complicado. Si quieres pon el código de tus ficheros en alguna web tipo http://paste.ideaslabs.com , ponme por aquí los enlaces y si tengo un rato le echaré un ojo 😉

  19. Carlos
    dilluns, 25 de gener de 2010 a les 17:56 | #22

    …Tenías más razón que un santo. El problema lo tenía en el fichero ‘images.js’, una llave cerrada donde no tocaba…A veces hay que mirar el código desde lejos para ver las cosas claras jajaja.

    Bueno pues ya comprobado que todo funciona bien, mi próximo paso es intentar almacenar en BD las imágenes; ¿tienes alguna experiencia en ello?, ¿conoces alguna librería o implementación que funcione con uploadify?. Lo que intentaré es hacer un guardado mediante las rutas donde se han subido las imagenes al servidor; ya te contaré que tal ha ido 😉

    Saludos!

  20. dilluns, 25 de gener de 2010 a les 21:04 | #23

    Ui ui Carlitos… creo que andas un poco perdido :s

    Fíjate que en el script de mi ejemplo muestro la imagen subida e inputs donde el usuario deberá introducir los datos de la imagen.

    La idea es que estos inputs los metas dentro de un formulario y que, una vez subidas todas las imágenes, el usuario le de al botón de ‘guardar’ del formulario.

    Hecho esto sólo tienes que guardar los datos en la BD :)

  21. Carlos
    dimarts, 26 de gener de 2010 a les 00:32 | #24

    Umm o no te he entendido yo bien a ti, o tu a mi. Yo lo que pretendo es guardar la imagen en la BD, el jpg en sí vamos; ya que después lo que pretendo es que otra aplicación que tiene acceso a esa BD recupere las imagenes para mostrarlas.

    En el esquema sql que has propuesto, lo único que guardas de la imagen es información externa a ella: descripción, ruta, … Pero no la imagen en sí.

    A esto me refería en mi comentario anterior 😉

  22. Carlos
    dimecres, 27 de gener de 2010 a les 13:35 | #26

    Veo que vas en la misma onda que yo ;). Efectivamente tengo mis campos LONGBLOB para guardar la imagen ‘full’ y la ‘thumb’, y estoy usando las funciones (fread, fopen, addslashes) que has mencionado.

    Pero parece que tengo algún problema al hacer el save, ya que no me guarda nada en los blobs…

    Pego el trozo de código que he modificado, a ver si veis porqué no me guarda nada en BD:

    // Iniciamos un bucle con todas las imágenes que recibamos
    foreach($this->data[‘Image’][‘name’] as $key=>$name){

    // Nombre de fichero
    $imageFile = $this->data[‘Image’][‘file’][$key];
    // Datos a guardar de la imagen
    $loc = realpath(WWW_ROOT . ‘img/upload/thumb/’ . $imageFile);
    $fileThumb = fread(fopen($loc, “r”), filesize($loc));

    $imageData = array(
    ‘Image’=>array(
    ‘name’ => $name,
    ‘description’ => $this->data[‘Image’][‘description’][$key],
    ‘file’ => $imageFile,
    ‘thumb’ => $fileThumb));

    // Inicializamos el modelo
    $this->Image->create($imageData);

    // Validamos los campos
    if ($this->Image->validates()){

    // Guardamos la imagen en la base de datos
    $image = $this->Image->save($imageData);

    como veis utilizo un campo ‘thumb’ en mi tabla ‘images’, el cual es de tipo LONGBLOB, y en el cual inserto la imagen que tengo guardada en la ruta del servidor.

    He comprobado la variable, y si que tiene los bytes leidos de la imagen, pero a la hora de hacer el save, no guarda nada en ese campo. Estoy un poco desorientado con esto… a ver si veo la luz 😀

    Saludos!

  23. dimecres, 27 de gener de 2010 a les 20:28 | #27

    Pues así vote pronto… lo único que veo que puede estar fallando es que no has utilizado addslashes:

    $fileThumb = fread(fopen($loc, “r”), filesize($loc));

    Debería ser…

    $fileThumb = addslashes(fread(fopen($loc, “r”), filesize($loc)));

    Ya que sin él puede que falle la inserción SQL.

    ¿Qué te aparece en el debug de Cake? Has llegado a ver que te haga el insert??

  24. Carlos
    dimecres, 27 de gener de 2010 a les 22:43 | #28

    Bueno al fin he conseguido que funcione. No he cambiado gran cosa, pero ahora si que guarda los ficheros.


    $loc = realpath(WWW_ROOT . ‘img/upload/thumb/’ . $imageFile);
    $fp = fopen($loc,”r”);
    $size = filesize($loc);
    $fileThumb = fread($fp, $size);
    fclose($fp);

    luego añado la variable $fileThumb a mi array de datos a guardar, como expliqué antes.

    He probado a usar addslashes y no usarlo, y me sucede lo siguiente:
    – con addslashes: guarda la imagen en BD pero no puedo visualizarla con MySQL Query Browser, dice que la imagen es corrupta, supongo que dirá esto porque la he alterado con dicha función (¿que debería hacer al recuperarla?, ¿algo inverso no?)
    – sin addslashes: guarda la imagen en BD y la muestra correctamente. ¿Que problema podría haber sino uso la función?, supongo que cree una query no válida por las comillas. ¿conoceis alguna imagen por ahí para probar ese error?

    Gracias por aguantarme, y espero que esto le sirva a alguien que quiera hacer lo mismo. Saludos!

  25. dimecres, 27 de gener de 2010 a les 23:48 | #29

    Para volver a mostrar la imagen deberás utilizar ‘stripslashes’:

    http://php.net/manual/en/function.stripslashes.php

    😉

    Si no utilizas la función, MySQL probablemente no te permita insertar los datos, ya que al leer la imagen puede que se guarde algún cacho de texto del palo…

    cßšâ<w£è~

    Esa coma que hay ahí será la causante del fallo, porque a la hora de insertar los datos, Cake hará lo siguiente:

    "INSERT INTO tabla VALUES ('…','cßšâ<'w£è~','…');"

    Como puedes ver, el valor ha estado englobado con comillas simples ('). Al haber una coma simple de por medio, petará.

    Addslashes convierte esa cadena a:

    cßšâ<\'w£è~

    Al meter la contrabarra la inserción en MySQL se hará correctamente.

    Por otro lado, stripslashes simplemente elimina esas contrabarras.

    No te preocupes por preguntar, me gusta ver que alguien sigue el blog y que os sirven de algo mi ayuda :)

    Edit: Me he colado, no había visto la mitad de tu comentario… >_< (aquí debajo está la respuesta que tocaba..)

  26. dilluns, 1 de febrer de 2010 a les 20:05 | #30

    Buah, no me leí la mitad de tu comentario Carlos! xD

    Tienes que hacerlo con addslashes. Olvídate de lo que te diga el Query Browser, tú guardalo con addslashes y luego leelo directamente de la BD añadiendo la cabecera y todo para ver si te ha guardado la imagen correctamente.

  27. Carlos
    dilluns, 1 de febrer de 2010 a les 20:31 | #31

    Gracias, por recontestar 😉

  28. diumenge, 14 de març de 2010 a les 18:00 | #32

    hola soy nuevo en esto de cakephp te comento que he seguido los dos antiguos tutoriales sin lograr resultados, espero que con este que esta muy bien explicado lo logre, de ante mano gracias por compartir tus conocimientos.. saludos desde peru

  29. dilluns, 15 de març de 2010 a les 09:32 | #33

    Si no logras hacerlo con este tutorial no dudes en preguntar!

    De todos modos, en cuanto los de Cake saquen la versión 1.3 estable seguramente haré otro tutorial más sobre carga de ficheros. Eso sí, sería un poco más avanzado. Esta vez utilizando plantillas .ctp de Cake en lugar de generando el resultado con JavaScript.

  30. dimarts, 1 de juny de 2010 a les 14:46 | #34

    Buenas! muy buen tutorial.

    tengo un problemilla. Cuando tengo dos imagenes con el mismo nombre, en la base de datos lo guarda con el mismo nombre el campo file, aunque si es verdad que en la carpeta de imagenes crea imagenes con el nombre y un número.

  31. dimarts, 1 de juny de 2010 a les 16:07 | #35

    sigo haciendo prueba de subir diferentes imagenes con nombres iguales.
    si antes de volver a subir la misma imagen refresco la pantalla dos veces, entonce lo sube bien.

    puede ser que se quede algo en cache?

  32. dimarts, 1 de juny de 2010 a les 16:53 | #36

    Hola @deldan, aparentemente parece que no estás poniendo bien alguna variable.

    Por lo que he entendido, veo que subes “Imagen.png” (por ejemplo) y en el servidor se te guardan “Imagen.png” e “Imagen1.png” mientras que en la base de datos sólo se te está guardando “Imagen.png”.

    De ser así parece ser que, en lugar de estar guardando el nombre resultante que devuelve la aplicación Cake una vez ha guardado el fichero, lo que está guardando es el nombre original del fichero.

    Revisa que hayas puesto bien todas las variables en tu código JavaScript.

  33. dilluns, 5 de juliol de 2010 a les 01:46 | #37

    First of all, thanks for this tutorial and my apologies for posting in English. I hope you’ll understand what I’m saying.

    I’m having a few issues, first of all, when uploading multiple files and a file finishes uploading before all of your files are queued, no more files are added to the queue.

    I encountered my second problem when I tried to work around the first one by setting ‘auto’ to false. But it doesn’t work.. they will always start sending automatically. I’ve tried setting auto to false in both images.js and the uploadify.js, but no luck.

    Last issue.. in firefox, after clicking ‘save’, the “Loading…” message pops up, but never goes away, even though the files are saved to the database.

  34. dilluns, 5 de juliol de 2010 a les 09:00 | #38

    Don’t worry about writing in english @Ben ; all that I’ve learned was reading in English : )

    Could you paste somewhere (like http://pastebin.com/) your JS code? Maybe I could you help better if I read the code first.

    It seems there is an error with the counter or something similar that avoids the script continuing. Maybe is an error in the callback, so when you upload the first file, the uploader breaks.

    I’ll try to help you once I’ve seen a little bit more of code.

    PS. Sorry about my english : )

  35. dilluns, 5 de juliol de 2010 a les 09:28 | #39

    Thanks for the quick reply @Booletaire , and I can understand you fine, thanks for making the effort. Meanwhile, the “auto” issue has fixed itself while I was sleeping, and I realized last night that the queue issue was my own fault. (My Mac was showing a list of images containing duplicates, it just didn’t add the duplicates to the queue)

    Leaves me with two other issues.. first of all, saving the images in firefox works, but it seems one of the callbacks is misfiring. As the ‘Loading…’ message never disappears, the thumbnails and forms of the uploaded images do not fade out either, and of course, the redirect to the image index doesn’t happen.

    A second issue I’m having is that the “select images” button (the flash object) is not shown in IE8 (32bit) on Windows 7 (64 bit).

    This is my javascript:
    (the unchanged) uploadify v2.1.0: http://pastebin.com/6mtxhP0f
    Images.js: http://pastebin.com/KsvZYdFk
    And finally, since I’m working with cake 1.3.2, I had to update the view a bit, so here it is: http://pastebin.com/rL5AiUL0

    If you want something else, let me know.

  36. dilluns, 5 de juliol de 2010 a les 09:44 | #40

    Yep… you’re right. The upload button is not shown under IE8, but I can’t see it with the 64bit version. With the 32 bit version works fine. I turned on JS debugging and it says that “webroot” is not defined. I will attempt to fix it when I get home (now I’m working).

    I think that the error you are having is a firebug issue. If you comment / delete all the “console.log” statements the upload will work (I think…). If you don’t have firebug opened & working, the “console.log” breaks the execution, so the callback won’t work.

    I hope I’ve helped you.

    PS. As I said, I’ll take a look at the IE8 issue when I get home.

  37. Ben
    dilluns, 5 de juliol de 2010 a les 10:04 | #41

    Yep, you’re right, it is a firebug issue! When it is open the callback works. I’ve seen that issue in the comments here before but I must’ve missed the context, google translate isn’t perfect yet 😉

    Thanks again, and I await your reply about IE8. BTW, it IS a 32bit internet explorer, it’s just windows 7 that is 64bit. As far as I know, there is no flash support for a 64bit IE.

  38. dimecres, 7 de juliol de 2010 a les 00:43 | #42

    Men donde esta el Metodo de la Conexion a la BD que no lo Consigo y cual es que index

  39. dimecres, 7 de juliol de 2010 a les 10:28 | #43

    @Ben sorry for the delay. As you said, there is no Flash version for the 64bit version of IE8.

    Otherwise, I tested it under IE8 (32bit) under windows 7 (64bit) and it works for me.

    I regret not being able to help you more

    @Manuel , a qué conexión te refieres? Eres consciente de que este tutorial es para CakePHP? De ser así, deberías saber que CakePHP está permanentemente conectado a la BD y que tan sólo tienes que configurar el fichero “database.php” para que se conecte a la base de datos.

    Si no es a lo que te referías intenta ser un poco más conciso con tu pregunta por favor.

    Salud

  40. Ben
    dimecres, 7 de juliol de 2010 a les 10:51 | #44

    @Booletaire No problem, thanks for taking the time, I will investigate it some more.

  41. dimecres, 7 de juliol de 2010 a les 11:33 | #45

    If you find any solution, please, let us know : )

  42. Ben
    dimecres, 7 de juliol de 2010 a les 13:06 | #46

    Solution found, if you want to use flash in a browser, make sure you have the flash player installed for that browser 😉
    Silly that I overlooked that, but I was under the assumption that IE would tell me it was missing flash player.

  43. dimecres, 7 de juliol de 2010 a les 14:01 | #47

    These things happen… xD

    Nice to see you solved it : )

  44. Jaime
    dissabte, 12 de març de 2011 a les 08:49 | #48

    Hola como estas, Bueno mi consulta es la siguiente yo hice mi codigo pero lamentablemente carga solamente 1 imagen, pero si en mi tabla por decir tengo 2 campos q se requieren cargar archivos como lo haria espero q me ayudes en esa parte por favor se te agradeceria mucho.

  45. dimecres, 16 de març de 2011 a les 13:43 | #49

    Si no me das más explicación veo difícil ayudarte… por dos campos a qué te refieres? Dos imágenes distintas?

    Si son dos imágenes distintas, igual te interese poner dos cargadores de imágenes.. sinó… tendrás que apañártelas para rellenar un input y otro con los datos que te devuelva uploadify.

  46. m16u31
    dimecres, 4 de maig de 2011 a les 04:53 | #50

    hola como soluciono esto
    “Necesitas JavaScript y Flash para poder subir ficheros

  47. m16u31
    dimecres, 4 de maig de 2011 a les 05:11 | #51

    disculpa pero ese error me mostraba por trabajar con cake 1.3,, alguna solucion ahi gracias,,es muy util el trbajo q hciciste

  48. dimecres, 4 de maig de 2011 a les 10:53 | #52

    Eso es porque tienes algo mal del JavaScript (o puede incluso te hayas olvidado de cargar parte de éste). Revisa tu código.

  49. dilluns, 9 de maig de 2011 a les 17:47 | #53

    Hola
    En primer lugar, gracias por el tutorial.

    Pero tengo un problema para su aplicación.
    He descargado todos los archivos, los puso en las carpetas correctas.

    El uploadify aparentemente funciona con normalidad, la barra de progreso llegue al 100%, No obstante, las imágenes no se envían a las carpetas.

    Agradecería cualquier ayuda.

    • dilluns, 9 de maig de 2011 a les 19:34 | #54

      Hola Carlos, creo recordar que el fichero zip que subí es exactamente el mismo código que hay en el ejemplo del tutorial.. así que si no me equivoco en el método add se eliminan todas las imágenes de la base de datos y del sistema para ahorrar espacio (de mi servidor, claro).

      Si en realidad lo que te sucede es que las imágenes no llegan a tu servidor, quizás debas revisar la configuración de PHP, sobre todo en todo lo relacionado a subida de ficheros (max_upload_size por ejemplo).

      A parte de eso debo decir que este tutorial está más que obsoleto y la programación más bien tosca y guarra.. así que remírate bien el código y trata de mejorarlo por tu cuenta.

  50. dimarts, 24 de gener de 2012 a les 18:44 | #55

    Hola estoy intentando implementar el ejemplo y creo que no me carga bien el componente upload me dice lo siguiente:

    call_user_func_array() expects parameter 1 to be a valid callback, class ‘UploadComponent’ does not have a method ‘initialize’ [CORE\Cake\Utility\ObjectCollection.php, line 110]
    Warning (2): call_user_func_array() expects parameter 1 to be a valid callback, class ‘UploadComponent’ does not have a method ‘beforeRender’ [CORE\Cake\Utility\ObjectCollection.php, line 110]

Comment pages
Comentaris tancats.