Saturday, December 22, 2012

Site tracking with Piwik

I used Piwik in a previous Web Community project. So when I was asked how to add web statistics to Alfresco, my first idea was to integrate it with Piwik. So let's start to try it out:

At first a Piwik installation is required. My target test system is an OpenSuse 12.1 and so all the dependencies are available from the software repository. They are further described here: http://piwik.org/docs/requirements/ .

So after you installed an Apache, all required PHP modules and a MySQL database (BTW: This is something which I do not like regarding Piwik. The only supported database is MySQL. I would like to see at least Postgres support. ) you can begin to install your Piwik instance. Therefore you can follow the following instructions: http://piwik.org/docs/installation/ .

The most interesting part may be the database setup:

mysql> CREATE USER 'piwik'@'localhost' IDENTIFIED BY '${Your pwd here}';
mysql> CREATE DATABASE piwikdb;
mysql> GRANT ALL PRIVILEGES ON piwikdb.* TO 'piwik'@'localhost' WITH GRANT OPTION;


Then unzip the latest Piwik zip to your Apache2 web server and open the URL 'http://localhost/piwik'. For OpenSuse the htdocs folder is located at '/srv/www'.

If calling Piwik the first time it will prompt you to perform the following file permission changes:

chown -R www-data:www-data /srv/www/htdocs/piwik
chmod -R 0777 /srv/www/htdocs/piwik/tmp
chmod -R 0777 /srv/www/htdocs/piwik/tmp/templates_c/
chmod -R 0777 /srv/www/htdocs/piwik/tmp/cache/
chmod -R 0777 /srv/www/htdocs/piwik/tmp/assets/
chmod -R 0777 /srv/www/htdocs/piwik/tmp/tcpdf/

It's maybe required to create the user, group or directories above. You should also enable a temp. write access to '/srv/www/htdocs/piwik/config/'.

An error occurs that your php installation need zlib-suppot. You can find 'php5-zlib' in the OpenSuse software repository. Do not forget to restart Apache after installing this extension.

The next steps are quite easy. Just follow the Installation Wizard by entering your database connection details.

Also part of the installation is the generation of the tracking code. The installer says: "Here is the JavaScript Tracking code to include on all your pages, just before the </body> tag"

<!-- Piwik --> 

<script type="text/javascript">

var pkBaseURL = (("https:" == document.location.protocol) ? "https://localhost/piwik/" : "http://localhost/piwik/");

document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E"));

</script><script type="text/javascript">

try {

var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 1);

piwikTracker.trackPageView();

piwikTracker.enableLinkTracking();

} catch( err ) {}

</script><noscript><p><img 
src="http://localhost/piwik/piwik.php?idsite=1" style="border:0" alt="" 
/></p></noscript>

<!-- End Piwik Tracking Code -->

Important is the 'idsite' property. The value '1' is the id of the site which I configured as 'http://localhost:8080/share'. OK, Piwik is now up and running. Now let's investigate how to customize Alfresco in order to use it.

So to enable global tracking we can search a header or footer element which is used by every page of Alfresco. So let's check which site web scripts are available and let's see if we can put our snippet to one of the freemarker templates.

One good candidate seems to be '${WEBAPPS}/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/header'.  So I just placed the script above after the first other '<script>' block.

A test showed the following result:


Piwik can do a lot more. But for now this shows exactly what we required. It answers the question which site was accessed how often.







Thursday, December 20, 2012

First contact with Bootstrap

Insprired by Thomas Glaser and Jan Pfitzner, I thought it could be a good idea to get in contact with Twitter's Bootstrap framework. So here a simple skeleton page which uses Bootstrap components to reflect a simple web site. jQuery is used to interact with menu and the content area.

Here the result:



And finally the code:

<!-- (0) Create an empty HTML page -->
<!DOCTYPE html>
<html>
    <!-- (1) Some basic header info -->
    <head>
        <meta charset="utf-8">
            <title>Basic web site</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta name="description" content="">
            <meta name="author" content="">

        <!-- (2) Import the provided CSS file -->
        <link href="css/bootstrap.min.css" rel="stylesheet" media="screen">   
    </head>
   
    <body>
         <!-- (3) Import jQuery and Bootstrap -->
        <script src="js/jquery-latest.js"></script>
        <script src="js/bootstrap.min.js"></script>
       
        <!-- (5) At last add some JavaScript (jQuery) here to fill the page with life -->
        <script>
        $(document).ready(function(){
         
           $("#header-home").click(function() {
            $('div.my-content').html('<p>Home ...</p>');
            });

          $("#header-contact").click(function() {
            $('div.my-content').html('<p>Contact ...</p>');
            });


          $("#header-about").click(function() {
            $('div.my-content').html('<p>About ...</p>');
            });

          $("#header-contact").click(function() {
            $('div.my-content').html('<p>Contact ...</p>');
            });

         });     
            </script>

       
        <!-- (4) Bootstrap decorated HTML here -->
        <div id="container">

           <!-- A navigation header bar which is fixed to the top -->
           <div class="navbar navbar-fixed-top navbar-inverse">
                   <div class="navbar-inner">
                       <a class="brand" href="#"> Basic web site</a>
                       <ul id="header" class="nav">
                           <li class="active"><a href="#" id="header-home">Home</a></li>
                        <li><a href="#about" id="header-about">About</a></li>
                        <li><a href="#contact" id="header-contact">Contact</a></li>
                    </ul>
                         </div>
               </div>
       
             <!-- (4.1) A simple web site header -->       
           <div class="hero-unit">
                <h1>Basic web site</h1>
                <p>This site explains the Twitter Bootstrap a little bit.</p>
                <p>
                </p>
               </div>
         

           <!-- (4.2) A HTML grid with 12 columns, in this case we -->
             <div class="row">
            <!-- 4 columns are used for the left hand side navigation bar -->
                <div class="span4 bs-docs-sidebar">
                <ul class="nav nav-list bs-docs-sidenav">
                     <li><a href="#Marker1"><i class="icon-chevron-right"></i> Marker 1</a></li>
                     <li><a href="#Marker2"><i class="icon-chevron-right"></i> Marker 2</a></li>
                     <li><a href="#Marker2"><i class="icon-chevron-right"></i> Marker 3</a></li>
                </ul>
            </div>
               
            <!-- (4.3) 8 columns are used for the right hand side content area -->
            <div class="span8">
               
                <!-- (4.4) The content area which can be decorated by own css -->
                <div class="my-content">
                      <p> Home ...</p>
                </div>
            </div>
               </div>
            </div>   
    </body>
</html>

Friday, December 14, 2012

About the anatomy of ArchiveLink

Preamble

I just played arround with ArchiveLink, and so I decided to read the specification to inform myself how it internally works. What you can use in order to archive your SAP documents (invoices, ...) is an ArchiveLink speaking HTTP Content Server. So here a short summary of what I understood from the ArchiveLink specification so far.

Terms

We want to store documents inside a content repository. I guess it is not required to explain this term further. ArchiveLink knows the term 'component', whereby a component represents a content unit on a administrative level. Several component types are used (for instance 'data'),  Components are summarized via a document header. So here a scenario:

  • One Content Repository contains multiple document headers
  • One document header references multiple components
  • One component contains one content unit
A document header (id, status, date, ...) and a component (content type, status, ...) has some adminstrative meta data attached.

So an archive needs to reflect that terms somehow. The most intuitive way seems to reflect it by using folders. So the content repository contains a folder of the type header which contains component folders those are containing documents to keep the content. Important is that ArchiveLink does by design not transfer any SAP business object related meta data. Only administrative meta data is transfered to the archive.

The protocol

HTTP is used to exchange date on a lower level. So we are speaking about a kind of RESTFul service access. The format is 'http://${server}:${port}/${service name}/${command}?${command parameters}. If we take security into account (more about it later) and take a spot on the get command by retrieving the data component then the URL has the following format:

http://${host}:${port}/${service name}?get&contRep=${repo id}&docId=${doc id}&compId=data&accessMode=r&authId=${user id}&expiration=${time}?secKey=${base64 encoded security key}.

The following commands are available:

  • get: get a content unit
  • info: get info about a document
  • docGet: get the whole content
  • create: Create a new document
  • update: Modify an existing document
  • append: Append data to a content unit
  • search: Search by using a pattern inside a content unit (full text search)
  • attrSearch: Search for a specific attribute value
  • mCreate: Create new documents
  • serverInfo: Retrieve information about the content server
  • putCert: Transfer the client certificate
The resonse is a little bit old scool (the specification document is from 2001) and so does not return JSON, but Multipart Form Data.

Here an example response from the publically available specification document:

HTTP/1.1 200 (OK)
Server: Microsoft-IIS/4.0
Date: Wed, 04 Nov 1998 07:41:03 GMT
Content-Type: multipart/form-data; boundary=A495ukjfasdfddrg4hztzu...
...some more header informations...
Content-Length: 32413
X-dateC: 1998-10-07
X-timeC: 07:55:57
X-dateM: 1998-10-07
X-timeM: 07:55:57
X-contRep: K1
X-numComps: 2
X-docId: ID
X-docStatus: online
X-pVersion: 0045
--A495ukjfasdfddrg4hztzu898aA0jklmAxcvla12319981147528895
Content-Type: application/x-alf; charset=
Content-Length: 2591
X-compId: descr
X-Content-Length: 2591
X-compDateC: 1998-10-07
X-compTimeC: 07:55:57
X-compDateM: 1998-10-07
SAP AG SAP ArchiveLink (BC-SRV-ARL)
docGet
April 2001 265
X-compTimeM: 07:55:57
X-compStatus: online
X-pVersion: 0045
...component data ...
--A495ukjfasdfddrg4hztzu898aA0jklmAxcvla12319981147528895
Content-Type: application/x-alf; charset=
Content-Length: 29313
X-compId: data
X-Content-Length: 29213
X-compDateC: 1998-10-07
X-compTimeC: 07:55:57
X-compDateM: 1998-10-07
X-compTimeM: 07:55:57
X-compStatus: online
X-compStatus: online
X-pVersion: 0045
...component data ...
--A495ukjfasdfddrg4hztzu898aA0jklmAxcvla12319981147528895--
Beim docGet-Kommando auf ein leeres Dokument steht im Response-Body beispielsweise:
--A495ukjfasdfddrg4hztzu898aA0jklmAxcvla1231999102562159269
--A495ukjfasdfddrg4hztzu898aA0jklmAxcvla1231999102562159269--
Summary

This is the furst article of several ones those are focusing on ArchiveLink. What we should have learned is the following: An ArchiveLink archive can be realized by using a HTTP Content Server. The Content Server then could speak with a simple File System or even better an DMS. The content server provides several commands and functions to store and access content items. There are several kinds of content items, wrapped by components. A specific URL pattern is used on side of the HTTP Content Server. The response of the HTTP Content Server has the Multipart Form Data format.


Monday, December 3, 2012

How to setup an Alfresco Maven project in a few steps

 Install the tools and libraries
  1.  Install Maven (E.G. use 'sudo apt-get install maven2')
  2. Install Subversion (E.G. use 'sudo apt-get install subversion')
  3. Install the Subversion Java bindinds (E.G. use 'sudo apt-get install  libsvn-java)
 Install Eclipse and the required plug-ins (optional)
  1. Install Eclipse (you will need it as the I(ntegrated) D(evelopment) E(nvironment)
  2. Install the Eclipse Maven plug-in (The download site is 'http://download.eclipse.org/technology/m2e/releases'
  3. Install the Subversion plug-in (The easisest is to enter subversion in the Eclipse Market Place)
  4. In Eclipse create a new workspace
Create the project for the repository extensions
  1.  Change the directory to the Eclipse workspace
  2. Then run the following command
mvn archetype:generate -DarchetypeGroupId=org.alfresco -DarchetypeArtifactId=maven-alfresco-amp-archetype \
-DarchetypeVersion=3.9.1 -DgroupId=${Your domain here, E.G. de.ecg} -DartifactId=${Your project name -repo here, E.G. my-repo} -Dversion=1.0-SNAPSHOT \
-DarchetypeRepository=https://artifacts.alfresco.com/nexus/content/repositories/releases -DinteractiveMode=false
 
 The above command contains 2 place holders. One specifies your domain the other one should name the project. For example the project 'my-repo' will be created.

Create the project for the Share extensions
  1.  Change the directory to the Eclipse workspace
  2. Then run the following command

mvn archetype:generate -DarchetypeGroupId=org.alfresco.maven -DarchetypeArtifactId=maven-alfresco-share-archetype \
-DarchetypeVersion=3.9.1 -DgroupId=${Your domain here} -DartifactId=${Your project name -share here} -Dversion=1.0-SNAPSHOT \
-DarchetypeRepository=https://artifacts.alfresco.com/nexus/content/repositories/releases -DinteractiveMode=false
 
Create the Eclipse projects
  1. Open Eclipse
  2. Click on 'Import -> Maven -> Existing Maven Project into Workspace'
  3. Navigate into the folder of the repo project and confirm
  4. The download of the several dependent artifacts / libraries to your local Maven cache may take a while
  5. Do the same for the Share project
A really cool and easy way to get an Alfresco project set up quite simpler than with ANT script. In the pom.xml you can see that the referenced Alfresco version is 4.0.2b which seems to be an older Community Edition release. An open question is how to setup a similar project for the Alfresco Enterprise Edition.

Wednesday, July 25, 2012

How to enable DQL tracing

I am currently analyzing some Documentum performance issues. To be able to reconstruct the problem maybe more easier or to check the database indexes it is useful to identify the problematic DQL queries. DQL is Documentum's Query Language. In fact a DQL statement is translated into an SQL one. The Documentum Administrator can return you the SQL for every running DQL. So what you need to get the DQL is to add the following lines to your dfc.properties file on side of your Content Server installation (E.G. ./product/6.5/shared/config/dfc.properties):
dfc.tracing.enabled = false
dfc.tracing.recordParameters = on
dfc.tracing.recordReturnValue = on
dfc.tracing.stackDepth = 100
dfc.tracing.combineDMCL = on
dfc.tracing.dir = /tmp/dfclogs
Any further ideas how to trace the DQL? Then just post a comment to this article!

Wednesday, June 20, 2012

A simple pinboard dashlet

0.) Preamble

This example shows how you could develop simple Alfresco Dashlets. So here some requirements:

  • A dashlet is required to see and post short messages
  • Everybody should be able to post messages to the specific site where the dashlet is available.
1.) Define the Content Model 

At first we need to define the content model for our pinboard entry type. The content model looks as the following:


<?xml version="1.0" encoding="UTF-8"?>

<!-- Model definition -->
<model name="ecg:pinboardmodel" xmlns="http://www.alfresco.org/model/dictionary/1.0">

    <!-- Optional meta-data about the model -->
    <description> The pinboard model </description>
    <author> David Maier </author>
    <version> 1.0 </version>

    <!-- Imports are required to reference definitions in other models -->
    <imports>
           <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" />
           <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm" />
    </imports>

    <!-- The name space of our model -->
    <namespaces>
           <namespace uri="http://www.ecmgeek.de/model/content/1.0" prefix="ecg" />
    </namespaces>
   

    <!-- Content types -->
    <types>
   
           <!-- Default types -->
          <type name="ecg:document">
            <title>Document</title>
            <parent>cm:content</parent>
         </type>
        
         <type name="ecg:folder">
            <title>Folder</title>
            <parent>cm:folder</parent>        
         </type>

           <!-- Specific types -->
           <type name="ecg:pinboardentry">
              <title>PinboardEntry</title>
              <parent>ecg:document</parent>
              <properties>
                 <property name="ecg:subject">
                    <type>d:text</type>
                 </property>
                 <property name="ecg:description">
                    <type>d:text</type>
                 </property>
              </properties>
           </type>         
</types>
</model>

You can see that we just extended the default types by deriving the type 'ecg:pinboardentry'.

I deployed the model by just adding it to the Data Dictionary.

2.) Prepare the site 

The next step is to prepare the site which you would like to use. This I just created a subfolder 'pinBoard' inside the site's folder.

3.) Create Data Web Scripts

Two Web Scripts are required. One to add a pinboard entry to a specific site and another one to get all pinboard entries from a specific site.

3.1.) Add entry Web Script 

Here the descriptor:
pinboardadd.get.desc.xml
<?xml version="1.0" encoding="UTF-8"?>
<webscript>
    <shortname>Add Pinboard entry</shortname>
    <description>To add a pinboard entry</description>
    <url>/alfintra/pinboard/add?name={nameArgument}&amp;desc={descArgument}&amp;site={siteArgument}</url>
    <format default="xml">extension</format>
    <authentication>user</authentication>
    <transaction>required</transaction>
</webscript>

And the Java Script controller:

pinboardadd.get.js
/**
 * Arguments
 */
//The target site name
var siteName = args["site"];

//The name of the future entry
var entryName = args["name"];

//The description of the future entry
var entryDesc = args["desc"];


/**
 * Argument validation
 */
var siteFolder = companyhome.childByNamePath("Sites/" + siteName + "/pinBoard" );

if (siteFolder == undefined )
{
   status.code = 404;
   logger.log(status.code);
   status.message = "The site's pinBoard folder was not found. Did you prepare your site in order to use the pinboard?";
   logger.log(status.message);
   status.redirect = true;
}
else
{
    addPinboardEntry(siteFolder, entryName, entryDesc);
}


/**
 * Script logic
 */
function addPinboardEntry(siteFolder, entryName, entryDesc)
{
    logger.log("Entering addPinboardEntry");
   
    logger.log("Setting properties");
      var props = new Array();
    props["ecg:subject"] = entryName;
    props["ecg:description"]= entryDesc;
       
    logger.log("Creating node");
      siteFolder.createNode(entryName,"ecg:pinboardentry", props);

    logger.log("Setting model");
     model.created = "true";

    logger.log("Leaving addPinboardEntry");
}

Finally the presentation template. This template shows nothing. It just redirects to the page from which the html page was called.

pinboardadd.get.html.ftl
<script type="text/javascript">

  var ref = document.referrer;
  location.replace(ref);
</script>

3.2.) Get entries Web Script

Here the descriptor:

pinboardlist.get.desc.xml 
<?xml version="1.0" encoding="UTF-8"?>
<webscript>
    <shortname>List pinboard entries</shortname>
    <description>To list the pinboard entries of a site</description>
    <url>/alfintra/pinboard/list?site={siteArgument}</url>
    <format default="xml">extension</format>
    <authentication>user</authentication>
    <transaction>required</transaction>
</webscript>

 The Java Script controller:

pinboardlist.get.js
/**
 * Arguments
 */

//The target site name
var siteName = args["site"];

/**
 * Argument validation
 */
var siteFolder = companyhome.childByNamePath("Sites/" + siteName + "/pinBoard" );

if (siteFolder == undefined )
{
   status.code = 404;
   logger.log(status.code);
   status.message = "The site's pinBoard folder was not found.";
   logger.log(status.message);
   status.redirect = true;
}
else
{
    listPinboardEntries(siteFolder);
}

function listPinboardEntries(siteFolder)
{
    logger.log("Entering listPinboardEntries");
      logger.log("Getting all childs");
    model.entries = siteFolder.children;
    logger.log("Leaving listPinboardEntries");   
}

The JSON output:

pinboardlist.get.json.ftl
<#escape x as jsonUtils.encodeJSONString(x)>
{
"pinboard":
  {
   "entries":
    [
        <#list entries as node>
              <#if node.properties["ecg:subject"]?exists>
                   <#if node.properties["ecg:description"]?exists>  
        {"name":"${node.properties['ecg:subject']}", "desc":"${node.properties['ecg:description']}", "site":"${args.site}"}
       <#if node_has_next>,</#if>                  
                  </#if>
            </#if>
    </#list>
    ]
 }
}
</#escape>

4.) Create the Dashlet
The idea is now that that the controller of the Web Script accesses the data which is provided by the data web script. The controller passes the data to the model and the presentation template renders it inside a dashlet. The content is presented within the dashlet's body container.

To create a new entry the URL of the 'Add Entry' Web Script is called as a form action. Here we do not use AJAX, instead a simple HTML form is used to call the entry creating Web Script. After this Web Script is called, it just redirects back to the previous page. By using additional Java Script inside the presentation template, it should be also possible to perform such an action without the need to redirect to the previous called page by refreshing this one. However, the dashlet shows how the interaction with the Data Web Scripts can basically work. The form is presented within the dashlet' toolbar container.

So what we need at first is a Dashlet Descriptor:

.../site-webscripts/de/ecmgeek/components/dashlets/pinboardlist.get.desc.xml
 <webscript>
    <shortname>List pinboard entries</shortname>
    <description>To list pinboard entries</description>
    <family>site-dashlet</family>
    <url>/components/dashlets/pinboardlist</url>
</webscript>

We also need a controller which gets the data from our Data Web Script and passes it to the model:

.../site-webscripts/de/ecmgeek/components/dashlets/pinboardlist.get.js
var siteName = page.url.templateArgs.site;
var data = remote.call("/alfintra/pinboard/list.json?site=" + siteName);
var results = eval('(' + data + ')');

model.entries = results.pinboard.entries;
Finally we need to create the dashlet's UI. You could basically add any HTML which you want to the dashlet's html template. But to integrate better with Alfresco, you should at least define the following containers:
  • dashlet
    • toolbar
    • body
 If you want that your dashlet is resizable you should also instantiate an Alfresco Dashlet Resizer by using the following line inside a JavaScript block:
  •  new Alfresco.widget.DashletResizer("${args.htmlid}", "${instance.object.id}");)
Interesting to know is that Alfresco Share comes with a Proxy servlet which allows you to access the Data Web Scripts of the Alfresco Repository. This proxy can be also used to avoid problems with the same origin policy if using client side Java Script to access the Data Web Script.

Our dashlet code looks now as the following:

 .../site-webscripts/de/ecmgeek/components/dashlets/pinboardlist.get.html.ftl

<script type="text/javascript">//<![CDATA[
   
   //Make the dashlet resizeable
   new Alfresco.widget.DashletResizer("${args.htmlid}", "${instance.object.id}");
//]]>
</script>
 
<div class="dashlet">
    <div class="title">
        ${msg("title")}
    </div>
     
    <div class="toolbar">
        
           <form name="postToPinboardForm" method="get" action="/share/proxy/alfresco/alfintra/pinboard/add.html">
             <table>
                <tr>
                   <td> ${msg("subject")} </td>
                   <td>  ${msg("desc")} </td>
                   <td> &nbsp; </td>        
                </tr>
 
                <tr> 
                   <td> <input type="text" name="name"> </td>
                   <td> <input type="text" name="desc"> </td>
                   <td> <input type="hidden" name="site" value="${page.url.templateArgs.site}"></td>
                   <td> <input type="submit" value="Post"> </td>
                </tr>
             </table>
            </form>        
    </div>
     
 
    <div class="body  scrollableList" id="${args.htmlid}-body">
  
        <table>
       
        <#list entries as e>
              <tr> <td> <b> ${e.name} </b> </td> </tr>
              <tr> <td> ${e.desc} </td> </tr>
              <tr> <td> &nbsp; </td> </tr>
        </#list>
 
        </table>
     
    </div>
</div>

The template reads some properties from the following properties file (or message bundle):

 .../site-webscripts/de/ecmgeek/components/dashlets/pinboardlist.get.properties
title=Pinboard
subject=Subject
desc=Description

Even if this is not the most complicated dashlet, I hope that it may help you to get started with dashlet development. Finally the dashlet looks this way:








 

Tuesday, June 12, 2012

Theme it

Today I had to investigate how to add a custom theme to my Alfresco 4.x installation. Some parts were a bit tricky, but the most of it was quite simple. Here some useful steps:

  1. Navigate to $WEBAPPS/share/WEB-INF/classes/alfresco/site-data/themes . BTW: $WEBAPPS is the deployment folder of your servlet container.
  2. Copy the file yellowTheme.xml to myTheme.xml
  3. Edit the file myTheme.xml by setting the title and the id. The id has to be 'theme.myTheme'
  4. Navigate to $WEBAPPS/themes!
  5. Make a copy of the complete folder 'yellowTheme' by naming it to 'myTheme'.
  6. Naviagate to $WEBAPPS/themes/myTheme/images!
  7. Open all the images by using E.G. the G(nu) I(mage) M(anipulation) P(rogram) . The Command is 'gimp'. Then colorize the images to fit your required base color. I just colorized every image which had a yellow color before. The main loge image is named 'app_logo.png' and can be replaced by your prefered one.
  8. Navigate back to $WEBAPPS/themes/myTheme!
  9. Edit the file presentation.css by setting the required colors. Again, I just copied the color codes to GIMP by only replacing the yellow color tones with my prefered ones.
  10. Now the tricky part. There are some css dependencies those need to be changed inside this file. So just replace every occurrence of 'yellowTheme' with 'myTheme' inside this file.
  11. Naviagete to $WEBAPPS/themes/myTheme/yui/assets
  12. Inside the file 'skin.css', also replace every occurrence of 'yellowTheme' with 'myTheme'.
  13. Restart Alfresco
  14. Open Alfresco Share, create a new site and choose the entry 'myTheme' as the site theme.
  15. Perform some CSS changes by being able to test your changes immediately by refreshing the just created site.
  16. Open the Administration Console and set the theme 'myTheme' as the gloabl one.
I hope this article helps you to get quickly started with customizing your Alfresco theme.