<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Jamie Nay &#187; seo</title>
	<atom:link href="http://jamienay.com/tag/seo/feed/" rel="self" type="application/rss+xml" />
	<link>http://jamienay.com</link>
	<description>A PHP web developer writing about the web.</description>
	<lastBuildDate>Wed, 02 Jun 2010 17:09:28 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Cascading Dynamic Meta Tags and Page Titles in CakePHP 1.2</title>
		<link>http://jamienay.com/2009/06/cascading-dynamic-meta-tags-and-page-titles-in-cakephp-12/</link>
		<comments>http://jamienay.com/2009/06/cascading-dynamic-meta-tags-and-page-titles-in-cakephp-12/#comments</comments>
		<pubDate>Tue, 16 Jun 2009 14:00:09 +0000</pubDate>
		<dc:creator>Jamie</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[meta tags]]></category>
		<category><![CDATA[seo]]></category>

		<guid isPermaLink="false">http://jamienay.com/?p=162</guid>
		<description><![CDATA[In the course of developing my general purpose CakePHP CMS, I needed a way to manipulate the meta tags, page title, and page heading for every URL in the website. Now, this task is pretty simple when dealing with the pages controller &#8211; just add the appropriate meta columns to the pages table and you&#8217;re [...]]]></description>
			<content:encoded><![CDATA[<p>In the course of developing my general purpose CakePHP CMS, I needed a way to manipulate the meta tags, page title, and page heading for every URL in the website. Now, this task is pretty simple when dealing with the pages controller &#8211; just add the appropriate meta columns to the pages table and you&#8217;re off to the races. But what about URLs that handle other controllers, especially ones that don&#8217;t have CMS-editable page content? </p>
<p>Well, I hmmm&#8217;ed over the idea for a while before settling on my solution: a separate database table, <em>metas</em>. This table and its data would be manipulated like any other MVC component in the CMS, except that each row in the <em>metas</em> table would be associated with a URL on the website. And, since it would be a lot of work to cover absolutely every URL in a website, this Meta feature should cascade, so that if, for example, I don&#8217;t have meta information for &#8220;/places/canada/victoria&#8221;, the code would automatically pull the metas for &#8220;/places/canada&#8221; (and if that&#8217;s empty, &#8220;/places/&#8221;, or just &#8220;/&#8221;).</p>
<p>So I wrote it and, hey, it turned out to be pretty simple and useful, so much so that I whipped up a little tutorial to implement the Meta feature. So without further delay, here&#8217;s the tutorial, and here&#8217;s how it&#8217;s going to go down:</p>
<ol>
<li>Create a <em>metas </em>database table</li>
<li>Create a controller, model, and a couple of views to manipulate our metas</li>
<li>Write a function to get the meta details of the current page</li>
<li>Add meta initialization to AppController</li>
<li>Add the dynamic metas to our layout</li>
</ol>
<h3>Create a <em>metas </em>database table</h3>
<p>Before we build any of the exciting stuff we need to create a <em>metas </em>table in our database (I&#8217;m using MySQL). Mine looks lke this:</p>
<pre class="brush: sql">

CREATE TABLE `metas` (
 `id` int(11) NOT NULL auto_increment,
 `created` datetime default NULL,
 `modified` datetime default NULL,
 `url` varchar(255) NOT NULL,
 `page_header` varchar(255) NOT NULL,
 `meta_description` text,
 `meta_keywords` text,
 `head_title` varchar(255) NOT NULL,
 PRIMARY KEY  (`id`)
) ENGINE=MyISAM;
</pre>
<p>Pretty straightforward: &#8216;url&#8217; is, predictably, the URL of a page we want to create meta information for. &#8216;page_header&#8217; defines the title of the page within the content, while &#8216;head_title&#8217; is used for the title tag in the page head.</p>
<h3>Create a controller, model, and a couple of views to manipulate our metas</h3>
<p>Now that we have our database table, we&#8217;ll need to add the standard MVC components. Let&#8217;s start with the controller.</p>
<p>The <strong>controller</strong> is simple &#8211; it&#8217;s basically a baked version of a standard controller. Nothing fancy at all, except that, instead of having separate admin_edit and admin_add functions, I combine them both in admin_edit (thanks to <a href="http://pseudocoder.com" target="_blank">Matt Curry</a> for the idea). So, /app/controllers/metas_controller.php:</p>
<pre class="brush: php">
&lt;?php
class MetasController extends AppController {
	var $name = &#039;Metas&#039;;

	function admin_index() {
		$this-&gt;set(&#039;metas&#039;, $this-&gt;paginate());
	}

	function admin_edit($id = null) {
		if (!empty($this-&gt;data)) {
			if ($this-&gt;Meta-&gt;save($this-&gt;data)) {
				$this-&gt;Session-&gt;setFlash(__(&#039;The meta information has been saved&#039;, true));
				$this-&gt;redirect(array(&#039;action&#039;=&gt;&#039;index&#039;));
			} else {
			}
		}
		if ($id &amp;amp;&amp;amp; empty($this-&gt;data)) {
			$this-&gt;data = $this-&gt;Meta-&gt;read(null, $id);
		}
	}

	function admin_delete($id = null) {
		if (!$id) {
			$this-&gt;Session-&gt;setFlash(__(&#039;Invalid id for meta&#039;, true));
			$this-&gt;redirect(array(&#039;action&#039;=&gt;&#039;index&#039;));
		}
		if ($this-&gt;Meta-&gt;del($id)) {
			$this-&gt;Session-&gt;setFlash(__(&#039;Meta information deleted&#039;, true));
			$this-&gt;redirect(array(&#039;action&#039;=&gt;&#039;index&#039;));
		}
	}

}
?&gt;
</pre>
<p>The <strong>views </strong>are just as simple as the controller. Again, mostly baked stuff.</p>
<p>/app/views/metas/admin_index.ctp:</p>
<pre class="brush: html">
&lt;div id=&quot;controller_actions&quot;&gt;
 &lt;?php echo $html-&gt;link(__(&#039;New Meta Entry&#039;, true), array(&#039;action&#039;=&gt;&#039;edit&#039;)); ?&gt;&lt;/div&gt;
&lt;div class=&quot;metas index&quot;&gt;
&lt;h2&gt;&lt;?php __(&#039;Meta&#039;);?&gt;&lt;/h2&gt;
&lt;?php
echo $paginator-&gt;counter(array(
&#039;format&#039; =&gt; __(&#039;Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%.&#039;, true)
));
?&gt;
&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tbl_list&quot;&gt;
&lt;tr&gt;
&lt;th&gt;&lt;?php echo $paginator-&gt;sort(&#039;url&#039;);?&gt;&lt;/th&gt;
&lt;th&gt;&lt;?php echo $paginator-&gt;sort(&#039;page_header&#039;);?&gt;&lt;/th&gt;
&lt;th&gt;&lt;?php echo $paginator-&gt;sort(&#039;created&#039;);?&gt;&lt;/th&gt;
&lt;th&gt;&lt;?php echo $paginator-&gt;sort(&#039;modified&#039;);?&gt;&lt;/th&gt;
&lt;th class=&quot;actions&quot;&gt;&lt;?php __(&#039;Actions&#039;);?&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;?php
$i = 0;
foreach ($metas as $meta):
 $class = null;
 if ($i++ % 2 == 0) {
 $class = &#039; class=&quot;altrow&quot;&#039;;
 }
?&gt;
&lt;tr&lt;?php echo $class;?&gt;&gt;
&lt;td&gt;
 &lt;?php echo $meta[&#039;Meta&#039;][&#039;url&#039;]; ?&gt;&lt;/td&gt;
&lt;td&gt;
 &lt;?php echo $meta[&#039;Meta&#039;][&#039;page_header&#039;]; ?&gt;&lt;/td&gt;
&lt;td&gt;
 &lt;?php echo $time-&gt;nice($meta[&#039;Meta&#039;][&#039;created&#039;]); ?&gt;&lt;/td&gt;
&lt;td&gt;
 &lt;?php echo $time-&gt;nice($meta[&#039;Meta&#039;][&#039;modified&#039;]); ?&gt;&lt;/td&gt;
&lt;td class=&quot;actions&quot;&gt;
 &lt;?php echo $html-&gt;link(__(&#039;Edit&#039;, true), array(&#039;action&#039;=&gt;&#039;edit&#039;, $meta[&#039;Meta&#039;][&#039;id&#039;])); ?&gt;
 &lt;?php echo $html-&gt;link(__(&#039;Delete&#039;, true), array(&#039;action&#039;=&gt;&#039;delete&#039;, $meta[&#039;Meta&#039;][&#039;id&#039;]), null, sprintf(__(&#039;Are you sure you want to delete # %s?&#039;, true), $meta[&#039;Meta&#039;][&#039;id&#039;])); ?&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;?php endforeach; ?&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paging&quot;&gt;
 &lt;?php echo $paginator-&gt;prev(&#039;&lt;&lt; &#039;.__(&#039;previous&#039;, true), array(), null, array(&#039;class&#039;=&gt;&#039;disabled&#039;));?&gt;
 | 	&lt;?php echo $paginator-&gt;numbers();?&gt;
 &lt;?php echo $paginator-&gt;next(__(&#039;next&#039;, true).&#039; &gt;&gt;&#039;, array(), null, array(&#039;class&#039;=&gt;&#039;disabled&#039;));?&gt;&lt;/div&gt;
</pre>
<p>/app/views/metas/admin_edit.ctp:</p>
<pre class="brush: html">
&lt;div id=&quot;controller_actions&quot;&gt;
&lt;ul&gt;
 &lt;?php echo $html-&gt;link(__(&#039;Delete&#039;, true), array(&#039;action&#039;=&gt;&#039;delete&#039;, $form-&gt;value(&#039;Meta.id&#039;)), null, sprintf(__(&#039;Are you sure you want to delete # %s?&#039;, true), $form-&gt;value(&#039;Meta.id&#039;))); ?&gt; |
 &lt;?php echo $html-&gt;link(__(&#039;List Metas&#039;, true), array(&#039;action&#039;=&gt;&#039;index&#039;));?&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h2&gt;Add/Edit Meta Entry&lt;/h2&gt;
&lt;?php echo $form-&gt;create(&#039;Meta&#039;, array(&#039;class&#039; =&gt; &#039;editor_form&#039;));?&gt;
 &lt;?php
 echo $form-&gt;input(&#039;id&#039;);
 echo $form-&gt;input(&#039;url&#039;);
 echo $form-&gt;input(&#039;title&#039;, array(&#039;label&#039; =&gt; &#039;Page Title&#039;));
 echo $form-&gt;input(&#039;head_title&#039;, array(&#039;label&#039; =&gt; &#039;Head Title&#039;));
 echo $form-&gt;input(&#039;meta_description&#039;);
 echo $form-&gt;input(&#039;meta_keywords&#039;);
 ?&gt;
&lt;?php echo $form-&gt;end(&#039;Submit&#039;);?&gt;
</pre>
<p>And finally, the<strong> model</strong>. We&#8217;ll make a simple one now and then, in the next step, we&#8217;ll write our meta-finding method.</p>
<p>/apps/models/meta.php:</p>
<pre class="brush: php">
&lt;?php
class Meta extends AppModel  {
 var $name = &#039;Meta&#039;;
}
</pre>
<h3>Write a function to get the meta details of the current page</h3>
<p>OK, so now that we&#8217;ve finished our MVC implementation of the Meta model, let&#8217;s write the meat of our new feature: a function that finds the meta information for the current URL. If no such information is found, we&#8217;ll &#8216;cascade&#8217; down through the URL until we find a match, eventually settling on the root entry (&#8220;/&#8221;) if we can&#8217;t find any other metas.</p>
<p>So, put this function in /app/models/meta.php:</p>
<pre class="brush: php">
/*
 * Get the meta model for the current page ($this-&gt;here from AppController).
 */
function __findCurrentPage($options = array()) {

	if (!isset($options[&#039;url&#039;])) {
		return NULL;
	}

	$url = rtrim($options[&#039;url&#039;], &#039;/&#039;);

	/*
	 * First we try to find a complete match for the URL. If we can find it, or if
	 * we&#039;re at the root of the site, return the results.
	 */
	$meta = $this-&gt;find(&#039;first&#039;, array(&#039;conditions&#039; =&gt; array(&#039;url&#039; =&gt; $url)));
	if (!empty($meta) || $url == &#039;/&#039;) {
		return $meta;
	}

	/*
	 * We didn&#039;t find a match (or we&#039;re not in the root), so now we explode the URL
	 * into its parts (separated by /), and look for a match. In other words, we cascade
	 * down the URL until the root in order to find a meta entry.
	 */
	$urlParts = explode(&quot;/&quot;, trim($url, &quot;/&quot;));
	krsort($urlParts);

	foreach ((array)$urlParts as $part) {
		$url = str_replace(&#039;/&#039;.$part, &#039;&#039;, $url);
		if ($url) {
			$meta = $this-&gt;find(&#039;first&#039;, array(&#039;conditions&#039; =&gt; array(&#039;url&#039; =&gt; $url)));
			if (!empty($meta)) {
				return $meta;
			}
		}
	}

	/*
	 * Still no matching meta, so now we just return the metas for the root.
	 */
	$meta = $this-&gt;find(&#039;first&#039;, array(&#039;conditions&#039; =&gt; array(&#039;url&#039; =&gt; &#039;/&#039;)));
	return $meta;
}
</pre>
<p>So, if our URL is &#8220;/canucks/the-team/ryan-kesler&#8221;, the function will first look for a complete URL match, then move down to &#8220;/canucks/the-team&#8221;, then just &#8220;/canucks&#8221;, and then simply &#8220;/&#8221;. A pretty simple and intuitive way to handle CMS-manipulated meta information.</p>
<h3>Add meta initialization to AppController</h3>
<p>Now that we&#8217;ve finished the bulk of our work, we just need to grab the meta info so it&#8217;s available to our page views. We&#8217;ll accomplish this by adding a handful of simple functions to AppController (/app/app_controller.php):</p>
<pre class="brush: php">
/**
 * Search the metas table for an entry matching the current URL. If no match is
 * found, keep cascading through the URL path separator (&#039;/&#039;) until we find an entry.
 *
 */
private function _configureMeta()
{
	$meta = ClassRegistry::init(&#039;Meta&#039;)-&gt;find(&#039;currentPage&#039;, array(&#039;url&#039; =&gt; $this-&gt;here));
	$meta = $meta[&#039;Meta&#039;];
	$this-&gt;meta = $meta;

	return $this-&gt;meta;
}

// Page title (&lt;title&gt;)
private function _pageTitle()
{
   return ($this-&gt;meta[&#039;head_title&#039;] ? $this-&gt;meta[&#039;page_header&#039;] : NULL);
}

// Page header (&lt;h1)
private function _pageHeader()
{
   return ($this-&gt;meta[&#039;page_header&#039;] ? $this-&gt;meta[&#039;page_header&#039;] : NULL);
}       

// Meta keywords
private function _metaKeywords()
{
	   return (!empty($this-&gt;meta[&#039;meta_keywords&#039;]) ? $this-&gt;meta[&#039;meta_keywords&#039;] : NULL);
}

// Meta description
private function _metaDescription()
{
   return (!empty($this-&gt;meta[&#039;meta_description&#039;]) ? $this-&gt;meta[&#039;meta_description&#039;] : NULL);
}
</pre>
<p>The methods are nothing fancy: _configureMeta() loads the meta info for the current page, calling up the findCurrentPage function we wrote in the Meta class. The other four methods simply grab the information. We&#8217;ll call all of these methods &#8211; thereby setting the view variables &#8211; in AppController::beforeRender():</p>
<pre class="brush: php">
function beforeRender() {
...
    // Grab our dynamic page &lt;title&gt;.
    $this-&gt;pageTitle = $this-&gt;_pageTitle();

    // Set the page header.
    $this-&gt;set(&#039;pageHeader&#039;, $this-&gt;_pageHeader());

    // Grab our dynamic meta keywords and description.
    $this-&gt;set(&#039;metaKeywords&#039;, $this-&gt;_metaKeywords());
    $this-&gt;set(&#039;metaDescription&#039;, $this-&gt;_metaDescription());
...
}
</pre>
<p>OK! We&#8217;ve set $title_for_layout with _pageTitle(), and we&#8217;ve added three new variables to the view, $pageHeader, $metaKeywords, and $metaDescription. There&#8217;s just one more thing to do&#8230;</p>
<h3>Add the dynamic metas to our layout</h3>
<p>I&#8217;m not going to give a complete layout file here, since this is a basic step. Basically I just want to remind everyone to use CakePHP&#8217;s HTML helper for the meta keywords and meta description (the other two variables just need simple echo calls):</p>
<pre class="brush: php">
echo $html-&gt;meta(&#039;keywords&#039;, $metaKeywords);
echo $html-&gt;meta(&#039;description&#039;, $metaDescription);
</pre>
<p>And that&#8217;s it. Now you can have dynamic, cascading meta tags and page titles for your CakePHP application. As always, suggestions, improvements, critiques welcome.</p>
]]></content:encoded>
			<wfw:commentRss>http://jamienay.com/2009/06/cascading-dynamic-meta-tags-and-page-titles-in-cakephp-12/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
