3

I'm trying to sort the output of a repeater field from the WordPress plugin Advanced Custom Fields (ACF) using the WordPress Timber plugin implementation of Twig 1.34. The basic PHP example to sort from ACF below is from https://www.advancedcustomfields.com/resources/how-to-sorting-a-repeater-field/ and there are no usable answers for my question in the ACF forums.

So I'm trying to convert this function from the example to Timber/Twig:

// get repeater field data
$repeater = get_field('repeater');

// vars
$order = array();

// populate order
foreach( $repeater as $i => $row ) {
    $order[ $i ] = $row['id'];
}

// multisort
array_multisort( $order, SORT_DESC, $repeater );

// loop through repeater
if( $repeater ): ?>

    <ul>

    <?php foreach( $repeater as $i => $row ): ?>

        <li><?php echo $row['id']; ?>. <?php echo $row['name']; ?></li>

    <?php endforeach; ?>

    </ul>

<?php endif; ?>

What I have implemented in a view.twig file that works is below. My ACF repeater field is called timeline, and has two subfields, date and description.

{% set repeater = timeline %}

   {% for i, row in repeater %}

      {{ row.date}}

      {{ row.description}}

  {% endfor %}

That outputs the existing fields (an array) of the ACF repeater timeline - the two fields date and description - in the date order of oldest first, which is the default, like this:

Jan 2010

Lorum Ipsum blah blah blah

Jul 2011

Lorum Ipsum blah blah blah Lorum Ipsum

But I want to sort by newest date first.

According to this answer Twig_Error_Syntax for "Unknown filter" with a Twig filter in Timber I need to create a filter function that uses Twig_SimpleFilter and does that sorting on the repeater field array.

I've tested that, and the sample rot13 filter from that answer works on {{ 'test text'|rot13 }}.

But when trying to use the same Twig_SimpleFilter structure with the code to sort the array by date, i.e. using this in my theme's functions.php file:

add_filter('timber/twig', function($twig) {
   $twig->addExtension(new Twig_Extension_StringLoader());

   $twig->addFilter(
     new Twig_SimpleFilter(
       'sort_timeline', 
       function($string) {

$repeater = get_field('timeline');

$order = array();

foreach( $repeater as $i => $row ) {
    $order[ $i ] = $row['date'];
}

array_multisort( $order, SORT_DESC, $repeater );

       }
     )
   );

   return $twig;
});

and calling the filter like this {% for i, row in repeater|sort_timeline %} on the for loop in view.twig

{% set repeater = timeline %}

       {% for i, row in repeater|sort_timeline %}

          {{ row.date}}

          {{ row.description}}

      {% endfor %}

all I get is a whitescreen and no errors in the php log.

FWIW, using the rot13 filter like {% for i, row in repeater|rot13 %} also shows a whitescreen, so something is wrong with the overall Twig_SimpleFilter.

(As an aside, I also realize that I may need to convert the php date format in $row['date'] to correctly sort, maybe using strtotime, since it is now simply month and year.)

Is using a filter in Twig the right way to try this? Or is it possible to adapt and use the array_multisort( $order, SORT_DESC, $repeater ); function directly in the .twig template?


Edit 4/17/18

@num8er's code works for php5+. The php7 operator seems to be an issue.

And, this other function below will sort the backend rows of the repeater; change values and add to the theme's functions.php file. See https://support.advancedcustomfields.com/forums/topic/sort-repeater-in-back-end-where-data-is-entered/

To get the MySQL field_123456789 name of the row you want to sort on, go to the ACF export page and export the field group. https://support.advancedcustomfields.com/forums/topic/how-to-retrieve-a-group-field-key/

add_filter('acf/load_value/name=timeline', 'my_acf_load_value', 10, 3); //repeater name is timeline
function my_acf_load_value( $rows)
{
 foreach( $rows as $key => $row ) {
  $column_id[ $key ] = $row['field_5967e7639a09b'];

 }

 array_multisort( $column_id, SORT_DESC, $rows );
 return $rows;
}
BlueDogRanch
  • 721
  • 1
  • 16
  • 43

2 Answers2

2

Direct solution to Your question.

Try this code:

add_filter('timber/twig', function($twig) {
   $twig->addExtension(new Twig_Extension_StringLoader());

   $twig->addFilter(
     new Twig_SimpleFilter(
       'timeline_latest_first', 
       function($timeline) {

         usort($timeline, function($a, $b) {
           if(strtotime($a['date']) === strtotime($b['date'])) return 0;
           return strtotime($a['date']) > strtotime($b['date']) ? -1 : 1; 
           // or simply (if php 7.x):
           // return -1*(strtotime($a['date']) <=> strtotime($b['date']));
         });

         return $timeline;
       }
     )
   );

   return $twig;
});


{% set timeline_sorted = timeline|timeline_latest_first %}

{% for i, row in timeline_sorted %}

  {{ row.date}}

  {{ row.description}}

{% endfor %}
num8er
  • 18,604
  • 3
  • 43
  • 57
  • Works! I had to change the `>` to `<` to sort newest first for php5+. And for php7, as far as I can tell, usort works differently and I can't see why the php7 code doesn't work. https://stackoverflow.com/questions/44542089/usort-difference-php7-1-vs-php5-6 – BlueDogRanch Apr 16 '18 at 17:33
  • @BlueDogRanch as I know spaceship operator returns -1;0;1 and usort requires second argument to be function or closure that returns that -1;0;1. Seems like in that link that You put in comment user only returns -1 everytime - it's not correct. Think about stack of cards that You just take per card and put to down. At last You'll get to result where the first card You take will be on top of stack again) – num8er Apr 16 '18 at 23:19
  • 1
    Don't know about php7; I'll look at it again. but it works fine for php5+. Thanks! – BlueDogRanch Apr 17 '18 at 16:53
0

In Twig, you can create your custom filter and add it to the running Twig environment. This way, you can create your own multisort filter:

$multisort_filter = new Twig_Filter('multisort', function ($repeaters) {
    $order = array();


    foreach( $repeaters as $i => $row ) {
       $order[ $i ] = $row['id'];
    }

    // multisort
    array_multisort( $order, SORT_DESC, $repeaters );

    return $repeaters;
});

Then add it to twig:

$twig = new Twig_Environment($loader);
$twig->addFilter($filter);

Now you can call multisort filter from your template.

{% for i, row in repeater|multisort %}

      {{ row.date}}

      {{ row.description}}

{% endfor %}

More about extending Twig can be find in their document: https://twig.symfony.com/doc/2.x/advanced.html

Thai Duong Tran
  • 2,453
  • 12
  • 15
  • Thanks, that's interesting; I had seen examples of custom filters but hadn't been able to get one to work. With your filter, I get a "Cannot instantiate abstract class Twig_Filter" error. It seems to be related to the newer way of add a filters using Twig_SimpleFunction, but I don't know. – BlueDogRanch Apr 13 '18 at 01:28
  • Check your Twig version, if it's version 1.x you might need to try `Twig_SimpleFilter` – Thai Duong Tran Apr 13 '18 at 03:17
  • I am using 1.34; how would I use Twig_SimpleFilter? Using the simplest rot13 filter example on https://twig.symfony.com/doc/1.x/advanced.html gives me a Fatal error: Uncaught exception 'Twig_Error_Syntax' with message 'Unknown "rot13" filter – BlueDogRanch Apr 13 '18 at 16:09
  • Sorry I think I made a mistake there about `Twig_SimpleFilter`, haven't touched the 1.x version for a long time. The document that I posted is for version 2.x, you can check the 1.x version here: https://twig.symfony.com/doc/1.x/advanced_legacy.html. – Thai Duong Tran Apr 14 '18 at 11:31