Words about stuff, and things related to stuff

Use safemode=true to bypass Mura plugin loading

An associate asked for help on a Mura CMS site which was redirecting in an endless loop. From what we could see, any page request was being sent to /admin, which in turn was being directed back to the site's primary domain.

This site was previously working as expected, with no changes made by developer or site owner. Our only clue was that the CF server had been restarted the night before.

Suspecting something not properly initiated, i.e. a 'stuck' value, the usual advice would be to reload the Mura site's application. This is done by putting 'appreload=[my application reload key]' in the url, e.g www.mysite.com/anypage/?appreload=myreloadkey (Note the appreload key is set in Mura's settings.ini.cfm file in the /config/ directory)

However, with this tricky redirect happening, we couldn't even get that far. So the next step was to try loading the site without instantiating any of the Mura plugins. (I had actually seen this issue before, so I had a clue as to the solution - but bypassing plugins is usually a good place to start if you can't even get into your Mura admin area.)

To view any page of a Mura CMS site without running any of the plugin code, simply use "?safemode=true" in the url, e.g. www.mysite.com/folder/page/?safemode=true

In our case, the site loaded up perfectly. As a double check, without the 'safemode' in the URL we had the loop again. So, now the double whammy - skip the plugins and reload the application

www.mysite.com/folder/page?safemode=true&appreload=myreloadkey

Sure enough, the site came back up and stayed up after that. Something in one of the plugins was causing a redirect loop but resetting the application cleared out whatever value was stuck.

Remove empty paragraphs (and Mura editable regions) with CSS

When logged into a Mura site and looking at a front end page, the 'editable region' for the page body often contains an empty paragraph tag created by the wysiwyg, even if there isn't any content in the body, and can take on an unwanted space on the page. This is a common CMS/wysiwyg thing in general, and an annoying waste of time if you go chasing it down each time it happens.

The fix is simple - put this in your default stylesheet, and those empty

elements go away:

view plain print about
1p:empty {
2 display: none;
3}

Add index.cfm as default doucment in .htaccess

Simple tip: Add this line to your .htaccess file

view plain print about
1DirectoryIndex index.cfm

This tells the server to default to /index.cfm inside of any directory.

Why I needed it: I just installed Mura CMS on a Railo/cPanel server where index.cfm is not set as a global default document, and the mura admin kept redirecting me back to the home page. I just added that line to the .htaccess in the web root, and we're good to go.

Load Mura content based on Extended Attribute value in URL

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>

Dynamic Bootstrap Layout on any markup with make-column and make-row via LESS

If you've spent any time working with bootstrap templates, or the new Bootstrap grid system in BS3, you've probably seen markup with the col-x grid classes, for example a two column layout:

view plain print about
1<div class="container">
2<div class="row">
3<div id="primary" class="col-lg-9 col-md-9 col-sm-8 col-xs-12">
4</div>
5<div id="sidebar" class="col-lg-3 col-md-3 col-sm-4 col-xs-12">
6</div>
7</div>
8</div>

This will result in a two column layout on large screens, with the proportions shifting for "sm" or small screens, and stacked columns on "xs" or extra-small screens.

While amazingly simple to use once you get the hang of it, maintaining this code is messy and can be both a visual and mathematical challenge when working in a multi-column layout.

Continued...

Access Restricted message in Mura: a workaround

When creating custom display objects using ajax, the response from any file inside of the site /includes directory will be a simple message, "Access Restricted". This is by design, but it is easy to get around.

The message comes from Mura's default Application.cfc file, which is inside the /includes directory, and contains only the message, and a tag. The purpose of this file is to keep any content stored inside the /includes directory from being delivered directly, i.e. if a user guessed the path to a display object, or some poorly-executed code was to somehow direct a user to a file in /includes.

But, since the Application.cfc has a cascading priority, you can simply place your own Application.cfc file inside the directory where your desired content is stored.

Continued...

Log as user out of Mura with a URL (custom logout link)

Easy: ?doaction=logout

e.g.

view plain print about
1<a href="http://www.mydomaincom/any-page-name/?doaction=logout">Log Out</a>

or for example to redirect to the home page, logged out

view plain print about
1<cflocation url="/?doaction=logout" addtoken="false">

Log in a Mura user programmatically with loginByUserID()

I'm writing a routine that creates some content in a front end form and attaches it to a Mura user. At that point I need to log in the user automatically, just as if they had used the site's log in form.

I spent a while looking for this. Here it is to save you (and me) time when you need it:

view plain print about
1<cfset userLogin = application.serviceFactory.getBean("userUtility").loginByUserID([USER ID HERE], [SITE ID HERE])>

Of course, the userID can be a literal string, or usually, a variable containing the user id (like $.getCurrentUser().getUserID() ). Same for the siteid.

To see the results (i.e. whether you're logged in or not, and some other related stuff), simply dump out 'session.mura' which contains a flag called isLoggedIn

view plain print about
1<cfdump var=#session.mura#>

Note: I also found a loginByUserID function in the loginManager, but it is not the right one to be using inline in your code. It takes a struct of values from the userbean, and triggers the redirection to a login screen as used when logging into the admin. Use the userUtility function above instead.

Add content to a category in Mura content bean, and how to see other methods

A quick 'n easy one that took me a while to find...

When managing Mura content via the content bean, it is really easy to add your content to a Mura category.

Continued...

Mura Extended Attributes not being saved, a headsmack! solution

I'm working on a system for a Mura site to allow creation and updating of existing Mura content items from front end forms. As much as possible I am attempting to mimic the Mura admin functionality, creating forms via XML much like the way class extensions and extended attributes can be configured in an XML file for the admin. Neat stuff, with lots of trial and error as I make my way through.

I've had a lot of victories as I battle on, but just spent way too long on something way too simple, so I'm repeating here for anyone else who may come this way. In the part of this little system that saves the form submission and updates the actual content, we use the mura contentBean to assemble our values and then write the whole thing into the database with "save()". So simple!

Continued...

Searching and collecting Mura CMS content by Extended Attribute Value with feed.addParam()

The Extended Attributes functionality in Mura CMS is remarkably powerful, allowing developers to extend their content objects to include custom fields and attributes of almost any type.

However, it isn't always clear how to collect or search on the values of the custom attributes you have added, since Mura's default 'feed' object does not contain the extended attributes you have added.

Continued...

Read More...

Recent Comments

Importing Events Calendar Data into Mura CMS
Jon said: Hi Michael, Thanks for this post - similarly looking to import data from excel spreadsheet into mur... [More]

New TinyMCE lets you paste as plain text automatically
PRR said: I am using tinyMCE 4.2.3 and configured as suggested in this post but when i try pasting in editor i... [More]

Searching and collecting Mura CMS content by Extended Attribute Value with feed.addParam()
Armando Faes said: After a few hours of head banging - finally thanks to this - Once more THANK YOU! [More]

CF Gallery Creator & jQuery Slider Gallery !New and Improved!
Jan Willis said: Okay, I realized that you are calling my root as the Site - not the wwwroot. So, I moved the JS, CS... [More]

CF Gallery Creator & jQuery Slider Gallery !New and Improved!
Michael Evangelista said: @Jan, it has been a long time (6+ years) so my memory of the details is vague, but I think the image... [More]

_UNKNOWNTRANSLATION_