{"id":2015,"date":"2019-07-23T16:30:48","date_gmt":"2019-07-23T21:30:48","guid":{"rendered":"https:\/\/xlinesoft.com\/blog\/?p=2015"},"modified":"2019-07-24T11:00:36","modified_gmt":"2019-07-24T16:00:36","slug":"seo-friendly-pages-in-your-web-application","status":"publish","type":"post","link":"https:\/\/xlinesoft.com\/blog\/2019\/07\/23\/seo-friendly-pages-in-your-web-application\/","title":{"rendered":"SEO-friendly pages in your web application"},"content":{"rendered":"<p>There was an <a href=\"https:\/\/asprunner.com\/forums\/topic\/6341-mod-rewrite-for-seo-friendly-pages\/\">article<\/a> 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. <\/p>\n<p>We have helped one of our clients to implement this solution in their project. They run a website that lists public voters&#8217; 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. <\/p>\n<p>So, for instance, instead of this View page URL:<\/p>\n<pre name=\"code\" class=\"php:nocontrols\">https:\/\/www.keyword-rich-website-name.com\/tablename_view.php?editid1=125745441-Dean-Abbondanza-Venice-FL<\/pre>\n<p>We want to see this:<\/p>\n<pre name=\"code\" class=\"php:nocontrols\">https:\/\/www.keyword-rich-website-name.com\/voters-125745441-Dean-Abbondanza-Venice-FL<\/pre>\n<p>And we would want to prettify List page URLs as well. For instance, instead of <\/p>\n<pre name=\"code\" class=\"php:nocontrols\">https:\/\/www.keyword-rich-website-name.com\/tablename_list.php?goto=3<\/pre>\n<p>We want to see this:<\/p>\n<pre name=\"code\" class=\"php:nocontrols\">https:\/\/www.keyword-rich-website-name.com\/voters-list-3<\/pre>\n<p>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. <\/p>\n<h2>1. Redirect rules<\/h2>\n<p>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:<\/p>\n<pre name=\"code\" class=\"php:nocontrols\">Options +FollowSymLinks\r\nRewriteEngine On\r\nRewriteRule ^voters-([0-9][A-Za-z0-9-]+)$ vsar0604_view.php?editid1=$1\r\nRewriteRule ^voters-list-([A-Za-z0-9-]+)$ vsar0604_list.php?goto=$1<\/pre>\n<p><strong>vsar0604<\/strong> here is a table name. There are only two rules, one per page type and they are easy to understand. <strong>([A-Za-z0-9-]+)$<\/strong> 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 <strong>$1<\/strong> is our match from the first part of the rule. <\/p>\n<p>This may sound complicated if you never worked with regular expressions before. I found this <a href=\"https:\/\/htaccess.madewithlove.be\/\">.htaccess tester tool<\/a> very valuable. You enter your rules and the URL to test against and it tells you what rules will match. <\/p>\n<p>If you want to learn more about URL Rewrite rules I recommend this <a href=\"https:\/\/aloneonahill.com\/blog\/url-rewriting-for-beginners\">excellent article<\/a>.<\/p>\n<p><a href=\"https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2019\/07\/htaccess_tester.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2019\/07\/htaccess_tester-300x173.png\" alt=\"\" width=\"300\" height=\"173\" class=\"alignnone size-medium wp-image-2019\" srcset=\"https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2019\/07\/htaccess_tester-300x173.png 300w, https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2019\/07\/htaccess_tester-768x443.png 768w, https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2019\/07\/htaccess_tester-1024x591.png 1024w, https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2019\/07\/htaccess_tester-900x515.png 900w, https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2019\/07\/htaccess_tester.png 1165w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<h2>2. Project changes <\/h2>\n<p>We would need to make changes to the way links to view page and pagination links are displayed. <\/p>\n<h4>List Page: After Record Processed event<\/h4>\n<p>PHP code:<\/p>\n<pre name=\"code\" class=\"php:nocontrols\">$link = $record[\"viewlink_attrs\"];\r\n$p1 = strpos($link, \"href='\");\r\n$p2 = strpos($link, \"editid1=\");\r\n$url = substr($link,$p2+8,-1);\r\n$link = substr($link,0,$p1).\" href='voters-\".$url.\"'\";\r\n$record[\"viewlink_attrs\"] = $link;<\/pre>\n<p>C# code: <\/p>\n<pre name=\"code\" class=\"csharp:nocontrols\">dynamic link = null, p1 = null, p2 = null, url = null;\r\nlink = XVar.Clone(record[\"viewlink_attrs\"]);\r\np1 = XVar.Clone(CommonFunctions.strpos((XVar)(link), new XVar(\"href='\")));\r\np2 = XVar.Clone(CommonFunctions.strpos((XVar)(link), new XVar(\"editid1=\")));\r\nurl = XVar.Clone(CommonFunctions.substr((XVar)(link), (XVar)(p2 + 8), new XVar(-1)));\r\nlink = XVar.Clone(MVCFunctions.Concat(CommonFunctions.substr((XVar)(link), new XVar(0), (XVar)(p1)), \" href='voters-\", url, \"'\"));\r\nrecord.InitAndSetArrayItem(link, \"viewlink_attrs\");<\/pre>\n<h4>List Page: Before Display event<\/h4>\n<p>PHP code: <\/p>\n<pre name=\"code\" class=\"php:nocontrols\">function my_getPaginationLink($pageNum, $linkText, $active = false)\r\n{\r\n   return '&lt;li class=\"' . ( $active ? \"active\" : \"\" ) . '\">&lt;a href=\"voters-list-'.$pageNum.'\">'.$linkText.'&lt;\/a>&lt;\/li>';\r\n}\r\n\r\n$rs = CustomQuery(\"select count(*) as cnt from (\".$pageObject->querySQL.\") as a\");\r\n$data = db_fetch_array($rs);\r\n$numRowsFromSQL = $data[\"cnt\"];\r\n\r\nif(!$_SESSION[$pageObject->sessionPrefix.\"_mypagesize\"])\r\n\t$_SESSION[$pageObject->sessionPrefix.\"_mypagesize\"] = $pageObject->gPageSize;\r\nif(postvalue(\"pagesize\"))\t\r\n\t$_SESSION[$pageObject->sessionPrefix.\"_mypagesize\"] = postvalue(\"pagesize\");\r\n\r\n$gPageSize=$_SESSION[$pageObject->sessionPrefix.\"_mypagesize\"];\r\n\r\n$separator = \"\";\r\n$advSeparator = \"\";\r\n$myPages = postvalue(\"goto\");\r\nif(!$myPages)\r\n\t$myPages=1;\r\n\r\nif($gPageSize && $gPageSize!=-1)\r\n\t$maxPages = ceil($numRowsFromSQL \/ $gPageSize);\r\nif($myPages > $maxPages)\r\n\t$myPages = $maxPages;\r\nif($myPages < 1)\r\n\t$myPages = 1;\r\n$recordsOnPage = $numRowsFromSQL -($myPages - 1) * $gPageSize;\r\nif($recordsOnPage > $gPageSize && $gPageSize!=-1)\r\n\t$recordsOnPage = $gPageSize;\r\n\r\n$pageObject->jsSettings[\"tableSettings\"][$pageObject->tName]['maxPages'] = $maxPages;\r\n\r\n$firstDisplayed = ( $myPages - 1 )\t * $gPageSize + 1;\r\n$lastDisplayed = ( $myPages ) * $gPageSize;\r\nif( $gPageSize < 0 || $lastrecord > $numRowsFromSQL )\r\n\t$lastDisplayed = $numRowsFromSQL;\r\n\r\n$pageObject->prepareRecordsIndicator( $firstDisplayed, $lastDisplayed, $numRowsFromSQL );\r\n$xt->assign(\"pagination_block\", false);\r\n$limit=10;\r\nif($maxPages > 1) \r\n{\r\n\t$xt->assign(\"pagination_block\", true);\r\n\t$pagination = '';\r\n\t$counterstart = $myPages - ($limit-1);\r\n\r\n\tif($myPages % $limit != 0)\r\n\t\t$counterstart = $myPages -($myPages % $limit) + 1;\r\n\t$counterend = $counterstart + $limit-1;\r\n\tif($counterend > $maxPages)\r\n\t\t$counterend = $maxPages;\r\n\tif($counterstart != 1) \r\n\t{\r\n\t\t$pagination.= my_getPaginationLink(1,\"First\") . $advSeparator;\r\n\t\t$pagination.= my_getPaginationLink($counterstart - 1,\"Prev\").$separator;\r\n\t}\r\n\t$pageLinks = \"\";\r\n\tfor($counter = $counterstart; $counter <= $counterend; $counter ++) \r\n\t{\r\n\t\t$pageLinks .= $separator . my_getPaginationLink($counter,$counter, $counter == $myPages );\r\n\t}\r\n\t\r\n\t$pagination .= $pageLinks;\r\n\tif($counterend < $maxPages) \r\n\t{\r\n\t\t$pagination.= $separator . my_getPaginationLink($counterend + 1,\"Next\") . $advSeparator;\r\n\t\t$pagination.= my_getPaginationLink($maxPages,\"Last\");\r\n\t}\r\n\t$pagination = '<nav class=\"text-center\"><ul class=\"pagination\">' . $pagination . '<\/ul><\/nav>';\r\n}\r\n\r\n$xt->assign(\"pagination\",$pagination);<\/pre>\n<p>C# code: <\/p>\n<pre name=\"code\" class=\"csharp:nocontrols\">string my_getPaginationLink(dynamic pageNum, dynamic linkText, dynamic active = null) {\r\nreturn MVCFunctions.Concat(\"&lt;li class=\\\"\", (XVar.Pack(active) ? XVar.Pack(\"active\") : XVar.Pack(\"\")), \"\\\">&lt;a href=\\\"voters-list-\", pageNum, \"\\\">\", linkText, \"&lt;\/a>&lt;\/li>\");\r\n}\r\n\r\ndynamic advSeparator = null, firstDisplayed = null, gPageSize = null, lastDisplayed = null, lastrecord = null, limit = null, maxPages = null, myPages = null, numRowsFromSQL = null, pagination = null, recordsOnPage = null, separator = null;\r\nrs = XVar.Clone(CommonFunctions.db_query(MVCFunctions.Concat(\"select count(*) as cnt from (\", pageObject.querySQL, \") as a\"), GlobalVars.conn));\r\ndata = XVar.Clone(CommonFunctions.db_fetch_array((XVar)(rs)));\r\nnumRowsFromSQL = XVar.Clone(data[\"cnt\"]);\r\nif(XVar.Pack(!(XVar)(XSession.Session[MVCFunctions.Concat(pageObject.sessionPrefix, \"_mypagesize\")])))\r\n{\r\n\tXSession.Session[MVCFunctions.Concat(pageObject.sessionPrefix, \"_mypagesize\")] = pageObject.gPageSize;\r\n}\r\nif(XVar.Pack(MVCFunctions.postvalue(new XVar(\"pagesize\"))))\r\n{\r\n\tXSession.Session[MVCFunctions.Concat(pageObject.sessionPrefix, \"_mypagesize\")] = MVCFunctions.postvalue(new XVar(\"pagesize\"));\r\n}\r\ngPageSize = XVar.Clone(XSession.Session[MVCFunctions.Concat(pageObject.sessionPrefix, \"_mypagesize\")]);\r\nseparator = new XVar(\"\");\r\nadvSeparator = new XVar(\"\");\r\nmyPages = XVar.Clone(MVCFunctions.postvalue(new XVar(\"goto\")));\r\nif(XVar.Pack(!(XVar)(myPages)))\r\n{\r\n\tmyPages = new XVar(1);\r\n}\r\nif((XVar)(gPageSize)  && (XVar)(gPageSize != -1))\r\n{\r\n\tmaxPages = XVar.Clone((XVar)Math.Ceiling((double)(numRowsFromSQL \/ gPageSize)));\r\n}\r\nif(maxPages < myPages)\r\n{\r\n\tmyPages = XVar.Clone(maxPages);\r\n}\r\nif(myPages < 1)\r\n{\r\n\tmyPages = new XVar(1);\r\n}\r\nrecordsOnPage = XVar.Clone(numRowsFromSQL - (myPages - 1) * gPageSize);\r\nif((XVar)(gPageSize < recordsOnPage)  &#038;&#038; (XVar)(gPageSize != -1))\r\n{\r\n\trecordsOnPage = XVar.Clone(gPageSize);\r\n}\r\npageObject.jsSettings.InitAndSetArrayItem(maxPages, \"tableSettings\", pageObject.tName, \"maxPages\");\r\nfirstDisplayed = XVar.Clone((myPages - 1) * gPageSize + 1);\r\nlastDisplayed = XVar.Clone(myPages * gPageSize);\r\nif((XVar)(gPageSize < XVar.Pack(0))  || (XVar)(numRowsFromSQL < lastrecord))\r\n{\r\n\tlastDisplayed = XVar.Clone(numRowsFromSQL);\r\n}\r\npageObject.prepareRecordsIndicator((XVar)(firstDisplayed), (XVar)(lastDisplayed), (XVar)(numRowsFromSQL));\r\nxt.assign(new XVar(\"pagination_block\"), new XVar(false));\r\nlimit = new XVar(10);\r\nif(1 < maxPages)\r\n{\r\n\tdynamic counter = null, counterend = null, counterstart = null, pageLinks = null;\r\n\txt.assign(new XVar(\"pagination_block\"), new XVar(true));\r\n\tpagination = new XVar(\"\");\r\n\tcounterstart = XVar.Clone(myPages - (limit - 1));\r\n\tif(myPages  %  limit != 0)\r\n\t{\r\n\t\tcounterstart = XVar.Clone((myPages - myPages  %  limit) + 1);\r\n\t}\r\n\tcounterend = XVar.Clone((counterstart + limit) - 1);\r\n\tif(maxPages < counterend)\r\n\t{\r\n\t\tcounterend = XVar.Clone(maxPages);\r\n\t}\r\n\tif(counterstart != 1)\r\n\t{\r\n\t\tpagination = MVCFunctions.Concat(pagination, CommonFunctions.my_getPaginationLink(new XVar(1), new XVar(\"First\")), advSeparator);\r\n\t\tpagination = MVCFunctions.Concat(pagination, CommonFunctions.my_getPaginationLink((XVar)(counterstart - 1), new XVar(\"Prev\")), separator);\r\n\t}\r\n\tpageLinks = new XVar(\"\");\r\n\tcounter = XVar.Clone(counterstart);\r\n\tfor(;counter <= counterend; counter++)\r\n\t{\r\n\t\tpageLinks = MVCFunctions.Concat(pageLinks, separator, CommonFunctions.my_getPaginationLink((XVar)(counter), (XVar)(counter), (XVar)(counter == myPages)));\r\n\t}\r\n\tpagination = MVCFunctions.Concat(pagination, pageLinks);\r\n\tif(counterend < maxPages)\r\n\t{\r\n\t\tpagination = MVCFunctions.Concat(pagination, separator, CommonFunctions.my_getPaginationLink((XVar)(counterend + 1), new XVar(\"Next\")), advSeparator);\r\n\t\tpagination = MVCFunctions.Concat(pagination, CommonFunctions.my_getPaginationLink((XVar)(maxPages), new XVar(\"Last\")));\r\n\t}\r\n\tpagination = XVar.Clone(MVCFunctions.Concat(\"<nav class=\\\"text-center\\\"><ul class=\\\"pagination\\\">\", pagination, \"<\/ul><\/nav>\"));\r\n}\r\nxt.assign(new XVar(\"pagination\"), (XVar)(pagination));\r\nreturn null;<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>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&#8217; information in certain Florida counties. The objective is to make these pages rank high when someone searches for county, city or neighborhood&#8230;<span class=\"clearfix clearfix-post\"><\/span><a href=\"https:\/\/xlinesoft.com\/blog\/2019\/07\/23\/seo-friendly-pages-in-your-web-application\/\" class=\"more-link\">Continue Reading <span class=\"screen-reader-text\">&#8220;SEO-friendly pages in your web application&#8221;<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[16,10,1,4,8],"tags":[],"_links":{"self":[{"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/posts\/2015"}],"collection":[{"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/comments?post=2015"}],"version-history":[{"count":12,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/posts\/2015\/revisions"}],"predecessor-version":[{"id":2028,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/posts\/2015\/revisions\/2028"}],"wp:attachment":[{"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/media?parent=2015"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/categories?post=2015"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/tags?post=2015"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}