Tableless

Busca Menu

Criando uma galeria de imagens com administração em CakePHP, Fancybox e WideImage)

Seja o primeiro a comentar por

Eu sou da opinião que todo bom desenvolvedor frontend tem que ter pelo menos uma noção de backend, e o contrário também tem que acontecer, é comum as pessoas virem me perguntar como que faz pra fazer o Fancybox funcionar no CakePHP, ou como que eu integro um plugin para Jquery no site, puxa, a maioria das vezes é adicionar um seletor, chamar o javascript no Html e configurar de acordo com a documentação, vamos ver se simplifico pra vocês.

A ideia é criar uma galeria de imagens com painel de administração e separada por álbuns. Iremos instalar também o DebugKit pra medir o uso de memória do CakePHP e saber se a nossa aplicação está pesada ou não.

Para quem o CakePHP é interessante?

O CakePHP é interessante para devs backend que precisam ganhar tempo ao desenvolver ou querem entender como funciona o esquema de MVC.

Também é interessante para os devs frontend que precisam automatizar algumas tarefas, entender como é o mundo do backend e até ganhar tempo com o sistema de temas, layouts e view do CakePHP (camada V).

O que vamos usar?

Vamos precisar de:

Modo de preparo

Primeiro você pega a água, o pó de café e a cafeteira pra fazer um café esperto e acompanhar este tutorial.

Preparando o ambiente!

  1. Em seguida, descompacte o CakePHP no seu servidor local (eu uso o Xampp: https://www.apachefriends.org/pt_br/index.html) para facilitar aqui.
  2. Descompacte o DebugKit na pasta app/Plugin do CakePHP e renomeie o diretório debug_kit-master para DebugKit
  3. O FancyBox vai em app/webroot, mas apenas a pasta source, e renomeie para fancy.
  4. O WideImage pode ir em app/Vendor/wideimage e dentro os arquivos dele de forma que o WideImage.php fique disponível em app/Vendor/wideimage/WideImage.php

Agora que temos todos os arquivos, vamos ativar o DebugKit pra poder ter as métricas de uso de memória, execução dos SQLs e outros dados.

  1. Abra o arquivo app/Config/bootstrap.php e cole o código abaixo (cod.1) na linha 72
  2. Após isso abra o AppController.php (em app/Controller) e cole o bloco cod.2 na linha 35, entre as chaves ({}) da classe AppController.

Cod.1::

CakePlugin::load('DebugKit');

Cod.2::

public $components = array(
'DebugKit.Toolbar',
'Session'
);

Se acessar sua aplicação agora vai ver que temos um pequeno botão no canto superior direito, ao clicar nele você já tem acesso aos dados da sua aplicação, em timer você vê o uso de memória.

Pra fechar, acesse o core.php em app/Config, procure e descomente a linha a seguir (aqui era a 152) removendo as barras do começo (//)

Configure::write('Routing.prefixes', array('admin'));

Preparando a estrutura

Pra começar vamos precisar de um banco de dados (vamos usar o MySql), execute o código abaixo (se quiser, use algo como PHPMyAdmin para ajudar) pra criar as tabelas:

Cod.3:

-- --------------------------------------------------------

--
-- Estrutura da tabela `albuns`
--

CREATE TABLE IF NOT EXISTS `albuns` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `titulo` varchar(255) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

-- --------------------------------------------------------

--
-- Estrutura da tabela `imagens`
--

CREATE TABLE IF NOT EXISTS `imagens` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `url` varchar(250) NOT NULL,
  `titulo` varchar(250) NOT NULL,
  `descricao` text NOT NULL,
  `albun_id` int(11) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Preencha as linhas que comentei (host, login, password e database), com isso já temos o banco de dados também conectado, para verificar, acesse a aplicação no navegador e verifique a última mensagem dos blocos coloridos.

Para finalizar esta etapa de preparação, vamos criar os arquivos da galeria:

Em app/Model crie Albun.php e Imagen.php (e sim, com n, já que o CakePHP trabalha com singular e plural em inglês, até da pra mudar isso, mas o artigo já vai ficar enorme).

Em app/Controller crie AlbunsController.php e ImagensController.php

Em app/View crie 2 pastas (Albuns e Imagens) com 3 arquivos dentro de cada:

  • admin_novo.ctp
  • admin_editar.ctp
  • admin_index.ctp

E apenas na pasta Albuns, crie mais 2 arquivos além dos dois que devem estar dentro dela e da Imagens:

  • ver.ctp
  • index.ctp

A partir deste ponto é interessante que você conheça um pouco sobre MVC. Aqui vai uma dica: escrevi sobre isso aqui. Não precisa fazer o exemplo todo, apenas entender como é.

Relacionando as imagens com os albuns

Em primeiro lugar precisamos entender como as imagens se relacionam com cada álbum, a própria documentação do CakePHP já fala sobre isso (http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html), infelizmente está em inglês, vou eu mesmo mostrar como é.

Temos 4 tipos possíveis de relacionamento entre tabelas.

Um para um – hasOne
Um para muitos – hasMany
Muitos para um – belongTo
Muitos para muitos – hasAndBelongsToMany ou HABTM
Se você ler com calma já vai ver que é fácil de definir relacionamentos, no nosso caso imagens tem um álbum ou Muitos para Um, indo ao contrário, álbum te muitas imagens ou Um para Muitos, logo:

Album hasMany Imagem
Imagems belongsTo Album

Entendeu a lógica por traz de definir os relacionamentos? Então vamos informar isso nos models.

Abra os arquivos Album.php e Imagem.php que estão em app/Model e vamos configurar:

Cod. 5:

//Albun.php

class Albun extends AppModel
{
    public $hasMany = array('Imagen');
}

// Imagen .php

class Imagen extends AppModel
{
    public $belongsTo = array('Albun');
}

Pronto, com isso sempre que buscarmos algo no banco de dados ele já vai incluir automaticamente os dados relacionados, ou seja, se eu listar o álbum, ele vai trazer todas as imagens deste determinado álbum e o contrário também vai acontecer (dizer a qual álbum a imagem pertence).

Criando os controllers

Os controllers vão decidir o que será feito na aplicação, ou seja, exibir o que? Pegar que infromação no banco? Que hora o upload deve ser feito? É muito simples, os dois arquivos devem ser salvos em app/Controller:

Cod. 6:

//AlbunsController.php

class AlbunsController extends AppController
{
    
}

//ImagensController.php

class ImagensController extends AppController
{
    
}

Configurando a criação de álbuns de imagens na administração

No controller álbuns (AlbunsController.php) crie uma função chamada admin_novo, assim:

Cod. 7:

public function  admin_novo()
{
		if ($this->request->is('post')) {
			$this->Albun->create();
			if ($this->Albun->save($this->request->data)) {
				$this->Session->setFlash(__('Album criado.'));
				return $this->redirect(array('action' => 'index'));
			} else {
				$this->Session->setFlash(__('Erro ao criar o álbum, tente novamente.'));
			}
		}
}

Este action (o nome dado as functions de cada página nos controllers do CakePHP) vai testar se o carregamento da página foi um post, se sim, ele informa que estamos criando um registro, tenta salvar o registro e envia para a index caso tenha sucesso, com uma mensagem de “Album criado”, caso contrário, apenas informa que algo deu errado.

A view desta action, ou o cara que vai exibir os dados na tela do usuário fica assim (arquivo app/View/Albuns/admin_novo.ctp):

Cod. 7A:

<div class="albuns form">
<?php echo $this->Form->create('Albun'); ?>
 <fieldset>
 <legend>Novo album</legend>
 <?php
 echo $this->Form->input('titulo');
 ?>
 </fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>

Configurando a edição de álbuns de imagens na administração

Muito parecido com add, mas dessa vez checando se o álbum especificado existe antes de salvar.

Cod. 8:

public function admin_editar($id = null) {
		if (!$this->Albun->exists($id)) {
			throw new NotFoundException(__('Albun inválido'));
		}
		if ($this->request->is(array('post', 'put'))) {
			if ($this->Albun->save($this->request->data)) {
				$this->Session->setFlash(__('O álbum foi salvo.'));
				return $this->redirect(array('action' => 'index'));
			} else {
				$this->Session->setFlash(__('Erro ao criar o álbum, tente novamente.'));
			}
		} else {
			$options = array('conditions' => array('Albun.' . $this->Albun->primaryKey => $id));
			$this->request->data = $this->Albun->find('first', $options);
		}
}

E a view (app/View/Albuns/admin_editar.ctp)

Cod. 8A:

<div class="albuns form">
<?php echo $this->Form->create('Albun'); ?>
 <fieldset>
 <legend>Editando album</legend>
 <?php
 echo $this->Form->input('id');
 echo $this->Form->input('titulo');
 ?>
 </fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>

Configurando a remoção de álbuns de imagens na administração

Também preciso remover o álbum quando não precisar mais dele. Temos uma action que pode cuidar disso, é bem parecido com a admin_editar, mas desta vez ele usa delete em vez de save:

Cod. 9:

public function admin_remover($id = null) {
		$this->Albun->id = $id;
		if (!$this->Albun->exists()) {
			throw new NotFoundException(__('Invalid albun'));
		}
		$this->request->onlyAllow('post', 'delete');
		if ($this->Albun->delete()) {
			$this->Session->setFlash(__('The albun has been deleted.'));
		} else {
			$this->Session->setFlash(__('The albun could not be deleted. Please, try again.'));
		}
		return $this->redirect(array('action' => 'index'));
}

Este action redireciona para index, então ele não tem uma view.

Configurando a listagem de álbuns de imagens na administração

E pra fechar a administração de álbuns, a listagem de álbuns com paginação:

Cod. 10:

public function admin_index() {
	$this->Albun->recursive = -1;
	$this->set('albuns', $this->Paginator->paginate());
}

O recursive -1 impede que o álbum traga os dados relacionados que no nosso caso são as imagens, a segunda linha traz os álbuns salvos no banco com paginação (página 1, página 2…).

Não esqueça de ativar o componente paginator no seu controller acrescentando a variável $components no inicio da classe AlbunsController (logo abaixo tem um exemplo na classe imagens)

Cod. 11:

public $components = array('Paginator');

E a view (app/View/Albuns/admin_index.ctp):

Cod. 10A:

<div class="albuns index"> <h2>Albuns</h2>
 <table cellpadding="0" cellspacing="0">
 <tr>
 <th><?php echo $this->Paginator->sort('id'); ?></th>
 <th><?php echo $this->Paginator->sort('titulo'); ?></th>
 <th><?php echo $this->Paginator->sort('created'); ?></th>
 <th><?php echo $this->Paginator->sort('modified'); ?></th>
 <th class="actions"><?php echo __('Actions'); ?></th>
 </tr>
 <?php foreach ($albuns as $albun): ?>
 <tr>
 <td><?php echo h($albun['Albun']['id']); ?>&nbsp;</td>
 <td><?php echo h($albun['Albun']['titulo']); ?>&nbsp;</td>
 <td><?php echo h($albun['Albun']['created']); ?>&nbsp;</td>
 <td><?php echo h($albun['Albun']['modified']); ?>&nbsp;</td>
 <td class="actions">
 <?php echo $this->Html->link('Editar', array('action' => 'editar', $albun['Albun']['id'])); ?>
 <?php echo $this->Form->postLink('Remover', array('action' => 'remover', $albun['Albun']['id']), null, __('Tem certeza que quer remover # %s?', $albun['Albun']['titulo'])); ?>
 </td>
 </tr>
<?php endforeach; ?>
 </table>
 <p>
 <?php
 echo $this->Paginator->counter(array(
 'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
 ));
 ?> </p>
 <div class="paging">
 <?php
 echo $this->Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled'));
 echo $this->Paginator->numbers(array('separator' => ''));
 echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled'));
 ?>
 </div>
</div>
<div class="actions">
 <h3>Actions</h3>
 <ul>
 <li><?php echo $this->Html->link(__('Novo'), array('action' => 'novo')); ?></li>
 </ul>
</div>

Configurando a administração de imagens

A administração de imagens é bem parecida com a de álbuns, vou apenas passar o código pra gente não se estender muito, mas está bem simples de entender.

Primeiro o controller ImagensController.php

Cod. 12:

	public $components = array('Paginator');

	public function admin_index() {
		$this->Imagen->recursive = 0;
		$this->set('imagens', $this->Paginator->paginate());
	}

	public function admin_novo() {
		if ($this->request->is('post')) {
			$this->Imagen->create();
			$this->request->data['Imagen']['url'] = $this->Imagen->upload($this->request->data['Imagen']['url'], $this->request->data['Imagen']['albun_id']);
			if ($this->Imagen->save($this->request->data)) {
				$this->Session->setFlash(__('The imagen has been saved.'));
				return $this->redirect(array('action' => 'index'));
			} else {
				$this->Session->setFlash(__('The imagen could not be saved. Please, try again.'));
			}
		}
		$albuns = $this->Imagen->Albun->find('list', array('fields'=>array('id','titulo')));
		$this->set(compact('albuns'));
	}

	public function admin_editar($id = null) {
		if (!$this->Imagen->exists($id)) {
			throw new NotFoundException(__('Invalid imagen'));
		}
		if ($this->request->is(array('post', 'put'))) {
			$this->request->data['Imagen']['url'] = $this->Imagen->upload($this->request->data['Imagen']['url'], $this->request->data['Imagen']['albun_id']);
			if ($this->Imagen->save($this->request->data)) {
				$this->Session->setFlash(__('The imagen has been saved.'));
				return $this->redirect(array('action' => 'index'));
			} else {
				$this->Session->setFlash(__('The imagen could not be saved. Please, try again.'));
			}
		} else {
			$options = array('conditions' => array('Imagen.' . $this->Imagen->primaryKey => $id));
			$this->request->data = $this->Imagen->find('first', $options);
		}
		$albums = $this->Imagen->Albun->find('list', array('fields'=>array('id','titulo')));
		$this->set(compact('albums'));
	}

	public function admin_remover($id = null) {
		$this->Imagen->id = $id;
		if (!$this->Imagen->exists()) {
			throw new NotFoundException(__('Invalid imagen'));
		}
		$this->request->onlyAllow('post', 'delete');
		if ($this->Imagen->delete()) {
			$this->Session->setFlash(__('The imagen has been deleted.'));
		} else {
			$this->Session->setFlash(__('The imagen could not be deleted. Please, try again.'));
		}
		return $this->redirect(array('action' => 'index'));
	}

E as views:

Cod. 12A:

//app/View/Imagens/admin_index.ctp
<div class="imagens index">
 <h2>Imagens</h2>
 <table cellpadding="0" cellspacing="0">
 <tr>
 <th><?php echo $this->Paginator->sort('id'); ?></th>
 <th><?php echo $this->Paginator->sort('imagem'); ?></th>
 <th><?php echo $this->Paginator->sort('titulo'); ?></th>
 <th><?php echo $this->Paginator->sort('descricao'); ?></th>
 <th><?php echo $this->Paginator->sort('albun_id'); ?></th>
 <th><?php echo $this->Paginator->sort('created'); ?></th>
 <th><?php echo $this->Paginator->sort('modified'); ?></th>
 <th class="actions"><?php echo __('Actions'); ?></th>
 </tr>
 <?php foreach ($imagens as $imagen): ?>
 <tr>
 <td><?php echo h($imagen['Imagen']['id']); ?>&nbsp;</td>
 <td><?php echo h($imagen['Imagen']['url']); ?>&nbsp;</td>
 <td><?php echo h($imagen['Imagen']['titulo']); ?>&nbsp;</td>
 <td><?php echo h($imagen['Imagen']['descricao']); ?>&nbsp;</td>
 <td><?php echo h($imagen['Imagen']['albun_id']); ?>&nbsp;</td>
 <td><?php echo h($imagen['Imagen']['created']); ?>&nbsp;</td>
 <td><?php echo h($imagen['Imagen']['modified']); ?>&nbsp;</td>
 <td class="actions">
 <?php echo $this->Html->link('Editar', array('action' => 'editar', $imagen['Imagen']['id'])); ?>
 <?php echo $this->Form->postLink('Delete', array('action' => 'remover', $imagen['Imagen']['id']), null, __('Tem certeza que quer remover # %s?', $imagen['Imagen']['titulo'])); ?>
 </td>
 </tr>
<?php endforeach; ?>
 </table>
 <p>
 <?php
 echo $this->Paginator->counter(array(
 'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
 ));
 ?> </p>
 <div class="paging">
 <?php
 echo $this->Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled'));
 echo $this->Paginator->numbers(array('separator' => ''));
 echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled'));
 ?>
 </div>
</div>
<div class="actions">
 <h3>Actions</h3>
 <ul>
 <li><?php echo $this->Html->link(__('Novo'), array('action' => 'novo')); ?></li>
 </ul>
</div>

//app/View/Imagens/admin_novo.ctp
<div class="imagens form">
<?php echo $this->Form->create('Imagen',array('type'=>'file')); ?>
 <fieldset>
 <legend>Nova imagem</legend>
 <?php
 echo $this->Form->input('url',array('type'=>'file'));
 echo $this->Form->input('titulo');
 echo $this->Form->input('descricao');
 echo $this->Form->input('albun_id');
 ?>
 </fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>

//app/View/Imagens/admin_editar.ctp
<div class="imagens form">
<?php echo $this->Form->create('Imagen'); ?>
 <fieldset>
 <legend>Editando imagem</legend>
 <?php
 echo $this->Form->input('id');
 echo $this->Form->input('url');
 echo $this->Form->input('titulo');
 echo $this->Form->input('descricao');
 echo $this->Form->input('albun_id');
 ?>
 </fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>

Configurando o upload e tratamento de imagens

Note nas actions admin_novo e admin_editar do ImagensController que você tem uma linha assim:

$this->request->data['Imagen']['url'] = $this->Imagen->upload($this->request->data['Imagen']['url'], $this->request->data['Imagen']['albun_id']);

Ela carrega uma função do model Imagen.php chamada upload que cuida de salvar os dados do arquivo enviado ($this->request->data[‘Imagen’][‘url’]) e também criar uma pasta com o id do álbum atual ($this->request->data[‘Imagen’][‘albun_id’]) no diretório app/webroot/img, mas o que ela faz ao certo?

  • Primeiro vamos verificar se o arquivo enviado tem o tipo certo (jpeg, gif, png…)
  • Em seguida verificamos se deu tudo certo durante o upload
  • Caso tenha, checo se o diretório aonde as imagens vão fica existe (será em app/webroot/img/album/{id do álbum}), se não existir eu crio
  • Então parto pra etapa de conversão de nome, transformando em um formato aceito na web, mas sem perder o nome de arquivo que o usuário tinha antes
  • E uso o WideImage pra ajustar a imagem para um tamanho aproximado de 800x600px e criar uma miniatura de 250x250px exatos (cortando o excesso a partir de centro da imagem)

O código completo vai no app/Model/Imagen.php, logo a baixo da linha da variável $belongsTo:

Cod. 13:

   public function upload($arquivo, $id) {
        $ext = array(
            'image/jpeg',
            'image/pjpeg',
            'image/x-jps',
            'image/png',
            'image/gif',
        );
        
        if ($arquivo['error']!=0 || $arquivo['size']==0 || !in_array($arquivo['type'], $ext)){
            return false;
        }
        
        
        if(!is_dir(WWW_ROOT.'img'.DS.'album'.DS.$id)){  
            App::uses('Folder', 'Utility'); 
            
            $folder = new Folder();
            if($folder->create(WWW_ROOT.'img'.DS.'album'.DS.$id)){  
                //se conseguiu criar o diretório eu dou permissão  
                $folder->chmod(WWW_ROOT.'img'.DS.'album'.DS.$id, 0755, true);  
            } else {  
                return false;  
            }
            $folder = new Folder();
            if($folder->create(WWW_ROOT.'img'.DS.'album'.DS.$id.DS.'thumb')){  
                //se conseguiu criar o diretório eu dou permissão  
                $folder->chmod(WWW_ROOT.'img'.DS.'album'.DS.$id.DS.'thumb', 0755, true);  
            } else {  
                return false;  
            }
        }
        
        $pathinfo=pathinfo($arquivo['name']);
        $pathinfo['filename']=strtolower(Inflector::slug($pathinfo['filename'],'-'));
        $arquivo['name']=$pathinfo['filename'].'.'.$pathinfo['extension'];
        
        App::import('Vendor', 'wideimage/WideImage');  
        $img = WideImage::load($arquivo['tmp_name']);  
        $img = $img->resize(800,600,'outside');  
        $min = $img->resize(250,250,'outside');  
        $min = $min->crop('50%-125','50%-125',250,250);
        $img->saveToFile(WWW_ROOT.'img'.DS.'album'.DS.$id.DS.$arquivo['name']);
        $min->saveToFile(WWW_ROOT.'img'.DS.'album'.DS.$id.DS.'thumb'.DS.$arquivo['name']);
        
        return $arquivo['name'];
    }

Listando os álbuns

Bem, nossa aplicação já está salvando a imagem, já cria álbuns e associa um ao outro, mas ainda não mostra o resultado final, vamos configurar isso.

Primeiro precisamos de uma listagem de álbuns com uma miniatura e um titulo para o usuário escolher o que quer ver.

Vou configurar para que a miniatura seja a primeira imagem do álbum, vamos ao controller (app/Controller/AlbunsController.php):

Cod. 14:

	public function index() {
		$albuns = $this->Albun->find(
			'all',
			array(
				'contain'=>array(
					'Imagen'=>array(
						'limit'=>1
					)
				)
			)
		);
		$this->set('albuns', $albuns);
	}

Você viu que eu usei o contain ali dentro do array, isso é um behavior e ele precisa ser chamado no Model pra funcionar, para isso vá ao model Albun.php e acrescente a variável antes ou depois da var $hasMany:

Cod. 14A:

public $actsAs = array('Containable');

A view é a app/Albuns/index.ctp.

Cod. 14B:

<?php

foreach ($albuns as $v) :
 if(!empty($v['Imagen'][0]['url'])) {
 echo '<div style="float:left">';
 $img = $this->Html->image('album/'.$v['Albun']['id'].'/40/'.$v['Imagen'][0]['url']);
 echo $this->Html->link($img,'/albuns/ver/'.$v['Albun']['id'],array('escape'=>false)).'<br>';
 echo $v['Albun']['titulo'];
 echo '</div>';
 }
endforeach;

Listando as imagens de cada álbum

E pra fechar nossa galeria faltou apenas criarmos a exibição da galeria em si, para isso devemos trazer o nosso álbum e o CakePHP já vai trazer os dados relacionados a ele (que no nosso caso são as imagens):

Cod. 15:

	public function ver($id = null) {
		if (!$this->Albun->exists($id)) {
			throw new NotFoundException(__('Invalid albun'));
		}
		
		$options = array('conditions' => array('Albun.' . $this->Albun->primaryKey => $id));
		$album = $this->Albun->find('first', $options);
		$this->set('album', $album);
	}

A view ver.ctp(não vou falar o caminho desta vez, deve ter ficado claro pra você) de álbuns vai exibir o html já formatado para eu usar o Fancybox, ou seja, imagem dentro de um link.

Cod. 15A:

<h3><?php echo $album['Albun']['titulo'];?></h3>
<?php foreach ($album['Imagen'] as $v) :
 $img = $this->Html->image('album/'.$album['Albun']['id'].'/40/'.$v['url']);
 echo $this->Html->link(
 $img,
 '/img/album/'.$album['Albun']['id'].'/'.$v['url'],
 array(
 'escape'=>false,
 'class'=>'fancy',
 'alt'=>$v['descricao'],
 'title'=>$v['titulo'],
 'rel'=>$album['Albun']['titulo']
 )
 );
endforeach;?>

Integrando ao FancyBox

O Fancybox é um plugin para frontend, então o processo é o mesmo que em qualquer outro lugar, basta criar o HTML da galeria no formato que ele usa (fizemos isso no passo anterior) e chamar os javascripts, css e configurar o plugin.

Ainda na view ver.ctp de Albuns, faça isso logo no final:

Cod. 16:

Html->script(
		array(
			'//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js',
			'/fancy/jquery.fancybox.pack'
		),
		array('inline'=>false)
	);
	echo $this->Html->css(
		array('/fancy/jquery.fancybox'),
		null,
		array('inline'=>false)
	);
	echo $this->Html->scriptBlock(
		'$(".fancy").fancybox();',
		array('inline'=>false)
	);
?>

O primeiro bloco (Html->script) chama os javascripts (o jquery do server do Google e o jquery.fancybox.pack.js que está no diretório webroot).

O segundo bloco (Html->css) faz o mesmo com o css local.

E o terceiro cria um bloco … configurando o Fancybox, note que eu coloquei tudo com inline=>false assim ele não imprime isso na view e sim nas tags script e css que estão no nosso arquivo app/View/Layout/default.ctp (já vem no CakePHP) pra evitar a bagunça (como se o javacript direto na página fosse muito organizado, mas deu pra entender bem como proceder).

Como usar?

Fácil, você tem três urls base:
/albuns – listagem de álbuns
/admin/abuns – administração de álbuns
/admin/imagens – administração de imagens

Além disso você ainda pode usar estas urls, mas a navegação da galeria já vai te guia por elas:

/admin/novo
/admin/albuns/editar/{id do album}
/admin/albuns/remover/{id do álbum}
/admin/novo
/admin/imagens/editar/{id da imagem}
/admin/imagens/remover/{id da imagem}
/albuns/ver/{id do album}

Perceba que cada action gerou uma url e que o admin_ gerou um /admin em cada action, além dos ids e os controllers, no fim você entende que as urls seguem o padrão

/:prefixo/:controller/:action – lembrando que o prefixo é o admin
Ou
/:controller/:action
Ou
/:controller – e action passada é a index por padrão quando uma não for especificada.

Isso ainda pode ser personalizado, mas é outro assunto.

Conclusão

Note que esta é uma tarefa normalmente dispendiosa, é uma administração completa de galeria de imagens que embora simples é bem completa e eu consegui resumir tudo em poucas linhas de código e um artigo apenas.

Você ainda precisa proteger a administração da sua aplicação com senha, adicionar um controle de imagens melhor, checar se o arquivo existe e alterar o nome para não substituir, apagar a imagem quando o dado for apagado do banco (tanto o álbum quanto as imagens), remover a imagem antiga quando troca no admin_editar, setar imagem de capa do álbum… enfim, mas agora você tem um norte pra começar a trabalhar.

Aqui o arquivo pronto para download (o sql do banco está em app/Config/Schema/database.sql): http://blog.erikfigueiredo.com.br/galeria.zip

Obrigado a todos.

Publicado no dia