Using ColdFusion to Generate Meta Keywords and Description For Mura CMS

As with most content management systems, Mura CMS offers users the ability to enter a "summary" and "keywords" for each page (among several other options). The problem is, many users fail to utilize these fields. So I whipped up a couple of simple custom display objects to address this problem.

The meta keyword generator will read the main "content" of the page, parse it and then dynamically create a unique listing of keywords. I also use a custom list of keywords to delete common keywords such as "a, in, the," etc., which can easily be modified. This makes for good SEO (search engine optimization) too since you're actually generating keywords based on the actual contents of the page! Let's save the following file as "[siteid]/includes/display_objects/custom/dspMetaKeywordGenerator.cfm"


<cfsilent>
<!---

    Document:        dspMetaKeywordGenerator.cfm
    Version:        20091023.01
    Author:            Stephen Withington | www.stephenwithington.com
    
    Purpose:        I generate meta keywords based on the body content.
    
    Instructions:    Assuming you save this file to:
                    "[siteid]/includes/display_objects/custom/dspMetaKeywordGenerator.cfm"
                    
                    Locate your current meta keywords tag and replace with the following code:
        
                    <cfif len(trim(event.getContentRenderer().getMetaKeywords()))>
                        <meta name="keywords" content="#HTMLEditFormat(event.getContentRenderer().getMetaKeywords())#" />
                        <cfelse>
                        #event.getContentRenderer().dspInclude('display_objects/custom/dspMetaKeywordGenerator.cfm')#
                    </cfif>
                    
                    Typically, the meta keywords tag is found in this file:
                    "[siteid]/includes/themes/[themeName (i.e., "merced")]/templates/inc/html_head.cfm"
                    
    Notes:            Currently ONLY reads the body content and does not take any other content
                    into consideration. (i.e., content objects, etc.)

                    META KEYWORD GENERATOR PROCESS
                    Take the body content then:
                    1.    remove html
                    2.    remove puncuation
                    3.    remove whitespace, line breaks and convert to a list
                    4.    remove duplicate words
                    5.    remove common words (i.e., a, an, the, etc.)
                        - list can be modified in the variable "commonWords" below.
                    6.    sort the list (not necessary, but useful)
    
    Revision Log:    
    20091005.01 - sjw - first draft.
    20091023.01 - sjw - updated mura methods to reflect current best practice: event.getContentRenderer() and event.getContentBean() vs. renderer and request.contentbean, etc.

--->


<!--- LIST OF COMMON WORDS TO BE REMOVED --->
<cfset commonWords = "a, also, an, and, and, are, as, at, be, but, by, for, from, had, have, he, his, i, in, is, it, of, on, or, that, the, they, this, to, too, was, what, who, with" />

<!--- GRAB THE BODY CONTENT --->
<cfset str = event.getContentBean().getBody() />

<!--- helper functions from CFLib.org --->
<cfscript>
/**
* Case-insensitive function for removing duplicate entries in a list.
* Based on dedupe by Raymond Camden
*
* @param list List to be modified. (Required)
* @return Returns a list.
* @author Jeff Howden (cflib@jeffhowden.com)
* @version 1, July 2, 2008
*/

function listDeleteDuplicatesNoCase(list) {
var i = 1;
var delimiter = ',';
var returnValue = '';
if(ArrayLen(arguments) GTE 2)
    delimiter = arguments[2];
    list = ListToArray(list, delimiter);
    for(i = 1; i LTE ArrayLen(list); i = i + 1)
    if(NOT ListFindNoCase(returnValue, list[i], delimiter))
        returnValue = ListAppend(returnValue, list[i], delimiter);
    return returnValue;
}

/**
* Delete items from a list.
*
* @param variable An item, or a list of items, to remove from the list. (Required)
* @param qs The actual list to parse. Can be blank. (Optional)
* @return Returns a string.
* @author Alessandro Chisari (ruchizzy@hotmail.com)
* @version 1, May 17, 2006
*/

function listdelete(variable){
//var to hold the final string
var string = "";
//vars for use in the loop, so we don't have to evaluate lists and arrays more than once
var ii = 1;
var thisVar = "";
var thisIndex = "";
var array = "";
var qs = "";
if(arrayLen(arguments) GT 1)
qs = arguments[2];
//put the query string into an array for easier looping
array = listToArray(qs,",");
//now, loop over the array and rebuild the string
for(ii = 1; ii lte arrayLen(array); ii = ii + 1){
thisIndex = array[ii];
thisVar = thisIndex;
//if this is the var, edit it to the value, otherwise, just append
if(not listFindnocase(variable,thisVar))
string = listAppend(string,thisIndex,",");
}
//return the string
return string;
}
</cfscript>

<cfif len(trim(str))>
    <!--- 1. remove html (using built-in mura method) --->
    <cfset str = event.getContentRenderer().stripHTML(str) />
    <!--- 2. remove punctuation (this regex ain't pretty, but it works) --->
    <cfset str = REReplace(str, "[;\\/:""*?<>|\!\+\-\=\.`\##\&_\(\)\[\]\%\^\$\@~\',\{\}]+", "", "ALL") />

    <!--- 3. remove whitespace, line breaks and convert to a list --->
    <cfset str = REReplace(str, "\s|\r?\n", ",", "ALL") />
    <!--- 4. remove duplicate words --->
    <cfset str = listDeleteDuplicatesNoCase(str) />
    <!--- 5. remove common words --->    
    <cfset str = listdelete(commonWords,str) />
    <!--- 6. sort the list --->
    <cfset str = listSort(lcase(str), "text") />
</cfif>

</cfsilent>
<cfoutput><meta name="keywords" content="#str#" /></cfoutput>

The meta description generator will also read the main "content" of the page and grab the first twenty-five (25) words to automatically create the meta description. This also makes for good SEO since the description matches what's actually found on the page. Let's save the following file at "[siteid]/includes/display_objects/custom/dspMetaDescriptionGenerator.cfm"


<cfsilent>
<!---

    Document:        dspMetaDescriptionGenerator.cfm
    Version:        20091023.01
    Author:            Stephen Withington | www.stephenwithington.com
    
    Purpose:        I generate a meta description based on the body content.
    
    Instructions:    Assuming you save this file to:
                    "[siteid]/includes/display_objects/custom/dspMetaDescriptionGenerator.cfm"
                    
                    Locate your current meta description tag and replace with the following code:
        
                    <cfif len(trim(event.getContentRenderer().getMetaDesc()))>
                        <meta name="description" content="#HTMLEditFormat(event.getContentRenderer().getMetaDesc())#" />
                        <cfelse>
                        #event.getContentRenderer().dspInclude('display_objects/custom/dspMetaDescriptionGenerator.cfm')#
                    </cfif>

                    Typically, the meta description tag is found in this file:
                    "[siteid]/includes/themes/[themeName (i.e., "merced")]/templates/inc/html_head.cfm"
                    
    Notes:            Currently ONLY reads the body content and does not take any other content
                    into consideration. (i.e., content objects, etc.)

                    META DESCRIPTION GENERATOR PROCESS
                    Take the body content then:
                    1.    remove html
                    2.    remove whitespace, line breaks
                    3.    since Google will cut off anything more than
                        155(roughly) characters, let's limit this to the first 25 words.
    
    Props:            Ben Nadel's Post "Displaying A Blog Teaser":
                    http://www.bennadel.com/index.cfm?dax=blog:1718.view
                    John Whish's (www.aliaspooryorik.com/blog/) comments on Ben's Post above.
    
    Revision Log:    
    20091005.01 - sjw - first draft.
    20091023.01 - sjw - updated mura methods to reflect current best practice: event.getContentRenderer() and event.getContentBean() vs. renderer and request.contentbean, etc.
    
--->


<!--- GRAB THE BODY CONTENT --->
<cfset str = event.getContentBean().getbody() />

<cfif len(trim(str))>
    <!--- 1. remove html (using built-in mura method) --->
    <cfset str = event.getContentRenderer().stripHTML(str) />
    <!--- 2. remove whitespace, line breaks and replace with a space between each word --->
    <cfset str = REReplace(str, "[\s|\r?\n]+", " ", "ALL") />    
    <!--- 3. limit to the first 25 words --->
    <cfset javaArray = CreateObject("java","java.util.Arrays") />
    <cfset wordArray = javaArray.copyOf(str.Split( " " ), 26) />
    <cfset str = ArrayToList(wordArray, " ") />
</cfif>

</cfsilent>
<cfoutput><meta name="description" content="#trim(str)#" /></cfoutput>

How To Use

Since there may be users who actually do use the description and/or keywords fields in the admin area of Mura CMS, you want to be sure to check for that first. So, all we need to do is locate where the meta tags are being displayed at, and then replace with some simple code. In most installations, the meta tags can be found in this file "[siteid]/includes/themes/[themeName (i.e., "merced")]/templates/inc/html_head.cfm."


<cfif len(trim(event.getContentRenderer().getMetaDesc()))>
    <meta name="description" content="#HTMLEditFormat(event.getContentRenderer().getMetaDesc())#" />
    <cfelse>
    #event.getContentRenderer().dspInclude('display_objects/custom/dspMetaDescriptionGenerator.cfm')#
</cfif>

<cfif len(trim(event.getContentRenderer().getMetaKeywords()))>
    <meta name="keywords" content="#HTMLEditFormat(event.getContentRenderer().getMetaKeywords())#" />
    <cfelse>
    #event.getContentRenderer().dspInclude('display_objects/custom/dspMetaKeywordGenerator.cfm')#
</cfif>

Hope this helps!

Comments

Good stuff Stephen! I had an idea to create something similar. Having an SEO background (and a new Mura user), I have a ton of ideas that would make Mura way more conducive to SEO.

At this point, listing keywords in the meta tags isn't as important as it once was and it would be awesome to not have to expend extra energy populating this field in Mura. Some less popular engines do consider it, so it's worth it to put something in there. Your feature is the perfect solution.

What would make this even better . . .

Contextual interlinking is a very important aspect of SEO. Interlinking is how you link to your new or deep inner pages, from other already indexed or top level web pages residing under your domain.

While several content pages will sometimes contain the same keyword, there is usually a single page that is best optimized for it. If there was some way to designate this page in Mura as being the best page for that keyword, every other page on the site using that keyword would automatically link to it. Google's spiders loves this structure and it would help SEOs manage the keyword campaign using Mura.

If you were interested in building it, I could easily mock it up for you.
# Posted By Lou Lynch | 10/19/09 12:04 PM
@Lou,

I agree ... listing keywords and the extra time it takes just doesn't seem to have the payoff it once did, hence the reason I went forward with creating this.

Your thoughts on 'contextual interlinking' are very interesting. I'm not familiar though with how this information would be presented at the page level. Is there a meta tag or something else you would use to call this out? I guess it would help if you had a more concrete example for me to review.

If you need to email it to me, use my contact form and I'll reply right back.

Thanks!
# Posted By Stephen Withington | 10/19/09 3:45 PM
I think someone built one for wordpress. Let me see if I can find it.
# Posted By Lou Lynch | 10/19/09 4:33 PM
Found one and sent it to you via your contact form.

The way I see it on page is like this.

In the Mura backend, there would be a section called Keyword Management. In that section one could enter a list of ALL the most popular keyword terms for their niche based on their keyword research.

The report display would be the list of the keywords with one column that indicated the number of content pages containing that keyword and another column indicating whether a content page has been designated as "the best optimized page" for that keyword. Each record would be click-able so that the author could edit either the master keyword page or the supporting keyword pages. This type of dashboard could give the SEO/Author a snap shot on how well his/her content matches up with the most popular keyword terms as well as what content would need to be added to get traffic for terms that don't currently exist on your site.

The second place I see this showing up on-page is in the regular content management area. If after a page was created (maybe on submit), a report (or dialogue box) could somehow be available to the author that would indicate that a keyword(s) from the "keyword Manager" were in the content.

e.g. "The following keywords were found in your page: Keyword 1, Keyword 2, Keyword 3. Would you like to interlink these now?

Each of the keywords could have a check box next to them so the author could choose which words they would want to interlink.

The app would then automatically interlink all of the keywords on the supporting keyword page to master keyword page.

What do you think?
# Posted By Lou Lynch | 10/20/09 8:24 AM
oh one other thing.

If your keyword extractor pulled keywords for the meta tag that matched your keyword research list. It would be more effective to list those keywords first in the meta tag.
# Posted By Lou Lynch | 10/20/09 8:33 AM
@Lou,

Thanks for the information! I haven't had a chance to digest it all yet, but I promised to take a closer look. I've been busy at the office and also working on some presentations I'm giving this weekend.

After giving the quick skim though, it does look useful. I do agree there could be much more done to help improve the SEO-ability (is that even a word?) of Mura.

I like your idea about the "keyword-speedbump" when adding or editing a page. I had already thought about combining any keywords entered in the admin along with other unique keywords from the actual content of the pages too.

Definitely good stuff! Again, thanks and I'll let you know when I have a chance to explore this a bit more.
# Posted By Stephen Withington | 10/20/09 5:49 PM
I have created a couple of mock-up that may make it easier to digest. Where can I send/post them (they are PNGs)?

- Lou
# Posted By Lou Lynch | 10/21/09 8:37 AM
@Lou,
Did you receive an email from me yesterday? If so, go ahead an reply to that. If not, let me know.
# Posted By Stephen Withington | 10/21/09 10:07 AM
Hi Stephen, I am looking at this now, and playing with setting up on my main Mura install - I am a bit confused on renderer.getmetakeywords() vs request.contentBean.getmetakeywords() - the latter is in the Merced templates, while the former is what had in your examples.

With your code, then it seems to inherit keywords - right? When do I choose which, and what are the implications... ?

:)

Cheers,
Hugo
# Posted By Hugo Ahlenius | 10/23/09 8:44 AM
I can just add that the java/array trick didn't work out-of-the-box in OpenBD - but I just replaced that with a loop (to limit at 25 words). Not as clean and smooth, but it works...

Another thing I did - I also added an regexp to filter out html escaped stuff - I saw a lot of &nbsp; in there...
# Posted By Hugo Ahlenius | 10/23/09 12:23 PM
@Hugo,

Yeah, you can access many of the Mura methods all over the place as you're seeing (i.e., the 'request' scope, the 'renderer' scope, etc.).

You're actually going to start noticing the scope 'event' show up more and more in their methods. So, I would actually rewrite the methods you mentioned above as follows:

event.getContentRenderer().getMetaKeywords()
event.getContentRenderer().getMetaDescription()

I'll be updating my examples too.

Also, thanks for the information on Open BD. I'll see if I can find out if there might be a better workaround for you.

# Posted By Stephen Withington | 10/23/09 9:52 PM
Stephen, check out this snippet, it would work on all engines:
http://groups.google.com/group/openbd/browse_threa...
# Posted By Hugo Ahlenius | 11/4/09 4:41 AM

© 2024, Stephen J. Withington, Jr.  |  Hosted by Hostek.com

Creative Commons License   |   This work is licensed under a Creative Commons Attribution 3.0 Unported License.