Breadcrumb Zend View Helper

Por zsamer en Octubre 1, 2008

Como había prometido en post anteriores, hoy vamos a hablar sobre un interesante View Helper escrito por mi. Se trata de las miguitas de pan o más bien conocida en ingles como BreadCrumbs.

Las Miguitas de Pan es una técnica de navegación usada para desarrollar la Interfaz gráfica de usuario. Tiene como objetivo que el usuario guarde una ruta de su ubicación dentro del sitio web o documentos. El término en inglés es breadcrumb, que proviene del cuento clásico Hansel y Gretel.

Si estas interesado(a) sobre como implementar esta util herramienta en nuestros proyectos con Zend Framework, entonces por favor siguie leyendo.

Veamos como funciona este cuento, aquí tenemos un ejemplo en un controlador:

PHP:
  1. $this->view->breadCrumbs = $this->view->crumbs();
  2.         $this->view->breadCrumbs->addPrepend('/', 'Home')
  3.         ->setMvc(true);
  4.  
  5.         $this->view->breadCrumbs->init();

Interesante, primero agregamos un prepend (agregar al comienzo) para el primer link que es nuestra página de inicio, luego configuramos a MVC como true, esto quiere decir que se generarán automáticamente los links de nuestro Modulo, Controlador y Acción.

Esto quedaría así: Home » Proyectos » Grupos » Agregar

Tal como tenemos un método addPrepend, tenemos uno addAppend que es para agregar algún ítem después de los componentes MVC, en caso de ser necesario.

También podemos tener control de agregar ciertas partes del MVC y no todas, veamos cómo:

PHP:
  1. $this->view->breadCrumbs = $this->view->crumbs();
  2.         $this->view->breadCrumbs->addPrepend('/admin/', 'Admin')
  3.         ->setMvc(true);
  4.  
  5.         $this->view->breadCrumbs->setGenerateAction(false);
  6.         //$this->view->breadCrumbs->setGenerateController(false);
  7.         //$this->view->breadCrumbs->setGenerateModule(false);
  8.        
  9.         return $this->view->breadCrumbs->init();

El ejemplo anterior sólo generará del MVC el Módulo y Controlador pero no la acción. Configurar a true es redundante ya que bienes así por defecto.

El método init es el inicializador, es decir es el encargado de ejecutar todo el proceso y construcción del Breadcrumb en el contexto MVC.

Además podemos eliminar un nodo del Breadcrumb, por ejemplo, se genera el MVC automáticamente pero necesitamos eliminar un componente:

PHP:
  1. $this->view->breadCrumbs = $this->view->crumbs();
  2.         $this->view->breadCrumbs->addPrepend('/admin/', 'Admin')
  3.         ->setMvc(true);
  4.  
  5.  
  6.         //$this->view->breadCrumbs->delete('proyectos-module');
  7.         //$this->view->breadCrumbs->delete('grupos-controller');
  8.         $this->view->breadCrumbs->delete('agregar-action');
  9.        
  10.         return $this->view->breadCrumbs->init();

El nombre o id del nodo que se genera por cada componente del MVC agrega un guión y el tipo de componente, en el ejemplo estamos eliminando el nodo que corresponde a la acción agregar "agregar-action".

Cada nodo es un objeto del tipo "Zsamer_View_Helper_Crumbs_Node", ya sea de algún componente MVC o uno creado manualmente por nosotros (más abajo hay un ejemplo), y éste objeto se pueden obtener y modificar sus atributos:

PHP:
  1. $this->view->breadCrumbs = $this->view->crumbs();
  2.         $this->view->breadCrumbs->addPrepend('/admin/', 'Admin')
  3.         ->setMvc(true);
  4.  
  5.         $node = $this->view->breadCrumbs->getNode('agregar-action');
  6.         $node->setLabel('Otro Lebel')
  7.         ->setTitle('Otro Title ')
  8.         ->setLink('/otro/link/');
  9.        
  10.         return $this->view->breadCrumbs->init();

Ahora veamos como crear manualmente nuestros propios nodos, hay dos maneras:

PHP:
  1. $this->view->breadCrumbs = $this->view->crumbs();
  2.  
  3.         // Forma Nº 1
  4.         $this->view->breadCrumbs->addCrumb('id-nodo', 'Mi Level', 'Mi Titulo', '/linkto/');
  5.        
  6.         // Forma Nº 2
  7.         $node = new Zsamer_View_Helper_Crumbs_Node();
  8.         $node->setId('id-nodo2')
  9.         ->setLabel('Mi Level 2')
  10.         ->setTitle('Mi Titulo 2')
  11.         ->setLink('/linkto2/');
  12.         $this->view->breadCrumbs->add($node);

Ahora faltarían nuestras clases maestras de las que hemos hablado.

Clase nodo, debe de estar ubicada en el directorio de nuestro proyecto "library/Zsamer/View/Helper/Crumbs/Node.php":

PHP:
  1. <?php
  2. class Zsamer_View_Helper_Crumbs_Node
  3. {
  4.     private $id = null;
  5.  
  6.     private $label = null;
  7.  
  8.     private $title = null;
  9.  
  10.     private $link = null;
  11.  
  12.     private $first = false;
  13.  
  14.     private $last = false;
  15.    
  16.     private $linkable = true;
  17.  
  18.     public function setId($id)
  19.     {
  20.         $this->id = $id;
  21.         return $this;
  22.     }
  23.  
  24.     public function getId()
  25.     {
  26.         return $this->id;
  27.     }
  28.  
  29.     public function setLabel($label)
  30.     {
  31.         $this->label = $label;
  32.         return $this;
  33.     }
  34.  
  35.     public function getLabel()
  36.     {
  37.         return $this->label;
  38.     }
  39.  
  40.     public function setTitle($title)
  41.     {
  42.         $this->title = $title;
  43.         return $this;
  44.     }
  45.  
  46.     public function getTitle()
  47.     {
  48.         return $this->title;
  49.     }
  50.  
  51.     public function setLink($link)
  52.     {
  53.         $this->link = $link;
  54.         return $this;
  55.     }
  56.  
  57.     public function getLink()
  58.     {
  59.         return $this->link;
  60.     }
  61.  
  62.     public function setFirst($first)
  63.     {
  64.         $this->first = $first;
  65.         return $this;
  66.     }
  67.  
  68.     public function getFirst()
  69.     {
  70.         return $this->first;
  71.     }
  72.  
  73.     public function setLast($last)
  74.     {
  75.         $this->last = $last;
  76.         return $this;
  77.     }
  78.  
  79.     public function getLast()
  80.     {
  81.         return $this->last;
  82.     }
  83.    
  84.     public function setLinkable($linkable)
  85.     {
  86.         $this->linkable = $linkable;
  87.         return $this;
  88.     }
  89.  
  90.     public function getLinkable()
  91.     {
  92.         return $this->linkable;
  93.     }
  94. }

Clase Crumbs, debe de estar ubicada en el directorio de nuestro proyecto "library/Zsamer/View/Helper/Crumbs.php":

PHP:
  1. <?php
  2.  
  3. require_once 'Zsamer/View/Helper/Crumbs/Node.php';
  4.  
  5. class Zsamer_View_Helper_Crumbs implements Countable, IteratorAggregate
  6. {
  7.     static private $_mvc = false;
  8.  
  9.     private $_firstAndLastNode = true;
  10.  
  11.     private $_crumbs = null;
  12.  
  13.     private $_iterator = null;
  14.  
  15.     private $_request = null;
  16.  
  17.     private $_view = null;
  18.  
  19.     private $area = null;
  20.  
  21.     private $_disallowModule = array('index', 'admin');
  22.  
  23.     private $_disallowController = array('index', 'admin');
  24.  
  25.     private $_disallowAction = array('index');
  26.  
  27.     private $_prepend = null;
  28.  
  29.     private $_append = null;
  30.  
  31.     private $_module = null;
  32.  
  33.     private $_controller = null;
  34.  
  35.     private $_action = null;
  36.  
  37.     private $_context = null;
  38.  
  39.     private $_baseUrl = null;
  40.  
  41.     private $_separator = '&raquo;';
  42.  
  43.     private $_generateModule = true;
  44.  
  45.     private $_generateController = true;
  46.  
  47.     private $_generateAction = true;
  48.  
  49.     const DIR_SEPARATOR = '/';
  50.  
  51.     public function crumbs($prepend = array(), $append = array(), $separator = '&raquo;')
  52.     {
  53.         $this->_initCrumbs();
  54.         $this->_prepend = $prepend;
  55.         $this->_append = $append;
  56.         $this->_separator = $separator;
  57.         $this->_baseUrl = $this->getRequest()->getBaseUrl();
  58.         return $this;
  59.     }
  60.  
  61.     public function init()
  62.     {
  63.         $this->_buildPrepend();
  64.  
  65.         if(true === self::$_mvc){
  66.             $this->_buildCrumbs();
  67.         }
  68.  
  69.         $this->_buildAppend();
  70.  
  71.         return $this;
  72.     }
  73.  
  74.     private function _initCrumbs()
  75.     {
  76.         if(null === $this->_crumbs){
  77.             $this->_crumbs = new ArrayObject();
  78.         }
  79.  
  80.         if (!$this->_crumbs instanceof ArrayObject) {
  81.             throw new Zend_Controller_Exception('Zsamer_View_Helper_Crumbs: Invalid ArrayObject class');
  82.         }
  83.  
  84.         return $this;
  85.     }
  86.  
  87.     public function getRequest()
  88.     {
  89.         if(null === $this->_request){
  90.             $this->_request = Zend_Controller_Front::getInstance()->getRequest();
  91.         }
  92.  
  93.         if (!$this->_request instanceof Zend_Controller_Request_Abstract) {
  94.             throw new Zend_Controller_Exception('Zsamer_View_Helper_Crumbs: Invalid request class');
  95.         }
  96.  
  97.         return $this->_request;
  98.     }
  99.  
  100.     public function setRequest(Zend_Controller_Request_Abstract $request)
  101.     {
  102.         $this->_request = $request;
  103.     }
  104.  
  105.     public function setView(Zend_View_Abstract $view)
  106.     {
  107.         $this->_view = $view;
  108.         return $this;
  109.     }
  110.  
  111.     public function setMvc($mvc = false)
  112.     {
  113.         self::$_mvc = (boolean) $mvc;
  114.         return $this;
  115.     }
  116.  
  117.     public function setFirstAndLastNode($firstAndLastNode)
  118.     {
  119.         $this->_firstAndLastNode = (boolean) $firstAndLastNode;
  120.         return $this;
  121.     }
  122.  
  123.     public function setArea($area)
  124.     {
  125.         $this->area = $area;
  126.         return $this;
  127.     }
  128.  
  129.  
  130.     public function getArea()
  131.     {
  132.         return $this->area;
  133.     }
  134.  
  135.     public function setSeparator($separator)
  136.     {
  137.         $this->_separator = $separator;
  138.         return $this;
  139.     }
  140.  
  141.     public function getSeparator()
  142.     {
  143.         return $this->_separator;
  144.     }
  145.  
  146.     public function getModuleName()
  147.     {
  148.         return $this->_module;
  149.     }
  150.  
  151.     public function getControllerName()
  152.     {
  153.         return $this->_controller;
  154.     }
  155.  
  156.     public function getActionName()
  157.     {
  158.         return $this->_action;
  159.     }
  160.  
  161.     public function getGenerateModule()
  162.     {
  163.         return $this->_generateModule;
  164.     }
  165.  
  166.     public function setGenerateModule($generateModule)
  167.     {
  168.         $this->_generateModule = $generateModule;
  169.         return $this;
  170.     }
  171.  
  172.     public function getGenerateController()
  173.     {
  174.         return $this->_generateController;
  175.     }
  176.  
  177.     public function setGenerateController($generateController)
  178.     {
  179.         $this->_generateController = $generateController;
  180.         return $this;
  181.     }
  182.  
  183.     public function getGenerateAction()
  184.     {
  185.         return $this->_generateAction;
  186.     }
  187.  
  188.     public function setGenerateAction($generateAction)
  189.     {
  190.         $this->_generateAction = $generateAction;
  191.         return $this;
  192.     }
  193.  
  194.     private function getDictionary($text)
  195.     {
  196.         $elementArr = explode(' ', $text);
  197.  
  198.         foreach ($elementArr as $element){
  199.             $elementArrResult[] = ucwords($element);
  200.         }
  201.  
  202.         return implode(' ', $elementArrResult);
  203.     }
  204.  
  205.     public function delete($node)
  206.     {
  207.         if($this->_crumbs->offsetExists($node)){
  208.             $this->_crumbs->offsetUnset($node);
  209.         }
  210.         return $this;
  211.     }
  212.  
  213.     private function _addCrumb($crumbName, Zsamer_View_Helper_Crumbs_Node $node, $validate = true)
  214.     {
  215.         if(true === $validate){
  216.             if (true === isset($crumbName) && false === $this->_crumbs->offsetExists($crumbName) ){
  217.                 $this->_crumbs->offsetSet($crumbName, $node);
  218.             }
  219.         } elseif(true === isset($crumbName)){
  220.             $this->_crumbs->offsetSet($crumbName, $node);
  221.         } else {
  222.             throw new Exception('Error Exception: Crumb Name is Empty');
  223.         }
  224.  
  225.         return $this;
  226.     }
  227.  
  228.     public function add(Zsamer_View_Helper_Crumbs_Node $node)
  229.     {
  230.         $this->_addCrumb($node->getId(), $node);
  231.         return $this;
  232.     }
  233.  
  234.     public function addCrumb($crumbName, $label, $title, $link, $first = false, $last = false)
  235.     {
  236.         if(empty($crumbName) || empty($label)){
  237.             throw new Exception('Error Exception: Crumb Name or Label is Empty, must be a string');
  238.         }
  239.  
  240.         $node = new Zsamer_View_Helper_Crumbs_Node();
  241.         $node->setId($crumbName)
  242.         ->setLabel($label)
  243.         ->setTitle($title)
  244.         ->setLink($link);
  245.  
  246.         $this->_addCrumb($node->getId(), $node);
  247.     }
  248.  
  249.     public function addPrepend($url, $name)
  250.     {
  251.         $this->_prepend[$url] = $name;
  252.         return $this;
  253.     }
  254.  
  255.     public function addAppend($url, $name)
  256.     {
  257.         $this->_append[$url] = $name;
  258.         return $this;
  259.     }
  260.  
  261.     private function _buildCrumbs()
  262.     {
  263.         $this->_module = $this->getRequest()->getModuleName();
  264.         $this->_controller = $this->getRequest()->getControllerName();
  265.         $this->_action = $this->getRequest()->getActionName();
  266.  
  267.         if(null !== $this->getArea()){
  268.             $this->_context = self::DIR_SEPARATOR . $this->getArea() . self::DIR_SEPARATOR;
  269.         }
  270.  
  271.         if(true === $this->getGenerateModule()){
  272.             $this->_buildModule();
  273.         }
  274.  
  275.         if(true === $this->getGenerateController()){
  276.             $this->_buildController();
  277.         }
  278.  
  279.         if(true === $this->getGenerateAction()){
  280.             $this->_buildAction();
  281.         }
  282.     }
  283.  
  284.     private function _buildPrepend()
  285.     {
  286.         if($this->_prepend !== null)
  287.         {
  288.             foreach($this->_prepend as $url => $name)
  289.             {
  290.                 $this->addCrumb($name, $name, $name, $this->_baseUrl . $url);
  291.             }
  292.         }
  293.     }
  294.  
  295.     private function _buildAppend()
  296.     {
  297.         if($this->_append !== null)
  298.         {
  299.             foreach($this->_append as $url => $name)
  300.             {
  301.                 $this->addCrumb($name, $name, $name, $this->_baseUrl . $url);
  302.             }
  303.         }
  304.     }
  305.  
  306.     private function _buildModule()
  307.     {
  308.         if($this->_module !== null && !in_array($this->_module, $this->_disallowModule))
  309.         {
  310.             $name = $this->_module;
  311.  
  312.             $url = $this->_baseUrl . self::DIR_SEPARATOR . $this->_module . $this->_context;
  313.             $this->addCrumb($name . '-module', $this->getDictionary($name), $name, $url);
  314.         }
  315.     }
  316.  
  317.     private function _buildController()
  318.     {
  319.         if($this->_controller !== null && !in_array($this->_controller, $this->_disallowController))
  320.         {
  321.             $name = $this->_formatController($this->_controller);
  322.             $url$this->_baseUrl . self::DIR_SEPARATOR . $this->_module . $this->_context . $this->_controller . self::DIR_SEPARATOR;
  323.             $this->addCrumb($this->_controller . '-controller', $this->getDictionary($name), $name, $url);
  324.         }
  325.     }
  326.  
  327.     private function _buildAction()
  328.     {
  329.         if($this->_action !== null && !in_array($this->_action, $this->_disallowAction))
  330.         {
  331.             $name = $this->_formatAction($this->_action);
  332.             $url$this->_baseUrl . self::DIR_SEPARATOR . $this->_module . $this->_context . $this->_controller . self::DIR_SEPARATOR . $this->_action . self::DIR_SEPARATOR;
  333.             $this->addCrumb($this->_action . '-action', $this->getDictionary($name), $name, $url);
  334.         }
  335.     }
  336.  
  337.     private function _firstAndLastNode(ArrayIterator $iterator)
  338.     {
  339.         $this->getNode($iterator->key())->setFirst(true);
  340.         $this->getNode($this->count()-1)->setLast(true);
  341.     }
  342.  
  343.     public function getNode($index)
  344.     {
  345.         if(isset($index) && is_numeric($index)){
  346.             $iterator = $this->getIterator();
  347.             $iterator->seek($index);
  348.             $node = $iterator->current();
  349.         } elseif (isset($index) && $this->_crumbs->offsetExists($index)){
  350.             $node$this->_crumbs->offsetGet($index);
  351.         }
  352.  
  353.         if($node instanceof Zsamer_View_Helper_Crumbs_Node){
  354.             return $node;
  355.         }
  356.  
  357.         throw new Exception('Node: ' . $index . ' Not Exist in the Crumb Collection');
  358.     }
  359.    
  360.     protected function _formatController($theString)
  361.     {
  362.         return preg_replace('/admin-/', '', $theString);
  363.     }
  364.    
  365.     protected function _formatAction($theString)
  366.     {
  367.         return str_replace('_'," ", $theString);
  368.     }
  369.  
  370.     public function toString()
  371.     {
  372.         if ($this->count()> 0) {
  373.  
  374.             $iterator = $this->getIterator();
  375.  
  376.             if(true === $this->_firstAndLastNode){
  377.                 $this->_firstAndLastNode($iterator);
  378.             }
  379.  
  380.             $this->_view->crumbs = $this->getIterator();
  381.             $this->_view->separator = $this->_separator;
  382.             return $this->_view->render('breadcrumbs.phtml');
  383.         }
  384.  
  385.         return null;
  386.     }
  387.  
  388.     public function count()
  389.     {
  390.         return $this->_crumbs->count();
  391.     }
  392.  
  393.     public function getIterator()
  394.     {
  395.         if($this->_iterator instanceof ArrayIterator){
  396.             $this->_iterator->rewind();
  397.         } else {
  398.             $this->_iterator = $this->_crumbs->getIterator();
  399.         }
  400.  
  401.         return $this->_iterator;
  402.     }
  403.  
  404.     public function __toString()
  405.     {
  406.         try {
  407.             return $this->toString();
  408.         } catch (Exception $e) {
  409.             trigger_error($e->getMessage(), E_USER_WARNING);
  410.         }
  411.        
  412.         return '';
  413.     }
  414. }

y la vista:
\skins\scripts\breadcrumb\breadcrumbs.phtml

PHP:
  1. <?if($this->crumbs->count()> 0):?>
  2. <ul class="breadcrumbs">
  3.     <?foreach($this->crumbs as $crumbName => $crumb):?>
  4.         <li class="<?php echo $crumbName?>">
  5.         <?if($crumb->getLink() && false === $crumb->getLast()):?>
  6.             <a href="<?php echo $crumb->getLink()?>" title="<?php echo $crumb->getTitle()?>"><?php echo $crumb->getLabel()?></a>
  7.         <?elseif($crumb->getLast()):?>
  8.             <strong><?php echo $crumb->getLabel()?></strong>
  9.         <?else:?>
  10.             <?php echo $crumb->getLabel()?>
  11.         <?endif;?>
  12.         </li>
  13.         <?if(!$crumb->getLast()):?>
  14.         <li><?php echo $this->separator?></li>
  15.         <?endif;?>
  16.     <?endforeach;?>
  17. </ul>
  18. <?endif;?>

Por defecto utiliza Zend_View para el render, esto implica que hay que agregar el script-path de la vista del BreadCrumbs (breadcrumbs.phtml) al objeto Zend_View, y además hay que agregar el HelperPath para que nos encuentre el Helper en nuestro namespace "Zsamer/View/Helper", esto puede ser en el index/Bootstrap o bien en el metodo init de nuestro controlador:

Ejemplo en init de algun controlador:

PHP:
  1. class Cms_PageController extends Zend_Controller_Action
  2. {
  3.     public function init()
  4.     {
  5.         $this->view->baseUrl = $this->_request->getBaseUrl();
  6.         $this->view->addScriptPath(Core::getBaseDir() . DIRECTORY_SEPARATOR . 'skins/scripts/breadcrumb/');
  7.         $this->view->addHelperPath(Core::getBaseDir() . DIRECTORY_SEPARATOR . 'library/Zsamer/View/Helper', 'Zsamer_View_Helper_');
  8.     }

Una vez hecho ésto ya podemos utilizar nuestro helper view, y para hacer más grato el cuento les voy a entregar las css:

CSS:
  1. .breadcrumbs {  padding: 0; margin: 0 0 13px 0; font-size:.95em; line-height:1.25em; }
  2. .breadcrumbs li { display:inline; }

Este herramienta de navegación perfectamente podría ser implementada con un Helper Action o bien mesclar ambos: Helper Action con Helper View, pero por simpleza y comodidad lo hice sólo con Helper View.

Eso es todo, nos vemos en el próximo ;-)

Comentarios

6 Responses to “Breadcrumb Zend View Helper”

  1. lisandro on Octubre 9th, 2008 3:01 pm

    Funcionando a todo vapor ;)

    Saludos

  2. fede on Octubre 15th, 2008 10:39 am

    xD jeje que bueno!!!!
    Por cierto, tengo una pregunta. Llevo unos días pegándome con una función para enviar correos elelectrónicos.

    foreach($data as $_mail){
    $mail=new Zend_Mail();
    $cuerpo='Hola ';
    $nom=$_mail['nombre'];
    $pas=$_mail['pass'];
    $cuerpo=$cuerpo.$nom;
    $cuerpo=$cuerpo.’ su solicitud ha sido aceptada. Su contraseña es ‘;
    $cuerpo=$cuerpo.$pas;
    $cuerpo=$cuerpo.’ ENORAWENA’;
    $rte=$_mail['nombre'];
    $drio=’Ñu! ;)’;
    $asunto=’Solicitud aceptada’;

    $mail->setFrom(’xxx@gmail.com’,$rte);
    $mail->addTo(’xxx@gmail.com’,$drio);
    $mail->setSubject($asunto);
    $mail->setBodyText($cuerpo);
    $mail->send(/*¿¿$transport??*/);
    }

    no sé si poner el transport como smtp o sendmail, si lo pongo como smtp estoy muy limido, solo puedo enviar a algunas direcciones y con el otro no he sido aún capaz de hacer nada :S estoy un poco liado, a ver si alguien me echa una mano.
    Gracias.

  3. Pablo Morales on Diciembre 9th, 2008 7:30 pm

    @Zsamer: estas cosas ponelas en un repositorio svn, asi es mas facil robartelos. :)

  4. Websites tagged "breadcrumb" on Postsaver on Diciembre 24th, 2008 8:17 am

    [...] in SharePoint saved by prborel2008-12-10 - Delicious Tag Filtering saved by IonFarmer2008-12-10 - Breadcrumb Zend View Helper saved by jdblacklung2008-12-10 - Tuesday Recipe blogging: mushroom roast saved by [...]

  5. Erik on Enero 21st, 2009 7:45 pm

    Saludos Zsamer, gracias por tu blog, cada vez que trato de encontrar solucion a algo con Zend Framework google me dirige a ti :)

    pregunta para el siguiente escenario:

    tengo un modelo de Usuarios que devuelve un listado de usuarios, un controlador que lo llama y renderiza su views/script que me muestra el listado.

    El problema es:
    Quiero listar usuarios en muchos sitios de mi aplicacion, en todos los listados con la misma plantilla. Como puedo hacerlo?

    Con un view helper es el view helper el que ha de devolver HTML? no me gusta eso.
    Existe una manera de simplemente mandar a la vista el array de usuarios que ya tengo en el controlador, y pedirle a la vista que “importe” el “template de listado de usuarios” usando ese array que tengo en ella?

    O quizas algo parecido?

  6. Top Posts 2008 | Zend Framework: Estado del Arte on Febrero 2nd, 2009 2:09 pm

    [...] Breadcrumb Zend View Helper [...]

Deja tu comentario




XHTML: puedes usar estas etiquetas: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>