0
votes

CakePHP Version: 4.0.1

Introduction

I have 2 methods that both use the index view, index and search. On index the column can be selected from a select list and a value can be inputted via an input form control enabling a search by column and value. This data is sent via GET to the search method where empty values are checked and the query is executed and the index view is rendered.

In the later 3x versions with the below configuration the index view had the sort on the selected column which is what it is meant to do.

IE: Index view has due_date sorted on the initial load and I select task_name then submit the form to the search method. The task_name has the sort when the view is rendered.


TASKS CONTROLLER

Public pagination property:

public $paginate = [
    'sortWhitelist' => [
       'Tasks.due_date',
       'Tasks.task_name',
       'Tasks.type',
       'Tasks.priority',
       'Tasks.related_to_name',
       'Contacts.first_name',
       'Contacts.last_name',
       'Accounts.account_name',
       'Tasks.task_desc'
    ]
];

Search Method

I initialise the data received from the index method and apply the config to the pagination property and send the query object to the view.

$this->setPage('');
$this->setSort($this->request->getQuery('column'));
$this->setDirection('asc');

// Validation of the page, sort, direction and limit is done here.
// IE: The $this->getSort() must be a string and not be numeric and has a strlen check
// and the $this->getDirection() can only be a string with values 'asc' or 'desc' etc. 

if (!empty($this->getPage())) {
    $this->paginate['page'] = $this->getPage();
}
$this->paginate['sort'] = $this->getSort();
$this->paginate['direction'] = $this->getDirection();
$this->paginate['limit'] = $this->getLimit();

debug($this->paginate);

$tasks = $this->paginate($query);
$this->set(compact('tasks'));

The result of debug is:

[
    'sortWhitelist' => [
        (int) 0 => 'Tasks.due_date',
        (int) 1 => 'Tasks.task_name',
        (int) 2 => 'Tasks.type',
        (int) 3 => 'Tasks.priority',
        (int) 4 => 'Tasks.related_to_name',
        (int) 5 => 'Contacts.first_name',
        (int) 6 => 'Contacts.last_name',
        (int) 7 => 'Accounts.account_name',
        (int) 8 => 'Tasks.task_desc'
    ],
    'sort' => 'Tasks.task_name',
    'direction' => 'asc',
    'limit' => (int) 25
 ]

Result
The sort is on the task_name.


A couple of months ago I upgraded to 4 and have just revisted this functionality to find the sort is on the column that was present on index and not the column that was selected. I tried the below to fix the problem:

I referenced this information in the cookbook. And this from SO.

$config = $this->paginate = [
    'page' => $this->getPage(),
    'sort' => $this->getSort(),
    'direction' => $this->getDirection(),
    'limit' => $this->getLimit()
];

debug($config);

$tasks = $this->Paginator->paginate($query, $config);

debug($this->Paginator);

$this->set(compact('tasks'));

The result of debug $config is:

[
    'page' => '',
    'sort' => 'Tasks.task_name',
    'direction' => 'asc',
    'limit' => (int) 25
]

The result of debug $this->Paginator is:

object(Cake\Controller\Component\PaginatorComponent) {

    'components' => [],
    'implementedEvents' => [],
    '_config' => [
        'page' => (int) 1,
        'limit' => (int) 20,
        'maxLimit' => (int) 100,
        'whitelist' => [
            (int) 0 => 'limit',
            (int) 1 => 'sort',
            (int) 2 => 'page',
            (int) 3 => 'direction'
        ]
    ]

}

NOTE: The whitelist contains limit, sort, page and direction? And the limit is 20 and I don't even have a selection of 20?

Result
The sort is on the due_date and I need it on the task_name.

Extra Info
If I then click the sort on task_name the sort is on the task_name. All the sorts work just not on the initial load?


Question
How can I configure the pagination property so the sort is on the task_name from the initial load of the search method.

Thanks Z.

1
What exactly is that original code doing there in the first place? All these getter methods populating the pagination config, that looks like as if you're possibly injecting user input into the the paginator config? - ndm
@ndm Yes that is possible, the user can alter the page, sort, direction and limit in the URL and change the display based on those values. I didn't think this was an issue, these values are validated after they are set and before they are got to ensure that they are valid values but I haven't shown that in the post. I've edited the post to reflect that now. - Zenzs
I see, unless there's a specific reason for that, it probably shouldn't have been done that way, as the paginator sanitizes/validates the config user input (request data) on its own. The paginator config should normally just be used for configuring pagination defaults. - ndm
Fair enough, I didn't realise that. The reason I did validate it there was to negate a user injecting user input into the pagination property directly :-) If I'm correct you're saying that I do not need to do that validation anymore if I can access the paginator directly because this will invoke the $config where as you mention it sanitizes/validates the config user input anyway. Mind you there still valid values so they should still be ok to use in the config... - Zenzs
It's not about accessing the component directly, the paginator will validate/sanitize limit/sort/page/direction by default (after merging user request data and paginator config). That being said, specifying the default sorting in the pagination config works fine for me, the helper correctly "highlights" (adds the small arrow icon) as expected. You might need to boil this down to a more general, reproducible example. - ndm

1 Answers

0
votes

The fix is a bit costly and not ideal but it does work. I do a redirect on the initial load. Basically submit the form to search then redirect back to search. IE:

if ($this->request->getQuery('initial') === 'yes') {

    $redirect = $this->request->getQuery('redirect', [
        'action' => 'search',
         '?' => [
             'method' => 'search',
             'column' => $this->getColumn(),
             'input' => $this->getInput(),
             'page' => $this->getPage(),
             'sort' => $this->getSort(),
             'direction' => $this->getDirection(),
             'limit' => $this->getLimit(),
             'filter' => $this->getFilter(),
         ]
    ]);

    return $this->redirect($redirect);
    exit(0);

}

$config = $this->paginate = [
    'sortWhitelist' => [
       'Tasks.due_date',
       'Tasks.task_name',
       'Tasks.type',
       'Tasks.priority',
       'Tasks.related_to_name',
       'Contacts.first_name',
       'Contacts.last_name',
       'Accounts.account_name',
       'Tasks.task_desc'
     ],
     'page' => $this->getPage(),
     'sort' => $this->getSort(),
     'direction' => $this->getDirection(),
     'limit' => $this->getLimit()
 ];

 $tasks = $this->Paginator->paginate($query, $config);
 $this->set(compact('tasks'));

The sort is now on the task_name.

This negates the initial load problem and simulates usage after the page initially loads where I know the sorts work.