Custom Grouping Order

I am creating some custom grouping using a date field and UPCOMING, TODAY, CURRENT groups. Is there any way I could created custom sorting on these groups to display them in a TODAY, CURRENT, UPCOMING order?

		$callback = function($p) {
		  
		  
		  $close_date = date("Y-m-d",strtotime($p->close_date())); 

		  date_default_timezone_set('America/Los_Angeles');
		  $today = date("Y-m-d");
	
		  $openning_date = date("Y-m-d",strtotime($p->opening()));
		  
		  //if start_date is null, then it's an event
		  if ($p->start_date()->isNotEmpty()) {
				  
			$start_date = date("Y-m-d",strtotime($p->start_date())); 
			
		  }	else {
		    
			$start_date = null;   
			
			  
		  }
		  
		  //check to see if "Current show"
		  
		if ($start_date == null) {
	 
			 //this an event
			 if ($close_date == $today) return "Today";
			 return "Upcoming"; 
			 
			 }
	
		else {
			//these are exhibitions
			
			if ($openning_date == $today) return "Today";
			if ($close_date > $today && $start_date < $today) return "Currently";
			return "Upcoming";
		
		}		 
			 
			
		};

		
		//evaluate only the date		
		$groupedItems = $site->find('events', 'exhibitions')->
						children()->visible()->
						filter(function($child) {
						return $child->date("Y-m-d", 'close_date') >= date("Y-m-d");
        })
        ->group($callback);
	
		//dump($groupedItems);

I think you have gone a little overboard there. In the events pages i’ve done, i filtered by comparing the article to today and only used it if its greater:

$currentdatetime = date('Y-m-d');

  if ($openning_date >= $currentdatetime)
// up coming events
  }

 if ($openning_date == $currentdatetime)
// todays event
  }

Im not sure how to do greater then today but not equal to today in one statement.

hmm,

I’m trying to display a list of exhibitions (open & close dates) and events (only open date) like so:

Today


Currently


Upcoming


Need to use a callback function for the grouping to deal with the difference in dates. Trying to figure out a way to display the groupings in the order above and not the default ASC or DESC.

I see. Mine was slightly different, becuase i grouped by month headers, but i can tell you roughly how i did it.

I filled up an arrary with the important bits of the date data (in a controller):

$kirbyevents = $site->find("events")->children()->visible();

$kcaleventlist = array ();

  foreach ($kirbyevents as $kcalendarData) {

  $kstartdate = $kcalendarData->startdate();
  $kstarttime = $kcalendarData->starttime();
  $kenddate = $kcalendarData->enddate();
  $kendtime = $kcalendarData->endtime();

  $kstartisoDate = date('Y-m-d\TH:i:s', strtotime("$kstartdate $kstarttime"));
  $kendisoDate = date('Y-m-d\TH:i:s', strtotime("$kenddate $kendtime"));

  $kstartHumanMonth = date("F",strtotime($kstartisoDate));
  $kstartHumanYear = date("Y",strtotime($kstartisoDate));

  $ktitle = $kcalendarData->title()->value();

  $keventdatetime = date('Y-m-d', strtotime($kstartdate));

  if ($keventdatetime >= $currentdatetime)
  {
    $kcaleventlist[] = array(
      'ETitle' => $ktitle,
      'EStart' => $kstartisoDate,
      'EEnd' => $kendisoDate,
      'ELink' => $kcalendarData->url(),
      'EStartMonth' => $kstartHumanMonth,
      'EStartYear' => $kstartHumanYear,
      'EID' => $kcalendarData->uid(),
    );
  }
}

// Output the result...
$allevents = $kcaleventlist;

Then in a side bar snippet i was able to group by months:

<?php

// Rearrange the array by months
$fullmonty = array();

// get all the month keys
foreach($eventroot as $key => $item)
{
  $fullmonty[$item['EStartMonth']][$key] = $item;
}

// sort them:
ksort($fullmonty, SORT_NUMERIC);

// Spit the list out
foreach ($fullmonty as $monthName => $month) {

  echo '<h3>' . $monthName . '</h3>';

  echo '<ul class="article-subnav">';
  foreach ($month as $event) {
    echo '<li><a href="' . $event['ELink'] . '">' . $event['ETitle'] . '</a></li>';
  }
  echo '</ul>';

}

?>

then call it with:

<?php snippet('snippetname', ['eventroot' => $allevents]) ?>

Obviously your filter out put will be a little different in the snippet.

I know that doesn’t quite answer question, but it should get you most of the way.

One way to get the desired order into your groups would be to return a number instead of upcoming etc. Then you could create a value map and get the value for the number from that mapping array.

For example:

//...
if ($start_date == null) {
	 
			 //this an event
			 if ($close_date == $today) return "Today";
			 return 2;  // instead of upcoming
			 
			 }
//...
$array = [0 => 'Today', 1 => 'Current', 2 => 'Upcoming'];
foreach($groups as $key => $value) {
echo $array[$key];
}

Thanks so much for both of your responses.

texnixe - It seems as though grouping cannot accept an integer or even a string number?

I’m getting this error:
Invalid grouping value for key: events/fake-event-in-the-future-ii-also-member-event-and-emergency-event

Figured it out – return doesn’t like anything resembling a 0

changed the return values to:
1 => 'Today', 2 => 'Current', 3 => 'Upcoming'

Works like a charm!

@jimbobrjames will revisit your suggestions if ever get around to getting a better understanding of controllers.

thanks to you both!

@o-o-o-o-o Keep in mind that each item you want to group must have the field you want to group by, if the field is missing, you will get an error message.

right, thanks.

@texnixe actually – how do I sort the group? Still getting a similar behaviour with the grouping order.

The groups should be sorted automatically by their number. What do you get if you dump($groups)?

You should see a collection like this

Collection Object
(
    [1] => Pages Object
        (
            [0] => article-5
        )

    [2] => Pages Object
        (
            [0] =>article-3
        )

    [3] => Pages Object
        (
            [0] => article-1
        )

)

So the groups are sorted by key.

I’m seeing this:

Collection Object
(
    [3] => Pages Object
        (
            [0] => events/fake-upcoming-event-for-testing-purposes-1
        )

    [2] => Pages Object
        (
            [0] => exhibitions/every-building-in-baghdad
        )

)

Ok, I see, seems to depend on the order. Other idea (condition1etc. is just a placeholder for your conditions:

We map an order number to each item depending on condition and then sort by this order number.

$groups = $site->find('events','exhibitions')->children()->visible()->map(function($article) {
if(condition1) $article->order = 1;
else if(condition2))   $article->order = 2;
else if(condition3)) $article->order = 3;
else $article->order = 4;
return $article;
})->sortBy('order');

$groups = $groups->group(function($item) {
  return $item->order();
});
dump($groups);
// then as before using the mapping array

The resulting groups should now come out in the desired order provided that you apply your order numbers according to your sorting scheme.

Another option is to take your wrongly sorted groups from above, convert to an array, sort by key and convert back to collection:

$groups = $groups->toArray();
ksort($groups);
$groups = new Collection($groups);
dump($groups);

Or, the whole thing as a pages method (using some other conditions, but can be adapted as needed):

pages::$methods['groupByDatePeriod'] = function($pages) {
  return $pages
    ->map(function($p) {
        if($p->date() > time()) { $p->order = 1; $p->group = 'future';}
        else if($p->date('Y-m-d') == date('Y-m-d')) {$p->order = 2; $p->group = 'today';}
        else if($p->date() >= strtotime('-7 day'))   {$p->order = 3; $p->group = 'this week';}
        else if($p->date() >= strtotime('-1 month')) {$p->order = 4; $p->group = 'this month';}
        else {$p->order = 5; $p->group = 'older';}
        return $p;
      })
    ->sortBy('order')
    ->group(function($p){
       return $p->group();
	  });
	};
$groups = $site->find('projects','blog')->children()->visible()->groupByDatePeriod();

foreach($groups as $key => $items):?>
  <h2><?=  $key ?></h2>
  <ul>
    <?php foreach($items as $item): ?>
      <li><?= $item->title() . '--' . $item->date('Y-m-d') ?></li>
    <?php endforeach ?>
  </ul>
<?php endforeach ?>
2 Likes

Thanks for this @texnixe! Posting my implementation below (not the most elegant, but it works!).

I created a file called methods.php in the site plugins folder.

pages::$methods['groupByDatePeriod'] = function($pages) {
  
  return $pages
    ->map(function($p) {
	    
	      #date checking (exhibtions have an opening date, start and close date, events only have close dates)
	      
	      $today = date("Y-m-d");
		 
		  $opening_date = $p->opening_date()->toDate();
		  $close_date = $p->close_date()->toDate(); 	
		 	 
		  # if start_date is null, then it's an event
		  if ($p->start_date()->isNotEmpty()) {
				  
			$start_date = $p->start_date()->toDate(); 
			
		  }	else {
		    
			$start_date = null;   
			
			  
		  }
	    
	      if ($start_date == null) {
			 #this an event
			 if ($p->push_to_home()=='1') {$p->order = 1; $p->group = 'push';}
			 else if ($close_date == $today) {$p->order = 2; $p->group = 'Today';}
			 else if ($close_date > $today) {$p->order = 4; $p->group = 'Upcoming';}
			 else {$p->order = date('Y', strtotime($close_date)); $p->group = date('Y', strtotime($close_date));}

			 }
	
		  else {
			  
			#these are exhibitions
			if ($opening_date == $today) {$p->order = 2; $p->group = 'Today';}
			else if ($close_date > $today && $start_date < $today) {$p->order = 3; $p->group = 'Currently';}
			else if ($start_date > $today ){$p->order = 4; $p->group = 'Upcoming'; echo "Upcoming";}
			else {$p->order = date('Y', strtotime($close_date)); $p->group = date('Y', strtotime($close_date));}
		
			}		
			 
        return $p;
      })
    ->sortBy('order')
    ->group(function($p){
       return $p->group();
	  });
	};

Then called in my template using:
$groups = $site->find('projects','blog')->children()->visible()->groupByDatePeriod();