Searching and Sorting with the PageList object

Concrete CMS provides the Concrete\Core\Page\PageList object to make it easy for developers to query Concrete for a list of pages based on different criteria.

Any time you want to sort, search or filter a list of pages you should use this object (rather than querying the database directly), as it handles the compexities of permissions, page versions, and aliasing, and provides a nice API on top of the fairly complex table structure underneath.

To fetch a list of all pages in your sitemap:

$list = new \Concrete\Core\Page\PageList();
$pages = $list->getResults();

By default, this list will include all pages in your site, with the exception of:

  • Aliases
  • Inactive pages (e.g. drafts or pages in the trash)
  • Pages that the current user cannot access, based on permissions
  • System and dashboard pages

This is an array of all Page objects, with no limit.

Basic Filtering Examples

Filtering a PageList query involves calling a filterBy...() function prior to getResults().

To only return results for a specific page type:

$list = new \Concrete\Core\Page\PageList();
$list->filterByPageTypeHandle('blog_entry');
$pages = $list->getResults();

To include pages of several types:

$list->filterByPageTypeHandle(['blog_entry', 'press_release']);

To filter by keywords (simple):

$list->filterByKeywords('foobar');

Filtering by keywords performs a simple LIKE query against the name, description or text content of a page, as well as the textual representation of any page attributes that have been marked as "included in search index".

To perform a more advanced search on the name, description and text content fields using MySQL's FULLTEXT search indexing:

$list->filterByFulltextKeywords('foobar');

Filter by Attribute

The PageList object contains some 'magic methods' to filter easily by attributes. Simply add a StudlyCapsed attribute handle after filterBy and pass the data into the attribute:

$list->filterByExcludeNav(true);

The attribute's type determines what kind of data it takes in its filter methods. For example, if the attribute is a topic, it can take a topic tree node:

$topicNode = \Concrete\Core\Tree\Node::getByID(10);
$list->filterByBlogEntryTopic($topicNode);

Sorting

To sort the list by the public post date:

$list = new \Concrete\Core\Page\PageList();
$list->sortByPublicDate();
$pages = $list->getResults();

Or to reverse the order:

$list->sortByPublicDateDescending();

To sort alphabetically by name:

$list->sortByName();

To sort by the order shown in the sitemap:

$list->sortByDisplayOrder();

To sort by an page attribute:

$list->sortByAttributeName();

or

$list->sortBy('ak_attribute_name', 'ASC|DSC');

Permissions and Including Atypical Pages

To list all pages while ignoring permissions:

$list->ignorePermissions();

Include unusual pages:

$list->includeInactivePages();
$list->includeAliases();
$list->includeSystemPages();   

Multilingual site search

By default PageList will only return results from the active locale siteTree. To search in all siteTrees include this:

$list->setSiteTreeToAll();

Advanced Techniques

Custom Queries

The PageList class was completely rewritten in Concrete 5.7 on top of the Doctrine DBAL QueryBuilder. You can grab the underlying QueryBuilder object in order to operate on it directly from any PageList object:

$list = new \Concrete\Core\Page\PageList();
$list->getQueryObject()
     ->where('p.clsActive', 1)
     ->andWhere('p.clsTemplate', 0)
     ->orderBy('p.cDisplayOrder', 'ASC');
$pages = $list->getResults();

Subclassing

If you find yourself performing the same custom queries multiple times throughout the site, you can reduce the duplication by creating a new class that extends the built-in PageList class.

Here's an example of a custom class that only displays pages of a certain type, with a custom attribute for handling whether pages are included:

<?php

namespace PortlandLabs\ClassifiedList;

use Concrete\Core\Page\PageList;

class ClassifiedList extends PageList
{

    /** @var boolean */
    protected $includeInactive = false;

    public function __construct()
    {
        // First call the default constructor, and then add any filters and sort options
        // that should occur by default
        parent::__construct();

        $this->ignorePermissions();
        $this->filterByPageTypeHandle('classified');
        $this->setItemsPerPage(10);
        $this->sortByPublicDateDescending();
    }

    public function deliverQueryObject()
    {
        if (!$this->includeInactive) {
            $this->filterByClassifiedIsDeactivated(false);
        }
        return parent::deliverQueryObject();
    }

    public function includeInactive()
    {
        $this->includeInactive = true;
    }

    public function filterByActive($start, $end = null)
    {
        if (!$end) {
            $end = $start;
        }
        $this->filterByPublicDate(date('Y-m-d H:i:s', $end), "<=");
        $this->filterByAttribute('special_offer_end_date', date('Y-m-d H:i:s', $start), ">=");
    }

}

Pagination

Once you have filtered your PageList object, getResults() will return all matching pages.

Many times, however, you'll want to retrieve just few results at a time. For this, you'll want to use the Pagination object:

$list = new \Concrete\Core\Page\PageList();
$pagination = $list->getPagination();
$pagination->setMaxPerPage(10)->setCurrentPage(2);
$results = $pagination->getCurrentPageResults();

With Permissions

If your page list is honoring permissions (as it does by default), the $pagination object will be an instance of the Concrete\Core\Search\Pagination\PermissionablePagination object. This means that the entire result set (up to 1000) will be loaded and then segmented, with the permissions checker run against it.

Without Permissions

If your PageList object is ignoring permissions, it simply returns a basic Concrete\Core\Search\Pagination\Pagination object, which is simpler.

Pagination functions

The Pagination object provides several helpful functions. To get the total number of results:

echo $pagination->getTotalResults();

To get the total number of pages:

echo $pagination->getTotalPages();

To determine whether paging is necessary:

$pagination->hasNextPage();
$pagination->hasPreviousPage();

Rendering Pagination

Several common pagination styles are built-in, including Bootstrap 2, Bootstrap 3, Basic Pagination, and Concrete's default styling (which is heavily Bootstrap 3 inspired).

echo $pagination->renderDefaultView();  // Outputs HTML for Bootstrap 3, useful in the Dashboard, etc…

You can also render any pagination view supported by Pagerfanta from your Pagination object. More information available here.

API Reference

PageList API Reference

Pagination API Reference