Maximum function nesting level of '256' reached, aborting!

(Kirby v 3.3.0)

Taking the {{ gallery }} tag as an example, I’m making a {{ accordion }} tag.

But I’m running into this error: Maximum function nesting level … when I’m trying to display the accordion content panel.

  • This doesn’t work: <?= $item->accordion_content()->kt() ?>
  • This does work: <?= $item->accordion_content()->md() ?>

Using md() will limit me in the display of (custom) KirbyTags so I’d rather not use it.

Blueprint:

accordion:
  label: Accordion
  type: structure
  fields:
    accordion_heading:
      label: Heading
      type: text
    accordion_content:
      label: Content
      type: textarea
      buttons:
        - italic
        - bold
        - link
        - '|'
        - email
        - file
        - code 
        - '|'
        - ul
        - ol

Plugin (plugins/accordion/index.php):

<?php Kirby::plugin('kirby/accordion', [
    'hooks' => [
        'kirbytags:after' => function ($text, $data, $options) {

            if ($page = $data['parent']->accordion()->toStructure()) {
                $items = snippet('accordion', ['items' => $page], true);
            } else {
                $items = '';
            }

            return str_replace('{{ accordion }}', $items, $text);
        }
    ]
]);

Snippet (snippets/accordion.php) that doesn’t work:

<div id="accordionGroup" class="accordion" data-allow-toggle>
<?php 
    $n=0;
    foreach ($items as $item): $n++; 
?>
<h3>
  <button aria-expanded="<?= e($n > 1, 'false', 'true') ?>" class="accordion-trigger" aria-controls="<?= 'sect' . $n ?>" id="<?= 'accordion' . $n . 'id' ?>">
    <span class="accordion-title">
        <?= $item->accordion_heading()->html() ?>
        <span class="accordion-icon"></span>
    </span>
  </button>
</h3>
<div id="<?= 'sect' . $n ?>" role="region" aria-labelledby="<?= 'accordion' . $n . 'id' ?>" class="accordion-panel" <?= e($n > 1, 'hidden=""', '') ?>>
<div>
  <?= $item->accordion_content()->kt() ?>
</div>
</div>
<?php endforeach ?>
</div>

I remember running into this problem before and was able to solve it with something like: <?= kt($item->accordion_content()) ?> but that doesn’t help in this case.

Also I noticed: using the snippet with 4 spaces indentation will break my layout because <pre> tags are placed at certain points in the markup of the accordion.

You are running into an endless loop because you are trying to call the kirbytext() method inside your snippet, which will then trigger the hook etc.

Of course, thanks.

You can replace your hook code like this:

Kirby::plugin('kirby/accordion', [
    'hooks' => [
        'kirbytags:before' => function ($text, $data) {

            return preg_replace_callback('!{{(.*)}}!siU', function($match) use ($data) {
                $block = '';
                if ($items = $data['parent']->accordion()->toStructure()) {
                    $block = snippet('accordion', ['items' => $items]);
                }
                return $block;

            }, $text);
           
        }
    ]
]);

I had already given up on it, cool thanks a bunch! :100:

Just tried this: this places two accordions on the page. One before and one after a cover image.
Also doesn’t play nicely with the {{ gallery }} tag, breaks it.

Oh, I applied that to every tag, should of course just apply to the accordion tag:

   'kirbytags:before' => function ($text, $data) {

            return preg_replace_callback('!{{ accordion }}!siU', function($match) use ($data) {
                $block = '';
                if ($items = $data['parent']->accordion()->toStructure()) {
                    $block = snippet('accordion', ['items' => $items]);
                }
                return $block;

            }, $text);
           
        }

You might want to adapt the regex to make sure it also works when the user doesn’t use spaces between the curly braces.

Yes, I already tried that, but it doesn’t solve the double accordion.

Hm, I can’t reproduce the double accordion.

When I trash this toc-code it works:
Can you see the problem?

<?php Kirby::plugin('kirby/toc', [
    'fieldMethods' => [
        'headlines' => function($field, $headline = 'h2') {
            preg_match_all('!<' . $headline . '.*?>(.*?)</' . $headline . '>!s', $field->kt()->value(), $matches);
                    $headlines = new Collection;
                    foreach ($matches[1] as $text) {
                        $headline = new Obj([
                            'id'   => $id = '#' . Str::slug(Str::unhtml($text)),
                            'url'  => $id,
                            'text' => trim(strip_tags($text)),
                        ]);
                        $headlines->append($headline->url(), $headline);
                    }
                    return $headlines;
        }
    ]
]);

No, you took that code from the getkirby.com repo, right?

I can reproduce your issue with the duplicate accordions when I use the headlines method in the same template, but I currently don’t know how to solve this.

That’s right, it was from a thread where you explained how to use it and I followed that example.
I will use the markdown version for now and maybe you will have a :bulb: moment.

If you don’t call your headlines() method directly in the same template, but in a snippet like in the. getkirby.com repo, it should work:

/snippets/toc.php

<?php if($item->count() > 3): ?>
  <nav class="toc | -mb:large">

    <h2 class="h4">Table of Contents</h2>

    <ol>
      <?php foreach($item as $headline): ?>
        <li><a href="<?= $headline->url() ?>"><span><?= $headline->text() ?></span></a></li>
      <?php endforeach ?>
    </ol>

  </nav>
<?php endif ?>

And in template

snippet('toc', $page->description()->headlines())

Currently:

  • plugin: plugins/toc/index.php
  • snippet: toc.php (code where I have conditionals at work to combine toc with cover image)
  • snippet: page-content.php where I call: snippet('toc', $page->text()->headlines()) more stuff in here though
  • page template: snippet('page-content')

No doubt you would clean this up and maybe this is the source of all the troubles?
I’ll try your suggestion …

Actually, I had a typo in my code, so with the changes above, everything should actually work as expected. Still doesn’t in your case?

Bit confused about dump($field->headlines()); what $field etc.
But what typo was that?

That was just from my first test. The code just dumps the content of $field->headlines(). It’s a way to check what you get back from your code. Just stick with the toc snippet from the getkirby.com repo. I removed the dump() code again.

Just in my local code, I made changes and forgot to add true as third param to a snippet, which resulted in the wrong order.

snippet/toc.php:

<?php // ToC combined with cover
    if($item->count() and $page->toc()->bool() and $page->cover_toc()->bool()): ?>
    <nav class="toc-cover" aria-labelledby="toc-head">
        <div class="toc">
            <h2 class="toc-heading" id="toc-head">On this page</h2>
            <ol>
                <?php foreach($item as $headline): ?>
                    <li><a href="<?= $headline->url() ?>"><?= $headline->text() ?></a></li>
                <?php endforeach ?>
            </ol>
        </div>
        <?php // cover image
            if($cover = $page->cover()->toFile() and $page->showcover()->bool()): ?>
            <figure>
                <img src="<?= $cover->url(); ?>" srcset="<?= $cover->srcset() ?>" alt="<?= $cover->alt(); ?>">
            </figure>
        <?php endif ?>
    </nav>
<?php endif ?>

plugin:

<?php Kirby::plugin('kirby/toc', [
    'fieldMethods' => [
        'headlines' => function($field, $headline = 'h2') {
            preg_match_all('!<' . $headline . '.*?>(.*?)</' . $headline . '>!s', $field->kt()->value(), $matches);
                    $headlines = new Collection;
                    foreach ($matches[1] as $text) {
                        $headline = new Obj([
                            'id'   => $id = '#' . Str::slug(Str::unhtml($text)),
                            'url'  => $id,
                            'text' => trim(strip_tags($text)),
                        ]);
                        $headlines->append($headline->url(), $headline);
                    }
                    return $headlines;
        }
    ]
]);

snippet page-content:

<?php snippet('toc', $page->text()->headlines()) ?>

Which all worked fine but not with the accordion {{ accordion }} and the preg_replace_callback.

Thanks you are amazingly kind to stick with this.
I have to go now but again thanks for all your efforts!

I found the/a solution. In the toc plugin I replaced:

preg_match_all('!<' . $headline . '.*?>(.*?)</' . $headline . '>!s', $field->kt()->value(), $matches);

replaced with:

preg_match_all('!<' . $headline . '.*?>(.*?)</' . $headline . '>!s', $field->md()->value(), $matches);

Swapping kt() for md().

Resulting in a nice toc and one accordion (including kt link):

btw: I’m calling the toc and my page text field in the same template:

<?php // ToC and cover image see also plugins › toc
     snippet('toc', $page->text()->headlines()) ?>

<?php // page text replacing escaped opening parenthesis '(\' for kirbytext examples
     echo str_replace('(\\', '(', kirbytext($page->text())) ?>