3
votes

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;
}
2

2 Answers

2
votes

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 %}
0
votes

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