Page should only load sections with subpages that have children with a date field >= today

My calendar page loads subpages (sections) which are children of the page:

 <?php foreach(page('kalender')->children()->visible() as $section): ?>
 <?php snippet($section->uid(), array('data' => $section)) ?>
 <?php endforeach ?>

Lets say one of the subpages has the name “Concerts” and its children are events that have a “date” field .

Now i’d like to only show (or load) the subpages (sections) that have events in the future. How would i solve this?

I’m not quite sure what you want to filter here, the sections or subpages of the sections, but the Filter compendium has all the answers:

https://getkirby.com/docs/cookbook/filtering#fun-with-filtering-by-date

Sorry for the confusion. I would like to only show the sections that have subpages (events) with a date field >= today.

I have already tried to solve it with the filter compendium, but with no success. I will try again. Thanks!

Well, first of all, you could check if the section has any children with a date in the future:

<?php foreach(page('kalender')->children()->visible() as $section): ?>
 <?php if($section->children()->filter(function($p)  {
  return $p->date() >= time();
})->count(): ?>
 <?php snippet($section->uid(), array('data' => $section)) ?>
  <?php endif ?>
 <?php endforeach ?>

But for the rest, I would need the code in the snippet, because inside, you would have to apply the same filter to actually only get the future children.

There seems to be a syntax error somewhere in your code, not sure why.

Anyway, here is the stripped out code of the section:

<?php foreach($data->parent()->children()->children()->visible()->filterBy('date', '>=' , strtotime('today'))->sortBy('date', 'asc', 'start_time', 'asc') as $event): ?>
<?php echo $event->date('%a,'); ?>
<a href="<?= $event->url() ?>"><?= $event->title()->html() ?>
<?= $event->start_time() -> kb() ?>
<?php endforeach ?>

I have to take care of the kid now and will get back to the computer in the evening. Thanks! You should run a Kirby consultancy.

There’s a closing parenthesis missing after count():

<?php foreach(page('kalender')->children()->visible() as $section): ?>
 <?php if($section->children()->filter(function($p)  {
  return $p->date() >= time();
})->count()): ?>
 <?php snippet($section->uid(), array('data' => $section)) ?>
  <?php endif ?>
 <?php endforeach ?>

You should be able to find such errors if you enable debugging in your config.php

c::set('debug', true);

Why are you trying to get the parent and then two times the children instead of calling the children directly in that snippet?

<?php foreach($data->children()->visible()->filterBy('date', '>=' , time())->sortBy('date', 'asc', 'start_time', 'asc') as $event): ?>
<?php echo $event->date('%a,'); ?>
<a href="<?= $event->url() ?>"><?= $event->title()->html() ?>
<?= $event->start_time() -> kb() ?>
<?php endforeach ?>

What is kb() in the above? A custom method?

After fixing the error, the code should work now as expected.

Good luck with seeing the little one to bed :wink:

It works as expected now, thanks!

Why are you trying to get the parent and then two times the children instead of calling the children directly in that snippet?

That’s the reason for my next question (sorry!):

For the sections ‘concerts’ or ‘seminars’ i just use $data->children()->visible()…

But in the first and upper ‘all-events’ section there is an overview of all different kinds of events. Thats why i grab all the grandchildren of the parent ‘kalender’.

Now, that we filter out all sections that don’t have subpages, my overview section is fitered out as well.

If you don’t mind i’d be thankful for a hint how to keep my ‘all-events’ section (that has no subpages) on top of the filtered sections.

What is kb() in the above? A custom method?

Good question. I thought it was a regular Kirby method. I probably just copied it from somewhere else. I changed it to html() now.

She’s asleep :slight_smile:

Could you give me an outline of your structure within the calendar page, please.

Not sure what exactly you need.

kalender.php template:

<?php
snippet('header');
snippet('body');
snippet('docs-navbar');
snippet('sidenav');
?>
<main id="content" class="docs-content off-canvas-content" role="main">
  <div class="inner-content">
  <?php snippet('languages'); ?>
	<?php foreach(page('kalender')->children()->visible() as $section): ?>
	 <?php if($section->children()->filter(function($p)  {
	  		return $p->date() >= time();
			})->count()): ?>
		 <?php snippet($section->uid(), array('data' => $section)) ?>
	 <?php endif ?>
	<?php endforeach ?>
  <?php snippet('footer');?>
</div>

content folder:
36

blueprint for kalender.php

title:
  de: Kalender-Hauptpunkt
  en: Calendar-Main

files: false

options:
  preview: false
  status: false
  url: false
  delete: false
  update: true

fields:
  title:
    label:
      de: Titel
      en: Title
    type:  text

Do you need anything else, like snippets and blueprints for the subpages ? I wanted to ask first before i upload too much…

Ok, then I think it is best to remove the first if statement again:

  <div class="inner-content">
  <?php snippet('languages'); ?>
	<?php foreach(page('kalender')->children()->visible() as $section): ?>
	
		 <?php snippet($section->uid(), array('data' => $section)) ?>

	<?php endforeach ?>
  <?php snippet('footer');?>
</div>

Now we have the same code that i originally started with. :wink:

I changed the title to a more accurate description.

Why is it the same if you filter by date in your snippet? After all, each section has its own snippet, doesn’t it?

After all, you wrote above that you wanted to load sections that don’t have subpages as well, don’t you? That’s why the if statement didn’t work for you.

Of course, you could put the if statement back into the relevant snippets, if you prevent the snippet to output anything that is useless if there are no relevant children.

Snippet code:

<?php if($data->children()->filter(function($p)  {
  return $p->date() >= time();
 })->count()): ?>
     <?php foreach($data->children()->visible()->filterBy('date', '>=' , time())->sortBy('date', 'asc', 'start_time', 'asc') as $event): ?>
        <?php echo $event->date('%a,'); ?>
        <a href="<?= $event->url() ?>"><?= $event->title()->html() ?>
        <?= $event->start_time() -> kb() ?>
    <?php endforeach ?>
<?php endif ?>

But if there is no more code in your snippet than the above, the if statement doesn’t make any difference.

Now i got it! In the snippet i had to put the rest of my HTML markup inside the if statement as well. Before i only had the foreach loop inside, so the headline still showed up, obviously. Thanks again!

<?php if($data->children()->filter(function($p)  {
return $p->date() <= time();
})->count()): ?>

<!-- stuff that should be filtered -->

<?php endif ?>

How can one donate for your help?
(I found your PayPal link)

Oh my, thanks a lot for your donation :heart: :star_struck::tada:

I have a follow-on question. I’d like to switch the operator depending on the state of a radio button.

If button 1 is selected the operator should be “<”:
return $p->date() < time();

If button 2 is selected the operator should be “>=”:
return $p->date() >= time();

When i try to use the variable $termin_operator inside the function like this:
return $p->date() $termin_operator time();
i get:
syntax error, unexpected '$termin_operator' (T_VARIABLE), expecting ';'

I read about global vs local variables in functions etc. but i couldn’t get it to work.

My code looks like this for now:

<?php $termin_operator = $_POST['termine'] ?? ''; ?>
<?php if($data->children()->filter(function($p, $termin_operator)  {
return $p->date() $termin_operator time();
 })->count()):
 ?>
 <?php endif ?>

I don’t pretend to be any good at PHP, i’m learning but I’m pretty sure you cant use variables in place of things like operators. I think you need something more along the lines of this:

<?php $termin_operator = $_POST['termine'] ?? ''; ?>

<?php if ($termin_operator->bool()): ?>

  <?php if($data->children()->filter(function($p, $termin_operator)  {
  return $p->date() < time();
   })->count()):
   ?>
   <?php endif ?>

<?php else: ?>

  <?php if($data->children()->filter(function($p, $termin_operator)  {
  return $p->date() >= time();
   })->count()):
   ?>
   <?php endif ?>

<?php endif ?>

I think that can probably be shortened somehow but i don’t know how :slight_smile:

It will probably break because its probably not getting a correct value for $termin_operator, but i think its step in the right direction.

First of all: what sort of value does $_POST['termine']return? True? Do a var_dump($termin_operator) if not sure. Depending on what it does return, we can then act on it.

Secondly, in this part of your code:

<?php if($data->children()->filter(function($p, $termin_operator)  {
return $p->date() $termin_operator time();
 })->count()):
 ?>

you can’t pass the variable to the function like that, you would have to use the use() statement:

<?php if($data->children()->filter(function($p) use($termin_operator)  {
  // code here
 })->count()):
 ?>

so that you can use the variable inside the callable.

Inside the callable, you can then check the value of the variable:

<?php if($data->children()->filter(function($p) use($termin_operator)  {
if($termin_operator == 'whatever') {

  return $p->date() < time();
} else {
  return  $p->date() >= time();
 })->count()):
 ?>

However, first things first.

@jimbobrjames You can’t use the bool() method here, $termin_operator is not a field object.

1 Like

var_dump($termin_operator) is returning string(1) "<" and string(2) ">="

But i can change it to any other value. I will check out the rest of your reply tomorrow. Thanks and good night!

I think we can stick with these values.

However, what is not quite clear to me: What do you want to do if $_POST[‘termine’] is not set? Return all children?

Because with your current definition:

<?php $termin_operator = $_POST['termine'] ?? ''; ?>

the operator contains an empty string if it is not set. So even if using a variable as a comparison operator was possible (which it is not), then using an empty string would definitely return an error.

Good night to you, too.

Edit: Assuming that you don’t want to filter if $termin_operatoris an empty string and leaving the rest as is, we would end up with this:

<?php 
$termin_operator = $_POST['termine'] ?? ''; 
if($data->children()->filter(function($p) use($termin_operator)  {
  if($termin_operator === '<') {
    return $p->date() < time();
  } elseif($termin_operator === '>=') {
    return  $p->date() >= time();
  } else {
    return $p;
  }
 })->count()):
// do stuff
endif;
?>

Edit 2: Assuming that you want to do something with your filtered children, I’d change the code a bit so you don’t have to repeat yourself again:

$posts = $data->children()->filter(function($p) use($termin_operator)  {
  if($termin_operator === '>') {
    return  $p->date() < time();
  } elseif($termin_operator === '>=') {
    return  $p->date() >= time();
  } else {
    return $p;
  }
});
if($posts->count()):
  foreach($posts as $post):
    // do something with the post
  endforeach;
endif;