├── .gitattributes ├── docs └── breadcrumbs.png ├── .editorconfig ├── .gitignore ├── src ├── Exception │ ├── UnderflowException.php │ ├── OutOfBoundsException.php │ └── InvalidArgumentException.php └── Breadcrumbs.php ├── composer.json ├── LICENSE.txt ├── CHANGELOG.md └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | docs/* linguist-documentation 2 | 3 | -------------------------------------------------------------------------------- /docs/breadcrumbs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergeyklay/breadcrumbs/HEAD/docs/breadcrumbs.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | insert_final_newline = true 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Please do not use this ignore file to define platform specific files. 2 | # 3 | # For these purposes create a global .gitignore file, which is a list of rules 4 | # for ignoring files in every Git repository on your computer. 5 | # 6 | # https://help.github.com/articles/ignoring-files/#create-a-global-gitignore 7 | 8 | composer.lock 9 | /vendor 10 | -------------------------------------------------------------------------------- /src/Exception/UnderflowException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the 9 | * LICENSE.txt file that was distributed with this source code. 10 | */ 11 | 12 | namespace Phalcon\Breadcrumbs\Exception; 13 | 14 | /** 15 | * Phalcon\Breadcrumbs\Exception\UnderflowException 16 | * 17 | * Exceptions thrown in Phalcon\Breadcrumbs will use this class 18 | * 19 | * @package Phalcon\Breadcrumbs\Exception 20 | */ 21 | class UnderflowException extends \UnderflowException 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exception/OutOfBoundsException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the 9 | * LICENSE.txt file that was distributed with this source code. 10 | */ 11 | 12 | namespace Phalcon\Breadcrumbs\Exception; 13 | 14 | /** 15 | * Phalcon\Breadcrumbs\Exception\OutOfBoundsException 16 | * 17 | * Exceptions thrown in Phalcon\Breadcrumbs will use this class 18 | * 19 | * @package Phalcon\Breadcrumbs\Exception 20 | */ 21 | class OutOfBoundsException extends \OutOfBoundsException 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the 9 | * LICENSE.txt file that was distributed with this source code. 10 | */ 11 | 12 | namespace Phalcon\Breadcrumbs\Exception; 13 | 14 | /** 15 | * Phalcon\Breadcrumbs\Exception\InvalidArgumentException 16 | * 17 | * Exceptions thrown in Phalcon\Breadcrumbs will use this class 18 | * 19 | * @package Phalcon\Breadcrumbs\Exception 20 | */ 21 | class InvalidArgumentException extends \InvalidArgumentException 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sergeyklay/breadcrumbs", 3 | "type": "library", 4 | "description": "Powerful and flexible component for building site breadcrumbs in Phalcon 2+.", 5 | "keywords": [ 6 | "phalcon", 7 | "breadcrumbs", 8 | "crumbs" 9 | ], 10 | "homepage": "https://github.com/sergeyklay/breadcrumbs", 11 | "license": "BSD-3-Clause", 12 | "authors": [ 13 | { 14 | "name": "Serghei Iakovlev", 15 | "email": "serghei@phalcon.io" 16 | }, 17 | { 18 | "name": "Contributors", 19 | "homepage": "https://github.com/sergeyklay/breadcrumbs/graphs/contributors" 20 | } 21 | ], 22 | "require": { 23 | "php": ">= 5.5 <8.0", 24 | "ext-phalcon": ">=2.0 <4.0" 25 | }, 26 | "conflict": { 27 | "phalcon/breadcrumbs": "*" 28 | }, 29 | "require-dev": { 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Phalcon\\": "src/" 34 | } 35 | }, 36 | "support": { 37 | "issues": "https://github.com/sergeyklay/breadcrumbs/issues", 38 | "forum": "https://forum.phalcon.io/", 39 | "source": "https://github.com/phalcon/sergeyklay" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | New BSD License 2 | 3 | Copyright © 2016-2019, Serghei Iakovlev 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | * Neither the name of the Phalcon nor the 14 | names of its contributors may be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL PHALCON FRAMEWORK TEAM BE LIABLE FOR ANY 21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [Unreleased] 9 | ## [1.4.2] - 2019-09-26 10 | ### Removed 11 | - Removed not used dev dependencies and empty tests structure 12 | 13 | ## [1.4.1] - 2019-09-26 14 | ### Added 15 | - Allow adding multiple crumbs without a link [#23](https://github.com/sergeyklay/breadcrumbs/pull/23) 16 | 17 | ### Changed 18 | - Changed ownership 19 | 20 | ## [1.4.0] - 2018-05-24 21 | ### Fixed 22 | - Fixed, last element in chain has no link always 23 | 24 | ### Changed 25 | - Changed `CHANGELOG.md` 26 | 27 | ## [1.3.4] - 2018-01-20 28 | ### Added 29 | - Enabled support for PHP 7.2 30 | 31 | ### Changed 32 | - Used latest Phalcon 33 | - Updated dev dependencies 34 | 35 | ## [1.3.3] - 2017-10-21 36 | ### Changed 37 | - Used latest Phalcon 38 | - Updated dev dependencies 39 | 40 | ## [1.3.2] - 2017-09-10 41 | ### Changed 42 | - Used latest Phalcon 43 | - Updated tests 44 | - Updated docs 45 | 46 | ## [1.3.1] - 2017-04-18 47 | ### Fixed 48 | - Fixed invalid converting `$id` to `":null:"` if `$url` is not null in update function. 49 | 50 | ## [1.3.0] - 2017-04-10 51 | ### Changed 52 | - Changed organization 53 | - Minor `composer.json` improvements 54 | 55 | ## [1.2.1] - 2016-12-21 56 | ### Changed 57 | - Updated dev-dependencies 58 | - Minor grammar improvements 59 | - Refactored test environment 60 | 61 | ## [1.2.0] - 2016-03-26 62 | ### Added 63 | - Added `Breadcrumbs::count` 64 | 65 | ### Deprecated 66 | - PHP 5.4 is now fully deprecated 67 | 68 | ### Fixed 69 | - Fixed building with Phalcon 2.1.x 70 | 71 | ## [1.1.1] - 2016-03-12 72 | ### Added 73 | - Added Codeception support 74 | 75 | ### Changed 76 | - Cleanup documentation 77 | 78 | ## [1.1.0] - 2016-02-22 79 | ### Added 80 | - Added support of events 81 | - Added `Breadcrumbs::update` to update an existing crumb 82 | - Added the events: `breadcrumbs:beforeUpdate` and `breadcrumbs:afterUpdate` 83 | - Introduced domain exceptions 84 | - Detect empty `Breadcrumbs::$elements` on update or remove 85 | - Added `Breadcrumbs::setTemplate` to set rendering template 86 | - Added the events: `breadcrumbs:beforeSetTemplate` and `breadcrumbs:afterSetTemplate` 87 | 88 | ### Changed 89 | - Updated `Breadcrumbs::log` in order to add the ability to catch the exception in your custom listener 90 | 91 | ## 1.0.0 - 2016-02-22 92 | ### Added 93 | - Initial release 94 | 95 | [Unreleased]: https://github.com/sergeyklay/breadcrumbs/compare/v1.4.2...HEAD 96 | [1.4.2]: https://github.com/sergeyklay/breadcrumbs/compare/v1.4.1...v1.4.2 97 | [1.4.1]: https://github.com/sergeyklay/breadcrumbs/compare/v1.4.0...v1.4.1 98 | [1.4.0]: https://github.com/sergeyklay/breadcrumbs/compare/v1.3.4...v1.4.0 99 | [1.3.4]: https://github.com/sergeyklay/breadcrumbs/compare/v1.3.3...v1.3.4 100 | [1.3.3]: https://github.com/sergeyklay/breadcrumbs/compare/v1.3.2...v1.3.3 101 | [1.3.2]: https://github.com/sergeyklay/breadcrumbs/compare/v1.3.1...v1.3.2 102 | [1.3.1]: https://github.com/sergeyklay/breadcrumbs/compare/v1.3.0...v1.3.1 103 | [1.3.0]: https://github.com/sergeyklay/breadcrumbs/compare/v1.2.1...v1.3.0 104 | [1.2.1]: https://github.com/sergeyklay/breadcrumbs/compare/v1.2.0...v1.2.1 105 | [1.2.0]: https://github.com/sergeyklay/breadcrumbs/compare/v1.1.1...v1.2.0 106 | [1.1.1]: https://github.com/sergeyklay/breadcrumbs/compare/v1.1.0...v1.1.1 107 | [1.1.0]: https://github.com/sergeyklay/breadcrumbs/compare/v1.0.0...v1.1.0 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phalcon Breadcrumbs [![Build Status][:travis-badge:]][:travis-url:] 2 | 3 | ![Breadcrumbs Screenshot][:screenshot:] 4 | 5 | Phalcon Breadcrumbs is a powerful and flexible component for building site breadcrumbs. 6 | You can adapt it to your own needs or improve it if you want. 7 | 8 | Please write us if you have any feedback. 9 | 10 | Thanks! 11 | 12 | ## NOTE 13 | 14 | The `master` branch will always contain the latest stable version. If you wish 15 | to check older versions or newer ones currently under development, please 16 | switch to the relevant branch/tag. 17 | 18 | ## Getting Started 19 | 20 | ### Requirements 21 | 22 | To use this component, you need at least: 23 | 24 | * [Composer][:composer:] 25 | * PHP >= 5.5 26 | * [Phalcon Framework release][:phalcon:] extension enabled >= 2.x < 4.x 27 | 28 | **NOTE:** Support for legacy PHP versions (down to 7.0) is provided on a best-effort basis. 29 | 30 | ### Installing 31 | 32 | ```sh 33 | $ composer require sergeyklay/breadcrumbs 34 | ``` 35 | 36 | ### Define your breadcrumbs 37 | 38 | We recommend registering it with your application's services for even easier use: 39 | 40 | ```php 41 | setShared('breadcrumbs', function () { 47 | return new Breadcrumbs; 48 | }); 49 | ``` 50 | 51 | **Adding a crumb with a link:** 52 | 53 | ```php 54 | breadcrumbs->add('Home', '/'); 57 | ``` 58 | 59 | **Adding a crumb without a link (normally the last one):** 60 | 61 | ```php 62 | breadcrumbs->add('User', null, ['linked' => false]); 65 | ``` 66 | 67 | **Output crumbs:** 68 | 69 | Php Engine 70 | ```html 71 | 74 | ``` 75 | 76 | Volt Engine 77 | ```volt 78 | 81 | ``` 82 | 83 | **Change crumb separator:** 84 | 85 | ```php 86 | breadcrumbs->setSeparator(' » '); 89 | ``` 90 | 91 | **Make The last element is always not a link:** 92 | 93 | ```php 94 | breadcrumbs->setLastNotLinked(true); 97 | ``` 98 | 99 | **Delete a crumb (by url):** 100 | 101 | ```php 102 | breadcrumbs->remove('/admin/user/create'); 105 | 106 | // remove a crumb without an url 107 | $this->breadcrumbs->remove(null); 108 | ``` 109 | 110 | **Update an existing crumb:** 111 | 112 | ```php 113 | breadcrumbs->update('/admin/user/remove', ['label' => 'Remove']); 116 | ``` 117 | 118 | **Count crumbs:** 119 | ```php 120 | breadcrumbs->count(); 123 | ``` 124 | 125 | **Sets rendering template:** 126 | 127 | ```php 128 | breadcrumbs->setTemplate( 131 | '
  • {{icon}}{{label}}
  • ', // linked 132 | '
  • {{icon}}{{label}}
  • ', // not linked 133 | '' // first icon 134 | ); 135 | ``` 136 | 137 | **Multi-language support:** 138 | 139 | ```php 140 | 'Home', 147 | 'crumb-user' => 'User', 148 | 'crumb-settings' => 'Settings', 149 | 'crumb-profile' => 'Profile', 150 | ]; 151 | 152 | // Initialize the Translate adapter. 153 | $di->setShared('translate', function () use ($messages) { 154 | return new Translator(['content' => $messages]); 155 | }); 156 | 157 | // Initialize the Breadcrumbs component. 158 | $di->setShared('breadcrumbs', function () { 159 | return new Breadcrumbs; 160 | }); 161 | ``` 162 | 163 | **Errors logging:** 164 | 165 | ```php 166 | setShared('logger', function ($filename = null, $format = null) use ($config) { 179 | $formatter = new FormatterLine($config->get('logger')->format, $config->get('logger')->date); 180 | $logger = new FileLogger($config->get('logger')->path . $config->get('logger')->filename); 181 | 182 | $logger->setFormatter($formatter); 183 | $logger->setLogLevel($config->get('logger')->logLevel); 184 | 185 | return $logger; 186 | }); 187 | 188 | // Initialize the Breadcrumbs component. 189 | $di->setShared('breadcrumbs', function () { 190 | return new Breadcrumbs; 191 | }); 192 | ``` 193 | 194 | ### Events 195 | 196 | ```php 197 | setShared('eventsManager', function () { 204 | return new EventsManager; 205 | }); 206 | 207 | // Initialize the Breadcrumbs component. 208 | $di->setShared('breadcrumbs', function () use ($di) { 209 | $manager = $di->getShared('eventsManager'); 210 | $manager->attach('breadcrumbs', function ($event, $connection) { 211 | // We stop the event if it is cancelable 212 | if ($event->isCancelable()) { 213 | // Stop the event, so other listeners will not be notified about this 214 | $event->stop(); 215 | } 216 | 217 | // Receiving the data from the event context 218 | print_r($event->getData()); 219 | }); 220 | 221 | $breadcrumbs = new Breadcrumbs; 222 | $breadcrumbs->setEventsManager($manager); 223 | 224 | return $breadcrumbs; 225 | }); 226 | ``` 227 | 228 | Available events: 229 | 230 | ``` 231 | breadcrumbs:beforeAdd 232 | breadcrumbs:afterAdd 233 | breadcrumbs:beforeOutput 234 | breadcrumbs:afterOutput 235 | breadcrumbs:beforeTranslate 236 | breadcrumbs:afterTranslate 237 | breadcrumbs:beforeLogging 238 | breadcrumbs:afterLogging 239 | breadcrumbs:beforeRemove 240 | breadcrumbs:afterRemove 241 | breadcrumbs:beforeUpdate 242 | breadcrumbs:afterUpdate 243 | breadcrumbs:beforeSetTemplate 244 | breadcrumbs:afterSetTemplate 245 | ``` 246 | 247 | ## Copyright 248 | 249 | Phalcon Breadcrumbs is open-sourced software licensed under the [New BSD License][:license:]. 250 | © Serghei Iakovlev 251 | 252 | [:composer:]: https://getcomposer.org/ 253 | [:phalcon:]: https://github.com/sergeyklay/cphalcon/releases 254 | [:license:]: https://github.com/sergeyklay/breadcrumbs/blob/master/LICENSE.txt 255 | [:screenshot:]: https://github.com/sergeyklay/breadcrumbs/blob/master/docs/breadcrumbs.png 256 | [:travis-url:]: https://travis-ci.com/sergeyklay/breadcrumbs 257 | [:travis-badge:]: https://travis-ci.com/sergeyklay/breadcrumbs.svg?branch=master 258 | -------------------------------------------------------------------------------- /src/Breadcrumbs.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the 9 | * LICENSE.txt file that was distributed with this source code. 10 | */ 11 | 12 | namespace Phalcon; 13 | 14 | use Phalcon\Di\Injectable; 15 | use Phalcon\Breadcrumbs\Exception\InvalidArgumentException; 16 | use Phalcon\Breadcrumbs\Exception\OutOfBoundsException; 17 | use Phalcon\Breadcrumbs\Exception\UnderflowException; 18 | 19 | /** 20 | * \Phalcon\Breadcrumbs 21 | * 22 | * Handles the breadcrumbs for the application. 23 | * 24 | * @package Phalcon 25 | */ 26 | class Breadcrumbs extends Injectable 27 | { 28 | /** 29 | * Keeps all the breadcrumbs 30 | * @var array 31 | */ 32 | protected $elements = []; 33 | 34 | /** 35 | * Internal logger 36 | * @var null|Logger\AdapterInterface 37 | */ 38 | protected $logger; 39 | 40 | /** 41 | * Events Manager 42 | * @var null|Events\ManagerInterface 43 | */ 44 | protected $eventsManager; 45 | 46 | /** 47 | * Crumb separator 48 | * @var string 49 | */ 50 | protected $separator = ' / '; 51 | 52 | /** 53 | * Array holding output template 54 | * @var array 55 | */ 56 | protected $template = [ 57 | 'linked' => '
  • {{icon}}{{label}}
  • ', 58 | 'not-linked' => '
  • {{icon}}{{label}}
  • ', 59 | 'icon' => '' 60 | ]; 61 | 62 | /** 63 | * Internal translate adapter 64 | * @var null|Translate\AdapterInterface 65 | */ 66 | protected $translate; 67 | 68 | /** 69 | * Use implicit flush? 70 | * @var bool 71 | */ 72 | protected $implicitFlush = true; 73 | 74 | /** 75 | * Last element not link 76 | * @var bool 77 | */ 78 | protected $lastNotLinked = false; 79 | 80 | /** 81 | * Count null link 82 | * @var integer 83 | */ 84 | protected $countNull = 0; 85 | 86 | /** 87 | * Breadcrumbs constructor. 88 | */ 89 | public function __construct() 90 | { 91 | if ($this->getDI()->has('logger')) { 92 | $logger = $this->getDI()->getShared('logger'); 93 | if ($logger instanceof Logger\AdapterInterface) { 94 | $this->logger = $logger; 95 | } 96 | } 97 | 98 | if ($this->getDI()->has('translate')) { 99 | $translate = $this->getDI()->getShared('translate'); 100 | if ($translate instanceof Translate\AdapterInterface) { 101 | $this->translate = $translate; 102 | } 103 | } 104 | 105 | if ($this->getDI()->has('eventsManager')) { 106 | $eventsManager = $this->getDI()->getShared('eventsManager'); 107 | if ($eventsManager instanceof Events\ManagerInterface) { 108 | $this->eventsManager = $eventsManager; 109 | } 110 | } 111 | } 112 | 113 | /** 114 | * Gets internal translate adapter. 115 | * 116 | * @return null|Translate\AdapterInterface 117 | */ 118 | public function getTranslateAdapter() 119 | { 120 | return $this->translate; 121 | } 122 | 123 | /** 124 | * Sets internal translate adapter. 125 | * 126 | * @param Translate\AdapterInterface $translate Translate adapter 127 | * @return $this 128 | */ 129 | public function setTranslateAdapter(Translate\AdapterInterface $translate) 130 | { 131 | $this->translate = $translate; 132 | 133 | return $this; 134 | } 135 | 136 | /** 137 | * Gets internal logger. 138 | * 139 | * @return null|Logger\AdapterInterface 140 | */ 141 | public function getLogger() 142 | { 143 | return $this->logger; 144 | } 145 | 146 | /** 147 | * Sets internal logger. 148 | * 149 | * @param Logger\AdapterInterface $logger 150 | */ 151 | public function setLogger(Logger\AdapterInterface $logger) 152 | { 153 | $this->logger = $logger; 154 | } 155 | 156 | /** 157 | * Gets crumb separator. 158 | * 159 | * @return string 160 | */ 161 | public function getSeparator() 162 | { 163 | return $this->separator; 164 | } 165 | 166 | /** 167 | * Sets crumb separator. 168 | * 169 | * 170 | * // Set crumb separator 171 | * $breadcrumbs->setSeparator(' » '); 172 | * 173 | * // Remove crumb separator 174 | * $breadcrumbs->setSeparator(''); 175 | * 176 | * 177 | * @param string $separator Separator 178 | * @return $this 179 | */ 180 | public function setSeparator($separator) 181 | { 182 | try { 183 | if (!is_string($separator)) { 184 | $type = gettype($separator); 185 | throw new InvalidArgumentException( 186 | "Expected value of the separator to be string type, {$type} given." 187 | ); 188 | } 189 | 190 | $this->separator = $separator; 191 | } catch (\Exception $e) { 192 | $this->log($e); 193 | } 194 | 195 | return $this; 196 | } 197 | 198 | /** 199 | * Sets whether the output must be implicitly flushed to the output or returned as string. 200 | * 201 | * 202 | * // Enable implicit flush 203 | * $breadcrumbs->setImplicitFlush(true); 204 | * 205 | * // Disable implicit flush 206 | * $breadcrumbs->setImplicitFlush(true); 207 | * 208 | * 209 | * @param bool $implicitFlush Implicit flush mode 210 | * @return $this 211 | */ 212 | public function setImplicitFlush($implicitFlush) 213 | { 214 | $this->implicitFlush = (bool) $implicitFlush; 215 | 216 | return $this; 217 | } 218 | 219 | /** 220 | * Sets rendering template. 221 | * 222 | * Events: 223 | * * breadcrumbs:beforeSetTemplate 224 | * * breadcrumbs:afterSetTemplate 225 | * 226 | * 227 | * $this->breadcrumbs->setTemplate( 228 | * '
  • {{icon}}{{label}}
  • ', // linked 229 | * '
  • {{icon}}{{label}}
  • ', // not linked 230 | * '' // first icon 231 | * ); 232 | *
    233 | * 234 | * @param string $linked Linked template 235 | * @param string $notLinked Not-linked template 236 | * @param string $icon Icon template 237 | * @return $this 238 | */ 239 | public function setTemplate($linked, $notLinked, $icon) 240 | { 241 | if ($this->eventsManager) { 242 | $this->eventsManager->fire('breadcrumbs:beforeSetTemplate', $this, [$linked, $notLinked, $icon]); 243 | } 244 | 245 | try { 246 | if (!is_string($linked)) { 247 | $type = gettype($linked); 248 | throw new InvalidArgumentException("Expected value of the first argument to be string, {$type} given."); 249 | } 250 | 251 | if (!is_string($notLinked)) { 252 | $type = gettype($notLinked); 253 | throw new InvalidArgumentException( 254 | "Expected value of the second argument to be string, {$type} given." 255 | ); 256 | } 257 | 258 | if (!is_string($icon)) { 259 | $type = gettype($notLinked); 260 | throw new InvalidArgumentException("Expected value of the third argument to be string, {$type} given."); 261 | } 262 | 263 | $this->template = [ 264 | 'linked' => $linked, 265 | 'not-linked' => $notLinked, 266 | 'icon' => $icon 267 | ]; 268 | 269 | if ($this->eventsManager) { 270 | $this->eventsManager->fire('breadcrumbs:afterSetTemplate', $this, [$linked, $notLinked, $icon]); 271 | } 272 | } catch (\Exception $e) { 273 | $this->log($e); 274 | } 275 | 276 | return $this; 277 | } 278 | 279 | /** 280 | * Sets last elements not link. 281 | * @param bool $linked 282 | */ 283 | public function setLastNotLinked($linked) 284 | { 285 | $this->lastNotLinked = $linked; 286 | } 287 | 288 | /** 289 | * Adds a new crumb. 290 | * 291 | * Events: 292 | * * breadcrumbs:beforeAdd 293 | * * breadcrumbs:afterAdd 294 | * 295 | * 296 | * // Adding a crumb with a link 297 | * $breadcrumbs->add('Home', '/'); 298 | * 299 | * // Adding a crumb without a link (normally the last one) 300 | * $breadcrumbs->add('User', null, ['linked' => false]); 301 | * 302 | * 303 | * @param string $link The link that will be used 304 | * @param string $label Text displayed in the breadcrumb trail 305 | * @param array $data The crumb data [Optional] 306 | * @return $this 307 | */ 308 | public function add($label, $link = null, array $data = []) 309 | { 310 | if ($this->eventsManager) { 311 | $this->eventsManager->fire('breadcrumbs:beforeAdd', $this, [$label, $link, $data]); 312 | } 313 | 314 | try { 315 | if (!is_string($link) && !is_null($link)) { 316 | $type = gettype($link); 317 | throw new InvalidArgumentException( 318 | "Expected value of the second argument to be either string or null type, {$type} given." 319 | ); 320 | } 321 | 322 | if (!is_string($label)) { 323 | $type = gettype($label); 324 | throw new InvalidArgumentException( 325 | "Expected value of the third argument to be string type, {$type} given." 326 | ); 327 | } 328 | 329 | $linked = true; 330 | if (isset($data['linked'])) { 331 | $linked = (bool) $data['linked']; 332 | } 333 | 334 | $id = $link; 335 | if (is_null($id)) { 336 | $id = ':null'.$this->countNull.':'; 337 | $this->countNull++; 338 | } 339 | 340 | $this->elements[$id] = [ 341 | 'label' => $label, 342 | 'link' => (string) $link, 343 | 'linked' => $linked, 344 | ]; 345 | } catch (\Exception $e) { 346 | $this->log($e); 347 | } 348 | 349 | if ($this->eventsManager) { 350 | $this->eventsManager->fire('breadcrumbs:afterAdd', $this, [$label, $link, $data]); 351 | } 352 | 353 | return $this; 354 | } 355 | 356 | /** 357 | * Renders and outputs breadcrumbs based on previously set template. 358 | * 359 | * Events: 360 | * * breadcrumbs:beforeOutput 361 | * * breadcrumbs:afterOutput 362 | * * breadcrumbs:beforeTranslate 363 | * * breadcrumbs:afterTranslate 364 | * 365 | * 366 | * // Php Engine 367 | * $this->breadcrumbs->output(); 368 | * 369 | * // Volt Engine 370 | * {{ breadcrumbs.output() }}; 371 | * 372 | * 373 | * @return void|string 374 | */ 375 | public function output() 376 | { 377 | if ($this->eventsManager) { 378 | $this->eventsManager->fire('breadcrumbs:beforeOutput', $this); 379 | } 380 | 381 | if (empty($this->elements)) { 382 | if (true === $this->implicitFlush) { 383 | echo ''; 384 | if ($this->eventsManager) { 385 | $this->eventsManager->fire('breadcrumbs:afterOutput', $this); 386 | } 387 | } else { 388 | return ''; 389 | } 390 | } 391 | 392 | // We create the message with implicit flush or other 393 | $content = ''; 394 | 395 | $i = 0; 396 | foreach ($this->elements as $key => $crumb) { 397 | $i++; 398 | $label = $crumb['label']; 399 | if ($this->translate) { 400 | if ($this->eventsManager) { 401 | $this->eventsManager->fire('breadcrumbs:beforeTranslate', $this); 402 | } 403 | 404 | $label = $this->translate->query($label); 405 | 406 | if ($this->eventsManager) { 407 | $this->eventsManager->fire('breadcrumbs:afterTranslate', $this); 408 | } 409 | } 410 | 411 | if (true === $this->lastNotLinked && end($this->elements) == $crumb) { 412 | $crumb['linked'] = null; 413 | } 414 | 415 | if ($crumb['linked']) { 416 | $htmlCrumb = str_replace( 417 | ['{{link}}', '{{label}}'], 418 | [$crumb['link'], $label], 419 | $this->template['linked'] 420 | ); 421 | } else { 422 | $htmlCrumb = str_replace('{{label}}', $label, $this->template['not-linked']); 423 | } 424 | 425 | if (1 == $i) { 426 | $htmlCrumb = str_replace('{{icon}}', $this->template['icon'], $htmlCrumb); 427 | } else { 428 | $htmlCrumb = str_replace('{{icon}}', '', $htmlCrumb); 429 | } 430 | 431 | $this->remove($key); 432 | $htmlCrumb .= (!empty($this->elements) ? $this->separator : ''); 433 | 434 | if (true === $this->implicitFlush) { 435 | echo $htmlCrumb; 436 | } else { 437 | $content .= $htmlCrumb; 438 | } 439 | } 440 | 441 | // We return the breadcrumbs as string if the implicitFlush is turned off 442 | if (false === $this->implicitFlush) { 443 | return $content; 444 | } elseif ($this->eventsManager) { 445 | $this->eventsManager->fire('breadcrumbs:afterOutput', $this); 446 | } 447 | } 448 | 449 | /** 450 | * Gets breadcrumbs as array 451 | * 452 | * @return array 453 | */ 454 | public function toArray() 455 | { 456 | return $this->elements; 457 | } 458 | 459 | /** 460 | * Removes crumb by url. 461 | * 462 | * Events: 463 | * * breadcrumbs:beforeRemove 464 | * * breadcrumbs:afterRemove 465 | * 466 | * 467 | * $this->breadcrumbs->remove('/admin/user/create'); 468 | * 469 | * // remove a crumb without an url 470 | * $this->breadcrumbs->remove(null); 471 | * 472 | * 473 | * @param string|null $link Crumb url 474 | * @return $this 475 | */ 476 | public function remove($link) 477 | { 478 | if ($this->eventsManager) { 479 | $this->eventsManager->fire('breadcrumbs:beforeRemove', $this, [$link]); 480 | } 481 | 482 | try { 483 | if (empty($this->elements)) { 484 | throw new UnderflowException('Cannot remove crumb from an empty list.'); 485 | } 486 | 487 | if (!is_string($link) && !is_null($link)) { 488 | $type = gettype($link); 489 | throw new InvalidArgumentException( 490 | "Expected value of the first argument to be either string or null type, {$type} given." 491 | ); 492 | } 493 | 494 | if (is_null($link)) { 495 | $link = ':null:'; 496 | } 497 | 498 | if (!empty($this->elements) && array_key_exists($link, $this->elements)) { 499 | unset($this->elements[$link]); 500 | } 501 | } catch (\Exception $e) { 502 | $this->log($e); 503 | } 504 | 505 | if ($this->eventsManager) { 506 | $this->eventsManager->fire('breadcrumbs:afterRemove', $this, [$link]); 507 | } 508 | 509 | return $this; 510 | } 511 | 512 | /** 513 | * Update an existing crumb. 514 | * 515 | * Events: 516 | * * breadcrumbs:beforeUpdate 517 | * * breadcrumbs:afterUpdate 518 | * 519 | * 520 | * $this->breadcrumbs->update('/admin/user/remove', ['label' => 'Remove']); 521 | * 522 | * 523 | * @param string|null $url Crumb URL 524 | * @param array $data Crumb data 525 | * @return $this 526 | */ 527 | public function update($url, array $data) 528 | { 529 | $id = $url; 530 | try { 531 | if (empty($this->elements)) { 532 | throw new UnderflowException('Cannot update on an empty breadcrumbs list.'); 533 | } 534 | 535 | if (!is_string($id) && !is_null($id)) { 536 | $type = gettype($id); 537 | throw new InvalidArgumentException( 538 | "Expected value of the second argument to be either string or null type, {$type} given." 539 | ); 540 | } 541 | 542 | if (is_null($url)) { 543 | $id = ':null:'; 544 | } 545 | 546 | if (!array_key_exists($id, $this->elements)) { 547 | throw new OutOfBoundsException( 548 | sprintf("No such url '%s' in breadcrumbs list", is_null($url) ? 'null' : $id) 549 | ); 550 | } 551 | 552 | $this->elements[$id] = array_merge($this->elements[$id], $data); 553 | } catch (\Exception $e) { 554 | $this->log($e); 555 | } 556 | 557 | return $this; 558 | } 559 | 560 | /** 561 | * Logs error messages. 562 | * 563 | * Events: 564 | * * breadcrumbs:beforeLogging 565 | * * breadcrumbs:afterLogging 566 | * 567 | * @param \Exception $e 568 | */ 569 | protected function log(\Exception $e) 570 | { 571 | if ($this->eventsManager) { 572 | $this->eventsManager->fire('breadcrumbs:beforeLogging', $this, [$e]); 573 | } 574 | 575 | if ($this->logger) { 576 | $this->logger->error($e->getMessage()); 577 | } else { 578 | error_log($e->getMessage()); 579 | } 580 | 581 | if ($this->eventsManager) { 582 | $this->eventsManager->fire('breadcrumbs:afterLogging', $this, [$e]); 583 | } 584 | } 585 | 586 | /** 587 | * Count breadcrumbs 588 | * 589 | * @return integer 590 | */ 591 | public function count() 592 | { 593 | return count($this->elements); 594 | } 595 | } 596 | --------------------------------------------------------------------------------