diff --git a/api/components/com_config/src/View/Application/JsonapiView.php b/api/components/com_config/src/View/Application/JsonapiView.php index d8f9fe008910d..7f57489941fe4 100644 --- a/api/components/com_config/src/View/Application/JsonapiView.php +++ b/api/components/com_config/src/View/Application/JsonapiView.php @@ -58,38 +58,49 @@ public function displayList(array $items = null) $items = array_splice($items, $offset, $limit); - $firstPage = clone $currentUrl; - $firstPageQuery = $currentPageQuery; - $firstPageQuery['offset'] = 0; - $firstPage->setVar('page', $firstPageQuery); - - $nextPage = clone $currentUrl; - $nextPageQuery = $currentPageQuery; - $nextOffset = $currentPageQuery['offset'] + $limit; - $nextPageQuery['offset'] = ($nextOffset > ($totalPagesAvailable * $limit)) ? $totalPagesAvailable - $limit : $nextOffset; - $nextPage->setVar('page', $nextPageQuery); - - $previousPage = clone $currentUrl; - $previousPageQuery = $currentPageQuery; - $previousOffset = $currentPageQuery['offset'] - $limit; - $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0; - $previousPage->setVar('page', $previousPageQuery); - - $lastPage = clone $currentUrl; - $lastPageQuery = $currentPageQuery; - $lastPageQuery['offset'] = $totalPagesAvailable - $limit; - $lastPage->setVar('page', $lastPageQuery); + $this->document->addMeta('total-pages', $totalPagesAvailable) + ->addLink('self', (string) $currentUrl); + + // Check for first and previous pages + if ($offset > 0) + { + $firstPage = clone $currentUrl; + $firstPageQuery = $currentPageQuery; + $firstPageQuery['offset'] = 0; + $firstPage->setVar('page', $firstPageQuery); + + $previousPage = clone $currentUrl; + $previousPageQuery = $currentPageQuery; + $previousOffset = $currentPageQuery['offset'] - $limit; + $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0; + $previousPage->setVar('page', $previousPageQuery); + + $this->document->addLink('first', $this->queryEncode((string) $firstPage)) + ->addLink('previous', $this->queryEncode((string) $previousPage)); + } + + // Check for next and last pages + if ($offset + $limit < $totalItemsCount) + { + $nextPage = clone $currentUrl; + $nextPageQuery = $currentPageQuery; + $nextOffset = $currentPageQuery['offset'] + $limit; + $nextPageQuery['offset'] = ($nextOffset > ($totalPagesAvailable * $limit)) ? $totalPagesAvailable - $limit : $nextOffset; + $nextPage->setVar('page', $nextPageQuery); + + $lastPage = clone $currentUrl; + $lastPageQuery = $currentPageQuery; + $lastPageQuery['offset'] = ($totalPagesAvailable - 1) * $limit; + $lastPage->setVar('page', $lastPageQuery); + + $this->document->addLink('next', $this->queryEncode((string) $nextPage)) + ->addLink('last', $this->queryEncode((string) $lastPage)); + } $collection = (new Collection($items, new JoomlaSerializer($this->type))); // Set the data into the document and render it - $this->document->addMeta('total-pages', $totalPagesAvailable) - ->setData($collection) - ->addLink('self', (string) $currentUrl) - ->addLink('first', (string) $firstPage) - ->addLink('next', (string) $nextPage) - ->addLink('previous', (string) $previousPage) - ->addLink('last', (string) $lastPage); + $this->document->setData($collection); return $this->document->render(); } diff --git a/api/components/com_config/src/View/Component/JsonapiView.php b/api/components/com_config/src/View/Component/JsonapiView.php index 983ecce21a1ea..776ef6f846367 100644 --- a/api/components/com_config/src/View/Component/JsonapiView.php +++ b/api/components/com_config/src/View/Component/JsonapiView.php @@ -74,38 +74,49 @@ public function displayList(array $items = null) $items = array_splice($items, $offset, $limit); - $firstPage = clone $currentUrl; - $firstPageQuery = $currentPageQuery; - $firstPageQuery['offset'] = 0; - $firstPage->setVar('page', $firstPageQuery); - - $nextPage = clone $currentUrl; - $nextPageQuery = $currentPageQuery; - $nextOffset = $currentPageQuery['offset'] + $limit; - $nextPageQuery['offset'] = ($nextOffset > ($totalPagesAvailable * $limit)) ? $totalPagesAvailable - $limit : $nextOffset; - $nextPage->setVar('page', $nextPageQuery); - - $previousPage = clone $currentUrl; - $previousPageQuery = $currentPageQuery; - $previousOffset = $currentPageQuery['offset'] - $limit; - $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0; - $previousPage->setVar('page', $previousPageQuery); - - $lastPage = clone $currentUrl; - $lastPageQuery = $currentPageQuery; - $lastPageQuery['offset'] = $totalPagesAvailable - $limit; - $lastPage->setVar('page', $lastPageQuery); + $this->document->addMeta('total-pages', $totalPagesAvailable) + ->addLink('self', (string) $currentUrl); + + // Check for first and previous pages + if ($offset > 0) + { + $firstPage = clone $currentUrl; + $firstPageQuery = $currentPageQuery; + $firstPageQuery['offset'] = 0; + $firstPage->setVar('page', $firstPageQuery); + + $previousPage = clone $currentUrl; + $previousPageQuery = $currentPageQuery; + $previousOffset = $currentPageQuery['offset'] - $limit; + $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0; + $previousPage->setVar('page', $previousPageQuery); + + $this->document->addLink('first', $this->queryEncode((string) $firstPage)) + ->addLink('previous', $this->queryEncode((string) $previousPage)); + } + + // Check for next and last pages + if ($offset + $limit < $totalItemsCount) + { + $nextPage = clone $currentUrl; + $nextPageQuery = $currentPageQuery; + $nextOffset = $currentPageQuery['offset'] + $limit; + $nextPageQuery['offset'] = ($nextOffset > ($totalPagesAvailable * $limit)) ? $totalPagesAvailable - $limit : $nextOffset; + $nextPage->setVar('page', $nextPageQuery); + + $lastPage = clone $currentUrl; + $lastPageQuery = $currentPageQuery; + $lastPageQuery['offset'] = ($totalPagesAvailable - 1) * $limit; + $lastPage->setVar('page', $lastPageQuery); + + $this->document->addLink('next', $this->queryEncode((string) $nextPage)) + ->addLink('last', $this->queryEncode((string) $lastPage)); + } $collection = (new Collection($items, new JoomlaSerializer($this->type))); // Set the data into the document and render it - $this->document->addMeta('total-pages', $totalPagesAvailable) - ->setData($collection) - ->addLink('self', (string) $currentUrl) - ->addLink('first', (string) $firstPage) - ->addLink('next', (string) $nextPage) - ->addLink('previous', (string) $previousPage) - ->addLink('last', (string) $lastPage); + $this->document->setData($collection); return $this->document->render(); } diff --git a/libraries/src/MVC/Controller/ApiController.php b/libraries/src/MVC/Controller/ApiController.php index 7e9d664df459e..b3b9580ad08a2 100644 --- a/libraries/src/MVC/Controller/ApiController.php +++ b/libraries/src/MVC/Controller/ApiController.php @@ -200,15 +200,19 @@ public function displayList() { // Assemble pagination information (using recommended JsonApi pagination notation for offset strategy) $paginationInfo = $this->input->get('page', [], 'array'); + $limit = null; + $offset = null; if (\array_key_exists('offset', $paginationInfo)) { - $this->modelState->set($this->context . '.limitstart', $paginationInfo['offset']); + $offset = $paginationInfo['offset']; + $this->modelState->set($this->context . '.limitstart', $offset); } if (\array_key_exists('limit', $paginationInfo)) { - $this->modelState->set($this->context . '.list.limit', $paginationInfo['limit']); + $limit = $paginationInfo['limit']; + $this->modelState->set($this->context . '.list.limit', $limit); } $viewType = $this->app->getDocument()->getType(); @@ -243,16 +247,25 @@ public function displayList() // Push the model into the view (as default) $view->setModel($model, true); + if ($offset) + { + $model->setState('list.start', $offset); + } + /** * Sanity check we don't have too much data being requested as regularly in html we automatically set it back to * the last page of data. If there isn't a limit start then set */ - if (!$this->input->getInt('limit', null)) + if ($limit) + { + $model->setState('list.limit', $limit); + } + else { $model->setState('list.limit', $this->itemsPerPage); } - if ($this->input->getInt('limitstart', 0) > $model->getTotal()) + if (!is_null($offset) && $offset > $model->getTotal()) { throw new Exception\ResourceNotFound; } diff --git a/libraries/src/MVC/View/JsonApiView.php b/libraries/src/MVC/View/JsonApiView.php index d7706dcdb37d1..5136552c5661d 100644 --- a/libraries/src/MVC/View/JsonApiView.php +++ b/libraries/src/MVC/View/JsonApiView.php @@ -112,6 +112,11 @@ public function displayList(array $items = null) /** @var \Joomla\CMS\MVC\Model\ListModel $model */ $model = $this->getModel(); + // Get page query + $currentUrl = Uri::getInstance(); + $currentPageDefaultInformation = ['offset' => 0, 'limit' => 20]; + $currentPageQuery = $currentUrl->getVar('page', $currentPageDefaultInformation); + if ($items === null) { $items = []; @@ -136,32 +141,46 @@ public function displayList(array $items = null) } // Set up links for pagination - $currentUrl = Uri::getInstance(); - $currentPageDefaultInformation = array('offset' => $pagination->limitstart, 'limit' => $pagination->limit); - $currentPageQuery = $currentUrl->getVar('page', $currentPageDefaultInformation); - $totalPagesAvailable = ($pagination->pagesTotal * $pagination->limit); - - $firstPage = clone $currentUrl; - $firstPageQuery = $currentPageQuery; - $firstPageQuery['offset'] = 0; - $firstPage->setVar('page', $firstPageQuery); - - $nextPage = clone $currentUrl; - $nextPageQuery = $currentPageQuery; - $nextOffset = $currentPageQuery['offset'] + $pagination->limit; - $nextPageQuery['offset'] = ($nextOffset > ($totalPagesAvailable * $pagination->limit)) ? $totalPagesAvailable - $pagination->limit : $nextOffset; - $nextPage->setVar('page', $nextPageQuery); - - $previousPage = clone $currentUrl; - $previousPageQuery = $currentPageQuery; - $previousOffset = $currentPageQuery['offset'] - $pagination->limit; - $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0; - $previousPage->setVar('page', $previousPageQuery); - - $lastPage = clone $currentUrl; - $lastPageQuery = $currentPageQuery; - $lastPageQuery['offset'] = $totalPagesAvailable - $pagination->limit; - $lastPage->setVar('page', $lastPageQuery); + $totalItemsCount = ($pagination->pagesTotal * $pagination->limit); + + $this->document->addMeta('total-pages', $pagination->pagesTotal) + ->addLink('self', (string) $currentUrl); + + // Check for first and previous pages + if ($pagination->limitstart > 0) + { + $firstPage = clone $currentUrl; + $firstPageQuery = $currentPageQuery; + $firstPageQuery['offset'] = 0; + $firstPage->setVar('page', $firstPageQuery); + + $previousPage = clone $currentUrl; + $previousPageQuery = $currentPageQuery; + $previousOffset = $currentPageQuery['offset'] - $pagination->limit; + $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0; + $previousPage->setVar('page', $previousPageQuery); + + $this->document->addLink('first', $this->queryEncode((string) $firstPage)) + ->addLink('previous', $this->queryEncode((string) $previousPage)); + } + + // Check for next and last pages + if ($pagination->limitstart + $pagination->limit < $totalItemsCount) + { + $nextPage = clone $currentUrl; + $nextPageQuery = $currentPageQuery; + $nextOffset = $currentPageQuery['offset'] + $pagination->limit; + $nextPageQuery['offset'] = ($nextOffset > ($pagination->pagesTotal * $pagination->limit)) ? $pagination->pagesTotal - $pagination->limit : $nextOffset; + $nextPage->setVar('page', $nextPageQuery); + + $lastPage = clone $currentUrl; + $lastPageQuery = $currentPageQuery; + $lastPageQuery['offset'] = ($pagination->pagesTotal - 1) * $pagination->limit; + $lastPage->setVar('page', $lastPageQuery); + + $this->document->addLink('next', $this->queryEncode((string) $nextPage)) + ->addLink('last', $this->queryEncode((string) $lastPage)); + } $collection = (new Collection($items, $this->serializer)) ->fields([$this->type => $this->fieldsToRenderList]); @@ -172,13 +191,7 @@ public function displayList(array $items = null) } // Set the data into the document and render it - $this->document->addMeta('total-pages', $pagination->pagesTotal) - ->setData($collection) - ->addLink('self', (string) $currentUrl) - ->addLink('first', (string) $firstPage) - ->addLink('next', (string) $nextPage) - ->addLink('previous', (string) $previousPage) - ->addLink('last', (string) $lastPage); + $this->document->setData($collection); return $this->document->render(); } @@ -244,4 +257,18 @@ protected function prepareItem($item) { return $item; } + + /** + * Encode square brackets in the URI query, according to JSON API specification. + * + * @param string $query The URI query + * + * @return string + * + * @since 4.0.0 + */ + protected function queryEncode($query) + { + return str_replace(array('[', ']'), array('%5B', '%5D'), $query); + } }