3

I need to sort the entries for an Element API endpoint by a value calculated in a custom transformer. Is this possible?

The use case is this: We're dealing with Events/Courses that have multiple instances (sessions) that each may contain multiple dates. I have a Neo field called sessions with session blocks that contain date sub-blocks. The session block contains registrationLink and location, and each date block contains the fields date, startTime, and endTime.

However, these need to be sorted by nearest session date (the user could input the sessions/dates in any order), so I need to determine the nearest upcoming session by looking through all the sessions and finding the first date for that session. We also don't want to show sessions that have already passed. I wasn't able to figure out how to do this in Twig, so I thought it might be easier in Element API. I've got the transformer working, so I can get output like this:

{
  "entries": [
    {
      "title": "TITLE REMOVED",
      "url": "URL REMOVED",
      "nextSessionStart": "2017-08-09",
      "sessions": [
        {
          "location": "LOCATION REMOVED",
          "registrationLink": "URL REMOVED",
          "dates": [
            {
              "date": "2017-08-09",
              "startTime": "8:30AM",
              "endTime": "4:30PM"
            },
            {
              "date": "2017-08-10",
              "startTime": "8:30AM",
              "endTime": "4:30PM"
            }
          ]
        },
        {
          "location": "LOCATION REMOVED",
          "registrationLink": "URL REMOVED",
          "dates": [
            {
              "date": "2017-11-02",
              "startTime": "8:30AM",
              "endTime": "4:30PM"
            },
            {
              "date": "2017-11-03",
              "startTime": "8:30AM",
              "endTime": "4:30PM"
            }
          ]
        },
        {
          "location": "LOCATION REMOVED",
          "registrationLink": "URL REMOVED",
          "dates": [
            {
              "date": "2018-02-06",
              "startTime": "8:30AM",
              "endTime": "4:30PM"
            },
            {
              "date": "2018-02-07",
              "startTime": "8:30AM",
              "endTime": "4:30PM"
            }
          ]
        }
      ]
    },
    {
      "title": "TITLE REMOVED",
      "url": "URL REMOVED",
      "nextSessionStart": "2018-07-07",
      "sessions": [
        {
          "location": "LOCATION REMOVED",
          "registrationLink": "URL REMOVED",
          "dates": [
            {
              "date": "2018-07-07",
              "startTime": "5:30PM",
              "endTime": "8:30PM"
            },
            {
              "date": "2018-08-07",
              "startTime": "8:30AM",
              "endTime": "4:30PM"
            }
          ]
        }
      ]
    },
    {
      "title": "TITLE REMOVED",
      "url": "URL REMOVED",
      "nextSessionStart": "2017-07-11",
      "sessions": [
        {
          "location": "LOCATION REMOVED",
          "registrationLink": "URL REMOVED",
          "dates": [
            {
              "date": "2017-12-05",
              "startTime": "8:30AM",
              "endTime": "4:30PM"
            }
          ]
        },
        {
          "location": "LOCATION REMOVED",
          "registrationLink": "URL REMOVED",
          "dates": [
            {
              "date": "2017-07-11",
              "startTime": "8:30AM",
              "endTime": "4:30PM"
            }
          ]
        },
        {
          "location": "LOCATION REMOVED",
          "registrationLink": "URL REMOVED",
          "dates": [
            {
              "date": "2017-10-25",
              "startTime": "8:30AM",
              "endTime": "4:30PM"
            }
          ]
        }
      ]
    }
  ],
  "meta": {
    "pagination": {
      "total": 3,
      "count": 3,
      "per_page": 10,
      "current_page": 1,
      "total_pages": 1,
      "links": [

      ]
    }
  }
}

The problem is that I need to sort and paginate the results based on that nextSessionStart key.

Here's the elementapi.php config file:

    return [
        'defaults' => [
            'elementType' => ElementType::Entry,
            'elementsPerPage' => 10,
            'pageParam' => 'pg',
            'resourceKey' => 'entries'
        ],

        'endpoints' => [
            'courses.json' => function() {

                require craft()->path->getConfigPath().'elementapi/CoursesTransformer.php';

                return [
                    'criteria' => [
                        'section' => ['courses'],
                        'order' => 'nextSessionStart asc' //NOTE: this doesn't work and needs to be removed to get the above output
                    ],
                    'transformer' => new CoursesTransformer()
                ];
            }
        ]
    ];

The transformer looks like this:

    class CoursesTransformer extends TransformerAbstract {

        public function transform (EntryModel $entry) {

            $next_session_start = null;
            $sessions = array();
            $now = date('Y-m-d');

            foreach ($entry->sessions as $session) {
                if ($session->level == 1) {
                    $this_session = array(
                        'location' => $session->location,
                        'registrationLink' => $session->registrationLink,
                        'dates' => array()
                    );
                    foreach ($session->children as $date) {
                        $this_date = $date->date;

                        array_push($this_session['dates'], array(
                            'date' => date_format($this_date, 'Y-m-d'),
                            'startTime' => date_format($date->startTime, 'g:iA'),
                            'endTime' => date_format($date->endTime, 'g:iA')
                        ));

                        if ($this_date > $now && (is_null($next_session_start) || $this_date < $next_session_start)) {
                            $next_session_start = $this_date;
                        }
                    }
                    array_push($sessions, $this_session);
                }
            }

            return [
                'title' => $entry->title,
                'url' => $entry->url,
                'nextSessionStart' => date_format(new DateTime($next_session_start), 'Y-m-d'),
                'sessions' => $sessions
            ];

        }

    }

Any information about how I can resort based on the transformer output would be really appreciated. This project is sort of at a standstill until I can figure out how to sort these courses and events based on their next upcoming date.

1
  • Did you ever find a solution? I'm having the same issue. – Tom De Smet Jul 31 '20 at 10:45
1

I don't know if I ever got this working exactly as I initially intended, but this is the final transformer code I ended up going with. Basically the same but with some usort calls on sessions and dates. I then had to manually do the pagination in the Twig template where courses were listed. Hope it helps!

    <?php
namespace Craft;

require craft()->path->getPluginsPath().'elementapi/vendor/autoload.php';

use League\Fractal\TransformerAbstract;

class CoursesTransformer extends TransformerAbstract {

    public function transform (EntryModel $entry) {

        $next_session_start = null;
        $sessions = array();
        $now = date('Y-m-d');

        foreach ($entry->sessions as $session) {
            if ($session->level == 1) {
                $this_session = array(
                    'location' => $session->location,
                    'registrationLink' => $session->registrationLink,
                    'dates' => array()
                );
                foreach ($session->children as $date) {
                    array_push($this_session['dates'], array(
                        'date' => date_format($date->date, 'Y-m-d'),
                        'startTime' => date_format($date->startTime, 'g:iA'),
                        'endTime' => date_format($date->endTime, 'g:iA')
                    ));
                }

                //ensure dates are in correct order
                usort($this_session['dates'], function($a,$b) {
                    return $a['date'] > $b['date'];
                });

                array_push($sessions, $this_session);
            }
        }

        //ensure sessions are in correct order
        usort($sessions, function($a,$b) {
            return $a['dates'][0]['date'] > $b['dates'][0]['date'];
        });

        return [
            'title' => $entry->title,
            'url' => $entry->url,
            'nextSessionStart' => date_format(new DateTime($entry->nextSessionStartDate), 'Y-m-d'),
            'sessions' => $sessions
        ];

    }
}
1

I know this is answered but I just wanted to suggest a potentially better way.

I would suggest trying the Preparse field plugin. This way you can create a new field that automatically generates the data you want to sort/query (in your case, the first session date). This will only happen when you save the entry so it will likely speed up your API considerably.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.