mike.priest

mike.priest

Alfresco Certified Engineer Mike is a software engineer and a design enthusiast. He has spent the past 5 years creating small to large scale applications for a variety of business problems including two Content Management Systems (Progress & Toolbox), a number of productivity apps, document imaging, Epos software, websites and more. His main focus is on UX/UI architecture and is always ripping things apart to find out better ways to make them work and easier to work with.

Custom Share role breaks existing sites solution

ADDED: Your new group is added to the ACL of both site and document library spaces with the role taken from your new group name i.e. new group is  GROUP_site_SOMESITEID_SiteCollaboratorPlus, SiteCollaboratorPlus is the role.

When you create a custom role in Share you must know up front what roles you need – before your project begins because it breaks your existing sites if you deploy the role after the fact. This isn’t always how things work out so here is a quick little webscript that will fix existing sites when deploying a new custom role.

This is a java backed webscript that will search all existing sites that do not have your custom role security group. It will then take your new role name via Spring and dynamically add it to all sites in share (I put it in spring because we don’t want people creating new roles to ALL sites via a URL parameter).

If you are un-familiar with how the files talk to eachother or where to put things please see the Alfresco Wiki on Java backed webscripts. Once the webscript is installed, simply go to http://localhost/alfresco/wcs/ecm/sites/fixrole (Make sure you change the spring context to add YOUR custom role name)

The context file (webscript-context.xml):

This descriptor file is where you want to put your custom group name that its complaining about. Just replace your siteid with the :SITE param you see below.

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN 2.0//EN' 'http://www.springframework.org/dtd/spring-beans-2.0.dtd'>

<beans>

    <bean id="webscript.ca.abstractive.ecm.alfresco.sitefix.fix-role.get" 

      parent="webscript">

        <property name="serviceRegistry" ref="ServiceRegistry" />
        <property name="searchService" ref="SearchService" /> 
        <property name="nodeService" ref="NodeService" />
        <property name="authorityService" ref="AuthorityService" /> 

        <!-- Custom role SiteCollaboratorPlus -->
        <property name="authorityName">
            <value>GROUP_site_:SITE_SiteCollaboratorPlus</value>
        </property>  
    </bean>
</beans>

The webscript descriptor (fix-role.get.desc.xml):

<webscript>
  <shortname>Fix Share Roles</shortname>
  <description>Adds new role to all existing sites</description>
  <url>/ecm/sites/fixrole</url>
  <authentication>user</authentication>
  <format default="">argument</format>
  <family>Toolkit</family> 
</webscript>

The Java (UtilityRoleCreation.java):

package ca.abstractive.ecm.alfresco.sites;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.alfresco.model.ContentModel;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.ResultSetRow;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.web.bean.repository.Repository;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;

public class UtilityRoleCreation extends AbstractWebScript {
 
    protected ServiceRegistry serviceRegistry = null;
    protected SearchService searchService = null; 
    protected NodeService nodeService = null;
    protected AuthorityService authorityService = null;
    protected String authorityName = null;
    protected PermissionService permissionService = null;
    
    
    /**
     * Returns a list of sites in Alfresco
     *   
     * @return a list of site nodes 
     */  
    protected List<NodeRef> getSites(){
    
        //Get all sites
        String nodeQuery = "TYPE:\"st:site\"";

        SearchParameters params = new SearchParameters();
        params.setLanguage(SearchService.LANGUAGE_LUCENE);
        params.addStore(Repository.getStoreRef());
        params.setQuery(nodeQuery);
        ResultSet results = null;
        List<NodeRef> siteNodeRefs = new ArrayList<NodeRef>();
        
        try {
            results = this.searchService.query(params);
            if (results == null) {
                return null;

            } else {

                Iterator<ResultSetRow> siteNodes = results.iterator();
                while(siteNodes.hasNext()){
                     
                     ResultSetRow siteRow = siteNodes.next();
                     NodeRef nodeRef = siteRow.getNodeRef();
                     
                     siteNodeRefs.add(nodeRef);
                }
                
                return siteNodeRefs;
            }
            
        } finally {
            if (results != null) {
                results.close();
            }
        } 
         
    }    
    
    /**
     *  Webscript execute method
     *  
     */
    public void execute(WebScriptRequest req, WebScriptResponse res) 
            throws IOException  {
         
        try
        { 
            
            // build a json object
            JSONObject obj = new JSONObject();
            
            List<NodeRef> sites = this.getSites();
              
            Iterator<NodeRef> siteNodes = sites.iterator();
            while(siteNodes.hasNext()){
                 
                NodeRef nodeRef = siteNodes.next();
                  
                 //Get the sitename
                 String siteid = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);  
                 String siteGroup = new StringBuilder().append("GROUP_site_").append(siteid).toString();
                       
                 if(this.authorityService.authorityExists(siteGroup)){
                    
                    Set<String> authorities = this.authorityService.getContainedAuthorities(AuthorityType.GROUP, siteGroup, true);
                    String customRoleAuthorityName = this.authorityName.replace(":SITE", siteid);
                    
                    if (authorities.contains(customRoleAuthorityName) == false)
                     { 
                        obj.put(siteGroup, customRoleAuthorityName);
                        
                        //create the authority
                        String newAuthName = this.authorityService.createAuthority(AuthorityType.GROUP, customRoleAuthorityName.replace("GROUP_", "")); 
                        this.authorityService.addAuthority(siteGroup, newAuthName);
                         
                        // Assign the group the relevant permission on the site and document library
                        String[] bits = customRoleAuthorityName.split("_");
                        String role = bits[bits.length-1];
                        
                        permissionService.setPermission(nodeRef, customRoleAuthorityName, role, true);                            
                        NodeRef libraryNodeRef = nodeService.getChildByName(nodeRef, ContentModel.ASSOC_CONTAINS, "documentLibrary");
                        permissionService.setPermission(libraryNodeRef, customRoleAuthorityName, role, true);
                     }
                 }
            }
             
            // build a JSON string and send it back
            String jsonString = obj.toString();
            res.getWriter().write(jsonString);
        }
        catch(JSONException e)
        {
            throw new WebScriptException("Unable to serialize JSON");
        } 
    }
    
     
    
    /* ------------------------------------------------------------ */
    /* Getter/Setter methods */
    /* ------------------------------------------------------------ */
    
    /**
     * @return the serviceRegistry
     */
    public ServiceRegistry getServiceRegistry() {
        return serviceRegistry;
    }

    /**
     * @param serviceRegistry the serviceRegistry to set
     */
    public void setServiceRegistry(ServiceRegistry serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
        this.searchService = serviceRegistry.getSearchService(); 
        this.nodeService = serviceRegistry.getNodeService();
        this.permissionService = serviceRegistry.getPermissionService();
    }

    /**
     * @return the searchService
     */
    public SearchService getSearchService() {
        return searchService;
    }

    /**
     * @param searchService the searchService to set
     */
    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }
    
    /**
     * @return the authorityService
     */
    public AuthorityService getAuthorityService() {
        return authorityService;
    }

    /**
     * @param authorityService the searchService to set
     */
    public void setAuthorityService(AuthorityService authorityService) {
        this.authorityService = authorityService;
    }
      

    /**
     * @return the nodeService
     */
    public NodeService getNodeService() {
        return nodeService;
    }

    /**
     * @param nodeService the nodeService to set
     */
    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }


    public String getAuthorityName() {
        return authorityName;
    }


    public void setAuthorityName(String authorityName) {
        this.authorityName = authorityName;
    }
    
    /**
     * Set permission service
     */
    public void setPermissionService(PermissionService permissionService)
    {
        this.permissionService = permissionService;
    }  
    public PermissionService getPermissionService()
    {
        return permissionService;
    }
}

Alfresco 3.2+: Share Custom Actions

This short post will show you how to add a custom action in alfresco share’s document library. It will basically open a link to the Repo browser view showing the group and user level management. NOTE: Creating an action prior to 3.2 is different and if you’re looking for custom actions take a look at there WIKI. Its pretty well documented.

Here is a screen shot of the end result:

Locate the following files:

  1. share\WEB-INF\classes\alfresco\site-webscripts\org\alfresco\components\documentlibrary\documentlist.get.config.xml
  2. share\components\documentlibrary\documentlist.js
  3. share\WEB-INF\classes\alfresco\messages\slingshot.properties
  4. share\components\documentlibrary\documentlist.css

Step 1.

In documentlist.get.config.xml under actionSet id=”document” add the following (note the href reference, it will be used in some javascript to replace the url):

<actionSet id="document">
...

<!-- CUSTOM REPO PERMISSIONS -->
<action type="simple-link" permission="delete" id="onActionShowInRepo" href="{viewInRepo}" label="actions.document.show-in-repo" />
<!-- END CUSTOM -->

</actionSet>

Step 2.

In slingshot.properties add the label:

## Custom Action Repo Permissions actions.document.show-in-repo=Repository Permissions

Step 3.

In documentlist.js locate the function getActionUrls. There is a list of url replacements such as downloadUrl, viewUrl and documentDetailsUrl. Add the following to the list:

viewInRepo: “/share/page/manage-permissions?nodeRef=” + nodeRef,

NOTE: Remember to compress your javascript here and replace documentlist.min.js!

Step 4.

In documentlist.css add the following line for the repo icon:

.doclist .onActionShowInRepo a
{
background-image: url(“/share/res/components/images/header/repository.png”);
}

Reboot and take a look at your document library the link should take you to a screen like this:

Alfresco – HTML to PDF Transformation

Here is a quick way to create a “reliable” HTML (rendering) -> PDF.

Getting Started

First we will be using a simple shell utility to convert html to pdf using the webkit rendering engine, and qt. Click here to download wkhtmltopdf exe file.

NICE TO KNOW: This works on x64 Windows 7 too.

Install the exe on your machine and add find the install location. Usually in <drive>:\Program Files\wkhtmltopdf.

Add that directory to your windows path in windows environment variables. If you dont know what this means or how to click here

Once you have done that, test the install out. Open up CMD and type wkhtmltopdf google.ca test.pdf thats should output a pdf of google in what ever directory your CMD was just looking at.

Onwards…..

Alfresco Custom Transformation

Now we need to add an alfresco custom transformation. I dont know how everyone sets up there projects, so ill just tell you where the file is supposed to go.

Create a new xml file and call it: html2pdf-transform-context.xml

Paste the following into that file and drop it into the alfresco extension folder “\tomcat\webapps\alfresco\WEB-INF\classes\alfresco\extension”.

The file to paste:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>

<beans>
	<bean id="transformer.worker.html2pdf">
		<property name="mimetypeService">
			<ref bean="mimetypeService" />
		</property> 
		<property name="checkCommand">
			<bean>
				<property name="commandMap">
					<map>
						<entry key=".*">
							<value>wkhtmltopdf -help</value>
						</entry>
					</map>
				</property>
				<property name="errorCodes">
					<value>2</value>
				</property>
			</bean>
		</property>
		<property name="transformCommand">
			<bean>
				<property name="commandMap">
					<map>
						<entry key="Linux">
							<value>wkhtmltopdf '${source}' '${target}'</value>
						</entry>
						<entry key="Windows.*">
							<value>wkhtmltopdf "${source}" "${target}"</value>
						</entry>
					</map>
				</property>
				<property name="errorCodes">
					<value>2</value>
				</property>
			</bean>
		</property>
		<property name="explicitTransformations">
			<list>
				<bean
					class="org.alfresco.repo.content.transform.ExplictTransformationDetails">
					<property name="sourceMimetype">
						<value>text/html</value>
					</property>
					<property name="targetMimetype">
						<value>application/pdf</value>
					</property>
				</bean>
			</list>
		</property>
	</bean>
	<bean id="transformer.html2pdf" parent="baseContentTransformer">
        <property name="worker">
            <ref bean="transformer.worker.html2pdf" />
        </property>
    </bean>
</beans>

All this file does, is it performs an executable and uses alfresco paths for destinations.

Gotcha 1: LOCALTION, LOCATION, LOCATION – If you are using an archived HTML file in the repository for conversion, the source will be moved to a temporary file so make sure your URLS are absolute not relative.

Gotcha 2: If you are using HTML content nodes or Webscripts for the source, the executable does not have its own username and password therefore you will need to use the alfresco ticket authentication for things to work properly i.e. Append ticket=<ALF_TICKET> in freemarker you can use ${session.ticket}, if you need more help on tickets just post a comment. There are a couple of different ways to achive different things.

Gotcha 3: ……Gotcha!

Enjoy.

Alfresco Share Custom Pages

In this tutorial we will be creating Alfresco Share custom pages. We will be developing in the Alfresco Shared folder. Locate the web-extensions folder in \tomcat\shared\classes\alfresco\web-extension.

1.0 The file Structure

This folder contains the following:

site-data – Contains the configuration for pages, components and template instances
site-webscripts – Contains the webscripts for the page content
templates - Contains the Page template files.

1.1a Setting up the page – Page Configuration

In site-data/pages folder create a file called opg.xml. The file contents will look something like this:

<page>
   <title>OPG</title>
   <description>OPG Page Description</description>
   <template-instance>opg</template-instance>
   <authentication>user</authentication>
</page>

The template-instance property will be used later on to link this page to the page template.

1.1b Setting up the page – Template Configuration

In site-data/template-instances folder create a file called opg.xml. The file contents will look something like this:

<template-instance>
<template-type><b><your company=""></your></b>/opg</template-type>
 <properties>
      <pagefamily>opg</pagefamily>
      <container>opg</container>
   </properties> 
</template-instance>

The template-type property will tie in the file path for your template file in the next step.

1.1c Setting up the page – Template Definition

We should begin to see this pattern of relationships between files (If you dont see this pattern a Share PAGE (webpage) contains COMPONENTS which in turn displays WEBSCRIPTS):
Page -> Template -> Components -> Webscripts

In the templates folder (web-extension\templates) create a folder called . Within your company folder create two more folders: include and import. To get this to work with your versoin of alfresco copy the following files to these folders:

C:\< YOUR ALFRESCO >\tomcat\webapps\share\WEB-INF\classes\alfresco\templates\org\alfresco\include\alfresco-template.ftl – Copy to the include folder.
C:\< YOUR ALFRESCO >\tomcat\webapps\share\WEB-INF\classes\alfresco\templates\org\alfresco\import\alfresco-common.ftl – Copy to the import folder.
C:\< YOUR ALFRESCO >\tomcat\webapps\share\WEB-INF\classes\alfresco\templates\org\alfresco\import\alfresco-layout.ftl – Copy to the import folder.

Now create your template file in the templates/< YOUR COMPANY > folder. Call it opg.ftl. Feel free to copy this code and put it in your template file.
If you notice the path of this template file relative to the templates folder is < YOUR COMPANY >/opg.ftl this is the filepath from the configuration before.

<link href="/alfresco/css/custom-theme/jquery-ui-1.7.3.custom.css" rel="stylesheet" type="text/css" />
<link href="/alfresco/css/custom-theme/demo_table.css" rel="stylesheet" type="text/css" />
<link href="/alfresco/css/screen.css" rel="stylesheet" type="text/css" />

<#include "include/alfresco-template.ftl" />
<@templateHeader> 
   <@script type="text/javascript" src="${url.context}/js/alfresco-resizer.js"><!--@script--> 
   <@templateHtmlEditorAssets />     
<!--@--> 
<@templateBody>

<div id="alf-hd">

	<@region id="header" scope="global" protected=true />
	  <@region id="title" scope="global" protected=true />
	  <@region id="navigation" scope="global" protected=true />

</div>
<div id="bd">
	<@region id="body" scope="template" protected=true /></div>
<!--@--><p>
	<@templateFooter></p>
<div id="alf-ft">
	<@region id="footer" scope="global" protected=true /></div>
<!--@-->

The region hilighted blue above is the definition of a “bucket” or content container. You will notice the red hilights, these are crutial to the development of custom pages. ID: This is used to identify the content and location of the webscript it will pull in. SCOPE: This allows us to globalize regions accross templates, or more so allow components to be globalized accross templates. The definition of the name which you will see in a moment allows us to do this.

2.0 Component Definition

In site-data\components create a file called template.body.opg.xml. Notice the file name is specifically defined for SCOPE.ID.PAGE NAME.xml. If you have a file defined as global then you can use the global scope thus making the file global by name definition and not by region attribute. The naming convention seems to be vital in version 3.2. The header, title and navigation global files need to be copied over from: C:\ < YOUR ALFRESCO >\tomcat\webapps\share\WEB-INF\classes\alfresco\site-data\components to this folder in order for them to work as we are working from the WEB-EXTENSION in shared. In the new file (template.body.opg.xml):

<component>
	<scope>page</scope> 
	<region-id>body</region-id>
	<source-id>opg</source-id>
	<url>/components/dashlets/opg/home</url>
        <guid>template.body.opg</guid>  
</component>

The GUID will be the same as the filename. URL is the Webscript URL you set your websripts. This allows the component to point at the webscript.

3.0 Webscripts

In web-extension\site-webscripts\org\alfresco\components (If the folders dont exist just create them). Create a folder called opg and beneith that level another called home.
In the home folder create three files:

1. home.get.desc.xml – Configuration of the webscript
2. home.get.html.ftl – HTML/Freemarker page
3. home.get.js – JavaScript page for GET Request

In the home.get.desc.xml the file will look like this:

<webscript>
	   <shortname>OPG Home</shortname>
	   <description>Some description</description>
	   <family>dashlet</family>
	   <url>/components/dashlets/opg/home</url>
	</webscript>

Remember that URL we talked about in the component setup, the URL here is defining what this webscript URL will be thus the component can map to that.

In home.get.html.ftl feel free to add some HTML
Hello World!

4.0 Adding pages to the navigation

In the following file: C:\< YOUR ALFRESCO >\tomcat\webapps\share\WEB-INF\classes\alfresco\site-data\presets\presets.xml

Look for this line: [{"pageId":"wiki-page"}, {"pageId":"blog-postlist"}, {"pageId":"documentlibrary"}, {"pageId":"calendar"},{"pageId":"links"},{"pageId":"discussions-topiclist"}]

Add your pageId to the list it will look something like this (KEEP IN MIND THE PRESET ID SHOULD BE id=”site-dashboard”):

[{"pageId":"opg"}, {"pageId":"wiki-page"}, {"pageId":"blog-postlist"}, {"pageId":"documentlibrary"}, {"pageId":"calendar"},{"pageId":"links"},{"pageId":"discussions-topiclist"}]

Restart Alfresco and create a new site, your page and link will be in the navigation. Word!

Alfresco 4: Share custom actions

This post is a further demonstration from Custom share action 3.2+ showing how to add a custom action in Alfresco Share 4.

Here is a screen shot of the end result:

Locate the following files:

share\WEB-INF\classes\alfresco\share-documentlibrary-config.xml
share\WEB-INF\classes\alfresco\messages\slingshot.properties

Step 1.

In share-documentlibrary-config.xml

Action Groups
In Alfresco 3.2 you would put your extension in actionSets such as &lt;actionSet id=&quot;document&quot;&gt;. The actionSet id would tell you wheather it was to be shown on the document actions in the doclib list, in the document details etc…
In Alfresco 4 you only need to declare the action once and then add it to the action group. Like shown below:

Firstly create your action:

<!-- Document Library Actions config section -->
<config evaluator="string-compare" condition="DocLibActions">
...
<!-- View document in repo for permissions -->
<action id="document-show-in-repo" type="link" label="actions.document.show-in-repo">
<param name="href">/share/page/manage-permissions?nodeRef={node.nodeRef}</param>
</action>
...
</config>

Then add the action to the actionGroup:

<actionGroup id="document-details">
...
<action index="360" id="document-show-in-repo" />
...
</actionGroup>

Step 2.

In slingshot.properties add the label:

## Custom Action Repo Permissions
actions.document.show-in-repo=Repository Permissions

Step 3.

The icons are now automatically added inline to your anchor so it will look like this:

<a style="background-image:url(/share/res/components/documentlibrary/actions/document-show-in-repo-16.png)" href="#" title="Repository Permissions" id="yui-gen121"><span id="yui-gen123">Repository Permissions</span></a>

Take a note at the URL. Now go and add the image you want to use using the action id and the image size this one is using 16px x 16px. url(“/share/res/components/documentlibrary/actions/document-show-in-repo-16.png”)

Reboot and take a look at your document library