Problem with filtering, merging and sorting structure field contents

Hi,

I’m trying to filter a list of entries with three different states (visible, auto, spoiler) and recombine them into two lists. On list should contain all visible plus n auto entries and the other list should contain all spoiler plus the remaining auto entries. But I made some mistake. To reproduce the problem pleas try the following.

Download the starter kit (2.2.3) and add these two files

<!-- templates/test.php -->
<?php snippet('header') ?>

  <main class="main" role="main">

    <div class="text">
      <h1><?php echo $page->title()->html() ?></h1>

    <?php
        $n = 10;
        $entries = $page->entries()->toStructure();
        $visibleEntries = $entries
            ->filterBy('visibility', 'visible')
            ->merge($entries->filterBy('visibility', 'auto')->limit($n))
            ->sortBy('sort');
        $spoilerEntries = $entries
            ->filterBy('visibility', 'spoiler')
            ->merge($entries->filterBy('visibility', 'auto')->offset($n))
            ->sortBy('sort');
    ?>
    
    <h2>Visible Entries</h2>
    <p>
        This list should contain <emph>all</emph> entries with
        <code>visibility: visible</code> and the first <code>$n</code>
        elements with <code>visibility: auto</code>.
    </p>
    <ul>
        <?php foreach ($visibleEntries as $e) : ?>
        <li><?php echo $e->text() ?></li>
        <?php endforeach ?>
    </ul>
    
    <h2>Spoiler Entries</h2>
    <p>
        This list should contain <emph>all</emph> entries with
        <code>visibility: spoiler</code> and the elements with
        <code>visibility: auto</code> exept the first <code>$n</code>.
    </p>
    <ul>
        <?php foreach ($spoilerEntries as $e) : ?>
        <li><?php echo $e->text() ?></li>
        <?php endforeach ?>
    </ul> 
       
    <h2>All Entries</h2>
    <p>
        This list should contain <emph>all</emph> entries.
    </p>
    <ul>
        <?php foreach ($entries->sortBy('sort') as $e) : ?>
        <li><?php echo $e->text() ?></li>
        <?php endforeach ?>
    </ul>

    </div>
  </main>

<?php snippet('footer') ?>
# content/test/test.txt
Title: TEST

----

Entries: 

- 
  sort: 16
  text: 16 (visible)
  visibility: visible
- 
  sort: 15
  text: 15 (visible)
  visibility: visible
- 
  sort: 14
  text: 14 (spoiler)
  visibility: spoiler
- 
  sort: 13
  text: 13 (auto)
  visibility: auto
- 
  sort: 12
  text: 12 (auto)
  visibility: auto
- 
  sort: 11
  text: 11 (auto)
  visibility: auto
- 
  sort: 10
  text: 10 (spoiler)
  visibility: spoiler
- 
  sort: 9
  text: 9 (spoiler)
  visibility: spoiler
- 
  sort: 8
  text: 8 (auto)
  visibility: auto
- 
  sort: 7
  text: 7 (auto)
  visibility: auto
- 
  sort: 6
  text: 6 (auto)
  visibility: auto
- 
  sort: 5
  text: 5 (auto)
  visibility: auto
- 
  sort: 4
  text: 4 (auto)
  visibility: auto
- 
  sort: 3
  text: 3 (auto)
  visibility: auto
- 
  sort: 2
  text: 2 (auto)
  visibility: auto
- 
  sort: 1
  text: 1 (auto)
  visibility: auto

and then open the preview of test. As you will see the first list contains $n element, while it should contain $n plus # of visible = 7 entries. What am I doing wrong?

Thanks in advance :slight_smile:
Tobi

This seems weird and I have no idea if this is expected behavior but certainly not what I would expect. Seems as if the number is limited to the max number of elements of either one of the collections, no matter if you do it the way above or merging two separate collections.

Even without the limit, you never get more results then 11, i.e. the max no of auto entries. Using append() is even more weird, then the max number of shown entries is limited to the number of entries of the first collection.

I’d regard this a bug and created an issue on GitHub

1 Like

I believe the issue is that structure items don’t have unique IDs, so they will get overwritten when merging with another collection.

That may be, I think at some point in time, they had unique IDs, but then they were dropped again for some reason, which I don’t remember anymore.

@texnixe: Thanks for creating the issue and proving that I didn’t mad a stupid mistake :slight_smile:

An now the big question: Is there a way to find a workaround? I tried to play with toArray but had no success. Here’s my test project (ZIP) containing the above version with toStructure and a non-working new version with toArray:

In the latter version the a::show in

$entries = $page->entries()->toStructure()->toArray();
a::show($entries);

seems to cause an infinit loop.

How about using the yaml() method instead and then filter the array with array_filter()?

$n = 5;
$entries = $page->entries()->yaml();

$visible = array_filter($entries, function($e) { return $e['visibility'] == 'visible'; });
$auto = array_filter($entries, function($e) { return $e['visibility'] == 'auto'; });
$merge = array_merge($visible, array_slice($auto, 0, $n));
1 Like

Great idea :slight_smile: that works fine.

<?php snippet('header') ?>

  <main class="main" role="main">

    <div class="text">
      <h1><?php echo $page->title()->html() ?></h1>
      
    <?php
        $n = 5;
        $entries = $page->entries()->yaml();
        //a::show($entries);
        $visibleEntries = array_filter($entries, function($e) { return $e['visibility'] == 'visible'; });
        $spoilerEntries = array_filter($entries, function($e) { return $e['visibility'] == 'spoiler'; });
        $autoEntries = array_filter($entries, function($e) { return $e['visibility'] == 'auto'; });
        $visibleEntries = array_merge($visibleEntries, array_slice($autoEntries, 0, $n));
        $spoilerEntries = array_merge($spoilerEntries, array_slice($autoEntries, $n));
        usort($visibleEntries, function($a, $b) {
            return -strcmp($a['sort'], $b['sort']);
        });
        usort($spoilerEntries, function($a, $b) {
            return -strcmp($a['sort'], $b['sort']);
        });
    ?>
    
    <h2>Visible Entries</h2>
    <p>
        This list should contain <emph>all</emph> entries with
        <code>visibility: visible</code> and the first <code>$n</code>
        elements with <code>visibility: auto</code>.
    </p>
    <ul>
        <?php foreach ($visibleEntries as $e) : ?>
        <li><?php echo $e['text'] ?></li>
        <?php endforeach ?>
    </ul>
    
    <h2>Spoiler Entries</h2>
    <p>
        This list should contain <emph>all</emph> entries with
        <code>visibility: spoiler</code> and the elements with
        <code>visibility: auto</code> exept the first <code>$n</code>.
    </p>
    <ul>
        <?php foreach ($spoilerEntries as $e) : ?>
        <li><?php echo $e['text'] ?></li>
        <?php endforeach ?>
    </ul> 
       
    <h2>All Entries</h2>
    <p>
        This list should contain <emph>all</emph> entries.
    </p>
    <ul>
        <?php foreach ($entries as $e) : ?>
        <li><?php echo $e['text'] ?></li>
        <?php endforeach ?>
    </ul>

    </div>
  </main>

<?php snippet('footer') ?>

To see it in action visit http://rudolphlab.com/#publications.