(Quick Reference)

6 Developer Guide - Reference Documentation

Authors: Stephan Albers, Mark Palmer, July Antonicheva

Version: 1.4-SNAPSHOT

6 Developer Guide

This section covers the internal details that can be used to customize Weceem within your application, create custom content types and other more technical subjects.

6.1 Hooking into the Admin interface

Hooking into the Admin interface of Weceem

Applications can add items to the Weceem "Administration" page by using navigations from Platform-core plugin, to add items to the navigation group "weceem_admin". The Weceem Application does this :

  1. Prepare AppNavigation.groovy file in "grails-app/conf/"
  2. Put some more configuration there

navigation = {
    weceem_admin {
        users(controller:'CMSUser', action:'list', order:5) {

} }

}

More information about navigation is available in documentation of Platform-core plugin (Navigation part)

6.2 Create custom content types

Weceem provides many content types as standard. Each content type has a specific purpose, with different fields in the editor.

Some content types are directly rendered when requested, others are used by other content nodes to compose the page seen by the user.

The standard content types available in Weceem are :

  • Action - A server-side action that will execute the code in a Groovy Script node, for example to create new content nodes or send an email
  • Blog - The root node of a Blog, which has Blog Entry nodes as children
  • Blog Entry - An entry (child of) a Blog node
  • Comment - A comment on a content node. Any nodes can have comments attached or created.
  • External Link - A URL of another website. When this URI is rendered, it redirects to the external site
  • Folder - A folder for grouping together content nodes and creating URI hierarchy
  • Groovy Script - A piece of Groovy code to execute on the server, used by Action nodes
  • HTML - A piece of HTML which may contain GSP (Grails Server Page) tags
  • JavaScript - A piece of JavaScript source
  • Server File - A file uploaded to the Weceem repository and stored on the server filesystem, e.g. an image file
  • Server Directory - A directory on the server filesystem which contains Server File nodes
  • Stylesheet - A CSS style sheet
  • Template - A GSP template used to layout and decorate the current content node
  • Widget - A reusable GSP fragment for rendering sections of a page, e.g. a header/footer or a remote news feed.

Creating custom content types

You can add new content types that will be editable by your users and displayed in the repository along with the standard content types.

These custom types can have full control over how a request is handled (e.g. issue a redirect, like WcmExternalLink does) or delegate to established rendering mechanisms within Weceem. You might have it call out to a PDF generator for example, or render some data using an alternative presentation technology e.g. wiki rendering or Freemarker. They can also specify how their properties should be edited in the repository, and hook into events.

To create a new content type, you just need to create a new Grails domain class that extends the WcmContent class and follow a few simple conventions. You can create a basic template for a new content type using the create-content-class script :

grails create-content-class com.mycompany.Product

This will create the domain class under grails-app/domain/, with some default placeholder code. Here is an of the contentions.

import org.weceem.util.ContentUtils
import org.weceem.content.WcmContent

class PressRelease extends WcmContent {

// Set to false if this content node should never actually be rendered // i.e. Groovy Script nodes cannot be rendered directly. static standaloneContent = false

String getMimeType() { "text/html" }

String content

/** * Must be overriden by content types that can represent their content as text. * Used for search results and versioning */ public String getContentAsText() { ContentUtils.htmlToText(content) }

/** * Should be overriden by content types that can represent their content as HTML. * Used for wcm:content tag (content rendering) */ public String getContentAsHTML() { content }

static constraints = { content(nullable: false, maxSize: 65536) }

static editors = { content(editor:'HtmlCode') }

static transients = WcmContent.transients }

It declares a new type which, when created, will immediately become editable in the repository when you run your application. There are several conventions that apply, described in more detail below -

Basic considerations

Your descendent class has certain conventions it must honour for correct operation :

  • the Grails "transients" property must be populated with the sum of the superclass transients and any new transient properties you add (should not be necessary for newer Grails versions)
  • the "searchable" property convention must be defined as per Grails Searchable plugin, to add any fields that should be indexed for searching

Controlling the editing of your content type

To customize behaviour for the properties of the domain class, the "editors" closure property is used, like Grails constraints closure - but in this case how they are treated in the Weceem editor.

For each field of the content domain class you can define the order in which they appear in the editor - based on the order in which they are listed in the editors closure.

You can also set the editor used for each property. These have names like HtmlCode, ReadOnly, CssCode, GroovyCode etc. You can also define your own editors if the supplied editors are not suitable. See the section on customizing content editors.

Some properties are for internal use only, for those you can set hidden:true.

Finally, you can push your properties into the "Extras" editor group that is displayed separately by setting "group" to "extra":

static editors = {
    isbn(editor:'ISBNEditor')
    genre(editor:'GenrePopup', group:'extra')

Displaying content as text or HTML

For various reasons such as search results and versioning, Weceem needs to be able to get some kind of representation of your custom content as both plain text and HTML. If your needs are different from the defaults, you can override the methods for getContentAsText and getContentAsHTML.

The default behaviour is:

/**  * Must be overriden by content types that can represent their content as text.
 * Used for search results and versioning
 */ String getContentAsText() { "" }
/**
 * Should be overriden by content types that can represent their content as HTML.
 * Used for wcm:content tag (content rendering)
 */ String getContentAsHTML() { contentAsText ? contentAsText.encodeAsHTML() : '' }

So as a minimum, override getContentAsText() - but if there is a reasonable HTML representation, e.g. for HTML, Wiki or other richly formatted content, you should also implement getContentAsHTML().

Defining mime type

Weceem's content controller will ask the content for its MIME type when building the response. In many content types this is a constant value and is not persisted, but in types such as WcmContentFile this is persisted so that the type can be overridden by the user.

To return the correct MIME type, just implement the getter:

String getMimeType() { "text/html" }

This non-static value is read whenever the content is to be served. This means that you can determine it on a per-node basis, which is how the WcmContentFile type specifies the mime types of downloadable files.

Customizing rendering of your content

Weceem delegates rendering of content to the content node itself. If there is no rendering implementation provided, it will simply render the node as text to the client. This mechanism is used by the built in content types to render themselves appropriately. Some types render HTML decorated by a template, and others issue redirects.

There are two conventions that control the custom rendering of content:

  • Static "standaloneContent" property is set to false if content type is not intended for direct rendering. This prevents node types used by other types from being rendered when their URI is requested - e.g. is the built-in template and widget types, which make no sense being rendered on their own.
  • Static "handleRequest" Closure property provides custom implementation of rendering if you require it.

The handleRequest closure is executed so that it delegates to Weceem's content controller. This means you can use all the regular Grails dynamic controller methods and properties (render, redirect, response, request, session) as well as some custom methods to render Weceem content nodes using the normal GSP and templating mechanisms.

Here's an example of how WcmHTMLContent renders its regular HTML or GSP code :

static handleRequest = { content ->
    if (content.allowGSP) {
        renderGSPContent(content)
    } else {
        renderContent(content)
    }
}

This implementation calls WcmContentController's renderContent method if the content is HTML, which will render the content inside a Template if the node has one. Alternatively if the content is classed as GSP content, it calls the controller's renderGSPContent method which renders the content as GSP code, inside a template if one is found for the node.

Another example is the External Link type which redirects the browser to the target URL of the node :

static handleRequest = { content ->
   redirect(url:content.url)
}

This implementation uses the standard Grails "redirect" method to redirect to the URL specified in the content's "url" property.

The Template, Widget and HTML content nodes can use Weceem tags and other Grails tags to perform view rendering logic.

Interacting with content events

Content nodes can hook into common events, such as deletion and updates. To do this your content class needs to implement methods for any events it wants to support. None of these are mandatory, and you can implement only those you need. The available event methods are defined in WeceemDomainEvents.groovy

The event methods are:

boolean contentShouldBeCreated(WcmContent parentNodeOrNull)

This method allows the content to veto whether or not it can be created. This is called on the newly populated but not saved content node, and is passed the parent node that it would be attached to, which can be null if it is root level content.

void contentDidGetCreated()

This is called after the content node has been created and saved.

boolean contentShouldBeDeleted()

This allows the content type to veto whether or not the node can or should be deleted - e.g. it may not be possible to delete a content node if it has children.

void contentWillBeDeleted()

This event is triggered just before the node is deleted.

void contentDidGetDeleted()

This event is triggered immediately after the node is deleted.

void contentDidChangeTitle(String previousTitle)

This event is called when the title property of a node has changed.

void contentDidGetUpdated()

This event is triggered after a node has been edited and the new values saved.

boolean contentShouldMove(WcmContent targetParent)

This event is called to allow it to veto being moved to parent nodes that are not compatible. Returning false will prevent the user moving the node (changing its parent).

void contentDidMove()

This is called after the content's parent has been changed and saved.

boolean contentShouldAcceptChildren()

This event is called to find out if the current node can accept child nodes.

boolean contentShouldAcceptChild(WcmContent newChild)

This event is called to find out if the current node can accept the new child node. There might be per-node reasons for vetoing new children.

You can also hook into many events outside of the domain classes themselves, using the Event Service.

Customizing the icon shown in the repository

You can supply an icon for your custom content types, that will be used in the admin UI of Weceem, e.g. in the repository view. To do this, define a static "icon" property which contains the arguments you would pass to Grails g:resource tag to create a link to the resource :

static icon = [
   plugin: "weceem",
   dir: "_weceem/images/weceem/content-icons",
   file: "widget-32.png"
]

It is vital to include the "plugin" attribute and value if you are placing your custom content class in a plugin of your own.

Content versioning

In order to participate in Weceem's content versioning system, you need to add any properties you wish to have serialized to the getVersioningProperties() implementation of your class. This function returns a map of values that are stored in the content revision.

These are simple values that must be expressible as strings. By necessity you may refer to objects not available at the time the user views your revision, so all complex object references that need to be versioned should be coerced to some kind of text representation.

For a real example, here's the code from WcmHTMLContent :

Map getVersioningProperties() {
    def r = super.getVersioningProperties() + [
        menuTitle:menuTitle,
        htmlTitle:htmlTitle,
        keywords:keywords,
        template:template?.ident() // A string representation of template object
    ] 
    return r
}

In almost all cases you will want to call the super method to ensure all inherited properties are correctly versioned.

6.3 Custom content editors

As mentioned in the create custom content type section, you can specify what kind of editor is used for each property of content types. By following some simple procedure, you can also create your own new editors.

Weceem will locate the appropriate editor using the name of the type of the property, if a custom property editor name is not given.

Content Editors

Weceem has quite a few editors that are available to edit all the predefined content type properties.

Editor Field NameDescription
Stringfor single-line strings
LongStringfor multi-line strings
Integerfor whole numbers
Booleanfor boolean fields
Datefor dates, includes datepicker and HH and MM fields
Titleuses a larger font for editing the title
TagsUI for displaying and add/remove tags via ajax
ContentFileUploadrenders a file upload field or file information if already uploaded
RichHTMLHTML editing with rich editor
HTMLContentHTML content editor that checks allowGSP property on content and switches between RichHTML and HtmlCode as appropriate
HtmlCodeCode editor with highlighting for HTML
JSCodeCode editor with highlighting for JavaScript
GroovyCodeCode editor with highlighting for Groovy code
CSSCodeCode editor with highlighting for CSS code
LanguageLista language selection drop-down list
WcmScriptFor selecting any WcmScript node in the current space
WcmStatusFor selecting any WcmStatus instance
WcmTemplateFor selecting any WcmTemplate instance in the current space
WcmSpaceFor selecting a Space
ReadOnlya text-only rendering of the value with no editing capability
ReadOnlyURIa text-only URI value, using URL encoding of restricted chars
ReadOnlyDatea read-only display of a date

Writing your own content editor

Creating a custom editor is simply a case of defining one or two Grails tags in the "wcm" namespace.

Editors are resolved by looking for a tag with the target editor name appended to "wcm:editorField". So for example a new editor for choosing a colour might be called Colour and the tag would be called "editorFieldColour" :

class MyWeceemTagLib {
    static namespace = "wcm"

def editorFieldColour = { attrs -> out << someFancyColourPicker(pageScope.content[attrs.property]) } }

These tags are called with the following attributes:

  • property - property name of the content object for which the tag should render a field

Your tags can access the current content object being edited using :

pageScope.content[attrs.property]

In the above example, you would not need to specify editor:'Colour' on your custom content classes if the property type is actually an instance of a Colour class.

Note : Polymorphism is not yet supported in this scenario so only exact class matches will work.

Providing resources needed by custom editors

Some editors - e.g. the FCK and code editors used by Weceem, require JavaScript and CSS resources to be included in the <head> section of the editor page. To achieve this, Weceem looks for a tag with a name like "wcm:editorResourcesColour" where the type name is appended to "editorResources". This tag can output anything you like into the <head> section of the page:

def editorResourcesTags = { attrs ->
        out << g.render(template:'/editors/tags_resources', plugin:'weceem', 
            model:[name:attrs.property, value:pageScope.content[attrs.property]])
    }

Here, the Tags editor uses another GSP to output the more complex resource elements it requires, setting up JS code in the <head> section to bind add/remove buttons using jQuery.

6.4 Custom request/form submission with groovy scripts

Capture data from forms and perform an action, such as sending mail after submission of a contact form by writing some Groovy code that you store in the content repository. This happens dynamically and you don't need to redeploy your Weceem application or restart it.

Two content types work together in Weceem to enable you to write arbitrary server-side code. The Scripted Action node type is used to receive requests, and delegates the work to a Groovy Script node.

For example, to create a Contact form that sends mail to a known address, you could implement it like this :

  1. Create a Groovy Script node in your repository, and paste into it the following code :

def args = [
  recipients:'admin@yourserver.com',
  subject:'Message from website visitor'
]
try {
  sendMail {
    to args.recipients
    if (args.ccRecipients) {
      cc args.ccRecipients
    }
    subject args.subject

from params.senderAddress body params.message } redirect(uri:params.success) } catch (Exception e) { log.error "Can't send mail", e redirect(uri:params.error) }

This code expects the Grails "mail" plugin to be installed (as it is by default in the Weceem application WAR file). This code is exactly what you would write if you were writing your own Grails controller action You can call redirect, render and other Grails controller methods as usual. The request parameters are in "params".

WeceemDomainEvents.groovy

2. Now that you have a script to run, you need to create a Scripted Action node to respond to requests for a specific URI and run the script. When you do this, select the script you created in the "Script" field. The full repository URI of this Scripted Action content node is the URI that needs to be used in your HTML form's action attribute. You can create links to this action inside your own Widget or Template nodes using the wcm:createLink tag.

With the above example, the HTML form needs to supply fields "senderAddress" and "message". It will redirect the user to the value of the "success" parameter which will usually be a URI in your content repository created using the wcm:createLink tag.

You can write any Groovy code you like in the Groovy Script node. The possibilities are endless for example, you can call out to external web services, perform calculations, render complex results in alternative formats (e.g. JSON).

However, you can easily degrade the performance of your server by writing inefficient code in a Groovy Script that is executed frequently.

It is strongly recommended that you limit the users that can create Groovy Scripts using the Weceem Security Policy.

6.5 Embedding content

In some custom applications you may want to embed some Weceem content within your own Grails GSP views. This is easily achieved for most situations using the new wcm:render tag as of release 1.1.

However, if you need to render content with more control over the process - e.g. using an artificial request/response to render content for use in emails, you can use the wcmRenderEngine bean.

This bean offers methods for rendering a specific content node.

You must typically pass in a request and response to use - given that most of Weceem functionality uses Grails tags and these require features of requests and responses - although you can usually also pass in your own controller instance if you have one.

See the source code for RenderEngine on github for more details.

6.6 Exporting and importing content

Each Weceem space (repository) can be individually exported and imported individually and is useful for backups or migrating content between servers or spaces.

For this :

  1. Go to the Administration section
  2. Select option - "Spaces"
  3. Click the Export or Import buttons
  4. Select space when prompted.

Use the default import/exporter - it is the native Weceem exporter.

The exported ZIP file will include all content nodes and uploaded files for that space. You can then import this ZIP into another Weceem instance, or to a different space in the same instance.

Importing will replace any content in the space!

Note :

Exported spaces will not include your user accounts or any custom WcmStatus nodes you created, as these are not Space-specific.

6.7 Hooking into content events

In custom Grails applications that use Weceem plugin you may need to perform actions when certain events occur on content.

To do this, register a listener with the WcmEventService and implement any of the event notification methods you require.

First, you inject the WcmEventService using Grails dependency injection, or obtain it via the Spring ApplicationContext, and then call the "addListener" method :

class MyService implements InitializingBean {
    def wcmEventService

void afterPropertiesSet() { wcmEventService.addListener(this) }

void contentDidGetUpdated(WcmContent node) { log.info "Look! Content ${node.title} was updated" } }

The list of available events is defined in the WeceemEvents class, which uses an optional event callback method mechanism -

package org.weceem.event

import org.weceem.content.WcmContent

class WeceemEvents { static events = { /* Called just before saving the node */ contentWillBeCreated { WcmContent contentNode, WcmContent parentNode -> } /* Called after a new node has been created */ contentDidGetCreated { WcmContent node -> }

/* Called before a node is deleted */ contentWillBeDeleted { WcmContent node -> } /* Called after a node is deleted */ contentDidGetDeleted { WcmContent node -> }

/* Called after a new node is updated */ contentDidGetUpdated { WcmContent node -> }

/* Called after a new node is moved to a new parent */ contentDidMove { WcmContent node, String previousURL, WcmContent previousParent -> } }

static { EventManager.define(WeceemEvents) } }

Implement the methods you need, using the signature defined in the WeceemEvents class.

There is a separate event interface called WeceemDomainEvents (given below) for developers that supply custom content types and for this you do not need to register as a lister - they are called as part of the regular lifecycle.

package org.weceem.event

import org.weceem.content.WcmContent

class WeceemDomainEvents { static events = { /* Called before a new node is created. Return true to allow the creation */ contentShouldBeCreated { WcmContent parentConent -> } /* Called just before saving the node */ contentWillBeCreated { WcmContent parentConent -> } /* Called after a node has been created, so that it can intialize anything that depends on it */ contentDidGetCreated()

/* Called to see if a node can be deleted. Return true to permit the deletion */ contentShouldBeDeleted() /* Called before a node is deleted. */ contentWillBeDeleted() /* Called after a node is deleted. */ contentDidGetDeleted()

/* Called after a node's title has changed (edited). */ contentDidChangeTitle { String previousTitle -> } /* Called after a node is updated (edited). */ contentDidGetUpdated()

/* Called before a node is moved to a new parent. Return true to permit the move. Parent may be null */ contentShouldMove { WcmContent newParent -> } /* Called after a node is moved to a new parent. */ contentDidMove { String previousURL, WcmContent previousParent -> }

/* Called to establish whether this node can have children. Return true to permit addition of children */ contentShouldAcceptChildren() /* Called before a node is moved to become a child of this node. Return true to permit addition of the new child */ contentShouldAcceptChild { WcmContent possibleChild -> } }

static { EventManager.define(WeceemDomainEvents) } }

6.8 Page rendering

When a request is made to Weceem, it goes through a number of steps to produce the final content.
  1. Weceem first locates the content at the URI path specified in the request
  2. Once located, it checks if content can be rendered. Content which is not "standalone" and used by other content cannot be rendered directly (e.g. Templates cannot be rendered directly).
  3. Next, it checks to see if the content implements rendering itself. If it does, it will call the content's handleRequest method to perform the rendering of the response.
  4. If there is no handleRequest method, Weceem will look for a "template" property on the content object and if it finds one, will ask that template to render the content.

Different content types may implement handleRequest differently - e.g. HTML Content first checks if the content is set to allow GSP tags to execute. If they are, it will execute the page and capture the output. The content is then passed to the template (if any) and returned to the client.

Note : If the content supports templates and a template is not set, Weceem will look up the content tree to see if any ancestors specify a template and will use the first one it finds.

In this most common case, the HTML and Template will work together and render one or more Widget nodes using <wcm:widget> to render common page elements like the header and footer sections.

Other types such as External Link will use handleRequest to issue a redirect to the browser.

Default document handling

Weceem supports httpd-style request URLs that end in /, it will automatically check for a child node of the URI, with one of the default names ("index.html" or "index"), much like a regular webserver.

6.9 Integrating the authentication and authorisation system

The Weceem plugin neither provides an authentication mechanism nor depends on authentication plugins. Just wire-in whichever authentication system you want to use - e.g. the Weceem CMS Application wires up the Weceem plugin to the Spring Security plugin for authentication and provides a simple user management UI as part of the Weceem Admin.

Customizing Authentication

The WcmSecurityService class has simple methods on it that the CMS uses to get information about the current logged-in user. To customize this, just replace or modify the delegate property on the service - e.g. to plug in Acegi (note that Spring Security replaces Acegi - see below for an off the shelf solution), you would place code like this in the BootStrap of your application :

def authenticateService = context.authenticateService

context.wcmSecurityService.securityDelegate = [ getUserName : { -> def princ = authenticateService.principal() if (princ instanceof String) { return null } else { return authenticateService.principal()?.username } }, getUserEmail : { -> def princ = authenticateService.principal() if (princ instanceof String) { return null } else { return authenticateService.principal()?.email } } getUserRoles : { -> authenticateService.userDomain()?.roleAuthorities ?: ['ROLE_GUEST'] }, getUserPrincipal : { -> authenticateService.principal() } ]

Integration with Spring Security

If you create your own application using Weceem plugin and wish to use the Grails Spring Security plugin, install the weceem-spring-security plugin to provide the bridge between Weceem and your application's Spring Security authentication/authorization scheme.

When you use this plugin there is no need to write your own UserDetailsService for the Spring Security plugin.

To do this, after installing the plugin you simply have to specify some configuration to tell the plugin how to map from your Spring Security domain classes, and the plugin will make this information available to Weceem :

weceem.springsecurity.details.mapper = { ->
    [ // Stuff required by weceem spring sec
        username: username,
        password: password,
        enabled: enabled,
        authorities: authorities,
        // optional stuff we add
        email: email,
        description: description,
        firstName: firstName,
        lastName: lastName,
        id: id
    ]
}

This closure must return a map of data used by Spring Security to store the information about your domain class. The "it" passed to the closure is the domain class instance, so the above is returning a map with values copied from the domain class.

Note : Only information required by Weceem is the username, password, enabled and authorities. Everything else is optional and only used by your application if you code it to do so.

The application remains responsible for defining the domain classes and performing the relevant Spring Security configuration to protect your URIs.

6.10 Styling With Templates

Templates are content nodes used to set page layouts on your site. Certain content types such as HTML and Blog, support setting a template when creating or editing nodes. If a template is not set, it is inherited from the content's ancestors.

Typically the template will include the head and body tags, pull in the CSS and other resources required, and provide some common layout elements.

They use standard Grails tags and Weceem tags to render content within the body of the page, as well as meta data about the current page e.g. dynamic titles.

Here's a simple example that renders a dynamic title and renders the content of the current node.

<html>
  <head>
    <title>${page.title.encodeAsHTML()}</title>
  </head>
  <body>
    <div id="content">
      <wcm:content/>
    </div>
  </body>
</html>

The <wcm:content/> tag is used to render the content of the page the user requested. Its important to remember that your Template is not actually requested by the user by URI - it is simply used to decorate the content the user requested.

You might add a news panel to this layout, by using the <wcm:widget> tag to render a reusable block that iterates over children of the "News" node to list the titles of articles and links to them.