Skip to content

Instantly share code, notes, and snippets.

@staabm
Last active July 30, 2020 14:07
Show Gist options
  • Save staabm/774655974d7af5728953f6a3e3accca4 to your computer and use it in GitHub Desktop.
Save staabm/774655974d7af5728953f6a3e3accca4 to your computer and use it in GitHub Desktop.
redaxo navigation iterator
<?php
/**
* Klasse zum Erstellen von Navigationen, v0.1.
*
* benötigt PHP7!
*
* @package redaxo\structure
*/
/*
* Beispiel-Konfiguration einer Haupt-Navigation:
*
* $it = rex_navigation_iterator::factory()
* ->ignoreOfflines(true)
* ;
*
* foreach($it as $navItem) {
* /* @var rex_article|rex_category $ooObj das aktuelle Element */
* /* @var int $depth die aktuelle tiefe/ebene */
* /* @var int $nth ein forlaufender Zähler je Ebene */
* /* @var int $count Anzahl Elemente auf der aktuellen Ebene */
* list($ooObj, $depth, $nth, $count) = $navItem;
*
* if ($nth == 1) {
* if ($depth == 1) {
* $class = "nav navbar-nav";
* } else {
* $class = "dropdown";
* }
* echo '<ul class="'. $class .'">';
* }
*
* echo "<li class='rex-navi-depth-". $depth ." rex-nav-". $nth ."nth-child'>";
* echo "<a href='". $ooObj->getUrl() ."'>". $ooObj->getName() ."</a>";
* echo "</li>\n";
*
* if ($nth == $count) {
* echo '</ul>';
* }
* }
*
* Bespiel-Konfiguration einer Seiten-Navigation
*
* $it = rex_navigation_iterator::factory()
* ->startCategory(27)
* ->depthLimit(3)
* ->ignoreOfflines(true)
*
* foreach($it as $navItem) {
* /* @var rex_article|rex_category $ooObj das aktuelle Element */
* /* @var int $depth die aktuelle tiefe/ebene */
* /* @var int $nth ein forlaufender Zähler je Ebene */
* /* @var int $count Anzahl Elemente auf der aktuellen Ebene */
* list($ooObj, $depth, $nth, $count) = $navItem;
*
* if ($nth == 1) {
* if ($depth == 1) {
* $class = "nav side-nav";
* } else {
* $class = "";
* }
* echo '<ul class="'. $class .'">';
* }
*
* echo "<li class='rex-navi-depth-". $depth ." rex-nav-". $nth ."nth-child'>";
* echo "<a href='". $ooObj->getUrl() ."'>". $ooObj->getName() ."</a>";
* echo "</li>\n";
*
* if ($nth == $count) {
* echo '</ul>';
* }
* }
*
* Bespiel-Konfiguration einer Breadcrumb-Navigation
*
* $it = rex_navigation_iterator::factory()
* ->activePath(true)
* ->ignoreOfflines(true)
*
* echo '<ul class="breadcrumb-nav">';
* echo '<li><a href="' . rex_getUrl(rex_article::getSiteStartArticleId()) . '">STARTSEITE</a></li>';
* foreach($it as $navItem) {
* /* @var rex_article|rex_category $ooObj das aktuelle Element */
* /* @var int $depth die aktuelle tiefe/ebene */
* /* @var int $nth ein forlaufender Zähler je Ebene */
* /* @var int $count Anzahl Elemente auf der aktuellen Ebene */
* list($ooObj, $depth, $nth, $count) = $navItem;
*
* echo "<li>";
* echo "<a href='". $ooObj->getUrl() ."'>". $ooObj->getName() ."</a>";
* echo "</li>\n";
* }
* echo '</ul>';
*/
class rex_navigation_iterator implements IteratorAggregate
{
use rex_factory_trait;
private $startCategory = 0;
private $ignoreOfflines = false;
private $activePath = false;
private $depthLimit = -1; // Wieviele Ebene tief, ab der Startebene
private $path = [];
private $filter = [];
private $current_category_id = -1; // Aktuelle Katgorie
private function __construct()
{
// nichts zu tun
}
/**
* @return static
*/
public static function factory()
{
$class = self::getFactoryClass();
return new $class();
}
/**
* @param int $categoryId Id der Wurzelkategorie, -1 für alle Kategorien (default)
*
* @return $this
*/
public function startCategory($categoryId) {
$this->startCategory = $categoryId;
return $this;
}
/**
* @param int $limit Anzahl der Ebenen die angezeigt werden sollen, -1 kein Limit (default)
*
* @return $this
*/
public function depthLimit($limit) {
$this->depthLimit = $limit;
return $this;
}
/**
* @param bool $ignoreOfflines FALSE, wenn offline Elemente angezeigt werden (default), sonst TRUE
*
* @return $this
*/
public function ignoreOfflines($ignoreOfflines) {
$this->ignoreOfflines = $ignoreOfflines;
return $this;
}
/**
* @param bool $activePath True, wenn nur Elemente der aktiven Kategorie angezeigt werden sollen, sonst FALSE (default)
*
* @return $this
*/
public function activePath($activePath) {
$this->activePath = $activePath;
return $this;
}
/**
* Fügt einen Filter hinzu.
*
* @param string $metafield Datenbankfeld der Kategorie
* @param mixed $value Wert für den Vergleich
* @param string $type Art des Vergleichs =/</.
* @param int|string $depth "" wenn auf allen Ebenen, wenn definiert, dann wird der Filter nur auf dieser Ebene angewendet
*/
public function addFilter($metafield = 'id', $value = '1', $type = '=', $depth = '')
{
$this->filter[] = ['metafield' => $metafield, 'value' => $value, 'type' => $type, 'depth' => $depth];
}
public function getIterator()
{
$this->_setActivePath();
if ($this->ignoreOfflines) {
$this->addFilter('status', 1, '==');
}
yield from ($this->_getNavigation($this->startCategory));
}
private function _setActivePath()
{
$article_id = rex_article::getCurrentId();
if (!$OOArt = rex_article::get($article_id)) {
throw new Exception("Unable to determine current article-id");
}
$path = trim($OOArt->getPath(), '|');
$this->path = [];
if ($path != '') {
$this->path = explode('|', $path);
}
$this->current_category_id = $OOArt->getCategoryId();
}
private function checkFilter(rex_category $category, $depth)
{
foreach ($this->filter as $f) {
if ($f['depth'] == '' || $f['depth'] == $depth) {
$mf = $category->getValue($f['metafield']);
$va = $f['value'];
switch ($f['type']) {
case '<>':
case '!=':
if ($mf == $va) {
return false;
}
break;
case '>':
if ($mf <= $va) {
return false;
}
break;
case '<':
if ($mf >= $va) {
return false;
}
break;
case '=>':
case '>=':
if ($mf < $va) {
return false;
}
break;
case '=<':
case '<=':
if ($mf > $va) {
return false;
}
break;
case 'regex':
if (!preg_match($va, $mf)) {
return false;
}
break;
case '=':
case '==':
default:
// =
if ($mf != $va) {
return false;
}
}
}
}
return true;
}
protected function _getNavigation($category_id, $depth = 1)
{
if ($category_id < 1) {
$nav_obj = rex_category::getRootCategories();
} else {
$nav_obj = rex_category::get($category_id)->getChildren();
}
$checked = [];
foreach ($nav_obj as $nav) {
if ($this->checkFilter($nav, $depth)) {
$checked[] = $nav;
}
}
$nth = 1;
foreach($checked as $nav) {
if (!$this->activePath || $this->activePath && ($nav->getId() == $this->current_category_id || in_array($nav->getId(), $this->path))
) {
yield [$nav, $depth, $nth, count($checked)];
++$depth;
if ($this->depthLimit >= $depth || $this->depthLimit < 0) {
yield from ($this->_getNavigation($nav->getId(), $depth));
}
--$depth;
$nth++;
}
}
}
}
@staabm
Copy link
Author

staabm commented Aug 3, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment