Modx REST APi questions

I am following Modx rest api documentation and video tutorials in youtube.
And I am stuck in Pagination thing with api. I want to know/learn how to get pagination via calling in json ?
I want to fetch only specific number of resources in json. Link 5 or 10 at once instead of all.

currently we get all resources with this call -

http://localhost/modx-all-test-1/rest/resources/

and only specific resource by putting id -

http://localhost/modx-all-test-1/rest/resources/1

here 1 is the id of resource.

I dont want to fetch all resources in my android app. I want to fetch only first 5 or 10 resources…
In wordpress, we get this by following example call -
/ wp/v2/posts?per_page=5’
I want per page pagination in Rest APi json data.
Also I want to fetch collections Extra’s child resources.
And tags as for my app for which iam using tagger in blogIt extra.

My knowledge is low and I need help to achieve this milestone.

My code is -

class MyControllerResources extends modRestController {

public $classKey = ‘modResource’;
// public $defaultSortField = ‘sortorder’;
public $defaultSortDirection = ‘ASC’;
// [‘tvvalue’] = $object->getTVValue(‘tvname’);
public $defaultSortField = ‘id’;
// public $defaultSortDirection = ‘DESC’;
public function prepareListObject(xPDOObject $object) {

     $data = array();

           $data['pagetitle'] = $object->pagetitle;
           $data['content'] = $object->content;
           $data['introtext'] = $object->introtext;
           $data['longtitle'] = $object->longtitle;
           $data['description'] = $object->description;
           $data['featuredimage'] = $object->getTVValue('myimagetv');
           $data['kitnelog'] = $object->getTVValue('mysimpletexttv');
           
     return $data;
         }

         public function read($id) {
           if (empty($id)) {
               return $this->failure($this->modx->lexicon('rest.err_field_ns',array(
                   'field' => $this->primaryKeyField,
                   )));
           }
        // @var xPDOObject $object 
   
           $c = $this->getPrimaryKeyCriteria($id);
           $this->object = $this->modx->getObject($this->classKey,$c);
           if (empty($this->object)) {
               return $this->failure($this->modx->lexicon('rest.err_obj_nf',array(
                   'class_key' => $this->classKey,
                   )));
           }
           $objectArray = $this->object->toArray();
           $objectArray += array(
               'featuredimage' => $this->object->getTVValue('myimagetv'),
               'kitnelog' => $this->object->getTVValue('mysimpletexttv')
               );
    
           unset(
               $objectArray['alias'],
               $objectArray['alias_visible'],
               $objectArray['parent'],
               $objectArray['type'],
               $objectArray['contentType'],
               $objectArray['link_attributes'],
               $objectArray['published'],
               $objectArray['pub_date'],
               $objectArray['isfolder'],
              // $objectArray['introtext'],
               $objectArray['richtext'],
               $objectArray['template'],
               $objectArray['menuindex'],
               $objectArray['searchable'],
               $objectArray['cacheable'],
               $objectArray['createdby'],
               $objectArray['createdon'],
               $objectArray['editedby'],
               $objectArray['editedon'],
               $objectArray['deleted'],
               $objectArray['deletedon'],
               $objectArray['deletedby'],
               $objectArray['published'],
               $objectArray['unpub_date'],
               $objectArray['publishedon'],
               $objectArray['publishedby'],
               $objectArray['menutitle'],
               $objectArray['donthit'],
               $objectArray['privateweb'],
               $objectArray['privatemgr'],
               $objectArray['content_dispo'],
               $objectArray['hidemenu'],
               $objectArray['class_key'],
               $objectArray['context_key'],
               $objectArray['content_type'],
               $objectArray['uri'],
               $objectArray['uri_override'],
               $objectArray['hide_children_in_tree'],
               $objectArray['show_in_tree'],
               $objectArray['properties']
               );
    
           $afterRead = $this->afterRead($objectArray);
           if ($afterRead !== true && $afterRead !== null) {
               return $this->failure($afterRead === false ? $this->errorMessage : $afterRead);
           }
    
           return $this->success('',$objectArray);
       }
      }
1 Like

There are really two issues here.

  1. How to paginate your results
  2. How to return the paginated results (and buttons) via AJAX.

First one to focus on is pagination.
This can be quite involved if you want a completely custom solution. For example, here’s a function within a class from a project I’m working on at the moment. It generates the pagination buttons/links to later be returned via AJAX.

/**
     * Generates navigation buttons for pagination.
     *
     * @return string
     */
    public function loadPaginationButtons() {
        $output = '';
        $buttons = [];
        $currentPage = 0;
        if($this->totalPages <= 1) return $output;

        // Get numbered pages buttons first by incrementing
        for($i = $this->pageNum; $i <= $this->totalPages; $i++){
            if ($i === $this->pageNum) {
                $buttons[] = "<li><a class=\"active\" href=\"?page={$i}\">{$i}</a></li>";
                $currentPage = $this->pageNum;
            } else {
                $buttons[] = "<li><a href=\"?page={$i}\">{$i}</a></li>";
            }
        }
        // Get previous numbered pages if any and prefix onto array
        if($this->pageNum > 1) {
            for ($i = $this->pageNum -1; $i > 0; $i--) {
                array_unshift($buttons,"<li><a href=\"?page={$i}\">{$i}</a></li>");
            }
        }

        // Shorten page list for large results. Only allow 7 pages shown.
        $buttonCount = count($buttons);
        if($buttonCount > 7) {
            if($currentPage < 4) {
                $first = 0;
                $buttons = array_slice($buttons, $first, 7);
            } else if(($buttonCount - $currentPage) < 4) {
                $first = $buttonCount - 7;
                $buttons = array_slice($buttons, $first, 7);
            } else {
                //$this->modx->log(MODX_LOG_LEVEL_INFO,count($buttons));
                $first = $currentPage - 4;
                $buttons = array_slice($buttons, $first, 7);
            }
        }

        // Get next and previous buttons
        $prevClass = 'class="disabled"';
        $prev = '1';
        if($this->pageNum > 1) {
            $prev = $this->pageNum -1;
            $prevClass = '';
        }
        array_unshift($buttons, "<li class=\"prev direction\"><a {$prevClass} href=\"?page={$prev}\">&lt;</a></li>");

        $nextClass = 'class="disabled"';
        $next = '1';
        if($this->pageNum < $this->totalPages) {
            $next = $this->pageNum +1;
            $nextClass = '';
        }
        $buttons[] = "<li class=\"next direction\"><a {$nextClass} href=\"?page={$next}\">&gt;</a></li>";

        // Get first and last buttons
        $firstClass = 'class="disabled"';
        if($this->pageNum > 1) {
            $firstClass = '';
        }
        array_unshift($buttons, "<li class=\"first direction\"><a {$firstClass} href=\"?page=1\">&lt;&lt;</a></li>");
        $lastClass = 'class="disabled"';
        if($this->pageNum < $this->totalPages) {
            $lastClass = '';
        }
        $buttons[] = "<li class=\"last direction\"><a {$lastClass} href=\"?page={$this->totalPages}\">&gt;&gt;</a></li>";
        $output = '<ul class="pageNav d-flex justify-content-center">'.implode('',$buttons).'</ul>';
        return $output;
    }

Luckily, if you don’t want to delve into that there are ready-to-go snippets such as getPage and pdoPage which should be able to do it for you.
You should be able to write your own snippet to return the results you want. Make sure it’s compatible with getPage https://www.markhamstra.com/xpdo/2011/preparing-custom-snippets-for-getpage/

Then, remove the code you currently have in your read() function and use $output = $this->modx->runSnippet() to call getPage and make sure you use the name of your custom snippet in the &element parameter.
https://docs.modx.org/current/en/extending-modx/modx-class/reference/modx.runsnippet

e.g.

$params = [
    'element' => 'yourCustomSnippetName',
    ... other params here...
];
$output = $this->modx->runSnippet('getPage',$params);

You can always check what output you’re getting with:

$this->modx->log(MODX_LOG_LEVEL_ERROR,'Output is: '.$output);

Hope that’s pointed you in the right direction!

3 Likes

Those things don’t really apply when using the rest classes, @digitalpenguin :wink: The pagination would probably be rendered client-side by whatever is fetching from the REST API, and you don’t need any snippets or getPage with the API either.


From checking the modRestController source, the getList method automatically checks the limit and an offset from the request.

While that isn’t entirely the same as pagination (a request with ?page=3), you can use it the same way (?start=40)

The standard query parameter names in the class are start and limit. You can change those by setting the propertyOffset and propertyLimit options when creating the modRestService instance (in your index file). The default limit is 20.

I really recommend reading the source code for the REST API. The docs are very slim, and the source is really quite straightforward. See modRestController::getList and modRestService::__construct for example.

If you could help out improving the restful api documentation that would also be really much appreciated.

5 Likes

Interesting… I’ll need to have a proper look at modRestController (and see what I can do about improving the docs).

My way does of course does need JavaScript to handle the responses and other functionality to handle the GET params. Although it’s certainly an option to render the results server-side and pass the different sections back within the JSON.
I mean, getPage’s navigation placeholder (page.nav) could just be passed back along with the results and then inserted on the page by the JavaScript that handles the response.

2 Likes

Limit thing was awesome. no need to do anything in code. Just called
for eg.http://localhost/modx-all-test-1/rest/resources?limit=2
and got only 2 resources. This is what I wanted.

But I am not getting with start=
What it is returning is minus of all resources. Like if I call http://localhost/modx-all-test-1/rest/resources?start=2
then it skips one resource.

What is the use of **start= ?????**

Sorry but it is very hard for me to understand this. I am following @markh 's steps.
but thanks for your help. I saved your code for my research. This will help to gain my understanding. :slight_smile:

1 Like

Start is the offset of the results - essentially where you want the results to start from.

For example in your first call you could limit the JSON results to 10 with &limit=10 and then in a second call you could use &start=10 to skip the first 10 results. ie:

Page 1: resources?limit=10
Page 2: resources?limit=10&start=10
Page 3: resources?limit=10&start=20
Page 4: resources?limit=10&start=30

3 Likes

Fantastic!1 now I understand.
I think start should rename to skip by default. Or maybe Im wrong about this suggestion. :smiley:

  • Now my next bridge is - how to get child resources of parent ? Like Collections Extra is parent for blog and there is 20 posts in Blog page. How to approach this ?

  • And then I also want to know how to get tags of each posts in collections ( in blogit extra which uses tagger extra).

After getting this done and learn all these stuffs, I will post a complete tutorial in Community for others to quickly get start with Modx REST api and cross platform app with Angular and Ionic 4.
A simple Blog/News App with Modx back end.
And then with Migx + Ionic 4.

I am also learning Electron cross platform desktop app creator. We can use Modx as back end with Electron for that too to create Invoice billing software, management systems etc. <3

Haha no problem. It seems I completely misunderstood what you were after if start and limit is all you needed! :slight_smile:

1 Like

Still looking for a help on child resources … :slight_smile:

I believe you would need to set up a separate endpoint to list items with a different criteria.

1 Like

Or add a condition in a prepareListQueryBeforeCount method that checks if a parent url parameter was provided ($this->getProperty('parent')) and add a where clause based on that.

    protected function prepareListQueryBeforeCount(xPDOQuery $c) {
        if ($parent = (int)$this->getProperty('parent')) {
            $c->where(['parent' => $parent]);
        }
        return $c;
    }

Then GET /resources?parent=123

2 Likes

AWESOME, Superb, Fantastic sir. got it working as I want… :slight_smile:

Just last one - how to get Tagger Tags in rest api ? :slight_smile:
I want to get tags as blog categories for my news app. And in app, I want to show list of Tagger tags as categories in sidebar menu or swipable tabs. So I want to get list of tags or a single tag in rest api. I am using Tagger Extra.
This is my last barrier … Please help me. PLEASE.

I think you want to create a new endpoint /taggertags or something like that

1 Like

Yeah, what Bruno says.

You add another controller for tags. Perhaps /categories mapped to /rest/Controllers/Categories.php (MyControllerCategories). The $classKey you’re looking for would probably be TaggerTag and you can find the full schema for tagger here.

You’ll also need to load the Tagger service before your controller is initialised, so probably in your index.php or a service class of your own, like:

$path = $modx->getOption('tagger.core_path',null,$modx->getOption('core_path').'components/tagger/').'model/tagger/';
$tagger = $modx->getService('tagger','Tagger', $path);

To list resources with a tag, you’d need to go back to your resource controller and add the logic for that to the prepareListQueryBeforeCount method again. That’s a bit more complex than the previous one, as you would need to add a join to get to the filtering.

Untested, but to give you an idea, it would be something like this:

    protected function prepareListQueryBeforeCount(xPDOQuery $c) {
        if ($parent = (int)$this->getProperty('parent')) {
            $c->where(['parent' => $parent]);
        }
        if ($tag = (string)$this->getProperty('category')) {
            $c->innerJoin('TaggerTagResource', 'TaggerTagResource', 'modResource.id = TaggerTagResource.resource');
            $c->innerJoin('TaggerTag', 'TaggerTag', 'TaggerTagResource.tag = TaggerTag.id');
            $c->where(['TaggerTag.alias' => $tag]);
        }
        return $c;
    }

with GET /resources?parent=123&category=some-tag (where some-tag is the tag alias).

Oh, and one note of warning, by default the REST controllers also support POST (to create), PUT (to update) and DELETE (to remove) requests. That means that unless you add authentication or limit the allowed methods, anyone can do anything to your resources and tags.

  • To add authentication, see verifyAuthentication()
  • To disable specific methods, add beforePost(), beforePut() and beforeDelete() methods that return false, or:
  • Add the public property $allowedMethods = array('GET', 'POST', 'PUT', 'DELETE'); with only the appropriate methods.

Think you can help improve the documentation now, @mayanktaker? :wink:

3 Likes

Oh, and one note of warning, by default the REST controllers also support POST (to create), PUT (to update) and DELETE (to remove) requests. That means that unless you add authentication or limit the allowed methods, anyone can do anything to your resources and tags.

QQ, does the following code not prevent unauthorised requests and check for the session?

// Make sure the user has the proper permissions, send the user a 401 error if not
if (!$rest->checkPermissions()) {
    $rest->sendUnauthorized(true);
}
1 Like

Let’s check the source…

so you’d need a custom modRestService implementation for that to actually restrict access.

You can also see where the controller its verifyAuthentication() method is called from. By default that’s also wide open to the world.

I guess there might be some protection, at least for modResource, in that xPDO wont return items you don’t have access to according to standard ACLs and probably also refuses the save if you don’t have that permission, but that wont apply to other models. As RESTful apis may also lack sessions, adding some type of custom authentication or at least restricting the create/update/delete methods would definitely be my recommendation.

3 Likes

thank you for taking the time to explain

2 Likes

Sorry for late reply sir… All is done and I now got the point. I learned and followed your instructions.
Now I have Parent, Tagger tags, limits, sorting, everything I was looking for.
Thank you very very much for helping me.
I have no experience of writing documentation but I will try to write first on my PC then will handle you my writing/example for RESTful api in Modx. :heart_eyes:

Thank you everyone for your time and help. :slight_smile:

Cant wait to try my hands on Ionic+React+Modx News App. I will make a tutorial post for the app. It will be simple but useful for others.

So can we expect a new video tutorial on react blog with REST???