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;
    }
}

Implementing a complete high availability Alfresco solution using open source technologies

As a proof of concept, I have done some research and experimenting to determine the best way of clustering Alfresco using completely open source components. I wanted a solution that offered load balancing as well as fault tolerance. There are three components outside of Alfresco that are needed to achieve this.

  1. Load balancer
  2. File system
  3. Database

The load balancer is the simplest component of the three, and the one with the most options available. We just need a load balancer that is able to handle sticky sessions. A dumb load balancer which round robins connections will not work for this scenario.

Alfresco stores all the content as regular files. (Unlike Sharepoint putting content in the database. Yikes!) In order to achieve HA on the content repository we need some sort of clustered or replicating file system. It was not long ago when clustered file systems were out of reach from the open source community. It is great that we now have some viable open source options now.

The last component needed, of course, is the database. Unfortunately, there is no viable multi-master open source option. There are many projects that are working towards this, such as Bucardo. But there is nothing currently that is a drop in replacement and/or production ready. The good news is we still have a master-slave(s) setup that can still achieve HA and some sort of load balancing.

Here is the complete solution I implemented:

 

Alfresco: Alfresco Enterprise 4.0.2

I used the latest version of Alfresco Enterprise at the time of writing this, just since it is what I deal with the most. I believe the Community Edition would work just fine as well in this scenario since the heart of Alfresco clustering is within Ehcache.

 

Load balancer: HAProxy

HAProxy is known to be very stable and currently used on some very high traffic web sites. It also gives us the functionality to keep track of sessions via the JSESSIONID cookie. Another great feature is we can take the fault detection further, and test a web script page in Alfresco to determine if Alfresco is currently running. (http://admin:passwd@server1/alfresco/wcs/s is a great page to check.)

There will be a small portion of people that were looking at this diagram and saying to themselves, “But there is a single point of failure!” HAProxy is a very simple component, and it would be easy to set up an active/passive automatic fail over. Also very stable physical and virtual options exist.

I should also note that we have tested HAProxy using single sign on authentication via Active Directory Kerberos. I assume NTLM would work just fine as well.

 

Clustered file system: GlusterFS

I have read good things about GlusterFS, but this was my first hands on experience with it. I was shocked how simple and quick this was to get up and running. A command to add the second server, and another to get the replication going. No messing with configuration files. You can even have 4 servers and enable replication and striping. Similar to the way RAID 10 (or 0+1) works, but across servers. This is a perfect fit for putting Alfresco’s content. Load balancing and seamless fault tolerance.

 

Replicating database: PostgreSQL + pgpool-II

MySQL is still an option, but I chose to go with Postgres here. I liked some of the HA features Postgres provided that seemed lacking in MySQL.  Unfortunately, either way we have to use a master-slave replication configuration.

In order to achieve load balancing and fault tolerance we need to put pgpool-II in front on the databases. It will take read only queries and load balance them between the master and slave(s). Commands that involve any kind of updates, or writes will be forwarded to the master which in turn get streamed to the slaves. This makes writes slower than a standalone database, but most Alfresco installs should be primarily reads for the average implementation. Pgpool can also be configured to use parallel queries. This means large queries can be split up amongst servers.

Pgpool will also detect any faults, so if any of the slaves go down it will just take them out of the pool. And if the master goes down, it will take one of the slaves and promote it to the new master. For the chance of a problem with Pgpool, a similar configuration with HAProxy, an active/passive configuration can be used to add some redundancy.

 

Enjoy your content management uptime! And feel free to drop me a comment.

Alfresco – Version Stamp Your AMP

I always find myself renaming AMP files after building them – I add the module version to the name. Being relatively lazy I decided to see if I could have Alfresco’s ANT build do this for me.

After a bit of trial and error I figured it out and created an ANT macro to do it for me.

I put my macro in a separate file called abstractive-macros.xml.

It has 2 parameters:

  1. modpropsdir is the directory containing the module.properties file. This is where we read the module version. It’s probably already defined in your build.properties under a property called dir.module.MYPROJECT.property.
  2. ampnameproperty is the NAME of the property that contains the AMP file name. We just want the name of the property (not its value) because we want to update the property itself so anything else that uses it will have the new name.

The macro:

  • pulls in the contents of module.properties
  • pulls the extension from the amp file name
  • gets the “module.version” from module.properties
  • builds a new amp file name that includes the version

It turns out you can use javascript within an ANT script – I didn’t know that. You can also use all kinds of other scripts such as groovy, ruby, jython, judoscript, etc.

Here’s the complete macro xml.

abstractive-macros.xml

<project name="abstractive-macros">

   <macrodef name="versionamp" description="Appends the module version to the amp file name">

      <!-- modpropsdir is the VALUE of the dir.module.XXXXX.property -->
      <attribute name="modpropsdir" />

      <!-- ampnameproperty is the NAME of the file.name.amp.XXXXX property
           and its value will be updated to include the module version -->
      <attribute name="ampnameproperty" />

      <sequential>
        <!-- append module version to amp file name -->
        <property file="@{modpropsdir}/module.properties"/>
        <script language="javascript"> <![CDATA[
          amp = project.getProperty("@{ampnameproperty}");
          title = amp.substring(0, amp.indexOf(".amp"));

          version = project.getProperty("module.version");
          project.setProperty("@{ampnameproperty}", title + "_" + version + ".amp");
        ]]> </script>
      </sequential>
   </macrodef>
</project>

The only change to build.xml is to include our new macro xml file.

build.xml

...
  <import file="macros.xml" />
  <import file="abstractive-macros.xml" />
  <import file="projects.xml" />
  ...

And in projects.xml we make the call to the macro. This is done in the package-MYMODULE-extension target. I pass it the path to module properties and the NAME of the property that contains the amp file name.

projects.xml

...
  <target name="package-MYMODULE-extension" depends="package-MYMODULE-jar">

    <versionamp modpropsdir="${dir.module.MYMODULE.property}"
      ampnameproperty="file.name.amp.MYMODULE" />

    <zip destfile="${dir.module.MYMODULE.dist}/${file.name.amp.MYMODULE}" update="true">
      <zipfileset file="${dir.module.MYMODULE.property}/module.properties" />
      <zipfileset file="${dir.module.MYMODULE.dist}/${file.name.jar.MYMODULE}" prefix="lib" />
      <zipfileset dir="${dir.module.MYMODULE}/${dir.name.lib}" prefix="lib" />
      <zipfileset dir="${dir.module.MYMODULE.config}" prefix="config">
        <exclude name="**/module.properties" />
      </zipfileset>
    </zip>
  </target>
  ...

And that’s all there is to it.  So if:

  • your module.properties contains module.version=1.10
  • in your build.properties your file.name.amp.MYPROJECT=MYPROJECT.amp

and you run the package-MYPROJECT-extension target it will generate an amp called MYPROJECT_1.10.amp.

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