Programmers Guide MuraCMS
Programmers Guide MuraCMS
Programmers Guide MuraCMS
1
FILE STRUCTURE! 4
Core Directories and Files!......................................................................................................................4
Site Directories! 4
Basic Structure!.......................................................................................................................................4
2
Using addEventHandler() in a Plugin!....................................................................................................31
Custom Caching! 51
How to use it!.........................................................................................................................................51
Additional Cache Settings!....................................................................................................................51
The cf_cacheomatic tag:!......................................................................................................................52
3
FILE STRUCTURE
Core Directories and Files
When you first install Mura CMS, you'll see a standard set of files and folders:
/admin - This contains the main Mura content administration code.
/config - This contains the configuration files - including the settings.ini.cfm where Mura stores itʼs core settings.
Attribute values can contain dynamic expression denoted
inside ${expression}.
/default - This directory contains all the site-specific files and will be duplicated and renamed for each site you create
within Mura. If you have multiple sites within your Mura instance, you will see each site's folder added at the same
level as /default (each site folder will be named based on the siteID you enter in the site settings when adding that
site)
/plugins - This contains deployed plugins. At initial Mura installation, this will be empty.
/requirements - This contains the core Mura CMS code, as well as any CFML. frameworks that Mura requires
/tasks - This directory contains various utilities including ckeditor and ckfinder.
Site Directories
Basic Structure
/{siteID}/includes/display_objects/custom - This directory is where developers can put custom display object code.
/{siteID}/includes/email - This directory contains the email template(s) used in the email broadcaster.
/{siteID}/includes/plugins - This directory is where plugins that are configured to run their display objects locally are
deployed (In addition to /plugins).
4
/{siteID}/includes/resourceBundles - This directory contains the resource bundles for the built-in display objects.
/{siteID}/includes/templates - In this directory are the templates available for any given site when no theme is
applied. This directory has been deprecated in favor of moving toward requiring themes.
/{siteID}/includes/themes/{theme}/templates - This directory contains the templates that are available for any given
site that is using this theme.
/{siteID}/includes/themes/{theme}/templates/
components - This directory contains the templates
designed for “Components” that are available for any
given site that is using this theme.
/{siteID}/includes/themes/{theme}/display_objects -
This directory contains display object files that have been
packaged with this theme.
/{siteID}/includes/themes/{theme}/resourceBundles -
This directory contains resource bundle files that have
been packaged with this theme. You only need to add
keys that are unique to this theme.
5
The Mura Tag
Mura provides a markup syntax that allows users to insert dynamic expressions into the
Mura admin UI.
Examples:
dspInclude
Displays the given cfm file in the context of your page:
[mura]$.dspInclude('templates/inc/theFile.cfm')[/mura]
[mura]$.dspInclude('display_objects/custom/theFile.cfm')[/mura]
dspObject
Rendering a feed:
[mura]$.dspObject('feed' ,{feedId})[/mura]
Rendering a Component:
[mura]$.dspObject('component', {contentId}[/mura]
createObject
ColdFusion Functions
[mura]now()[/mura]
OTHER USAGES:
• Feed advanced param critera values.
• Extended attribute default values, option list and option list label.
6
The Mura Event Model
The Mura application flow is a chain of events that fire in sequence. Each link in the chain
can be intercepted to either (1) provide additional or (2) replace existing business logic.
This allows for extreme ease of customization.
The most common objects used during a front end page request are:
7
Life-Cycle of a Front End Request
This is the general life-cycle of events that get fired when a Front-End Request is made
(page render, etc.). Note that Events that begin with "onGlobal" are defined on a per-Mura
instance basis.
Events that start with “standard” are actually units of implemented logic that you can replace. As a references you
can find them in the following directories:
• /requirments/mura/handler
• /requirments/mura/validator
• /requirments/mura/transaltor
Events that start with “on” are places where you can add additional logic.
onGlobalRequestStart
onSiteRequestInit
onSiteRequestStart
standardSetContentRenderer
standardSetContentHandler
! standardSetPreviewHandler
! standardSetAdTrackingHandler
! standard404Validator
! standard404Handler
! ! onSite404
standardWrongDomainValidator
standardWrongDomainHandler
standardTrackSessionValidator
! standardTrackSessionHandler
standardSetPermissionHandler
standardSetIsOnDisplayHandler
standardDoActionsHandler
standardRequireLoginValidator
! standardRequireLoginHandler
standardSetLocaleHandler
standardDoResponseHandler
! onRenderStart
! ! standardLinkTranslator
! ! standardFileTranslator
!! onBeforeFileRender
!! onAfterFileRender
! ! standardTranslationHandler
!! standardHTMLTranslator
! ! ! onSiteEditProfileRender
! ! ! onSiteSearchRender
! ! ! onSiteLoginPromptRender
! ! ! onContentOffLineRender
! ! ! onContentDenialRender
! ! ! on{type}{subType}BodyRender
8
! ! ! on{type}BodyRender
! ! ! onBeforeFormSubmitRedirect
Contextual Events
Contextual events are fired only in response to other events - they are only fired when
needed.
It is imported to note that contextual events only contain data that is directly supplied to it by the Mura core. If you
need to access to main Mura front end or Admin event you can access it with the Mura Scope object:
<cfset globalEvent =$.getGlobalEvent() >
Application Events
onApplicationLoad! ! ! onSiteSessionStart
onGlobalSessionStart! ! ! onSiteSessionEnd
onSiteMissingTemplate! ! ! onSiteError
onGlobalError! ! ! ! onBeforeAutoUpdate
onAfterAutoUpdate! ! ! onGlobalThreatDetect
9
Login Events
onSiteLogin! ! ! onGlobalLogin
onSiteLoginSuccess! ! onGlobalLoginSuccess
onSiteLoginBlocked! ! onGlobalLoginBlocked
Content Events
Node-Level events only fire for node-level content types (ie. Page, Portal, Gallery, File, Calendar, Gallery)
onAfter{type}Save
onAfter{type}{subType}Save
onAfterContentSave (Node Level Only)
onBeforeContentSort
onAfterContentSort
onAfterCommentUpdate!! ! onAfterCommentCreate
onAfterCommentSave! ! ! onAfterCommentDelete
10
Category Events
onBeforeCategoryUpdate! ! onBeforeCategoryCreate
onBeforeCategorySave! ! ! onBeforeCategoryDelete
onAfterCategoryUpdate!! ! onAfterCategoryCreate
onAfterCategorySave! ! ! onAfterCategoryDelete
Feed Events
onBeforeFeedUpdate! ! ! onBeforeFeedCreate
onBeforeFeedSave! ! ! onBeforeFeedDelete
onAfterFeedUpdate! ! ! onAfterFeedCreate
onAfterFeedSave! ! ! onAfterFeedDelete
User Events
onBeforeUserUpdate! ! ! onBeforeUserCreate
onBeforeUserSave! ! ! onBeforeUserDelete
onBeforeUser{subType}Update! ! onBeforeUser{subType}Create
onBeforeUser{subType}Save! ! onBeforeUser{subType}Delete
onAfterUserUpdate! ! ! onAfterUserCreate
onAfterUserSave! ! ! onAfterUserDelete
onAfterUser{subType}Update! ! onAfterUser{subType}Create
onAfterUser{subType}Save! ! onAfterUser{subType}Delete
11
The Mura Scope
The Mura Scope provides a standard, concise syntax for interfacing with Mura objects
(and their properties and events).
It is available as either "$" or "mura".
<cffunction name="onRenderStart" output="false">
<cfargument name="$">
<cfset property=$.event({property})>
<!--- do stuff--->
</cffunction>
If you are writing code that will run outside of the normal Mura event model you can obtain an istance it from the
application.serviceFactory object.
<cfset $=application.serviceFactory.getBean(“muraScope”)>
You then can init it with either an instance of an event object, a struct containing value pairs or simply a siteID string
value.
<cfset $.init(event)>
<cfset $.init(myStruct)>
<cfset $.init(session.siteID)>
If you init your MuraScope instance with an event object or struct be sure to include a siteID attribute if it is available.
<cfset event.setValue({siteID})>
<cfset $.init(event)>
<cfset myStruct.siteID={siteid}>
<cfset $.init(myStruct)>
Content Scope
The Content scope wraps the current front end request's contentBean. All values that you
would previously get with request.contentBean.get{Attribute} or event.getContentBean
().get{Attribute} are now accessible through the Content Scope.
It is important to understand that the content scope is only available during the front-end request lifecycle.
Returns value:
$.content({property});
12
Returns full contentBean:
$.content();
$.getParent();
$.content().getKidsIterator();
$.content().getCategoriesIterator();$.content().getRelatedContentIterator();
$.content().getCommentsIterator();$.content().getVersionHistoryIterator();
$.content().getStats().getComments();
$.content().getCrumbIterator();
$.content().getURL({querystring});
$.content().save();
Event Scope
The Event scope simply wraps the current request's event object which contains merged
data from both the CFML FORM and URL scopes.
Returns value:
$.event('property');
$.event();
In contextual events you are able to access the outer global event object with $.getGlobalEvent().
Component Scope
The Component scope is only available from within Mura Components and templates are
assigned to Mura Components.
Returns value:
$.component({property});
$.component();
13
A simple example of using the Component Scope in a template:
<cfoutput>
<div>#$.setDynamicContent($.component('body'))#</div>
</cfoutput>
$.currentUser({property},{propertyValue});
Returns sessionUserFacade:
$.currentUser();
You can also save any changes that may be made to user during a session:
$.currentUser(‘fname’,‘Robert’);
$.currentUser().save();
$.siteConfig();
14
Sets and returns the value:
$.globalConfig({property},{property value});
$.globalGlobal();
Any custom attributes set in the Mura instances /config/settings.ini.cfm are available:
$.siteConfig(‘customVar’);
Helper Methods
For example, value content, feed, user, category, userManager:
$.getBean({bean type});
$.announceEvent({EventToAnnounce});
$.renderEvent({EventToRender});
$.getPlugin({package|pluginID|moduleID});
$.dspInclude({path_to_file});
$.dspObjects({displayRegion});
$.dspObject({type}, {objectID});
$.createHREF(filename={filename} [, type={type}, contentID= {contentID}, complete=
{complete}, showMeta={showMeta}, queryString={queryString}] );
$.getTopID();
$.getTopVar({varName}};
...
Built-In Methods
$.currentUser().setFName({value});
$.currentUser().setValue({property},{property value});
$.currentUser().getMembershipsIterator();
$.currentUser().getAddressesIterator();
$.currentUser().readAddress(addressName={addressName});
$.currentUser().getInterestGroupsIterator();
$.currentUser().save();
....
Helper Methods
15
$.currentUser().isSuperUser();
$.currentUser().isPrivateUser();
$.currentUser().isInGroup({groupname} [,{isPublic}] );
$.currentUser().isLoggedIn();
$.currentUser().logout();
16
Understanding Base Mura Objects
Mura Beans
Beans provide you with the ability to directly access and manipulate their associated
records in the database. This can be accomplished by using the beans' helper methods
(see below for examples).
BEAN.LOADBY({PROPERTIES...}):
This new method gives you the ability to load persistent data from the database based on
the properties passed.
bean=$.getBean(
{beantype} ).loadBy({property}={propertyValue} [,siteID={siteID}] );
For example, if the following request had more that one matches:
The “loadByResponse” variable would be an array of contentBean objects and the “content” variable would the
contentBean in the first position of the array.
BEAN.GETVALUE({PROPERTY});
This existing method has been expanded to provide an easy hook to extended attributes. Simply pass in the variable
name for the attribute and your value will be retrieved. No more needing to use getExtendedAttribute (it's still in there
though, in case you rely on it).
Examples:
Get some extended data:
17
Get a common field:
value = bean.getMenuTitle();
value = bean.getValue(‘MenuTitle’);
Example:
Set extended information:
BEAN.SAVE()
The save method is quite simple. If you want to save the data in the bean to the database (add or update), just call
this method.
Example:
Set extended information:
bean.setValue( 'menuTitle', 'value' );
bean.save();
DELETE()
As long as the data is persistent, when you call this method, it will delete the associated record from the database.
Example:
Let's delete the record:
bean.delete();
Content Bean
Load by contentID:
content = $.getBean( "content" ).loadBy( contentID={contentID} [, siteID={siteID}] );
Load by contentHistID:
content = $.getBean( "content" ).loadBy( contentHistID={contentHistID} [, siteID=
{siteID}] );
Load by remoteID:
content = $.getBean( "content" ).loadBy( remoteID={remoteID} [, siteID={siteID}] );
18
Key methods:
content.set({args});
content.save();
content.delete();
content.getParent()
content.getIsNew();
content.getURL({queryString});
content.hasDrafts();
content.deleteVersion();
content.deleteVersionHistory();
content.getKidsIterator([{liveOnly},{aggregation}]);
content.getKidsQuery([{liveOnly},{aggregation}]);
content.getCrumbIterator();
content.getCrumbArray();
content.getCommentsIterator();
content.getCommentsQuery();
content.getCategoriesIterator();
content.getCategoriesQuery();
content.getVersionHistoryIterator();
content.getVersionHistoryQuery();
content.getRelatedContentIterator();
content.getRelatedContentQuery();
content.setNewFile({filePath/URL});
Key methods:
comment.set({args});
comment.save();
comment.delete();
comment.getUser();
comment.getParent();
comment.getIsNew();
comment.getKidsIterator([{boolean:isEditor}]);
comment.getKidsQuery([{boolean:isEditor}]);
comment.getCrumbIterator();
comment.getCrumbQuery();
User Bean
Load by userID:
user = $.getBean( "user" ).loadBy( userID={userID} );
Load by username:
19
user = $.getBean( "user" ).loadBy( username={username} [, siteID={siteID}] );
Load by remoteID:
Load by groupname:
group = $.getBean( "user" ).loadBy( groupname={groupname}, siteID={siteID} [,
{isPublic}] );
user.readAddress(addressID={addressID});
Key methods:
user.set({args});
user.save();
user.delete();
user.getIsnew();
user.addAddress({address});
user.getAddressssIterator();
user.getAddressesQuery();
user.getMembershipsIterator();
user.getMembershipsQuery();
user.getMembersIterator();
user.getMembersQuery();
user.getInterestGroupsIterator();
user.getInterestGroupsQuery();
Category Bean
Load by categoryID:
category = $.getBean( "category" ).loadBy( categoryID={categoryID} [, siteID=
{siteID}] );
Load by name:
category = $.getBean( "category" ).loadBy( name={name} [, siteID={siteID}] );
Load by remoteID:
20
Key methods:
category.set({args});
category.save();
category.delete();
category.getParent();
category.getIsNew();
category.getKidsIterator();
category.getKidsQuery();
category.getCrumbIterator();
category.getCrumbQuery();
Site Bean
The siteBean can be accessed from the Mura Scope as well as directly from the settingsManager. It contains key
information methods to reference a specific site configuration.
site=getBean("settingsManager").getSite({siteID});
Key Methods:
These methods return the starting URL path to be used with things like JS and CSS file referencing:
site.getAssetPath();
site.getThemeAssetPath();
These methods return the starting file path to be used for <cfinclude>:
site.getIncludePath();
site.getThemeIncludePath();
These methods return the starting component path for object instatiation:
site.getAssetMap();
site.getThemeAssetMap();
This returns an instance of the site's contentRenderer for use with static methods:
site.getContentRenderer();
This returns an instance of the themeʼs contentRenderer for use with static methods:
site.getThemeRenderer();
21
Mura Iterators
Mura iterators make managing content and users simpler by providing direct access to
objects.
A Mura Iterator wraps a traditional CFML query object. It is most often used in conditional loop statements:
<cfloop condition=”Iterator.hasNext()”>
And the first line inside of the condition loop is obtaining a reference to the actual next value object:
<cfset item=Iterator.next()>
And you then can use it for whatever the purpose of the iteration is for:
<cfoutput>#HTMLEditFormat(item.getValue(‘myVar’))#</cfoutput>
It is very important to note that the the Iterator.hasNext() is page aware. The Mura Iterator was built with pagination in
mind. This means that it will hasNext() with only return true if it was iterated through less than the value of it
Iterator.getNexttN() value.
For example, if an iterator has 20 items inside of it the following code would output the first 10:
<cfset iterator.setNextN(10)>
<cfloop condition=”Iterator.hasNext()”>
<cfset item=Iterator.next()>
<cfoutput>#HTMLEditFormat(item.getValue(‘myVar’))#</cfoutput>
</cfloop>
If you set the value of the Iteratorʼs nextN to 0 it will set the next to the recordcount of the current query.
<cfset iterator.setNextN(0)>
You can change the page that is iterated through by settings the iteratorʼs current page:
<cfset iterator.setPage(2)>
Key Methods
Iterator.setQuery();
Iterator.recordCount();
Iterator.setPage({pageNum});
Iterator.pageCount();
Iterator.setNextN({nextN});
Iterator.reset(); (Moves the current index to the beginning of the query)
Iterator.hasNext();
Iterator.next(); (Returns the next value)
Iterator.end(); (Moves the current index to the end of the query)
Iterator.hasPrevious();
22
Iterator.previous(); (Returns the previous value)
23
Mura Feeds
Feeds allow you to create custom Mura queries.
Content Feed
Load by feedID:
feed = $.getBean( "feed" ).loadBy( feedID={feedID} [, siteID={siteID}] );
Load by name:
feed = $.getBean( "feed" ).loadBy( name={name} [, siteID={siteID}] );
Load by remoteID:
EXAMPLE:
<!--- Create a feed bean --->
<cfset feed=$.getBean('feed')>
<cfset feed.setSiteID({siteID})>
<!--- Set the number of records to return, enter 0 for all--->
<cfset feed.setMaxItems(100)>
<!--- other sort by colums (menuTitle, title, lastUpdate, releaseDate, orderNo,
displayStart, created, rating, comments, credits, type, subType, {extendedAttribute})
--->
<cfset feed.setSortBy('created')>
<cfset feed.setSortDirection('desc')>
<!--- filter off of contentID --->
<!--- NOTE: the second argument (if true) appends to already existing value of
contentID --->
<cfset feed.setContentID('11223344', {append:false})>
<!--- filter category id --->
<cfset feed.setCategoryID('1122', {append:false})>
<!--- advanced filter --->
<cfset feed.addAdvancedParam(field='{table}.{field}', relationship='{AND|OR}'
condition='{EQUALS|EQ|IS|GT|LT|NEQ|GTE|LTE|BEGINS|CONTAINS}', criteria='{value}',
dataType='{varchar|numeric|timestamp|...}')>
<!--- get the iterator from the feed --->
<cfset feedIterator = feed.getIterator()>
<!--- or --->
<cfset feedQuery = feed.getQuery()>
User Feed
<!--- Create a feed bean --->
<cfset userFeed=$.getBean('userFeed')>
<cfset userFeed.setSiteID({siteID})>
<!--- filter off of groupID --->
<cfset userFeed.setGroupID('1122', false)>
<!--- advanced filter --->
<cfset userFeed.addAdvancedParam(field='tusers.lastname', relationship='AND'
condition='EQUALS', criteria='Smith', dataType='varchar')>
<!--- get the iterator from the feed --->
<cfset userIterator = userFeed.getIterator()>
24
Local ContentRenderer.cfc
Every site in your Mura instance has a local contentRenderer.cfc located at {siteID}/includes/contentRenderer.cfc. It
simply extends the base mura.content.contentRenderer component. It is provided to allow you to override or add new
rendering behaviours on a per-site basis. In addition, all local contentRenderer methods are available from the Mura
Scope site-specific events.
For example, if you were to add the following method to your site's local contentRenderer:
You would then be able to refer to that method in any site specific eventHandler method as:
<cfoutput>#$.dspDateTime()#</cfoutput>
This determines what JavaScript library Mura should use with its built-in display objects. The options are Prototype
and jQuery.
<cfset this.jslib="jquery"/>
This allows you set start standard navigation behavior at lower navigation levels. For example, this would be useful if
you have a section of your site that should have its own primary navigation.
<!---<cfset this.navOffSet=0/>--->
This sets the maximum depth that standard navigation will follow.
<!---<cfset this.navDepthLimit=1000/>--->
This allows developers to not rely on Mura to load the default JS framework and simply add it to their theme's
HTMLhead.
<!---<cfset this.jsLibLoaded=false>--->
<!---<cfset this.shortDateFormat="short"/>--->
This is a list of file extensions and content types that will not directly download, but instead will render in an Mura CMS
page with summary information.
<!---<cfset this.showMetaList="jpg,jpeg,png,gif">--->
This is a list of what image extensions should be shown in built in content listing display objects.
25
<!---<cfset this.imageInList="jpg,jpeg,png,gif">--->
This tells Mura whether to serve images indirectly through fileManager.renderImage() or create direct links.
<!---<cfset this.directImages=true/>--->
This allow developers to choose whether site personalizations such as ratings and favorites are attached to simple
cookies or an actual Mura user. Options are user or cookie. Users do not need to login in order to save cookie-based
personalizations.
<!---<cfset this.personalization="user">--->
This toggles whether the Mura tag call are actually processed by the setDynamicContent() method.
<!---<cfset this.enableMuraTag=true/>--->
This toggles whether the front end toolbar should be rendered for administrative users.
<!---<cfset this.showAdminToolBar=true/>--->
This toggles whether the front end toolbar should be rendered for site members.
<!---<cfset this.showMemberToolBar=false/>--->
This toggles whether editable display objects like components and content collections should be rendered.
<!---<cfset this.showEditableObjects=false/>--->
<!---<cfset this.renderHTMLHead=true/>--->
The following settings allow developers to change how Mura handles content hierarchy within its display objects. For
example, some developers prefer H1 tags for their page titles, some prefer H2 tags - you can adjust this here.
<!---<cfset this.headline="h2"/>--->
<!---<cfset this.subHead1="h3"/>--->
<!---<cfset this.subHead2="h4"/>--->
<!---<cfset this.subHead3="h5"/>--->
<!---<cfset this.subHead4="h6"/>--->
Options:
0 - Files and Links requests are either rendered as an HTML summary page or served directly according to the local
contentRenderer's showMetaList value.
26
HTMLHeadQueue
Mura provides an HTMLHeadQueue where you can add files to be rendered inside of the current request's
HTMLhead. Its main purpose is to reduce duplicate HTMLhead entries.
Tell Mura to add its main JS library to the HTMLHeadQueue. The queue will render in the same order that items are
added to it.
<cfset $.loadJSLib() />
Add your custom file to the HTMLHeadQueue. The pathing starts at the {siteid}/includes/display_objects directory.
<cfset $.addToHTMLHeadQueue("custom/htmlhead/slideshow.jquery.cfm")>
Or, if this is something that you would like to include in a theme you can also add your custom file there:
/{siteID}/includes/themes/{theme}/display_objects/{targetFile}
Look up hierarchy:
•/{siteID}/includes/themes/{theme}/display_objects/htmlhead/{targetFile}
• /{siteID}/includes/display_objects/htmlhead/{targetFile}
• /{siteID}/includes/themes/{theme}/display_objects/{targetFile}
• /{siteID}/includes/display_objects/{targetFile}
THE CONTENTS OF A TYPICAL FILE IN THE HTMLHEADQUEUE
Inside of an file that has been added to the HTMLHeadQueue you output the code that you want in the HTML Head.
<cfoutput>
<script src="#$.siteConfig(‘themeAssetPath’)#/display_objects/gallery/js/gallery.js"
type="text/javascript"></script>
<cfoutput>
Local EventHandler.cfc
Every site in your Mura instance also has a local eventHandler.cfc located {siteID}/includes/eventHandler.cfc. It is
provided to allow developers to define listening methods on a per-site basis. You can either directly define your
method or instantiate and register entire custom handlers.
27
Theme ContentRenderer.cfc
New as of Mura core versin 5.2.2439 you are now able to provide a theme specific contentRenderer.cfc. Simply place
the custom component into the root of your theme and all of itʼs methods will be available view the Mura Scope as:
<cfoutput>#$.dspThemeRendererMethod()#</cfoutput>
Method Injection
The base mura.cfobject now has injectMethod and deleteMethod functions that allow you to add or replace methods
to existing Mura objects. This would most often be used during the onApplicationLoad or onRenderStart events. In
most cases this eliminates the need to create a /config/coldspring.custom.xml.cfm.
Resource Bundles
Mura has a built in way to chain resource bundle factories so that you can
easily deploy your own.
Built-In Factories
Gaining access to your siteʼs default resource bundle factory instance:
<cfset rbFactory=$.siteConfig(‘RBFactory’)>
By default a siteʼs resource bundles are located at {siteid}/includes/resourceBundles/. They follow a naming
convention that matched the languages language and region values.
Syntax:
{language}_{region}.properties
Example:
en_us.properties
If Mura cannot find a direct language and region match it will look for just a language match.
Example:
{language}.properties
Example:
en.properties
28
If you have custom keys that you would like to deploy as part of a theme you can deploy them in a resource bundle
file within your siteʼs theme directory ({siteid}/includes/themes/{theme}/resourceBundles). You only need to add keys
that are unique to your theme. All other key values will come from the default resource bundles ({siteid}/includes/
resourceBundles).
For example, if your theme required a key named myCustomString and your site was using the default English
resource bundle you would create a file name en.properties in your siteʼs theme resourceBundles directory with the
following contents.
You could then reload Mura and pull that custom key out of the siteʼs default resource bundle and have access to that
key.
<cfset rbFactory=$.siteConfig(‘RBFactory’)>
<cfoutput>
#rbFactory.key(‘myCustomString’)#<br/>
#rbFactory.key(‘comments.comments’)#
</cfoutput>
Look up hierarchy:
•/{siteID}/includes/themes/{theme}/resourceBundles/{targetResourceBundle}
• /{siteID}/includes//resourceBundles/{targetResourceBundle}
• /requirements/mura/resourceBundles/resources/{targetResourceBundle}
Custom Factories
You can also easily create custom resource bundle factories.
<cfset customFactory=createObject
("component","mura.resourceBundle.resourceBundleFactory").init(ParentFactory=
$.siteConfig(‘rbFactory’),
ResourceBundle=‘Path/to/resoureBundleDirectory’ ,
Locale= $.siteConfig(‘JavaLocale’)
)>
At this point the custom factory would have the following look up hierarchy.
• {customResourceBundlerDirectory}/{targetResourceBundle}
• /{siteID}/includes/themes/{theme}/resourceBundles/{targetResourceBundle}
• /{siteID}/includes//resourceBundles/{targetResourceBundle}
• /requirements/mura/resourceBundles/resources/{targetResourceBundle}
29
Mapping Events in Mura
In addition to defined methods in your sites local contentRenderer.cfc, you can directly add
objects with the new pluginManager.addEventHandler() method. This can be very useful
within your local contentRenderer.cfc as well as within custom Mura plugins.
pluginManager.addEventHandler([component intance],'{siteID}')
pluginManager.addEventHandler('[component path]','{siteID}',[persist])
• /{siteID}/includes/exampleHandler.cfc
<cfcomponent extends="mura.cfobject">
<cfset variables.hits=0>
<!--- Mura uses on{name}, onGlobal{name} and standard{name} for binding events --->
<cffunction name="onAddHit" output="true" returntype="any">
<cfargument name="$">
<cfset variables.hits=variables.hits+1>
<cfset $,event("hits","<strong>Number of Hits:" & hits & "</strong>")>
</cffunction>
</cfcomponent>
30
Loading a Custom EventHandler
Here's how to use the local eventHandler onApplicationLoad event to instantiate the custom handler and register it
with the pluginManager.
<cfcomponent extends="mura.cfobject">
<cffunction name="onApplicationLoad" output="true" returntype="any">
<cfargument name="$">
<cfset var exampleHandler=createObject("component","exampleHandler")>
<!--- Notice that the second argument of "siteID" is hard coded. This is because the
onApplicationLoad event is not site bound
<cfset $.getPluginManager().addEventHandler(exampleHandler,"default")>
</cffunction>
</cfcomponent>Announcing an Event
Since the example handler that we created doesn't have any official Mura events you can use the Mura tag in a test
page to see it in action.
[mura]$.announceEvent('addHit',$)[/mura]
[mura]$.event('hits')[/mura]
Now reload your Mura instance and view the page and you should see the number of times that the onExample event
has been fired since the application was last loaded.
<plugin>
<name>Hit Counter</name>
<package>HitCounter</package>
<loadPriority>5</loadPriority>
<version>0.1</version>
<provider>Blue River</provider>
<providerURL>http://blueriver.com</providerURL>
<category>Example</category>
<settings/>
<eventHandlers>
<eventHandler event="onApplicationLoad" component="cfcs.eventHandler" persist="true"/>
</eventHandlers>
<displayobjects location="global"/>
</plugin>
31
Simple plugin eventHandler.cfc
Here you can see that the onApplicationLoad() uses the variables.pluginConfig.addEventHandler() to register the
entire component and all of its defined events for all of the plugin's assigned sites.
<cfcomponent extends="mura.plugin.pluginGenericEventHandler"><cfset variables.hits=0>
<cffunction name="onApplicationLoad">
<cfargument name="$">
<!--- Notice that there is no "siteID" argument. This is because the pluginConfig
is already aware of which sites it has been assigned to. --->
<cfset variables.pluginConfig.addEventHandler(this)>
</cffunction>
<cffunction name="onAddHit" output="true" returntype="any">
<cfargument name="$">
<cfset variables.hits=variables.hits+1>
<cfset $.event("hits","<strong>Number of Hits:" & variables.hits & "</strong>")>
</cffunction>
</cfcomponent>
The main thing here is that you can just add an event handler without explicitly mapping events that it's listening for.
32
Custom Types and Output
Class Extension Manager: Overview
The Class Extension Manager allows you to create subTypes which extend the base types
of content (users, pages, portals, etc.) found in Mura. This allows you to create highly
structured data by adding new fields/attributes to existing Mura CMS data types.
Here are a few examples of situations when creating a new subType in Mura CMS would be useful:
• Types: As mentioned above, types are the default data types in Mura CMS such as users, pages and portals.
Each data type serves a unique purpose in Mura, both on a data and a rendering level.
• SubTypes: SubTypes allow you extend a base Mura type. You can have as many subTypes as you'd like, each
defining the associated type further. For example, you could create a "Resume" subType. This would be a Mura
page but with a "Resume" subType, with the attributes we've assigned to that subType. Remember, keep your
naming convention strict (like a variable). It's important to note that if you build a subType labeled "Default" this
will cause EVERY instance of that Type (user, page, etc.) to automatically have its associated attributes added
to it. It's a way of giving you the ability to add global fields to use in Mura CMS.
• Attribute Sets are groups of attributes that associate to a given subType. Each group can be uniquely named
to help define its purpose. For instance, you could have a "Resume" subType. Most resumes contain a section
for listing out skills a user possesses. So we'll add an Attribute Set labeled "Skills" to store special attributes for
skill-related information.
It's also important to note that when you create a new subType Mura automatically creates a data set for that subType
labeled "Default". This is not a required data set in this case. It's simply a data set you can just start using.
For this example, we'll extend the base Page type by adding a new subType with new attributes. Our example will
include the creation of a "Resume" subType.
33
Access your site's Class Extension Manager:
• Log into your Mura Admin screens, and select the site you want to work with
• From the Site Settings menu in the upper right of the yellow bar in your Mura CMS admin screens, click Edit
Current Site
• On the Site Settings page, click the link to Class Extension Manager (directly under the title)
Click Add Class Extension - this will give you the ability to create a new subType.
Select "Page" from the base type options - we're going to call our example subType "Resume". Click the "Add" button.
Now you'll see the Class Extension Attribute Sets page. You'll see a few options:
• List All Class Extensions: This will take you back to the list of subTypes.
• Edit Class Extension: This will let you change the subType name. Here you will also be able to delete the
subType itself.
• Add/Edit Attribute Set: This will let you add/edit the Attribute Set.
Select the Add Attribute Set link. Name your Attribute Set "Skills". If you have any categories or interests setup for
your site you will have the option to associate them to this attribute set here if you so desire. You can also assign the
extend set to render its attributes either in the extended attributes tab, the basic tab or with custom UI.
34
Now you'll see your Skills Attribute Set details - but notice that it has no attributes yet. Select the "Add New Attributes"
link.
Here you can add the details of your Attributes. Attributes are the new "fields" you create per Attribute Set. An
example of a new Attribute would be a "Common skills" field (MutliSelect box) for the above mentioned "Skills"
Attribute set. These attributes sets along with their associated attributes would appear in all Extended Attribute tabs
that have the subType.
35
Here are the fields explained:
• Name: This is the "variable" name for the attribute. This is another case where you need to keep your naming
convention strict.
It is also important to namespace the variable based on its subType and Attribute Set's name. This isn't shown
in this example, but if you have many attributes in your site you could easily build two (or more) attributes with
the same name - don't do this! Mura always renders the first one found based on the attribute name itself.
• Label: This is the friendly label that you would want everyone to see. If one is not supplied then the name field
is used in its place.
36
• Hint: Next to each field within the "Extended Attributes" tab for each type you have the option to provide a hint
"pop-up" that will help the user understand what that field is meant for in the admin view of that subType.
• Input Type: This field dictates how the field will be rendered (Selectbox, MultiSelect, HTMLEditor [FCKEditor],
etc.).
• Default Value: This field holds a default value if one is not selected by the user.
• Required: This field ensures that a user supplies an entry for the attribute. If an entry is not supplied then the
"Validation Message" error pop-up message appears for them and halts the process from going any further.
Note: The Mura tag can be used in the "Default Value", "Option List" and "Option Label List" attribute values.
You should now see your attribute within the Attribute Set. Here you will find a "Reorder" link that will give you the
ability to reorder the attributes if you like - this is primarily for display purposes.
37
Now that the new subType is fully setup we can now use it within a page. To do this, simply go to your site manager,
choose any page that you would like to have the subType assigned to and select it.Once the subType is selected it
will automatically update the Extended Attributes tab with that subType's associated Attribute Sets/Attributes.
38
It's important to know that each Mura type displays differently from the other. In the above we focused on how a page
type would be set up, but a user type will be a little different (showing extended attributes on the edit profile screen,
etc).
Also, you can also use similar code within the body section of the page via Mura tags [ mura]{code}[/mura ]
(remember to remove spaces around the Mura tag).
Throughout the system these are referred to as subTypes (example: "Page / SubType"). These enable you to create
custom data structures within Mura CMS.
Note that AutoApprove does not exist yet, but getAttributeBy Name will look for it and if not found give me a new bean
to use.
39
Customizing Default Output
It's possible to customize standard Mura output without editing the main display files. This
allows developers to upgrade their Mura instances without worrying about having their
customizations overwritten, as well as opens the door for more structured content.
There are three ways to change a portal's rendering without touching the main code.
GLOBAL RESEST
The first way can be done for any display object file in your site's /{siteID}/includes/display_objects/. If you want to
customize the rendering for all portals you can do a global reset by copying:
• /{siteID}/includes/display_objects/dsp_portal.cfm
to
• /{siteID}/includes/display_objects/custom/dsp_portal.cfm
and changing it.
Or, if this is something that you would like include in to a theme you can also add your custom file there:
• /{siteID}/includes/themes/{theme}/display_objects/dsp_portal.cfm
Look up hierarchy:
•
/{siteID}/includes/themes/{theme}/display_objects/{targetFile}
• /{siteID}/includes/display_objects/custom/{targetFile}
• /{siteID}/includes/display_objects/{targetFile}
TARGETING A SUBTYPE WITH AN INCLUDE
This way you can create files that only target a specific type and subType. You can modify the output of any base type
in Mura (page, gallery, portal, etc.)
In this example we're going to modify the output of the Portal type. First, extend the base "Portal" type in your site's
class extension manager by creating a subType named "News" .
• /{siteID}/includes/display_objects/dsp_portal.cfm
to
• /{siteID}/includes/display_objects/custom/extensions/dsp_Portal_News.cfm
There you will be able to redefine the markup. Notice the the naming convention for the file.
• dsp_{Type}_{SubType}.cfm
Or, if this is something that you would like include in to a theme you can also add your custom file there:
• /{siteID}/includes/themes/{theme}/display_objects/custom/extensions/dsp_{Type}_
{SubType}.cfm
Look up hierarchy:
•
/{siteID}/includes/themes/{theme}/display_objects/custom/extensions/dsp_{Type}_
{SubType}.cfm
• /{siteID}/includes/display_objects/custom/extensions/dsp_{Type}_{SubType}.cfm
40
USING THE SITE LOCAL EVENTHANDLER.CFC
The way we tend do it is by creating the same class extensions as in step two but using the local eventHandler.cfc (/
{siteID}/includes/eventHandler.cfc). There you can create a method that follows a specific naming convention that will
trigger Mura to use it instead of the default code.
• on[Type]BodyRender
• on[Type][SubType]BodyRender
The methods can directory output the content or return a string:
Direct Output
<cffunction name="onPortalNewsBodyRender" output="true" returntype="void">
<cfargument name="$">
<cfoutput>
<!--- The setDynamicContent() method is what executes the [mura] tag --->
#$.setDynamicContent($.content("body"))#
<!---You can create structured output with custom attributes--->#$.content
('customVar')#
</cfoutput>
</cffunction>
Returning a String
<cffunction name="onPortalNewsBodyRender" output="false" returntype="String">
<cfargument name="$">
<cfreturn $.setDynamicContent($.content("body")) />
</cffunction>
The benefit of this approach is that you can later take this method and register it as a plugin if you want to redistribute
it.
41
Creating Plugins in Mura CMS
Plugins are the primary way to create custom extensions for Mura.
Key components of a plugin are:
• /plugin/config.xml{|.cfm}
• /plugin/plugin.cfc
• /plugin/license.txt
• /plugin/config.cfm
• index.cfm
• PluginConfig
Config.xml
The config.xml is a configuration file that tells Mura what it needs to know about the custom extension. It can be
either /plugin/config.xml or /plugin/config.xml.cfm.
<plugin>
The package value becomes a part of the directory name where the plugin is installed. It becomes available as a CF
mapping as well a value that can be use to reference the plugin's config object from the base mura.cfobject.getPlugin
("[package|pluginID|moduleID]") .
<package>HelloWorldPlugin</package>
With the directoryFormat attribute you can tell Mura to use only the package attribute value for the plugin install
directory instead of adding a _{pluginID} to the end of it:
<directoryFormat>packageOnly</directoryFormat>
The version attribute is simple meta data for the developer to store the plugins specific version:
<version>1.1</version>
The loadPriority determines the order that the plugins will file the onApplicationLoad event. This allows plugins to use
other plugins as services.
<loadPriority>8</loadPriority>
The provider attribute is simple meta data informing end users what person or organization provided the plugin.
<provider>Blue River</provider>
The providerURL is simple meta data to provide end users with a link to your plugin's resource website.
<providerURL>http://blueriver.com</providerURL>
The category attribute is simple meta data to inform end users about the render purpose of the plugin.
<category>Application</category>
42
The settings element contains the individual settings that the plugin requires in or to function. They are basically the
same as the attributes in the Mura Class Extension Manager.
<settings>
<setting>
The Name attribute is what you will pull the settings value from the pluginConfig.getSettings('[setting name]').
<name>YourName</name>
The Label element acts a reader-friendly alias for the settings element
<label>Enter Your Name</label>
The contents of the Hint element show up as a tool tip for the user installing the plugin.
The Type element determines the type of input that will be used for this setting.
<type>text|radioGroup|textArea|select|multiSelectBox</type>
<required>true</required>
The Regex element is used when the Validation element is set to regex.
<regex></regex>
The Message element is used to prompt the user when the value submitted does not pass validation.
<message></message>
The Default values element acts as the settings' default values when the user is first installing the plugin.
<defaultvalue></defaultvalue>
These next two elements are for select boxes and radio groups. They should contain a karat-delimited (^) list .
<optionlist></optionlist>
<optionlabellist></optionlabellist>
</setting>
</settings>
EventHandlers enable developers to explicitly map either a script file or a component method to Mura events. The
event that are mapped can be either core Mura events or custom events that are announced within your plugin with:
<cfset $.announceEvent("myCustomEvent")>
The component pathing starts from the plugin root. So your eventHandler is located at {pluginRoot}/eventHandlers/
myHandler.cfc it's component element value would be "eventHandlers.myHandler".
The "persist" attribute for component based eventHandlers determine whether they are cached or instantiated on a
per-request basis.
<eventHandlers>
<eventHandler event="onApplicationLoad" component="eventHandlers.myHandler"
persist="false"/>
</eventHandlers>
43
Display Objects allow developers to provide widgets that end users can apply to a content node's display regions
when editing a page (using the Content Objects tab). Mappings to both the displayObject component and
displayObjectFile are done the same way as when using eventHandlers.
The "persist" attribute for component based displayObjects determine whether they are cached or instantiated on a
per-request basis.
<displayobjects location="global">
<displayobject name="Say Hello (cfc)" displaymethod="sayHello"
component="displayObjects.sayHello" persist="false"/>
<displayobject name="Say Hello (cfm)" displaymethod="sayHello"
displayobjectfile="displayObjects/sayHello.cfm"/>
</displayobjects>
</plugin>
Plugin.cfc
The /plugin/plugin.cfc allows developers to run code that is need to properly install, update or delete their plugin. The
plugin's config is automatically available as variables.pluginConfig.
<cfcomponent extends="mura.plugin.plugincfc" output="false">
<cffunction name="install">
<!--- Do custom installation stuff--->
<cfset super.install()>
</cffunction>
<cffunction name="update">
<!--- Do custom update stuff--->
<cfset super.update()>
</cffunction>
<cffunction name="delete">
<!--- Do custom installation stuff--->
<cfset super.delete()>
</cffunction>
</cfcomponent>
license.txt
If you place a file named license.txt in your /plugin directory it will be presented to the end user while both updating
and initially installing the plugin. The end user will be required to accept the license conditions in order to proceed.
44
config.cfm
The config.cfm file is not required - it is a helper file that you can include into your plugin's root index.cfm (and other
files that are directly access via the Mura admin). It provides the request with the plugin's pluginConfig. The main
concept is that the request.pluginConfig is already available when the plugin code is executing as part of a front end
request. It is only when the plugin is being called as a top level-application that it needs to know how to obtain its own
configuration information.
<cfsilent>
<cfif not isDefined("pluginConfig")>
<cfset package=listFirst(listGetat(getDirectoryFromPath(getCurrentTemplatePath
()),listLen(getDirectoryFromPath(getCurrentTemplatePath
()),application.configBean.getFileDelim())-1,application.configBean.getFileDelim
()),"_")>
<cfset pluginConfig=application.pluginManager.getConfig(package)>
<cfset hasPluginConfig=false>
<cfelse>
<cfset hasPluginConfig=true>
</cfif>
<cfif not hasPluginConfig and not isUserInRole('S2')>
<cfif not structKeyExists(session,"siteID") or not
application.permUtility.getModulePerm(pluginConfig.getValue
('moduleID'),session.siteID)>
<cflocation url="#application.configBean.getContext()#/admin/"
addtoken="false">
</cfif>
</cfif>
<cfif not isDefined("$")>
<cfset $=application.serviceFactory.getBean("muraScope").init(session.siteid)>
</cfif>
</cfsilent>
index.cfm
The root index.cfm for a plugin is where developers can either display documentation or create custom admin
functionalilty.
A simple example:
45
A key method for keeping the Mura admin look and feel is the application.pluginManager.renderAdminTemplate()
method. It wraps the provided body string argument with the main admin layout allowing for seamless user
experience.
#application.pluginManager.renderAdminTemplate(body={body}, pageTitle=
{htmlPageTitle}, jsLib= {prototype|jquery}, jsLibLoaded={true|false} )#
PluginConfig
The variables.pluginConfig object is available in all plugin components that extend the base
mura.plugin.pluginGenericEventHandler.cfc. You can access a plugin's pluginConfig object through the base
mura.cfobject that all Mura object extend through getPlugin({plugin|moduleID|package}).
The pluginConfig provides developers with access to the properties configured in the /plugin/config.xml.
Application Object
The pluginConfig object has it own application object that provides a container for key value pairs that you would like
to persist for the plugin. It has an optional purge boolean argument to tell the pluginConfig to return a brand new
object which defaults to false.
<cfset pApp=variables.pluginConfig.getApplication()>
<cfset pApp.setValue("myService",this)>
<cfset pVar=pApp.getValue("myService").doMethod()>
Session Object
The pluginConfig object has it own session object that provides a container for key value pairs that you would like to
persist for users interacting with the plugin. It has an optional boolean purge argument to tell the pluginConfig to return
a brand new object which defaults to false.
<cfset pSession=variables.pluginConfig.getSession()>
<cfset pSession.setValue("myService",this)>
<cfset pSession=pSession.getValue("myService").doMethod()>
EXAMPLE SERVICE 1
<cfcomponent extends="mura.cfobject" output="false">
<cfset variables.service2="">
<cffunction name="setService2" output="false">
<cfargument name="service2">
46
<cfset variables.service2=arguments.service2>
</cffunction>
<cffunction name="getService2" output="false">
<cfreturn variables.setService2>
</cffunction>
</cfcomponent>
EXAMPLE SERVICE 2
<cfcomponent extends="mura.cfobject" output="false">
<cfset variables.service1="">
<cfset variables.userManager="">
<cfset variables.pluginConfig="">
<cffunction name="setService1" output="false">
<cfargument name="service1">
<cfset variables.service2=arguments.service2>
</cffunction>
<cffunction name="getService1" output="false">
<cfreturn variables.setService1>
</cffunction>
<cffunction name="setUserManager" output="false">
<cfargument name="userManager">
<cfset variables.userManager=arguments.userManager>
</cffunction>
47
<!--- The pluginConfig also never explicitly set. But since the plugin application
object know what plugin is belongs to it can automatically set it as well --->
<cfset var pluginConfig=service2.getPluginConfig()
</cffunction>
</cfcomponent>
48
Adding to the HTMLHeadQueue
The pluginConfig object also allow developers to add the current requests HTMLHeadQueue:
The "path" argument is a file path starting from the root of your plugin.
• {plugin root}/htmlhead/inc.cfm
The contents of "inc.cfm":
<cfoutput>
<link href="#pluginPath#css/contributor_tools.css" rel="stylesheet" type="text/css" />
</cfoutput>
One import note is that if the code that you are injecting into the head of the page requires the JS library to be loaded
(ie. jquery) you need to explicitly do so.
<cfset $.loadJSLib()>
<cfset variables.pluginConfig.addToHTMLHeadQueue("htmlhead/inc.cfm") >
EVENTHANDLER AUTO-WIRING
An import method available is the pluginConfig.addEventHandler({component}). It allow developers to autowire an
entire component's methods by naming convention.
Registered Patterns:
•standard{event}
• on{event}
<cfcomponent extends="mura.plugin.pluginGenericEventHandler">
<cffunction name="onApplicationLoad" output="false>
<cfargument name="$">
<cfset variables.pluginConfig.addEventHandler(this)>
</cffunction>
<cffunction name="onCustomEvent" output="false">
<cfargument name="$">
<!--- do custom stuff --->
</cffunction>
</cfcomponent>
<cfset rsSites=getPlugin("myPlugin").getAssignedSites()>
<cfloop query="rsSites">
<cfoutput>#rsSites.siteid#<br/></cfoutput>
</cfloop>
49
Retrieving a Plugin's pluginConfig
If you need to retrieve a pluginʼs pluginConfig object from outside of that plugins normal phase of execution you can
access it by using the base mura.cfobject method getPlugin(). This methods takes a single argument of either
pluginID, moduleID, Name or Package.
$.getPlugin( {pluginID} );
$.getPlugin( {moduleID} );
$.getPlugin( {name} );
$.getPlugin( {package} );
Retrieving a pluginConfig by the value of the pluginʼs “Package” element in itʼs /plugin/config.xml allows developers to
have a consistent way of referencing the plugin from Mura instance to Mura instance.
• {siteID}/includes/contentRenderer.cfc
• {siteID}/includes/eventHandler.cfc
• {siteID}/includes/servlet.cfc
• {siteID}/includes/loginHandler.cfc
• {siteID}/includes/themes
• {siteID}/includes/email
• {siteID}/includes/display_objects/custom
• {siteID}/includes/plugins
50
Custom Caching
Mura has a built in caching mechanism that (when enabled) will store content information
in memory for reuse throughout a given site. The purpose for this is to improve overall site
performance by decreasing the amount of reads to the database.
It's important to note that caching is done in a "lazy load" fashion. This means that the item will not be cached until it is
called upon for the first time. It is also important to note that caching uses "soft references". This allows for improved
memory management for the JVM. To ensure that there are no issues with soft references, whenever a page is
updated the cache gets flushed entirely starting the caching process over again.
The first way is to use the built in cf_cacheomatic tag. For those who are not familiar with the tag, it give the user the
ability to cache any desired display content.
The second way is more or less baked into Mura itself but is worth mentioning here. Whenever a page is requested a
content bean is created. If caching is turned on then the contents of the content bean are automatically cached for
reuse.
How to use it
Within Mura's admin console you can go to any given site's Site Settings "Basic" tab and turn the "Site Caching"
option to "on".
Cache Free Memory Threeshold: The Cache Free Memory Threshold sets a minimum required percent of available
jvm memory in order to cache new key. This must have a value. If 0% is entered it will default to 40%.
51
The cf_cacheomatic tag:
<cf_CacheOMatic key="{a unique key}" nocache="{1=don't cache, 0=cache}">
Content here ....
</cf_CacheOMatic>
Example Usage
<cf_CacheOMatic key="page#$.content('contentID')#" nocache="#$.event('noCache')#">
<p>
<cfoutput>
The title of this page is #$.content('menuTitle')#.
</cfoutput>
</p>
</cf_CacheOMatic>
52