To avoid the issue of duplicate title tags in paginated content in concrete5, we can override the page list block's controller to automatically output recommended rel="next" and rel="prev" link tags in the page head. Read on for the code.

One important search engine optimisation consideration is to check whether your website has duplicate pages or content as search engines penalise such occurrences.

While discussing SEO with a fellow concrete5 website developer and reviewing our respective websites, we found that we were both encountering warnings that our sites had pages with duplicate title tags - we discovered that this was due to our paginated blog listings. 

Concrete5 provides an easy way to create paginated lists of pages using the page list block. The block automatically creates the pagination links required to navigate forward and backward through lists of pages. However, when a search engine traverses these links it sees new pages and content, but because it's technically the same page (just with a different url parameter), the page title and headings remain the same, triggering duplicate title tag warnings in Google's Webmaster Tools (as well as other SEO tools).

Fortunately Google (and I believe Bing) provide a way to markup pages that are part of a paginated series, mitigating the duplicate title issue. They ask that you add to the head section of your pages tags with the attributes rel="next" and rel="prev", providing hints to the search engines that there is a sequence of pages in play, not just pages with similar content. Knowing that this option was available, I was determined to implement this improvement on our sites.

To implement these extra link tags I needed to override the page list block's controller. Fortunately, the way the more recent versions of concrete5 have been designed, we don't have to re-declare the whole controller again, just include the functions we want to override/replace.

To be able to inject these link tags in the header, I created a custom on_page_view function for the controller as this gets called before the page starts being built. In this, I used the function $this->addHeaderItem(...), to inject the link tags. While I was at it, I also added some logic to output a link tag with rel="canonical", to handle if page 1 is visited via the pagination links.

At this point in the execution though, the list of pages haven't been retrieved and the pagination object hasn't been created. To solve this, I found that I could call the $this->getPages() function of the page list controller, which initialises things far enough to gain access to the pagination object.

The only negative of this is that we'd be performing an unnecessary retrieval of information twice. To fix this, I overrode the getPages() function (which fortunately was small) and adjusted it to only do the actual work the first time only.

Enough talking, here's the code:

<?php 
	defined('C5_EXECUTE') or die("Access Denied.");
	class PageListBlockController extends Concrete5_Controller_Block_PageList {
		 
		public function getPages($return = true) {
			$existingpl = $this->get('pl');
			
			if (is_object($existingpl)) {
				$pl = $existingpl;
			} else {
				$pl = $this->getPageList();
			}
			
			$this->set('pl', $pl);
			
			if ($return) {
				if ($pl->getItemsPerPage() > 0) {
					$pages = $pl->getPage();
				} else {
					$pages = $pl->get();
				}
				
				return $pages;
			}
		} 
		 
		public function on_page_view() {
			$this->getPages(false);
			$pl = $this->get('pl');
			
			if (is_object($pl) && $this->paginate) {
				$pagination = $pl->getPagination();
				$currentURL = $pagination->URL;
				$currentURL = substr($currentURL, 0, strpos($currentURL,'?'));
			
				if ($pagination->current_page > 0) {
					if ($pagination->getPreviousInt()+1 == 1) {
						$linkURL = $currentURL;  // if on page two, prev link should be the canonical url
					} else {
						$linkURL=str_replace("%pageNum%", $pagination->getPreviousInt()+1, $pagination->URL);	
					}
					// output rel="prev"
					$this->addHeaderItem('<link rel="prev" href="'.BASE_URL . DIR_REL . $linkURL .'" />');
				} else {
					// output rel="canonical" with 'clean' url if on page one
					$this->addHeaderItem('<link rel="canonical" href="'.BASE_URL . DIR_REL .$currentURL .'" />');
				}
			
				// output rel="next"
				if ($pagination->number_of_pages > $pagination->current_page + 1) {
					$linkURL=str_replace("%pageNum%", $pagination->getNextInt()+1, $pagination->URL);
					$this->addHeaderItem('<link rel="next" href="'.BASE_URL . DIR_REL .$linkURL .'" />');
				}		
			}
		}
		
	}
?>

To implement this override, create a file at /blocks/page_list/controller.php and paste into it the above code. Then clear your concrete5 cache. With this in place, you should see in the HTML of your pages with a paginated page list block the appropriate link tags within the <head>.

They'll look something like this (this is looking at page 1):

<link rel="canonical" href="http://www.mesuva.com.au/blog/" />
<link rel="next" href="http://www.mesuva.com.au/blog/?ccm_paging_p_b704=2" />

...and hyperthetically if you were looking at page 4:

<link rel="prev" href="http://www.mesuva.com.au/blog/?ccm_paging_p_b704=3" />
<link rel="next" href="http://www.mesuva.com.au/blog/?ccm_paging_p_b704=5" />

Keep in mind that it's only suitable if you have one paginated page list block on a page at a time - if you've got multiple paginations happening on your pages this is going to output multiple (and therefore incorrect) next and prev links tags to your header, not a good idea.

This has been tested for concrete5 version 5.6.1.2,  it probably works for all versions of 5.6.

-Ryan