Browse Source

Paginator now supports ordering by multiple fields.

Code-Working 9 years ago
parent
commit
b0ec15030f

+ 7 - 3
src/Datasource/Paginator.php

@@ -215,6 +215,7 @@ class Paginator implements PaginatorInterface
             'sortDefault' => $sortDefault,
             'directionDefault' => $directionDefault,
             'scope' => $options['scope'],
+            'completeSort' => $order,
         ];
 
         $this->_pagingParams = [$alias => $paging];
@@ -323,7 +324,7 @@ class Paginator implements PaginatorInterface
      * the model friendly order key.
      *
      * You can use the whitelist parameter to control which columns/fields are
-     * available for sorting. This helps prevent users from ordering large
+     * available for sorting via URL parameters. This helps prevent users from ordering large
      * result sets on un-indexed values.
      *
      * If you need to sort on associated columns or synthetic properties you
@@ -333,6 +334,9 @@ class Paginator implements PaginatorInterface
      * You can use this to sort on synthetic columns, or columns added in custom
      * find operations that may not exist in the schema.
      *
+     * The default order options provided to paginate() will be merged with the user's
+     * requested sorting field/direction.
+     *
      * @param \Cake\Datasource\RepositoryInterface $object Repository object.
      * @param array $options The pagination options being used for this request.
      * @return array An array of options with sort + direction removed and
@@ -348,7 +352,8 @@ class Paginator implements PaginatorInterface
             if (!in_array($direction, ['asc', 'desc'])) {
                 $direction = 'asc';
             }
-            $options['order'] = [$options['sort'] => $direction];
+            $order = (isset($options['order']) && is_array($options['order'])) ? $options['order'] : [];
+            $options['order'] = [$options['sort'] => $direction] + $order;
         }
         unset($options['sort'], $options['direction']);
 
@@ -369,7 +374,6 @@ class Paginator implements PaginatorInterface
                 return $options;
             }
         }
-
         $options['order'] = $this->_prefix($object, $options['order'], $inWhitelist);
 
         return $options;

+ 64 - 0
tests/TestCase/Controller/Component/PaginatorComponentTest.php

@@ -934,6 +934,7 @@ class PaginatorComponentTest extends TestCase
             ]
         ];
         $result = $this->Paginator->validateSort($model, $options);
+
         $expected = [
             'model.author_id' => 'asc',
             'model.title' => 'asc'
@@ -941,6 +942,69 @@ class PaginatorComponentTest extends TestCase
 
         $this->assertEquals($expected, $result['order']);
     }
+    
+    /**
+     * test that multiple sort works in combination with query.
+     *
+     * @return void
+     */
+    public function testValidateSortMultipleWithQuery()
+    {
+        $this->loadFixtures('Posts');
+        $table = TableRegistry::get('PaginatorPosts');
+
+        $titleExtractor = function ($result) {
+            $ids = [];
+            foreach ($result as $record) {
+                $ids[] = $record->title;
+            }
+            return $ids;
+        };
+
+        // Define two columns as default sort order
+        $settings = [
+            'order' => [
+                'author_id' => 'asc',
+                'title' => 'asc'
+            ]
+        ];
+
+        // Test with empty query
+        $this->request->query = [];
+        $result = $this->Paginator->paginate($table, $settings);
+        $this->assertCount(3, $result, '3 rows should come back');
+        $this->assertEquals(['First Post', 'Third Post', 'Second Post'], $titleExtractor($result));
+        $this->assertEquals(
+            ['PaginatorPosts.author_id' => 'asc', 'PaginatorPosts.title' => 'asc'],
+            $this->request->params['paging']['PaginatorPosts']['totalOrder']
+        );
+
+        // Test overwriting a sort field defined in the settings
+        $this->request->query = [
+            'sort' => 'author_id',
+            'direction' => 'desc'
+        ];
+        $result = $this->Paginator->paginate($table, $settings);
+        $this->assertCount(3, $result, '3 rows should come back');
+        $this->assertEquals(['Second Post', 'First Post', 'Third Post'], $titleExtractor($result));
+        $this->assertEquals(
+            ['PaginatorPosts.author_id' => 'desc', 'PaginatorPosts.title' => 'asc'],
+            $this->request->params['paging']['PaginatorPosts']['totalOrder']
+        );
+
+        // Test sorting by a field not defined in the settings
+        $this->request->query = [
+            'sort' => 'id',
+            'direction' => 'asc'
+        ];
+        $result = $this->Paginator->paginate($table, $settings);
+        $this->assertCount(3, $result, '3 rows should come back');
+        $this->assertEquals(['First Post', 'Second Post', 'Third Post'], $titleExtractor($result));
+        $this->assertEquals(
+            ['PaginatorPosts.id' => 'asc', 'PaginatorPosts.author_id' => 'asc', 'PaginatorPosts.title' => 'asc'],
+            $this->request->params['paging']['PaginatorPosts']['totalOrder']
+        );
+    }
 
     /**
      * Tests that order strings can used by Paginator