Skip to content

Instantly share code, notes, and snippets.

@staabm
Last active July 30, 2020 14:07
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • 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++;
}
}
}
}
@tbaddade
Copy link

Ich verwende zum ersten mal die Iterator Class und komme da aktuell nicht weiter.
Denn ich bräuchte eine Möglichkeit zu wissen, ob das Item Kinder hat. Eine Art $navItem->hasChildren().

@tbaddade
Copy link

@staabm

Ich habe mal diese Zeile
https://gist.github.com/staabm/774655974d7af5728953f6a3e3accca4#file-navigation_iterator-php-L281

so angepasst

yield [$nav, $depth, $nth, count($checked), count($nav->getChildren($this->ignoreOfflines))];

@tbaddade
Copy link

tbaddade commented Jul 30, 2018

Stelle grad fest, das eine Verschachtelung mehrere Ebenen aktuell gar nicht möglich ist.
Das Markup wurde mit dem Beispielcode aus der Class erzeugt.

Erzeugtes Markup
screenshot 2018-07-30 16 28 38

Das äußerste <ul> ist das mit der class navbar
Die anderen zwei <ul class="dropdown"> gehören in das jeweilige vorherige <li>

Korrektes Markup
screenshot 2018-07-30 16 36 00

@staabm
Copy link
Author

staabm commented Jul 31, 2018

die klasse selbst generiert ja kein markup, daher muss man die beispiele abwandeln wenn man anderes markup haben will.

wenn du für dein genanntes markup ein beispiel hast, dass ich oben im kommentar hinzufügen soll, mach ich das gerne.

@staabm
Copy link
Author

staabm commented Jul 31, 2018

yield [$nav, $depth, $nth, count($checked), count($nav->getChildren($this->ignoreOfflines))];

würde man in diesem fall dann einen checkFilter benutzen, der alle offlines ignoriert und somit dann würde auch der count($checked) passsen, in deinem use-case, oder?

ich würde die zahl der "ge-yieldeten" variablen gerne so klein wie möglich halten, damit die resultierenden navi skripte möglichst einfach bleiben und auch für anfänger zu verstehen sind.

@tbaddade
Copy link

die klasse selbst generiert ja kein markup, daher muss man die beispiele abwandeln wenn man anderes markup haben will.

@staab Es geht um dein Beispiel ab Zeile 12.

Ich kann aktuell damit keine verschachtelten Listen ausgeben, da mir Infos fehlen um korrektes Markup zu erstellen. Siehe den obersten Screenshot. Das Markup ist mit deinem Bsp. erstellt.

Zumindest sehe ich hier keine Lösung. Vielleicht kannst du daher dein Bsp. ab Zeile 12 so anpassen, dass dort valides HTML entsteht.

@tbaddade
Copy link

@staab
Lass uns hier weiter diskutieren redaxo/redaxo#1935

@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