Foreach Page but only if linked to another page?

Hello,
I’m using Categories as Pages. I’m creating the individual Category pages and listing all of the Notes attached to it.
<?php foreach (page('notes')->children()->listed()->sortBy('date', 'desc')->limit(10) as $note): ?>
How would adding the filterBy work here?
Essentially, showing only Notes that are linked to a certain Page, I think.

How are they linked, via a pages field where you select one of the category pages?

Exactly. I’ve been using two methods to compare their utility. Either with a

fields:
  category:
    type: pages
    options: query
    query: site.find("notes").children

Or with

fields:
  category:
    type: select
    options: query
    query: site.find("notes").children
<?php 
$categoryNotes = page('notes')->children()->listed()->filterBy('category', $page->id())->sortBy('date', 'desc')->limit(10);
foreach ($categoryNotes as $note) {
  // code here
}

Note that $page->id() in the above snippet has to be replaced with what is actually stored in the file, I think it’s the ID by default but please double check.

1 Like

This is working very well. Thank you! I left the $page->id() alone and it worked.
Of note, this method works if you’re defining a Field using the select type in your blueprint, but using it as a pages Field type, it does not.
Presumably since you can select only one using select, but multiple with pages?

No, because the pages field stores its stuff in yaml format, to cater for that, you have to use the filter($callback) method (still assuming only one entry):

$categoryNotes = page('notes')->children()->listed()->filter(function($child) use($page) {
  if (($category = $child->category()->toPage()) && $category === $page) {
    return $child;
  }
})->sortBy('date', 'desc')->limit(10);

foreach ($categoryNotes as $note) {
  // code here
}

If you link to multiple categories in your pages field, you would have to adapt again.

1 Like

Oh, my. Thank you.
So, when multiples are used, you’d have to explode or pluck it from an array? I’m only guessing, I’m a noob.
Update: I’m not sure the code is correct. There seems to be an extra ), or something above.
Update2: I am also exploring the multiselect method instead of select Field.

Multiselect:

$categoryNotes = page('notes')->children()->listed()->filter(function($child) use($page) {
  $categories = $child->category()->toPages(','); // get a collection of pages from a comma separated list of page IDs
  return $categories->has($page->id());
})->sortBy('date', 'desc')->limit(10);

Pages field with multiple pages

$categoryNotes = page('notes')->children()->listed()->filter(function($child) use($page) {
  $categories = $child->category()->toPages();
  return $categories->has($page->id());
})->sortBy('date', 'desc')->limit(10);

There’s only a small difference between these to in how it converts the category field content to pages.

In the example above, a closing curly brace was missing, I’ve corrected it.

1 Like

This is working so well. I finally see the logic of the code, too. Thanks so much for your help, Sonja. Thanks for working on this on the weekend. Cheers! :wink:

Okay, so now I’m checking to see if any Notes exist with this Category, but my logic is flawed. I know this is the better way, checking to see if any exist, right?

So, if no Notes exist, how would I write that?

<?php if ($categoryNotes = … ?>
  <?php /* foreach loop here */ ?>
<?php else ?>
 <p>No Notes exist for this category</p>
<?php endif ?>

Hmmm.

<?php if ($categoryNotes->count()): ?>
  <?php /* foreach loop here */ ?>
<?php else ?>
 <p>No Notes exist for this category</p>
<?php endif ?>

If you want to be more specific:

<?php if ($categoryNotes->count() > 0): ?>
1 Like

Here’s another similar question for an entirely different site/setup.

Desired function: Return all Session subpages that use the talk.txt template that list the current Speaker so I can list Talks that are attached to this Speaker on their page (and sometimes there are multiple speakers).

Speaker page:
www.com/speakers/person-1

Talk page:
www.com/conferences/1999/sessions/friday/talk-one
www.com/conferences/1999/sessions/friday/talk-two
www.com/conferences/1999/sessions/friday/talk-three

A Session’s Talk page can have multiple speakers using a multiselect field.

Here is the blueprint for talk.yml which allows you to attach existing Speakers:

speakers:
    type: multiselect
    options: query
    query: site.find("speakers").children

On the Speaker.php page template, I’d like to list all of the Talks that are subpages to the /conferences/1999/sessions/friday/ page.

Guess:

So I’m guessing I need to setup this in the Speaker template:

$conferenceTalks = page('conferences')->children()->listed()->filter(function($child) use ($page) {
   $talks = $child->talk()->toPages();
   return $talks->has($page->id());
})->sortBy('date', 'desc')->limit(20);

and then inside a count() > 0 check, write a foreach loop:

<?php foreach ($conferenceTalks as $talk): ?>
   <li><a href="<?= $talk->url() ?>"><?= $talk->title() ?></a></li>
<?php endforeach ?>

…but I think I’m missing something.

Is there a better way to be doing this setup?

The principle outline is ok, but if your structure is as outlined above, then the children of the conferences page are not the ones you are looking for, because if has years and sessions and days…?

1 Like

Correct, I need any Talk inside a Session page which is a subpage of a conference.
Would I use a site index with a filter instead of this?
Because some speakers can speak at a conference every year.

Then it should probably be:

$conferenceTalks = page('conferences')->index()->listed()->template('talk')->filter(function($child) use ($page) {
   $talks = $child->talk()->toPages();
   return $talks->has($page->id());
})->sortBy('date', 'desc')->limit(20);

And $talks = $child->talk()->toPages(); should be $talks = $child->speakers()->toPages();

And ideally name the variable for what they are, not talks but speakers.

1 Like

Oh my goodness. That makes a ton of sense. Thank you!

This works most excellently! Thank you again, Sonja.

1 Like