SEO-friendly pages in your web application

ASP.NET, News, PHP, PHP Code Generator, Tutorials

There was an article posted back in 2007 in our forums that provided a solid start for many SEO-friendly URLs applications. It is about time to talk about how you can implement it in version 10.x of PHPRunner, ASPRunnerPro, and ASPRunner.NET.

We have helped one of our clients to implement this solution in their project. They run a website that lists public voters’ information in certain Florida counties. The objective is to make these pages rank high when someone searches for county, city or neighborhood name in Google. To do so we need to make sure that relevant keywords appear in the URL.

So, for instance, instead of this View page URL:

https://www.keyword-rich-website-name.com/tablename_view.php?editid1=125745441-Dean-Abbondanza-Venice-FL

We want to see this:

https://www.keyword-rich-website-name.com/voters-125745441-Dean-Abbondanza-Venice-FL

And we would want to prettify List page URLs as well. For instance, instead of

https://www.keyword-rich-website-name.com/tablename_list.php?goto=3

We want to see this:

https://www.keyword-rich-website-name.com/voters-list-3

This is a two-step process. We need to create redirect rules on the web server and also make changes to the project itself to use new-style links.

1. Redirect rules

These rules are for .htaccess file meaning that you need Linux-based web server and Apache. We will provide IIS instructions later. These are rules we have used in this project:

Options +FollowSymLinks
RewriteEngine On
RewriteRule ^voters-([0-9][A-Za-z0-9-]+)$ vsar0604_view.php?editid1=$1
RewriteRule ^voters-list-([A-Za-z0-9-]+)$ vsar0604_list.php?goto=$1

vsar0604 here is a table name. There are only two rules, one per page type and they are easy to understand. ([A-Za-z0-9-]+)$ is a Regex that defines a pattern to match and then in the second part of the rule we define a page to show where $1 is our match from the first part of the rule.

This may sound complicated if you never worked with regular expressions before. I found this .htaccess tester tool very valuable. You enter your rules and the URL to test against and it tells you what rules will match.

If you want to learn more about URL Rewrite rules I recommend this excellent article.

2. Project changes

We would need to make changes to the way links to view page and pagination links are displayed.

List Page: After Record Processed event

PHP code:

$link = $record["viewlink_attrs"];
$p1 = strpos($link, "href='");
$p2 = strpos($link, "editid1=");
$url = substr($link,$p2+8,-1);
$link = substr($link,0,$p1)." href='voters-".$url."'";
$record["viewlink_attrs"] = $link;

C# code:

dynamic link = null, p1 = null, p2 = null, url = null;
link = XVar.Clone(record["viewlink_attrs"]);
p1 = XVar.Clone(CommonFunctions.strpos((XVar)(link), new XVar("href='")));
p2 = XVar.Clone(CommonFunctions.strpos((XVar)(link), new XVar("editid1=")));
url = XVar.Clone(CommonFunctions.substr((XVar)(link), (XVar)(p2 + 8), new XVar(-1)));
link = XVar.Clone(MVCFunctions.Concat(CommonFunctions.substr((XVar)(link), new XVar(0), (XVar)(p1)), " href='voters-", url, "'"));
record.InitAndSetArrayItem(link, "viewlink_attrs");

List Page: Before Display event

PHP code:

function my_getPaginationLink($pageNum, $linkText, $active = false)
{
   return '<li class="' . ( $active ? "active" : "" ) . '"><a href="voters-list-'.$pageNum.'">'.$linkText.'</a></li>';
}

$rs = CustomQuery("select count(*) as cnt from (".$pageObject->querySQL.") as a");
$data = db_fetch_array($rs);
$numRowsFromSQL = $data["cnt"];

if(!$_SESSION[$pageObject->sessionPrefix."_mypagesize"])
	$_SESSION[$pageObject->sessionPrefix."_mypagesize"] = $pageObject->gPageSize;
if(postvalue("pagesize"))	
	$_SESSION[$pageObject->sessionPrefix."_mypagesize"] = postvalue("pagesize");

$gPageSize=$_SESSION[$pageObject->sessionPrefix."_mypagesize"];

$separator = "";
$advSeparator = "";
$myPages = postvalue("goto");
if(!$myPages)
	$myPages=1;

if($gPageSize && $gPageSize!=-1)
	$maxPages = ceil($numRowsFromSQL / $gPageSize);
if($myPages > $maxPages)
	$myPages = $maxPages;
if($myPages < 1)
	$myPages = 1;
$recordsOnPage = $numRowsFromSQL -($myPages - 1) * $gPageSize;
if($recordsOnPage > $gPageSize && $gPageSize!=-1)
	$recordsOnPage = $gPageSize;

$pageObject->jsSettings["tableSettings"][$pageObject->tName]['maxPages'] = $maxPages;

$firstDisplayed = ( $myPages - 1 )	 * $gPageSize + 1;
$lastDisplayed = ( $myPages ) * $gPageSize;
if( $gPageSize < 0 || $lastrecord > $numRowsFromSQL )
	$lastDisplayed = $numRowsFromSQL;

$pageObject->prepareRecordsIndicator( $firstDisplayed, $lastDisplayed, $numRowsFromSQL );
$xt->assign("pagination_block", false);
$limit=10;
if($maxPages > 1) 
{
	$xt->assign("pagination_block", true);
	$pagination = '';
	$counterstart = $myPages - ($limit-1);

	if($myPages % $limit != 0)
		$counterstart = $myPages -($myPages % $limit) + 1;
	$counterend = $counterstart + $limit-1;
	if($counterend > $maxPages)
		$counterend = $maxPages;
	if($counterstart != 1) 
	{
		$pagination.= my_getPaginationLink(1,"First") . $advSeparator;
		$pagination.= my_getPaginationLink($counterstart - 1,"Prev").$separator;
	}
	$pageLinks = "";
	for($counter = $counterstart; $counter <= $counterend; $counter ++) 
	{
		$pageLinks .= $separator . my_getPaginationLink($counter,$counter, $counter == $myPages );
	}
	
	$pagination .= $pageLinks;
	if($counterend < $maxPages) 
	{
		$pagination.= $separator . my_getPaginationLink($counterend + 1,"Next") . $advSeparator;
		$pagination.= my_getPaginationLink($maxPages,"Last");
	}
	$pagination = '';
}

$xt->assign("pagination",$pagination);

C# code:

string my_getPaginationLink(dynamic pageNum, dynamic linkText, dynamic active = null) {
return MVCFunctions.Concat("<li class=\"", (XVar.Pack(active) ? XVar.Pack("active") : XVar.Pack("")), "\"><a href=\"voters-list-", pageNum, "\">", linkText, "</a></li>");
}

dynamic advSeparator = null, firstDisplayed = null, gPageSize = null, lastDisplayed = null, lastrecord = null, limit = null, maxPages = null, myPages = null, numRowsFromSQL = null, pagination = null, recordsOnPage = null, separator = null;
rs = XVar.Clone(CommonFunctions.db_query(MVCFunctions.Concat("select count(*) as cnt from (", pageObject.querySQL, ") as a"), GlobalVars.conn));
data = XVar.Clone(CommonFunctions.db_fetch_array((XVar)(rs)));
numRowsFromSQL = XVar.Clone(data["cnt"]);
if(XVar.Pack(!(XVar)(XSession.Session[MVCFunctions.Concat(pageObject.sessionPrefix, "_mypagesize")])))
{
	XSession.Session[MVCFunctions.Concat(pageObject.sessionPrefix, "_mypagesize")] = pageObject.gPageSize;
}
if(XVar.Pack(MVCFunctions.postvalue(new XVar("pagesize"))))
{
	XSession.Session[MVCFunctions.Concat(pageObject.sessionPrefix, "_mypagesize")] = MVCFunctions.postvalue(new XVar("pagesize"));
}
gPageSize = XVar.Clone(XSession.Session[MVCFunctions.Concat(pageObject.sessionPrefix, "_mypagesize")]);
separator = new XVar("");
advSeparator = new XVar("");
myPages = XVar.Clone(MVCFunctions.postvalue(new XVar("goto")));
if(XVar.Pack(!(XVar)(myPages)))
{
	myPages = new XVar(1);
}
if((XVar)(gPageSize)  && (XVar)(gPageSize != -1))
{
	maxPages = XVar.Clone((XVar)Math.Ceiling((double)(numRowsFromSQL / gPageSize)));
}
if(maxPages < myPages)
{
	myPages = XVar.Clone(maxPages);
}
if(myPages < 1)
{
	myPages = new XVar(1);
}
recordsOnPage = XVar.Clone(numRowsFromSQL - (myPages - 1) * gPageSize);
if((XVar)(gPageSize < recordsOnPage)  && (XVar)(gPageSize != -1))
{
	recordsOnPage = XVar.Clone(gPageSize);
}
pageObject.jsSettings.InitAndSetArrayItem(maxPages, "tableSettings", pageObject.tName, "maxPages");
firstDisplayed = XVar.Clone((myPages - 1) * gPageSize + 1);
lastDisplayed = XVar.Clone(myPages * gPageSize);
if((XVar)(gPageSize < XVar.Pack(0))  || (XVar)(numRowsFromSQL < lastrecord))
{
	lastDisplayed = XVar.Clone(numRowsFromSQL);
}
pageObject.prepareRecordsIndicator((XVar)(firstDisplayed), (XVar)(lastDisplayed), (XVar)(numRowsFromSQL));
xt.assign(new XVar("pagination_block"), new XVar(false));
limit = new XVar(10);
if(1 < maxPages)
{
	dynamic counter = null, counterend = null, counterstart = null, pageLinks = null;
	xt.assign(new XVar("pagination_block"), new XVar(true));
	pagination = new XVar("");
	counterstart = XVar.Clone(myPages - (limit - 1));
	if(myPages  %  limit != 0)
	{
		counterstart = XVar.Clone((myPages - myPages  %  limit) + 1);
	}
	counterend = XVar.Clone((counterstart + limit) - 1);
	if(maxPages < counterend)
	{
		counterend = XVar.Clone(maxPages);
	}
	if(counterstart != 1)
	{
		pagination = MVCFunctions.Concat(pagination, CommonFunctions.my_getPaginationLink(new XVar(1), new XVar("First")), advSeparator);
		pagination = MVCFunctions.Concat(pagination, CommonFunctions.my_getPaginationLink((XVar)(counterstart - 1), new XVar("Prev")), separator);
	}
	pageLinks = new XVar("");
	counter = XVar.Clone(counterstart);
	for(;counter <= counterend; counter++)
	{
		pageLinks = MVCFunctions.Concat(pageLinks, separator, CommonFunctions.my_getPaginationLink((XVar)(counter), (XVar)(counter), (XVar)(counter == myPages)));
	}
	pagination = MVCFunctions.Concat(pagination, pageLinks);
	if(counterend < maxPages)
	{
		pagination = MVCFunctions.Concat(pagination, separator, CommonFunctions.my_getPaginationLink((XVar)(counterend + 1), new XVar("Next")), advSeparator);
		pagination = MVCFunctions.Concat(pagination, CommonFunctions.my_getPaginationLink((XVar)(maxPages), new XVar("Last")));
	}
	pagination = XVar.Clone(MVCFunctions.Concat(""));
}
xt.assign(new XVar("pagination"), (XVar)(pagination));
return null;

2 thoughts on “SEO-friendly pages in your web application

Leave a Reply

Your email address will not be published. Required fields are marked *