diff --git a/src/Knp/Menu/Renderer/BaseRenderer.php b/src/Knp/Menu/Renderer/BaseRenderer.php new file mode 100644 index 00000000..96a0d7aa --- /dev/null +++ b/src/Knp/Menu/Renderer/BaseRenderer.php @@ -0,0 +1,30 @@ +defaultOptions = array_merge(array( + 'depth' => null, + 'matchingDepth' => null, + 'currentAsLink' => true, + 'currentClass' => 'current', + 'ancestorClass' => 'current_ancestor', + 'firstClass' => 'first', + 'lastClass' => 'last', + 'compressed' => false, + 'allow_safe_labels' => false, + 'clear_matcher' => true, + 'leaf_class' => null, + 'branch_class' => null, + ), $defaultOptions); + } + + public abstract function render(ItemInterface $item, array $options = array()); +} diff --git a/src/Knp/Menu/Renderer/BreadcrumbRenderer.php b/src/Knp/Menu/Renderer/BreadcrumbRenderer.php new file mode 100644 index 00000000..4a87b3e6 --- /dev/null +++ b/src/Knp/Menu/Renderer/BreadcrumbRenderer.php @@ -0,0 +1,159 @@ +itemManipulator = $itemManipulator; + $defaultOptions = array_merge(array( + 'additional_path' => null, + 'root_attributes' => array(), + ), $defaultOptions); + + parent::__construct($defaultOptions, $charset); + } + + /** + * Renders a menu with the specified renderer. + * + * @param ItemInterface $item + * @param array $options + * @return string + */ + public function render(ItemInterface $item, array $options = array()) + { + $options = array_merge($this->defaultOptions, $options); + + $breadcrumb = $this->itemManipulator->getBreadcrumbsArray($item, $options['additional_path']); + + if (empty($breadcrumb)) { + return ''; + } + + return $this->renderBreadcrumb($breadcrumb, $options); + } + + /** + * Renders the breadcrumb + * + * @param array $breadcrumb + * @param array $options + * @return string + */ + protected function renderBreadcrumb(array $breadcrumb, array $options) + { + $html = $this->format('renderHtmlAttributes($options['root_attributes']).'>', 'ul', 0, $options); + $html .= $this->renderList($breadcrumb, $options); + $html .= $this->format('', 'ul', 0, $options); + + return $html; + } + + /** + * Renders the breadcrumb list + * + * @param array $breadcrumb + * @param array $options + * @return string + */ + protected function renderList(array $breadcrumb, array $options) + { + $html = ''; + foreach ($breadcrumb as $element) { + $element = array_replace(array('label' => null, 'uri' => null, 'item' => null), $element); + $html .= $this->renderItem($element['label'], $element['uri'], $options, $element['item']); + } + + return $html; + } + + /** + * @param string $label + * @param string $uri + * @param array $options + * @param ItemInterface|null $item + * @return string + */ + protected function renderItem($label, $uri, array $options, ItemInterface $item = null) + { + $isCurrent = null !== $item && $item->isCurrent(); + $attributes = $isCurrent ? array('class' => $options['currentClass']) : array(); + + // opening li tag + $html = $this->format('renderHtmlAttributes($attributes).'>', 'li', 1, $options); + + // render the text/link inside the li tag + if (null === $uri || ($isCurrent && !$options['currentAsLink'])) { + $content = $this->renderLabel($label, $options, $item); + } else { + $content = sprintf('%s', $this->escape($uri), $this->renderLabel($label, $options, $item)); + } + $html .= $this->format($content, 'link', 1, $options); + + // closing li tag + $html .= $this->format('', 'li', 1, $options); + + return $html; + } + + /** + * @param string $label + * @param array $options + * @param ItemInterface|null $item + * @return string + */ + protected function renderLabel($label, array $options, ItemInterface $item = null) + { + if ($options['allow_safe_labels'] && null !== $item && $item->getExtra('safe_label', false)) { + return $item->getLabel(); + } + + return $this->escape($label); + } + + /** + * If $this->renderCompressed is on, this will apply the necessary + * spacing and line-breaking so that the particular thing being rendered + * makes up its part in a fully-rendered and spaced menu. + * + * @param string $html The html to render in an (un)formatted way + * @param string $type The type [ul,link,li] of thing being rendered + * @param integer $level + * @param array $options + * @return string + */ + protected function format($html, $type, $level, array $options) + { + if ($options['compressed']) { + return $html; + } + + switch ($type) { + case 'ul': + case 'link': + $spacing = $level * 4; + break; + + case 'li': + $spacing = $level * 4 - 2; + break; + } + + return str_repeat(' ', $spacing).$html."\n"; + } +} diff --git a/src/Knp/Menu/Renderer/ListRenderer.php b/src/Knp/Menu/Renderer/ListRenderer.php index 4d7db5d0..5e355b6b 100644 --- a/src/Knp/Menu/Renderer/ListRenderer.php +++ b/src/Knp/Menu/Renderer/ListRenderer.php @@ -8,10 +8,9 @@ /** * Renders MenuItem tree as unordered list */ -class ListRenderer extends Renderer implements RendererInterface +class ListRenderer extends Renderer { protected $matcher; - protected $defaultOptions; /** * @param MatcherInterface $matcher @@ -21,22 +20,8 @@ class ListRenderer extends Renderer implements RendererInterface public function __construct(MatcherInterface $matcher, array $defaultOptions = array(), $charset = null) { $this->matcher = $matcher; - $this->defaultOptions = array_merge(array( - 'depth' => null, - 'matchingDepth' => null, - 'currentAsLink' => true, - 'currentClass' => 'current', - 'ancestorClass' => 'current_ancestor', - 'firstClass' => 'first', - 'lastClass' => 'last', - 'compressed' => false, - 'allow_safe_labels' => false, - 'clear_matcher' => true, - 'leaf_class' => null, - 'branch_class' => null, - ), $defaultOptions); - - parent::__construct($charset); + + parent::__construct($defaultOptions, $charset); } public function render(ItemInterface $item, array $options = array()) diff --git a/src/Knp/Menu/Renderer/Renderer.php b/src/Knp/Menu/Renderer/Renderer.php index a82de569..c30daa2e 100644 --- a/src/Knp/Menu/Renderer/Renderer.php +++ b/src/Knp/Menu/Renderer/Renderer.php @@ -6,18 +6,20 @@ define('ENT_SUBSTITUTE', 8); } -abstract class Renderer +abstract class Renderer extends BaseRenderer { protected $charset = 'UTF-8'; /** * @param string $charset */ - public function __construct($charset = null) + public function __construct($defaultOptions = array(), $charset = null) { if (null !== $charset) { $this->charset = (string) $charset; } + + parent::__construct($defaultOptions); } /** diff --git a/src/Knp/Menu/Renderer/TwigRenderer.php b/src/Knp/Menu/Renderer/TwigRenderer.php index 4d379f05..60b7a03f 100644 --- a/src/Knp/Menu/Renderer/TwigRenderer.php +++ b/src/Knp/Menu/Renderer/TwigRenderer.php @@ -5,14 +5,13 @@ use Knp\Menu\ItemInterface; use Knp\Menu\Matcher\MatcherInterface; -class TwigRenderer implements RendererInterface +class TwigRenderer extends BaseRenderer { /** * @var \Twig_Environment */ private $environment; private $matcher; - private $defaultOptions; /** * @param \Twig_Environment $environment @@ -24,21 +23,12 @@ public function __construct(\Twig_Environment $environment, $template, MatcherIn { $this->environment = $environment; $this->matcher = $matcher; - $this->defaultOptions = array_merge(array( - 'depth' => null, - 'matchingDepth' => null, - 'currentAsLink' => true, - 'currentClass' => 'current', - 'ancestorClass' => 'current_ancestor', - 'firstClass' => 'first', - 'lastClass' => 'last', - 'template' => $template, - 'compressed' => false, - 'allow_safe_labels' => false, - 'clear_matcher' => true, - 'leaf_class' => null, - 'branch_class' => null, + + $defaultOptions = array_merge(array( + 'template' => $template ), $defaultOptions); + + parent::__construct($defaultOptions); } public function render(ItemInterface $item, array $options = array()) diff --git a/tests/Knp/Menu/Tests/Renderer/BreadcrumbRendererTest.php b/tests/Knp/Menu/Tests/Renderer/BreadcrumbRendererTest.php new file mode 100644 index 00000000..8d75b2cf --- /dev/null +++ b/tests/Knp/Menu/Tests/Renderer/BreadcrumbRendererTest.php @@ -0,0 +1,116 @@ + true)); + + $expected = ''; + + $this->assertEquals($expected, $renderer->render($this->ch1)); + } + + public function testAdditionalPath() + { + $renderer = new BreadcrumbRenderer(new MenuManipulator, array('compressed' => true)); + + $expected = ''; + + $this->assertEquals($expected, $renderer->render($this->ch1, array('additional_path' => array('Foo' => 'http://example.com')))); + } + + public function testCurrentLink() + { + $this->pt1->setUri('foobar')->setCurrent(true); + + $renderer = new BreadcrumbRenderer(new MenuManipulator, array('compressed' => true)); + + $expected = ''; + + $this->assertEquals($expected, $renderer->render($this->ch1)); + } + + public function testCurrentNoLink() + { + $this->pt1->setUri('foobar')->setCurrent(true); + + $renderer = new BreadcrumbRenderer(new MenuManipulator, array('compressed' => true, 'currentAsLink' => false)); + + $expected = ''; + + $this->assertEquals($expected, $renderer->render($this->ch1)); + } + + public function testCurrentCustomClass() + { + $this->pt1->setCurrent(true); + + $renderer = new BreadcrumbRenderer(new MenuManipulator, array('compressed' => true, 'currentClass' => 'foo')); + + $expected = ''; + + $this->assertEquals($expected, $renderer->render($this->ch1)); + } + + public function testEscapedLabel() + { + $this->pt1->setExtra('safe_label', true)->setLabel('Foo'); + + $renderer = new BreadcrumbRenderer(new MenuManipulator, array('compressed' => true)); + + $expected = ''; + + $this->assertEquals($expected, $renderer->render($this->ch1)); + } + + public function testUnsafeLabel() + { + $this->pt1->setLabel('Foo'); + + $renderer = new BreadcrumbRenderer(new MenuManipulator, array('compressed' => true, 'allow_safe_labels' => true)); + + $expected = ''; + + $this->assertEquals($expected, $renderer->render($this->ch1)); + } + + public function testSafeLabel() + { + $this->pt1->setExtra('safe_label', true)->setLabel('Foo'); + + $renderer = new BreadcrumbRenderer(new MenuManipulator, array('compressed' => true, 'allow_safe_labels' => true)); + + $expected = ''; + + $this->assertEquals($expected, $renderer->render($this->ch1)); + } + + public function testPrettyRendering() + { + $renderer = new BreadcrumbRenderer(new MenuManipulator); + + $rendered = << +
  • + Root li +
  • +
  • + Parent 1 +
  • +
  • + Child 1 +
  • + + +HTML; + + $this->assertEquals($rendered, $renderer->render($this->ch1)); + } +}