├── .github └── workflows │ └── publish-to-redaxo.yml ├── LICENSE ├── README.md ├── fragments └── navigation_array │ └── navigation.php ├── lib └── NavigationArray │ └── BuildArray.php └── package.yml /.github/workflows/publish-to-redaxo.yml: -------------------------------------------------------------------------------- 1 | name: Publish release 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | redaxo_publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: FriendsOfREDAXO/installer-action@v1 14 | with: 15 | myredaxo-username: ${{ secrets.MYREDAXO_USERNAME }} 16 | myredaxo-api-key: ${{ secrets.MYREDAXO_API_KEY }} 17 | description: ${{ github.event.release.body }} 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Friends Of REDAXO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REDAXO Navigation Array 2 | 3 | Navigation Array ist eine PHP-Klasse für die einfache Erstellung einer Navigationsstruktur als Array. Diese Klasse bietet flexible Möglichkeiten, Navigationsdaten auszulesen, zu filtern und zu verarbeiten, insbesondere durch die Nutzung der `walk()`-Methode. 4 | 5 | ## Erklärung der Klasse 6 | 7 | Die `FriendsOfRedaxo\NavigationArray\BuildArray` Klasse bietet folgende Hauptfunktionalitäten: 8 | 9 | * **Erstellung eines Navigationsarrays:** Generiert ein verschachteltes Array, das die Kategorien und ihre Hierarchie darstellt. 10 | * **Filterung von Kategorien:** Ermöglicht das Herausfiltern von Kategorien basierend auf benutzerdefinierten Kriterien. 11 | * **Hinzufügen benutzerdefinierter Daten:** Fügt zusätzliche Informationen zu jedem Kategorie-Array hinzu. 12 | * **Rekursive Navigationstraversierung:** Die `walk()`-Methode erlaubt es, die Navigationsstruktur rekursiv zu durchlaufen und dabei individuelle Operationen auszuführen. 13 | * **Abrufen von Kategorieinformationen:** Mit der `getCategory()`-Methode können detaillierte Informationen zu einzelnen Kategorien abgerufen werden. 14 | 15 | ### Kernfunktionen 16 | 17 | - Berücksichtigung von Offline-Artikeln und YCom-Rechten. 18 | - Frei wählbare Startkategorie. 19 | - Festlegbare Tiefe der Navigation. 20 | - Filterung und Manipulation von Kategorien über Callbacks. 21 | 22 | ### Features 23 | 24 | - `getCategory`-Methode zum Abrufen von Kategorieinformationen (inkl. Kindkategorien). 25 | - `walk()`-Methode für einfache, rekursive Navigationstraversierung. 26 | - Mitgelieferte Fragmente für die HTML-Ausgabe der Navigation (siehe Beispiele unten). 27 | 28 | ## Auslesen der Daten 29 | 30 | ### Array-Struktur 31 | 32 | Das generierte Array enthält alle Kategorien und Unterkategorien bis zur angegebenen Tiefe. Offline-Kategorien und Rechte aus YCom werden vorher entfernt. 33 | 34 | ```php 35 | array:7 [ 36 | 0 => array:11 [▶] 37 | 1 => array:11 [▶] 38 | 2 => array:11 [▶] 39 | 3 => array:11 [▶] 40 | 4 => array:11 [ 41 | "catId" => 43 42 | "parentId" => 0 43 | "level" => 0 44 | "catName" => "Kontakt" 45 | "url" => "/kontakt/" 46 | "hasChildren" => true 47 | "children" => array:2 [ 48 | 0 => array:11 [ 49 | "catId" => 178 50 | "parentId" => 43 51 | "level" => 1 52 | "catName" => "Kontaktformular" 53 | "url" => "/kontakt/kontaktformular/" 54 | "hasChildren" => false 55 | "children" => [] 56 | "path" => array:1 [▶] 57 | "active" => false 58 | "current" => false 59 | ] 60 | 1 => array:11 [▶] 61 | ] 62 | "path" => [] 63 | "active" => true 64 | "current" => true 65 | ] 66 | 5 => array:11 [▶] 67 | 6 => array:11 [▶] 68 | ] 69 | ``` 70 | 71 | ### Array generieren 72 | 73 | #### Aufruf mit Konstruktor 74 | 75 | ```php 76 | // define namespace 77 | use FriendsOfRedaxo\NavigationArray\BuildArray; 78 | // create object 79 | $navigationObject = new BuildArray(-1, 3); 80 | // generate navigation array 81 | $navigationArray = $navigationObject->generate(); 82 | ``` 83 | 84 | #### Aufruf mit Methoden 85 | 86 | ```php 87 | // define namespace 88 | use FriendsOfRedaxo\NavigationArray\BuildArray; 89 | // create navigation array 90 | $navigationArray = BuildArray::create() 91 | ->setStart(-1) 92 | ->setDepth(4) 93 | ->setIgnore(true) 94 | ->setCategoryFilterCallback(CategoryFilter()) 95 | ->setCustomDataCallback(CustomData()) 96 | ->generate(); 97 | ``` 98 | 99 | ## Erstellen von Navigationen 100 | 101 | ### Verwendung der `walk()`-Methode 102 | 103 | Die `walk()`-Methode ermöglicht es, die Navigation rekursiv zu durchlaufen und dabei eine Callback-Funktion auf jedes Element anzuwenden. Dies ist ideal für benutzerdefinierte Ausgaben oder Manipulationen der Navigationsdaten. 104 | 105 | #### Verwendung 106 | 107 | ```php 108 | public function walk(callable $callback): void 109 | ``` 110 | 111 | #### Parameter 112 | 113 | - `$callback`: Eine `callable`-Funktion, die für jedes Navigationselement aufgerufen wird. Die Funktion erhält zwei Parameter: 114 | - `$item`: Das aktuelle Navigationselement (als `array`). 115 | - `$level`: Die aktuelle Ebene der Navigation (als `int`). 116 | 117 | #### Beispiel: Verschachtelte HTML-Liste mit `walk()` 118 | 119 | ```php 120 | setDepth(3) 129 | ->setExcludedCategories([32,34]) 130 | ->setCategoryFilterCallback(function(rex_category $cat){ 131 | return true; 132 | }) 133 | ->setCustomDataCallback(function(rex_category $cat){ 134 | return [ 135 | 'css_id' => 'cat-'.$cat->getId(), 136 | 'description' => $cat->getValue('description') 137 | ]; 138 | }); 139 | 140 | $items = []; // Sammle alle Items 141 | $navigation->walk(function ($item, $level) use (&$items) { 142 | $items[] = ['item' => $item, 'level' => $level]; 143 | }); 144 | 145 | // Verarbeite die Items 146 | for ($i = 0; $i < count($items); $i++) { 147 | $currentItem = $items[$i]; 148 | $level = $currentItem['level']; 149 | $item = $currentItem['item']; 150 | 151 | // Prüfe auf Leveländerungen 152 | if ($level > $currentLevel) { 153 | $htmlNavigation .= "\n" . str_repeat(' ', $level) . ''; 159 | if ($j < $diff - 1) { 160 | $htmlNavigation .= ''; 161 | } 162 | } 163 | } elseif ($i > 0) { 164 | // Auf gleichem Level: Schließe vorheriges li 165 | $htmlNavigation .= ''; 166 | } 167 | 168 | $htmlNavigation .= "\n" . str_repeat(' ', $level); 169 | $activeClass = $item['active'] ? ' class="active"' : ''; 170 | $htmlNavigation .= ''; 171 | $htmlNavigation .= ''; 172 | $htmlNavigation .= $item['catName']; 173 | $htmlNavigation .= ''; 174 | 175 | $currentLevel = $level; 176 | } 177 | 178 | // Schließe verbleibende offene Tags 179 | if ($currentLevel >= 0) { 180 | for ($i = $currentLevel; $i >= 0; $i--) { 181 | $htmlNavigation .= '' . "\n" . str_repeat(' ', $i) . ''; 182 | } 183 | } 184 | 185 | echo $htmlNavigation; 186 | ``` 187 | 188 | #### Beispiel: Logausgabe der Navigation mit `walk()` 189 | 190 | ```php 191 | setDepth(2); 195 | 196 | $navigation->walk(function ($item, $level) { 197 | echo str_repeat(" ", $level) . "Kategorie: " . $item['catName'] . ", Level: " . $level . ", URL: " . $item['url'] . "
"; 198 | }); 199 | ``` 200 | 201 | #### Beispiel: Einfache Navigation mit `walk()` und REDAXO Fragment 202 | 203 | Hier ein Beispiel, wie du die `walk()`-Methode in Verbindung mit einem REDAXO-Fragment für eine einfache Navigation verwenden kannst. 204 | 205 | **1. Fragment (`simple_navigation.php`)**: 206 | Erstelle eine Fragmentdatei im Verzeichnis `fragments/navigation_array` mit folgendem Inhalt: 207 | 208 | ```php 209 | getVar('items'); 211 | $level = $this->getVar('level', 0); 212 | $activeClass = $this->getVar('activeClass', 'active'); 213 | 214 | if (empty($items)) { 215 | return ''; 216 | } 217 | $output = ''; 232 | echo $output; 233 | ?> 234 | ``` 235 | 236 | **2. Aufruf im Template:** 237 | In deinem REDAXO-Template kannst du die Navigation wie folgt erstellen: 238 | ```php 239 | setDepth(3); 243 | $htmlNavigation = ''; 244 | $items = []; 245 | $navigation->walk(function ($item, $level) use (&$items) { 246 | $items[] = $item; 247 | }); 248 | 249 | 250 | $fragment = new rex_fragment(); 251 | $fragment->setVar('items', $items); 252 | $fragment->setVar('activeClass', 'active'); 253 | echo $fragment->parse('navigation_array/simple_navigation.php'); 254 | ``` 255 | 256 | Dieses Beispiel zeigt, wie du mit der `walk()`-Methode ein Array der Navigation erzeugen kannst und dieses dann mit einem REDAXO-Fragment in eine einfache Navigation umwandeln kannst. Die Rekursion wird nun über die Fragments realisiert, um einen wiederverwendbaren Code zu erhalten. 257 | 258 | #### Beispiel: Breadcrumb mit `walk()` 259 | 260 | Hier ist ein modernes Breadcrumb-Beispiel mit der `walk()`-Methode: 261 | 262 | ```php 263 | setDepth(10); 267 | $breadcrumbItems = []; 268 | 269 | $navarray->walk(function ($item, $level) use (&$breadcrumbItems) { 270 | if ($item['active']) { 271 | $liclass = ''; 272 | if ('REX_ARTICLE_ID' == $item['catId']) { 273 | $liclass = ' class="disabled"'; 274 | $item['url'] = ''; 275 | } 276 | $breadcrumbItems[] = '' . $item['catName'] . ''; 277 | } 278 | }); 279 | echo ''; 283 | 284 | ``` 285 | 286 | Dieses Beispiel zeigt, wie du die `walk()` Methode nutzen kannst um einen Breadcrumb zu generieren, und dabei von den bereitgestellten Informationen Gebrauch machst. 287 | 288 | ### Vergleich: `walk()` vs. Eigene Iteration (Gleiche Ausgabe) 289 | 290 | Um die Vorteile der `walk()`-Methode zu verdeutlichen, zeigen wir hier, wie man die gleiche verschachtelte Navigation mit beiden Methoden erstellt. Dies ermöglicht einen direkten Code-Vergleich. 291 | 292 | #### Mit `walk()`-Methode 293 | 294 | ```php 295 | setDepth(3); 299 | $htmlOutput = ''; 300 | 301 | $navigation->walk(function($item, $level) use (&$htmlOutput) { 302 | $htmlOutput .= str_repeat("  ", $level) . '' . $item['catName'] . '
'; 303 | }); 304 | 305 | echo $htmlOutput; 306 | ``` 307 | 308 | #### Mit eigener Iterationsfunktion 309 | 310 | ```php 311 | ' . $item['catName'] . '
'; 318 | if ($item['hasChildren']) { 319 | $output .= myCustomNavigation($item['children'], $level + 1); 320 | } 321 | } 322 | return $output; 323 | } 324 | 325 | $navigation = BuildArray::create()->setDepth(3)->generate(); 326 | echo myCustomNavigation($navigation); 327 | ``` 328 | 329 | **Analyse:** 330 | 331 | * **Klarheit:** Beide Code-Beispiele erzeugen *exakt* die gleiche HTML-Ausgabe. 332 | * **Code-Kürze:** Die `walk()`-Methode erfordert *deutlich weniger* Code und ist lesbarer, da die Rekursionslogik intern gehandhabt wird. 333 | * **Wartbarkeit:** Der Code mit `walk()` ist einfacher zu warten, da die Rekursionslogik gekapselt ist. 334 | * **Flexibilität:** Die `walk()`-Methode ermöglicht es, die Logik der Ausgabe in einem Callback zu definieren, was die Flexibilität erhöht. 335 | 336 | Dieser direkte Vergleich zeigt, dass die `walk()`-Methode eine prägnantere, klarere und wartungsfreundlichere Möglichkeit bietet, die Navigationsdaten zu verarbeiten. 337 | 338 | ## Weitere Beispiele 339 | 340 | Neben der Nutzung von `walk()`, können auch die generierten Arrays direkt für die Ausgabe genutzt werden. 341 | 342 | ### Fragmente 343 | 344 | Mit Fragmenten wird die Logik sauber vom Code getrennt. Das Fragment `navigation.php` kann kopiert, angepasst und in `project` oder `theme`-Ordner abgelegt werden. 345 | 346 | ```php 347 | // Festlegen des Namespace 348 | use FriendsOfRedaxo\NavigationArray\BuildArray; 349 | 350 | // Navigation Array erstellen 351 | $navigationObject = new BuildArray(-1, 3); 352 | $navigationArray = $navigationObject->generate(); 353 | 354 | //Fragmente laden Navigation Array und Klasse für aktive Elemente übergeben 355 | $fragment = new rex_fragment(); 356 | $fragment->setVar('navigationArray', $navigationArray); 357 | $fragment->setVar('activeClass', 'active'); 358 | 359 | // Navigation ausgeben 360 | 365 | ``` 366 | 367 | ### Weitere PHP-Funktionen (Auswahl) 368 | 369 | Hier sind weitere Beispiele für spezifische HTML-Ausgaben 370 | 371 | #### Beispiel erweiterte Navigation mit Callback 372 | 373 | ```php 374 | setCustomDataCallback(function($cat) { 379 | return ['navtype' => $cat->getValue('cat_navigationstyp')]; 380 | }) 381 | ->generate(); 382 | 383 | $mainnavigation_items = []; 384 | 385 | function createNavigationItems($navi) { 386 | $items = []; 387 | $class_active = $navi['active'] ? 'active' : ''; 388 | $class_current = $navi['current'] ? ' current' : ''; 389 | $class_has_child = $navi['children'] ? ' has-child' : ''; 390 | 391 | $items[] = "
  • {$navi['catName']}"; 392 | 393 | if ($navi['children']) { 394 | $items[] = ''; 395 | $items[] = '
      '; 396 | 397 | foreach ($navi['children'] as $nav_child) { 398 | $items[] = createNavigationItems($nav_child); 399 | } 400 | 401 | $items[] = '
    '; 402 | } 403 | 404 | $items[] = '
  • '; 405 | 406 | return implode($items); 407 | } 408 | 409 | foreach ($mainnavi_array as $navi) { 410 | $navtype_arr = explode('|', $navi['navtype']); 411 | 412 | if (in_array('main', $navtype_arr)) { 413 | $mainnavigation_items[] = createNavigationItems($navi); 414 | } 415 | } 416 | 417 | $mainnavigation = ''; 418 | ``` 419 | 420 | #### Beispiel: Bootstrap 5 Navigation 421 | 422 | ```php 423 | '; 450 | $sub[] = bsnavi5($cat['children']); 451 | $sub[] = ''; 452 | $subnavi = join("\n", $sub); 453 | } 454 | 455 | $catname = $cat['catName']; 456 | if ($cat['active'] == true) { 457 | $active = ' active'; 458 | } 459 | 460 | $catname = '' . $catname . ''; 461 | $output[] = '
  • ' . $catname . $subnavi . '
  • '; 462 | } 463 | return implode("\n", $output); 464 | } 465 | 466 | 467 | $NavigationArray = BuildArray::create()->setDepth(4)->generate(); 468 | ?> 469 | 470 | 477 | 478 | ``` 479 | 480 | #### Beispiel UIkit-Drop-Down 481 | 482 | ```php 483 | '; 500 | $sub[] = ''; 501 | $sub[] = myNavi_demo($cat['children']); 502 | $sub[] = ''; 503 | $sub[] = ''; 504 | $subnavi = join("\n", $sub); 505 | } 506 | $catname = $cat['catName']; 507 | if ($cat['active'] == true) { 508 | $catname = '' . $catname . ''; 509 | $liclass .= 'uk-active'; 510 | } 511 | if ($liclass != '') { 512 | $liclass = ' class="' . $liclass . '"'; 513 | } 514 | $catname = '' . $catname . ''; 515 | $output[] = '' . $catname . $subnavi . ''; 516 | } 517 | 518 | if (!empty($output)) { 519 | return implode("\n", $output); 520 | } 521 | } 522 | // Navigation erzeugen 523 | 524 | $NavigationArray = BuildArray::create()->setDepth(3)->generate(); 525 | $navigation = ' 526 |
      ' 527 | . myNavi_demo($NavigationArray) . 528 | '
    529 | '; 530 | ?> 531 | 532 | 547 | 548 | ``` 549 | 550 | ### `getCategory()` 551 | 552 | Liefert ein Array mit allen Informationen zu einer Kategorie. Funktioniert sowohl für die aktuelle Kategorie als auch für eine spezifische Kategorie-ID. 553 | 554 | ### Basis-Verwendung 555 | 556 | ```php 557 | getCategory(); 561 | 562 | // Spezifische Kategorie 563 | $category = BuildArray::create()->getCategory(5); 564 | ``` 565 | 566 | ### Rückgabe-Array 567 | 568 | ```php 569 | [ 570 | // Hauptkategorie-Informationen 571 | 'catId' => 5, // ID der Kategorie 572 | 'parentId' => 2, // ID der Elternkategorie 573 | 'catName' => 'News', // Name der Kategorie 574 | 'url' => '/news/', // URL der Kategorie 575 | 'hasChildren' => true, // Hat Unterkategorien 576 | 'path' => [0,2,5], // Pfad von Root zur Kategorie 577 | 'pathCount' => 3, // Anzahl der Ebenen von Root 578 | 'active' => true, // Ist im aktiven Pfad 579 | 'current' => true, // Ist aktuelle Kategorie 580 | 'cat' => Object, // REX Category Objekt 581 | 'ycom_permitted' => true, // YCom-Berechtigung 582 | 'filter_permitted' => true, // Filter-Erlaubnis 583 | 'is_permitted' => true, // Gesamtstatus der Berechtigungen 584 | 585 | // Kinder-Array (aus processCategory) 586 | 'children' => [ 587 | [ 588 | 'catId' => 15, // ID der Kindkategorie 589 | 'parentId' => 5, // ID der Elternkategorie (unsere Hauptkategorie) 590 | 'level' => 3, // Level aus processCategory 591 | 'catName' => 'Events', // Name der Kindkategorie 592 | 'url' => '/news/events/', // URL der Kindkategorie 593 | 'hasChildren' => false, // Hat diese Kindkategorie weitere Unterkategorien 594 | 'children' => [], // Eventuelle Kinder der Kindkategorie 595 | 'path' => [0,2,5,15], // Pfad dieser Kindkategorie 596 | 'active' => false, // Ist diese Kindkategorie aktiv 597 | 'current' => false // Ist diese Kindkategorie die aktuelle Kategorie 598 | ], 599 | [ 600 | 'catId' => 16, 601 | 'parentId' => 5, 602 | 'level' => 3, 603 | 'catName' => 'Blog', 604 | // ... weitere Eigenschaften wie oben 605 | ], 606 | // ... weitere Kindkategorien 607 | ], 608 | 609 | // Zusätzliche Custom-Daten (wenn definiert) 610 | 'custom_field' => 'Wert', 611 | 'another_field' => 'Wert' 612 | ] 613 | ``` 614 | 615 | ### Beispiele 616 | 617 | #### 1. Kategorie-Vergleich 618 | 619 | ```php 620 | getCategory(); 623 | $parentCat = BuildArray::create()->getCategory($currentCat['parentId']); 624 | 625 | echo '
    '; 626 | echo '

    ' . $parentCat['catName'] . '

    '; 627 | echo '

    Sie befinden sich hier: ' . $currentCat['catName'] . '

    '; 628 | echo '
    '; 629 | ``` 630 | 631 | #### 2. Mit Custom-Daten 632 | 633 | ```php 634 | setCustomDataCallback(function($cat) { 638 | return [ 639 | 'image' => $cat->getValue('cat_image'), 640 | 'description' => $cat->getValue('cat_description') 641 | ]; 642 | }) 643 | ->getCategory(5); 644 | 645 | if ($category['is_permitted']) { 646 | echo '
    '; 647 | echo '

    ' . $category['catName'] . '

    '; 648 | if ($category['image']) { 649 | echo ''; 650 | } 651 | echo '
    '; 652 | } 653 | ``` 654 | 655 | #### 3. Mehrere Kategorien verarbeiten 656 | 657 | ```php 658 | setCategoryFilterCallback(function($cat) { 662 | return $cat->getValue('show_in_nav') == 1; 663 | }); 664 | 665 | $categories = [ 666 | $builder->getCategory(3), // Kategorie mit ID 3 667 | $builder->getCategory(4), // Kategorie mit ID 4 668 | $builder->getCategory() // Aktuelle Kategorie 669 | ]; 670 | 671 | foreach ($categories as $category) { 672 | if ($category['is_permitted']) { 673 | echo '
    '; 677 | echo $category['catName']; 678 | echo '
    '; 679 | } 680 | } 681 | ``` 682 | 683 | ## Methoden 684 | 685 | ### Konstruktor 686 | 687 | ```php 688 | public function __construct( 689 | int $start = -1, // Start-Kategorie ID (-1 für Root) 690 | int $depth = 4, // Maximale Tiefe 691 | bool $ignoreOfflines = true, // Offline-Kategorien ignorieren 692 | $depthSaved = 0, // Gespeicherte Tiefe 693 | int $level = 0 // Aktuelles Level 694 | ) 695 | ``` 696 | 697 | Erstellt eine neue Instanz der NavigationArray-Klasse. 698 | 699 | * `$start` (`int`, optional): Die ID der Startkategorie. Verwenden Sie `-1` für die YRewrite Mount-ID oder die Root-Kategorie. Standard: `-1`. 700 | * `$depth` (`int`, optional): Die maximale Tiefe der Navigation. Standard: `4`. 701 | * `$ignoreOfflines` (`bool`, optional): Gibt an, ob Offline-Kategorien ignoriert werden sollen. Standard: `true`. 702 | * `$depthSaved` (`int`, optional): Gespeicherte Tiefe, wird intern verwendet. Standard: `0`. 703 | * `$level` (`int`, optional): Aktuelles Level, wird intern verwendet. Standard: `0`. 704 | 705 | ### Statische Methoden 706 | 707 | #### `create()` 708 | 709 | ```php 710 | public static function create(): self 711 | ``` 712 | 713 | Factory-Methode zum einfachen Erstellen einer neuen Instanz. 714 | 715 | * Rückgabewert: Eine neue Instanz von `BuildArray` (`self`). 716 | 717 | ### Hauptmethoden 718 | 719 | #### `generate()` 720 | 721 | ```php 722 | public function generate(): array 723 | ``` 724 | 725 | Generiert das Navigations-Array mit allen Kategorien und Unterkategorien basierend auf den konfigurierten Einstellungen. 726 | 727 | * Rückgabewert: Das generierte Navigations-Array (`array`). 728 | 729 | #### `toJson()` 730 | 731 | ```php 732 | public function toJson(): string 733 | ``` 734 | 735 | Generiert das Navigations-Array und gibt es als JSON-formatierte Zeichenkette zurück. 736 | 737 | * Rückgabewert: Das generierte Navigations-Array als JSON-String (`string`). 738 | 739 | ### Setter-Methoden 740 | 741 | #### `setExcludedCategories()` 742 | 743 | ```php 744 | public function setExcludedCategories(int|array $excludedCategories): self 745 | ``` 746 | 747 | Legt fest, welche Kategorien von der Navigation ausgeschlossen werden sollen. 748 | 749 | * `$excludedCategories` (`int` oder `array` von `int`): Eine einzelne Kategorie-ID oder ein Array von Kategorie-IDs. 750 | * Rückgabewert: Die aktuelle Instanz von `BuildArray` (`self`). 751 | 752 | #### `setStart()` 753 | 754 | ```php 755 | public function setStart(int|array $start): self 756 | ``` 757 | 758 | Setzt die Start-Kategorie(n) für die Navigation. 759 | 760 | * `$start` (`int` oder `array` von `int`): Die ID der Startkategorie oder ein Array von Kategorie-IDs. Verwenden Sie `-1` für die YRewrite Mount-ID oder die Root-Kategorie, `0` für die Root-Kategorie oder eine positive Zahl für eine spezifische Kategorie-ID. 761 | * Rückgabewert: Die aktuelle Instanz von `BuildArray` (`self`). 762 | 763 | #### `setDepth()` 764 | 765 | ```php 766 | public function setDepth(int $depth): self 767 | ``` 768 | 769 | Legt die maximale Tiefe der Navigation fest. 770 | 771 | * `$depth` (`int`): Die maximale Tiefe der Navigation. 772 | * Rückgabewert: Die aktuelle Instanz von `BuildArray` (`self`). 773 | 774 | #### `setIgnore()` 775 | 776 | ```php 777 | public function setIgnore(int $ignore): self 778 | ``` 779 | 780 | Legt fest, ob Offline-Kategorien ignoriert werden sollen. 781 | 782 | * `$ignore` (`int`): `true` (1) oder `false` (0) um Offline-Kategorien zu ignorieren. 783 | * Rückgabewert: Die aktuelle Instanz von `BuildArray` (`self`). 784 | 785 | #### `setLevel()` 786 | 787 | ```php 788 | public function setLevel(int $lvl): self 789 | ``` 790 | 791 | Setzt das aktuelle Level in der Navigation. 792 | 793 | * `$lvl` (`int`): Das aktuelle Level der Navigation. 794 | * Rückgabewert: Die aktuelle Instanz von `BuildArray` (`self`). 795 | 796 | ### Callback-Methoden 797 | 798 | #### `setCategoryFilterCallback()` 799 | 800 | ```php 801 | public function setCategoryFilterCallback(callable $callback): self 802 | ``` 803 | 804 | Setzt eine Callback-Funktion zum Filtern von Kategorien. 805 | 806 | * `$callback` (`callable`): Eine Funktion, die ein `rex_category` Objekt als Parameter erhält und `true` (für die Aufnahme der Kategorie) oder `false` (für deren Ausschluss) zurückgibt. 807 | * Rückgabewert: Die aktuelle Instanz von `BuildArray` (`self`). 808 | 809 | #### `setCustomDataCallback()` 810 | 811 | ```php 812 | public function setCustomDataCallback(callable $callback): self 813 | ``` 814 | 815 | Setzt eine Callback-Funktion zum Hinzufügen benutzerdefinierter Daten. 816 | 817 | * `$callback` (`callable`): Eine Funktion, die ein `rex_category` Objekt als Parameter erhält und ein `array` mit zusätzlichen Daten zurückgibt. 818 | * Rückgabewert: Die aktuelle Instanz von `BuildArray` (`self`). 819 | 820 | ### Verwendung von `toJson()` 821 | 822 | ```php 823 | setDepth(3); 827 | $jsonString = $nav->toJson(); 828 | 829 | // Mit allen Optionen 830 | $nav = BuildArray::create() 831 | ->setStart(5) // Startet bei Kategorie ID 5 832 | ->setDepth(2) // Zwei Ebenen tief 833 | ->setIgnore(true) // Ignoriert Offline-Kategorien 834 | ->setExcludedCategories([10, 15]) // Schließt Kategorien aus 835 | ->toJson(); // Gibt JSON zurück 836 | 837 | // JSON in Variable speichern und ausgeben 838 | $jsonNavigation = $nav->toJson(); 839 | echo $jsonNavigation; 840 | 841 | // Direkt als AJAX Response verwenden 842 | header('Content-Type: application/json'); 843 | echo BuildArray::create() 844 | ->setDepth(3) 845 | ->toJson(); 846 | 847 | // JSON decodieren für weitere Verarbeitung 848 | $navigationArray = json_decode($nav->toJson(), true); 849 | ``` 850 | 851 | Das generierte JSON sieht etwa so aus: 852 | 853 | ```json 854 | [ 855 | { 856 | "catId": 1, 857 | "parentId": 0, 858 | "level": 0, 859 | "catName": "Home", 860 | "url": "/", 861 | "hasChildren": true, 862 | "children": [ 863 | { 864 | "catId": 5, 865 | "parentId": 1, 866 | "level": 1, 867 | "catName": "Über uns", 868 | "url": "/ueber-uns/", 869 | "hasChildren": false, 870 | "children": [], 871 | "path": [1, 5], 872 | "active": false, 873 | "current": false 874 | } 875 | ], 876 | "path": [1], 877 | "active": true, 878 | "current": false 879 | } 880 | ] 881 | ``` 882 | 883 | ## Autor 884 | 885 | **Friends Of REDAXO** 886 | 887 | - 888 | - 889 | 890 | **Projektleitung** 891 | 892 | [Thomas Skerbis](https://github.com/skerbis) 893 | -------------------------------------------------------------------------------- /fragments/navigation_array/navigation.php: -------------------------------------------------------------------------------- 1 | getVar('navigationArray') as $categoryArray): ?> 2 |
  • 3 | 4 | 5 | 6 | 7 |
      8 | subfragment('navigation_array/navigation.php', ['navigationArray' => $categoryArray['children']]); 10 | ?> 11 |
    12 | 13 |
  • 14 | -------------------------------------------------------------------------------- /lib/NavigationArray/BuildArray.php: -------------------------------------------------------------------------------- 1 | start = $start; 33 | $this->depth = $depth; 34 | $this->ignoreOfflines = $ignoreOfflines; 35 | $this->level = $level; 36 | } 37 | 38 | /** 39 | * Set categories to exclude from the navigation (int or array of ints with category ids). 40 | * 41 | * @param int|array $excludedCategories 42 | * @return $this 43 | */ 44 | public function setExcludedCategories(int|array $excludedCategories): self 45 | { 46 | if (is_int($excludedCategories)) { 47 | $excludedCategories = [$excludedCategories]; 48 | } 49 | 50 | if (!is_array($excludedCategories)) { 51 | $message = 'Excluded categories must be an integer or an array of integers.'; 52 | rex_logger::logError(E_USER_ERROR, $message, __FILE__, __LINE__); 53 | throw new rex_exception($message); 54 | } 55 | 56 | $this->excludedCategories = $excludedCategories; 57 | return $this; 58 | } 59 | 60 | /** 61 | * Set ID of the category to start with (default: -1, yrewrite mountID or root category). 62 | * 63 | * @param int $start 64 | * @return $this 65 | */ 66 | public function setStart(int $start): self 67 | { 68 | $this->start = $start; 69 | return $this; 70 | } 71 | 72 | /** 73 | * Set how many levels should the navigation show (default: 4). 74 | * 75 | * @param int $depth 76 | * @return $this 77 | */ 78 | public function setDepth(int $depth): self 79 | { 80 | $this->depth = $depth; 81 | return $this; 82 | } 83 | 84 | /** 85 | * Set whether offline categories should be ignored (default: true). 86 | * 87 | * @param int $ignore 1 for true, 0 for false 88 | * @return $this 89 | */ 90 | public function setIgnore(int $ignore): self 91 | { 92 | $this->ignoreOfflines = (bool)$ignore; 93 | return $this; 94 | } 95 | 96 | /** 97 | * @param int $lvl 98 | * @return $this 99 | */ 100 | public function setLevel(int $lvl): self 101 | { 102 | $this->level = $lvl; 103 | return $this; 104 | } 105 | 106 | /** 107 | * Create a new instance of BuildArray. 108 | * 109 | * @return self 110 | */ 111 | public static function create(): self 112 | { 113 | return new self(); 114 | } 115 | 116 | /** 117 | * Generate the navigation array. 118 | * 119 | * @return array 120 | */ 121 | public function generate(): array 122 | { 123 | $result = []; 124 | $currentCat = rex_category::getCurrent(); 125 | $currentCatpath = $currentCat ? $currentCat->getPathAsArray() : []; 126 | $currentCat_id = $currentCat ? $currentCat->getId() : 0; 127 | 128 | $this->initializeStartCategory(); 129 | 130 | foreach ($this->startCats as $cat) { 131 | if ($this->isPermitted($cat)) { 132 | $result[] = $this->processCategory($cat, $currentCatpath, $currentCat_id); 133 | } 134 | } 135 | return array_filter($result); 136 | } 137 | 138 | /** 139 | * Set a callback to filter categories. 140 | * 141 | * @param callable $callback 142 | * @return $this 143 | */ 144 | public function setCategoryFilterCallback(callable $callback): self 145 | { 146 | $this->categoryFilterCallback = $callback; 147 | return $this; 148 | } 149 | 150 | /** 151 | * Set a callback to add custom data to the category array. 152 | * 153 | * @param callable $callback 154 | * @return $this 155 | */ 156 | public function setCustomDataCallback(callable $callback): self 157 | { 158 | $this->customDataCallback = $callback; 159 | return $this; 160 | } 161 | 162 | /** 163 | * Generate the navigation array as JSON. 164 | * 165 | * @return string 166 | */ 167 | public function toJson(): string 168 | { 169 | $array = $this->generate(); 170 | return json_encode($array, JSON_PRETTY_PRINT); 171 | } 172 | 173 | /** 174 | * Initialize the start category based on the provided start value 175 | * or fallback to yrewrite domain or root categories 176 | * 177 | * @return void 178 | */ 179 | private function initializeStartCategory(): void 180 | { 181 | // Erster Schritt: Startwert ermitteln, falls es -1 (Default) ist 182 | if ($this->start == -1) { 183 | // YRewrite Domain-Startpunkt versuchen zu holen 184 | if (rex_addon::get('yrewrite')->isAvailable()) { 185 | $domain = rex_yrewrite::getDomainByArticleId(rex_article::getCurrentId(), rex_clang::getCurrentId()); 186 | $this->start = ($domain !== null) ? $domain->getMountId() : 0; 187 | } else { 188 | // Fallback auf Root-Kategorien 189 | $this->start = 0; 190 | } 191 | } 192 | 193 | // Zweiter Schritt: Kategorien basierend auf dem ermittelten Startwert laden 194 | // Arrays von Kategorie-IDs 195 | if (is_array($this->start)) { 196 | $this->startCats = []; 197 | foreach ($this->start as $startCatId) { 198 | $startCat = rex_category::get($startCatId); 199 | if ($startCat) { 200 | $this->startCats[] = $startCat; 201 | } 202 | } 203 | return; 204 | } 205 | 206 | // Spezifische Kategorie-ID (nicht 0) 207 | if ($this->start != 0) { 208 | $startCat = rex_category::get($this->start); 209 | if ($startCat) { 210 | $this->startCats = $startCat->getChildren($this->ignoreOfflines); 211 | return; 212 | } 213 | } 214 | 215 | // Fallback auf Root-Kategorien 216 | $this->startCats = rex_category::getRootCategories($this->ignoreOfflines); 217 | } 218 | 219 | /** 220 | * Check if the category is permitted by ycom. 221 | * 222 | * @param rex_category $cat 223 | * @return bool 224 | */ 225 | private function isCategoryPermitted(rex_category $cat): bool 226 | { 227 | // Erst prüfen, ob das ycom-Addon überhaupt installiert und aktiviert ist 228 | if (!rex_addon::get('ycom')->isAvailable()) { 229 | return true; 230 | } 231 | 232 | // Dann prüfen, ob das auth-Plugin verfügbar ist 233 | $ycom_check = rex_addon::get('ycom')->getPlugin('auth')->isAvailable(); 234 | return !$ycom_check || $cat->isPermitted(); 235 | } 236 | 237 | /** 238 | * Check if category meets all navigation requirements 239 | * Prüft ob die Kategorie alle Anforderungen erfüllt (YCom, Ausschlüsse, Filter) 240 | * 241 | * @param rex_category $cat 242 | * @return bool 243 | */ 244 | private function isPermitted(rex_category $cat): bool 245 | { 246 | // Prüfe YCom Berechtigungen 247 | if (!$this->isCategoryPermitted($cat)) { 248 | return false; 249 | } 250 | 251 | // Prüfe ob Kategorie ausgeschlossen ist 252 | if (in_array($cat->getId(), $this->excludedCategories)) { 253 | return false; 254 | } 255 | 256 | // Prüfe Category Filter Callback 257 | if (is_callable($this->categoryFilterCallback) && !call_user_func($this->categoryFilterCallback, $cat)) { 258 | return false; 259 | } 260 | 261 | return true; 262 | } 263 | 264 | /** 265 | * @param rex_category $cat 266 | * @param array $currentCatpath 267 | * @param int $currentCat_id 268 | * @return array 269 | */ 270 | private function processCategory(rex_category $cat, array $currentCatpath, int $currentCat_id): array 271 | { 272 | $catId = $cat->getId(); 273 | 274 | // Base category data 275 | $categoryArray = [ 276 | 'catId' => $catId, 277 | 'parentId' => $cat->getParentId(), 278 | 'level' => $this->level, 279 | 'catName' => $cat->getName(), 280 | 'url' => $cat->getUrl(), 281 | 'path' => $cat->getPathAsArray(), 282 | 'active' => in_array($catId, $currentCatpath) || $currentCat_id == $catId, 283 | 'current' => $currentCat_id == $catId, 284 | ]; 285 | 286 | // Process children only if we haven't reached the maximum depth 287 | $children = []; 288 | if ($this->level < $this->depth) { 289 | $childCats = $cat->getChildren($this->ignoreOfflines); 290 | if ($childCats) { 291 | $this->level++; // Increment level for children 292 | foreach ($childCats as $child) { 293 | if ($this->isPermitted($child)) { 294 | $children[] = $this->processCategory($child, $currentCatpath, $currentCat_id); 295 | } 296 | } 297 | $this->level--; // Restore level after processing children 298 | } 299 | } 300 | 301 | $categoryArray['hasChildren'] = !empty($children); 302 | $categoryArray['children'] = $children; 303 | 304 | // Add custom data if callback is set 305 | if (is_callable($this->customDataCallback)) { 306 | $customData = call_user_func($this->customDataCallback, $cat); 307 | if (is_array($customData)) { 308 | $categoryArray = array_merge($categoryArray, $customData); 309 | } 310 | } 311 | 312 | return $categoryArray; 313 | } 314 | 315 | /** 316 | * Get category information either for current category or by ID 317 | * 318 | * @param int|null $categoryId Optional category ID 319 | * @return array 320 | */ 321 | public function getCategory(?int $categoryId = null): array 322 | { 323 | // Kategorie ermitteln (entweder durch ID oder current) 324 | $cat = null; 325 | if ($categoryId !== null) { 326 | $cat = rex_category::get($categoryId); 327 | } else { 328 | $cat = rex_category::getCurrent(); 329 | } 330 | 331 | if (!$cat) { 332 | return []; 333 | } 334 | 335 | // YCom-Berechtigungen prüfen 336 | $hasYcomPermissions = $this->isCategoryPermitted($cat); 337 | 338 | // Filter-Status prüfen 339 | $isFilterPermitted = true; 340 | if (is_callable($this->categoryFilterCallback)) { 341 | $isFilterPermitted = call_user_func($this->categoryFilterCallback, $cat); 342 | } 343 | 344 | $catId = $cat->getId(); 345 | $path = $cat->getPathAsArray(); 346 | $currentCat = rex_category::getCurrent(); 347 | $currentCatpath = $currentCat ? $currentCat->getPathAsArray() : []; 348 | $currentCat_id = $currentCat ? $currentCat->getId() : 0; 349 | 350 | // Kinder mit processCategory verarbeiten 351 | $children = []; 352 | $childCategories = $cat->getChildren($this->ignoreOfflines); 353 | if ($childCategories) { 354 | foreach ($childCategories as $childCat) { 355 | if ($this->isPermitted($childCat)) { 356 | $children[] = $this->processCategory($childCat, $currentCatpath, $currentCat_id); 357 | } 358 | } 359 | } 360 | 361 | $categoryArray = [ 362 | 'catId' => $catId, 363 | 'parentId' => $cat->getParentId(), 364 | 'catName' => $cat->getName(), 365 | 'url' => $cat->getUrl(), 366 | 'hasChildren' => !empty($children), 367 | 'children' => $children, // Kinder aus processCategory 368 | 'path' => $path, 369 | 'pathCount' => count($path), 370 | 'active' => in_array($catId, $currentCatpath) || $currentCat_id == $catId, 371 | 'current' => $currentCat_id == $catId, 372 | 'cat' => $cat, 373 | 'ycom_permitted' => $hasYcomPermissions, 374 | 'filter_permitted' => $isFilterPermitted, 375 | 'is_permitted' => $hasYcomPermissions && $isFilterPermitted 376 | ]; 377 | 378 | // Custom Data hinzufügen wenn ein Callback definiert ist 379 | if (is_callable($this->customDataCallback)) { 380 | $customData = call_user_func($this->customDataCallback, $cat); 381 | if (is_array($customData)) { 382 | $categoryArray = array_merge($categoryArray, $customData); 383 | } 384 | } 385 | 386 | return $categoryArray; 387 | } 388 | 389 | /** 390 | * Walk through the navigation and apply a callback to each item. 391 | * 392 | * @param callable $callback The callback function to apply to each item. 393 | * It will receive the item (category array) and the level as arguments. 394 | * @return void 395 | */ 396 | public function walk(callable $callback): void 397 | { 398 | $this->initializeStartCategory(); 399 | 400 | $currentCat = rex_category::getCurrent(); 401 | $currentCatpath = $currentCat ? $currentCat->getPathAsArray() : []; 402 | $currentCat_id = $currentCat ? $currentCat->getId() : 0; 403 | 404 | foreach ($this->startCats as $cat) { 405 | if ($this->isPermitted($cat)) { 406 | // Start mit Level 0 für die Root-Kategorien 407 | $this->walkRecursive($cat, $callback, $currentCatpath, $currentCat_id, 0); 408 | } 409 | } 410 | } 411 | 412 | /** 413 | * Recursive helper function for the walk method. 414 | * 415 | * @param rex_category $cat 416 | * @param callable $callback 417 | * @param array $currentCatpath 418 | * @param int $currentCat_id 419 | * @param int $level 420 | * @return void 421 | */ 422 | private function walkRecursive(rex_category $cat, callable $callback, array $currentCatpath, int $currentCat_id, int $level): void 423 | { 424 | // Prüfe zuerst, ob wir die maximale Tiefe überschritten haben 425 | if ($level > $this->depth) { 426 | return; 427 | } 428 | 429 | $item = $this->processCategory($cat, $currentCatpath, $currentCat_id); 430 | if (!empty($item)) { 431 | call_user_func($callback, $item, $level); 432 | } 433 | 434 | // Hole Kindkategorien nur wenn wir noch nicht die maximale Tiefe erreicht haben 435 | if ($level < $this->depth) { 436 | $childCats = $cat->getChildren($this->ignoreOfflines); 437 | if (!empty($childCats)) { 438 | foreach ($childCats as $child) { 439 | if ($this->isPermitted($child)) { 440 | $this->walkRecursive($child, $callback, $currentCatpath, $currentCat_id, $level + 1); 441 | } 442 | } 443 | } 444 | } 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /package.yml: -------------------------------------------------------------------------------- 1 | package: navigation_array 2 | version: '5.3.3' 3 | author: 'Friends Of REDAXO' 4 | supportpage: https://github.com/FriendsOfREDAXO/navigation_array 5 | info: 'REDAXO AddOn:Navigation Array' 6 | 7 | requires: 8 | redaxo: '^5.15.0' 9 | php: 10 | version: '>=8.1' 11 | --------------------------------------------------------------------------------