I'm working on a site where i want to capture part of the URL and pull up a Mura content item (class listing) based on a course ID number, which is stored as an extended attribute.

The goal is to do this without having an actual page in place for each class. (For this site, we also want to do this without the need for a class ID passed in the querystring. Instead, we want to use the Mura URL path, e.g. mysite.com/class/123456 instead of mysite.com/class?classid=123456 )

The overall solution was multiple parts.

Replacing the Content Bean

First, I discovered you can override the entire content bean (the whole ball o' wax in terms of what mura shows on a page, the HTML title, meta info, the works), by setting the value of "request.contentbean" to a valid Mura content object.

So, where in a normal Mura template you can show the values of the current content item structure using this notation:

view plain print about
1$.content('title')
2$.content('filename')
3etc

You can actually override the entire $.content() object, by overriding request.contentbean, like this:

view plain print about
1<cfset request.customid = 'Mura-Content-ID-Here'>
2<cfset request.contentbean = $.getBean('content').loadBy(siteid=request.siteid,contentid=request.customid)>

There are several ways to load the content bean. Google it. But the idea is, once you have a content bean loaded, you can set 'request.contentbean' and override whatever content mura would otherwise show on that request. So, this is good. If I can figure out which content to show, and get its ID (or another value I can use inside of "loadBy()") , I can push my own content into the page request, regardless of the URL.

Parse the Path

So, how do I know which content to get? In this case I am using a url like this: sitename.com/class/123456 (worth noting, sometimes there's a trailing slash, e.g. sitename.com/class/123456/ )

My goal was to parse that string from the end of the URL, then get the Mura content that matches it.

view plain print about
1<!--- if /class/ is in the url --->
2<cfif request.path contains '/class/'>
3    <!--- if class id is provided --->
4    <cfif right(request.path,1) is '/'>
5        <cfset request.classpath = left(request.path,len(request.path)-1)>
6    <cfelse>
7        <cfset request.classpath = request.path>
8    </cfif>
9    <cfset request.classnumber = listLast(request.classpath,'/')>    
10</cfif>

So, now I have a class number, taken from the URL. Of course I'll want to use a cfparam if I'm going to depend on the value of request.classnumber, since it may not always exist... but that's the basic idea of how I'm getting it.

Load by Extended Attribute (via Feed)

Ok, so now I have a value. 123456. So what. How can I get a Mura content bean based on that value? If it were the contentID or even the remoteID, I could just use loadby (see first example above) and load it up. But this is an Extended Attribute. not part of the standard LoadBy() logic (as far as i know).

So... custom stuff. A while ago, I created a wrapper function for Mura feeds, to work with a custom search interface. It has been useful many times since then and this is one of them.

view plain print about
1<cfset contentQuery = GWcontent.GWcontentSearch(
2    siteid=request.siteid,
3    type="Page",
4    parentid="B98EA962-155D-8F07-1D7F47530491001B",
5    extsearch="ClassNumber=#request.classnumber#"
6    )
>
    
7<cfif contentQuery.recordCount>            
8 <cfset request.customid = contentQuery.contentid[1]>
9 <cfset request.contentbean = $.getBean('content').loadBy(siteid=request.siteid,contentid=request.customid)>
10</cfif>

What this does: load up a Mura feed with the given siteid(required), page type (optional), parent id (optional - used here since I know the ID of the parent for all of these "class" entries), and the kicker extsearch (a custom wrapper that adds feed params to the Mura lookup)

In my case, since the CourseCode is unique, it will either return one item , or none.

So then, I can treat the mura feed like any query object, and latch on to the value of the 'contentid', which is used to load my bean, which is then used to override the current request's $.content() object.

Custom Functions

Wrapping this all together, I put it inside a function called "onSiteRequestStart", which is inside of my theme's eventHandler.cfc (it should also work in the site eventhandler.cfc).

view plain print about
1<!--- CUSTOM SITE REQUEST START --->
2<cffunction name="onSiteRequestStart" output="false">
3    <cfargument name="$" hint="mura scope" />
4        
5    <!--- CUSTOM handle class URLs --->
6    <cfparam name="request.path" default="">
7    <cfparam name="request.classpath" default="">
8    <cfparam name="request.classnumber" default="">
9    
10    <!--- if /class/ is in the url --->
11    <cfif request.path contains '/class/'>
12        <!--- if class id is provided --->
13        <cfif right(request.path,1) is '/'>
14            <cfset request.classpath = left(request.path,len(request.path)-1)>
15        <cfelse>
16            <cfset request.classpath = request.path>
17        </cfif>
18        <cfset request.classnumber = listLast(request.classpath,'/')>    
19    </cfif>     
20    <!--- attempt to set content bean --->
21    <cfif len(trim(request.classnumber))>        
22
23        <!--- get content functions --->
24        <cfset GWcontent = createObject('component','interactive.GWcontent')>
25        <!--- load a feed of the current class --->    
26        <cfset contentQuery = GWcontent.GWcontentSearch(
27            siteid=request.siteid,
28            type="Page",
29            parentid="B98EA962-155D-8F07-1D7F47530491001B",
30            extsearch="classCode=#request.classnumber#"
31            )
>
        
32
33    <cfif contentQuery.recordCount>            
34        <cfset request.contentbean = $.getBean('content').loadBy(siteid=request.siteid,contentid=contentQuery.contentid[1])>
35    </cfif>
36
37    </cfif>
38    <!--- /end class URLs --->    
39
40</cffunction>

And here's the current version of my GWcontentSearch() method, which is currently in a separate CFC (see the 'interactive' directory referenced in the example above), but can also be added to the eventHandler.cfc in Mura in which case the createObject reference above can be remoeved.

Custom Mura Content Search Function:

view plain print about
1<!--- // ---------- // GWcontentSearch // ---------- // --->
2<cffunction name="GWcontentSearch"
3            access="public"
4            output="false"
5            returntype="query"
6            hint="returns feed based on custom content"
7            >

8
9    <cfargument name="siteid"
10            required="true"
11            type="string"
12            hint="site id">

13            
14    <cfargument name="type"
15            required="false"
16            default="page"
17            type="string"
18            hint="a Mura content type to limit content to (page|folder|portal|etc)">
        
19            
20    <cfargument name="subtype"
21            required="false"
22            default=""
23            type="string"
24            hint="a Mura subtype to limit content to">
        
25            
26    <cfargument name="parentid"
27            required="false"
28            default=""
29            type="string"
30            hint="id of a content node that contains the item">

31            
32    <cfargument name="extsearch"
33            required="false"
34            default=""
35            type="string"
36            hint="list of name/value pairs to search by
37                    use ^ delimiter
38                 can use = for equals, ~ for LIKE
39                    eg attr1=blue^attr2~xl
40                    "
>

41            
42    <cfargument name="maxrows"
43            required="false"
44            default="0"
45            type="numeric"
46            hint="max number of items to return">

47        
48    <cfargument name="sortby"
49            required="false"
50            default=""
51            type="string"
52            hint="column to sort feed by">

53
54    <cfargument name="sortdir"
55            required="false"
56            default="asc"
57            type="string"
58            hint="sort direction">

59
60    <cfset var feed = ''>
61    <cfset var feedContents = ''>
62    <cfset var afield = ''>
63    <cfset var acrit = ''>
64
65    <!--- load feed --->
66    <cfset feed=application.feedManager.read('')>
67    <!--- set site id --->
68    <cfset feed.setSiteID(arguments.siteid)>
69    <!--- type --->
70    <cfif len(trim(arguments.type))>
71        <cfset feed.addParam(relationship="AND", field="type", condition="=", criteria=arguments.type, dataType="varchar")>
72        <cfset feed.addParam(relationship="AND", field="type", condition="=", criteria=arguments.type, dataType="varchar")>
73    </cfif>
74    <!--- subtype --->
75    <cfif len(trim(arguments.subtype))>
76        <cfset feed.addParam(relationship="AND", field="subType", condition="=", criteria=arguments.subtype, dataType="varchar")>
77    </cfif>
78    <!--- parent --->
79    <cfif len(trim(arguments.parentid))>
80        <cfset feed.addParam(relationship="AND", field="parentid", condition="=", criteria=arguments.parentid, dataType="varchar")>
81    </cfif>
82    <!--- extended attributes --->
83    <cfif len(trim(arguments.extsearch))>    
84        
85        <cfloop list="#trim(arguments.extsearch)#" index="a">
86            <!--- equals (=) --->
87            <cfif a contains '='>
88                <cfset afield = listFirst(a,'=')>
89                <cfset acrit = listLast(a,'=')>
90                <cfset feed.addParam(relationship="AND", field=afield, condition="=", criteria=acrit, dataType="varchar")>
91            <!--- like (~) --->
92            <cfelseif a contains '~'>
93                <cfset afield = listFirst(a,'~')>
94                <cfset acrit = listLast(a,'~')>
95                <cfset feed.addParam(relationship="AND", field=afield, condition="LIKE", criteria='%' & acrit & '%', dataType="varchar")>
96            </cfif>
97        </cfloop>
98    </cfif>
99    <!--- sort --->
100    <cfif len(trim(arguments.sortby))>
101        <cfset feed.setSortBy(arguments.sortby)>
102        <cfset feed.setSortDirection(arguments.sortdir)>
103    </cfif>
104    <!--- max rows --->
105    <cfset feed.setMaxItems(val(arguments.maxrows))>
106    
107    <!--- get query of feedcontents --->
108    <cfset feedContents=application.feedManager.getFeed(feed) />
109
110    <cfreturn feedContents>
111
112</cffunction>

See the arguments and descriptions of same in the code. Like everything this is a work in progress, comments welcome.

That does it for how I am loading up custom mura page content based on an extended attribute in the url. If you need to get the extended values as part of your content, keep reading...

Getting Extended Attributes

Important: the mura feed does not return the Extended Attributes as query columns! For that I have another wrapper function, which takes either (a) all the same attributes as the above, and passes them through, or (b) a specific content ID. Then it returns a structure of all the content attributes as if you'd run .getAllValues(), but with a lot of options via the arguments, such as what you actually want returned.

It currently looks like this

view plain print about
1<!--- // ---------- // GWcontentAttr // ---------- // --->
2<cffunction name="GWcontentAttr"
3            access="public"
4            output="false"
5            returntype="struct"
6            hint="returns structure of content attributes"
7            >

8
9    <cfargument name="siteid"
10            required="true"
11            type="string"
12            hint="site id">

13            
14    <cfargument name="type"
15            required="false"
16            default="page"
17            type="string"
18            hint="a Mura content type to limit content to (page|folder|portal|etc)">
        
19            
20    <cfargument name="subtype"
21            required="false"
22            default=""
23            type="string"
24            hint="a Mura subtype to limit content to">
        
25            
26    <cfargument name="parent"
27            required="false"
28            default=""
29            type="string"
30            hint="name of a Mura content node that contains the item">
        
31            
32    <cfargument name="parentid"
33            required="false"
34            default=""
35            type="string"
36            hint="ID of a Mura content node that contains the item">
        
37
38    <cfargument name="extsearch"
39            required="false"
40            default=""
41            type="string"
42            hint="^ delimited list of name/value pairs to search by">

43    
44    <cfargument name="extendset"
45            required="false"
46            default=""
47            type="any"
48            hint="list of extended attribute sets to include">

49
50    <cfargument name="returnvals"
51            required="false"
52            default="all"
53            type="string"
54            hint="values to return, options are all|extended|'{list,of,specific,values (extended can be added here as well)}' ">

55
56    <cfargument name="contentid"
57            required="false"
58            default=""
59            type="string"
60            hint="if supplied, this specific item is looked up">

61    
62    <cfset var contentQuery = ''>    
63    <cfset var contentBean = ''>
64    <cfset var parid = ''>
65    <cfset var subt = ''>
66    <cfset var extAttrs = structNew()>
67    <cfset var extStruct = structNew()>
68    <cfset var allStruct = structNew()>
69    <cfset var returnStruct = structNew()>
70    
71    <!--- if content id is provided --->
72    <cfif len(trim(arguments.contentid))>
73        <cfset contentId = arguments.contentid>
74    <cfelse>
75        <!--- get content ID for search by parent --->
76        <cfif len(trim(arguments.parentid))>
77            <cfset parid = trim(arguments.parentid)>
78        <cfelseif len(trim(arguments.parent))>
79            <cfset parid = application.contentManager.getActiveContentByFilename(arguments.parent,arguments.siteid).getcontentId()>    
80        </cfif>
81            
82        <!--- get content ID based on search parameters --->
83        <cfset contentQuery = GWcontentSearch(    arguments.siteid,
84                                                arguments.type,
85                                                arguments.subtype,
86                                                parid,
87                                                arguments.extsearch,
88                                                1 )
>

89        <cfset contentId = contentQuery.contentId>
90    </cfif>
91    <!--- /end if content id provided --->
92
93    <!--- get content --->
94    <cfset contentBean = application.contentmanager.getBean('content').loadBy(siteid=arguments.siteid,contentId=contentId)>
95
96    <!--- set known values (used if bean returns no match / blank) --->
97    <cfif len(trim(arguments.type))>
98        <cfset contentBean.setType(arguments.type)>
99    </cfif>
100    <cfif len(trim(arguments.subtype))>
101        <cfset contentBean.setSubtype(arguments.subtype)>
102    </cfif>
103    <cfif len(trim(parid))>
104        <cfset contentBean.setValue('parentid',parid)>
105    </cfif>
106    
107    <!--- get all values --->
108    <cfset allStruct = contentBean.getAllValues()>
109
110        <!--- get extended attributes --->
111        <cfif len(trim(arguments.extendset))>
112            <cfset subt = application.classExtensionManager.getSubTypeByName(arguments.type,arguments.subtype,arguments.siteid)>
113            <cfset extendSet = subt.getExtendSetByName(arguments.extendset)>
114            
115            <cfset extAttrs = extendSet.getAttributes()>
116            <cfif arrayLen(extAttrs)>
117                <cfloop from="1" to="#arrayLen(extAttrs)#" index="i">
118                    <!--- add extended values to values struct --->
119                    <cfset extStruct[extAttrs[i].getName()] = contentBean.getValue(extAttrs[i].getName())>
120                </cfloop>
121            </cfif>
122        </cfif>
123
124            <!--- return values depending on content requested --->
125            <cfswitch expression="#arguments.returnvals#">
126            
127                <!--- extended values only --->
128                <cfcase value="extended">    
129                    <cfset returnStruct = extStruct>
130                </cfcase>
131            
132                <!--- all values --->
133                <cfcase value="all">
134                    <cfset returnStruct = allStruct>
135                </cfcase>    
136            
137                <!--- if a list is specified --->
138                <cfdefaultcase>
139                    <cfif len(arguments.returnvals)>
140                        <cfloop list="#arguments.returnvals#" index="v">
141                            <cfif structKeyExists(allStruct,v)>
142                                <cfset returnStruct[v] = allStruct[v]>
143                            </cfif>
144                        </cfloop>
145                    <cfif listFindNoCase(arguments.returnvals,'extended')>
146                        <cfset structAppend(returnStruct,extStruct)>
147                    </cfif>
148                    </cfif>
149                    
150                </cfdefaultcase>
151            
152            </cfswitch>
153            <!--- /end return values --->
154
155    <cfreturn returnStruct>
156
157</cffunction>