├── .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 | '
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 | *