Extending Classes

Using MODX processing with external API data

gourmet fare

Custom List Processor

The MODX core modObjectGetListProcessor expects data from the database, using xPDO to manage the data acquisition. But what if your data is coming from another source, for example a third-part service's API?

Murray asked...

I'm making an MODX extra that draws its data from an external API and displays it in an ExtJS grid panel on a custom manager page. It's not too difficult to extend a basic processor, put the API data in a grid friendly format then return it to the grid. The problem is, I want it to take advantage of the powerful built-in grid functionality such as pagination and sorting. I originally started by extending modObjectGetListProcessor but of course this expects everything to be coming from a database table, and expects the data to be within a specific xPDO object format.

The Solution:

Murray says...

Thanks to @joeke in the MODX Community Slack channel (modx.org) for helping me...

This should work with any API data as long as you format it correctly. Just replace the code inside the getAPIData() function.

<?php
class modAPIGetListProcessor extends modProcessor {
    /** @var string $defaultSortField The default field to sort by */
    public $defaultSortField = 'id';
    /** @var string $defaultSortDirection The default direction to sort */
    public $defaultSortDirection = 'DESC';
 
    public function initialize() {
        $this->setDefaultProperties(array(
            'start' => 0,
            'limit' => 20,
            'sort' => $this->defaultSortField,
            'dir' => $this->defaultSortDirection,
            'combo' => false,
            'query' => '',
        ));
        return parent::initialize();
    }
 
    public function getAPIData() {
        $whmcsUrl = $this->modx->getOption('modwhmcs.whmcs_url');
        $username = $this->modx->getOption('modwhmcs.username');
        $password = $this->modx->getOption('modwhmcs.password');
 
        // Set post values
        $postfields = array(
            'username' => $username,
            'password' => md5($password),
            'action' => 'gettickets',
            'responsetype' => 'json',
        );
 
        // Call the API
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $whmcsUrl . 'includes/api.php');
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postfields));
        $response = curl_exec($ch);
        if (curl_error($ch)) {
            die('Unable to connect: ' . curl_errno($ch) . ' - ' . curl_error($ch));
        }
        curl_close($ch);
        // Attempt to decode response as json
        $jsonData = json_decode($response, true);
        $apiData['total'] = $jsonData['numreturned'];
        $apiData['results'] = $jsonData['tickets']['ticket'];
        return $apiData;
    }
 
    public function process() {
        $data = $this->getData();
        return $this->outputArray($data['results'],$data['total']);
    }
 
    public function getData() {
        $apiData = $this->getAPIData();
        $data = array();
        $data['results'] = array();
        $data['total'] = $apiData['total'];
 
        $limit = intval($this->getProperty('limit'));
        $start = intval($this->getProperty('start'));
 
        $count = 0;
        foreach ($apiData['results'] as $key => $value) {
            if ($key >= $start) {
                if($count < $limit) {
                    array_push($data['results'], $value);
                    $count++;
                }
            }
            if ($key > $limit) break;
        }
 
        //Sort
        if (empty($sortKey = $this->getProperty('sort'))) $sortKey = $this->defaultSortField;
        if (empty($sortDir = $this->getProperty('dir'))) $sortDir = $this->defaultSortDirection;
        if ($sortDir == 'DESC') {
            foreach ($data['results'] as $key => $row) {
                $dates[$key]  = $row[$sortKey];
            }
            array_multisort($dates, SORT_DESC, $data['results']);
        } else {
            foreach ($data['results'] as $key => $row) {
                $dates[$key]  = $row[$sortKey];
            }
            array_multisort($dates, SORT_ASC, $data['results']);
        }
 
        return $data;
    }
}
return 'modAPIGetListProcessor';

What's the Story?

Murray wanted a nice ExtJS grid, paginated and sortable, using MODX to generate and manage the data for it. Extending modObjectGetListProcessor itself wasn't going to do the job, because it expects its data to be an xPDO-brokered data object. So Murry instead extended the modProcessor class, borrowing heavily from the modObjectGetListProcessor except for the data source.

With help from @joeke in the MODX Community Slack channel the above code was developed, so the ExtJs grid could be completed with pagination and sorting.