Friday, October 27, 2017

Alfresco Web Scripts: Limiting Picker Search for Users

Both the Alfresco repository and Alfresco Share products extensively use Alfresco Web Scripts. Web Scripts are an important concept to understand in order to be able to customize Alfresco.

In this post we'll look at a requested customization for Alfresco Share and how it can be implemented by overriding the controller of an existing Alfresco Web Script.

First let's look at the following functionality in Alfresco Share.

By default, any user in Alfresco can start a workflow on a document which they have access to. If a user chooses to create a New Task (ad hoc task) workflow on a document, the workflow creation form is displayed, and on that form, the user is able to select a user to assign the new task to.

In the "Select..." dialog that pops up, the user is able to search across all registered Alfresco users.

It looks like this:


Customization Requirement


The requirement for the customization is, for all users other than admin users, to limit the search result to include only users that belong to the same groups which they are members of.

Implementation

How can we achieve that?

After a little investigation, we find that the user assignment popup is a kind of picker object, and that the client JavaScript code for the picker searches for users by making a call to a repository Web Script.  The controller for that Web Script is the following file:

alfresco/templates/webscripts/org/alfresco/repository/forms/pickerchildren.get.js.

[Note that this file can be found bundled in the repo jar file: alfresco-remote-api-5.x.x.jar]

Within the Web Script, the function that gets called to search for users is findUsers().  We'll change this method to limit the search results from the Picker to include only those users that belong to same groups that the user belongs to.








function findUsers(searchTerm, maxResults, results)
{
   var personRefs = people.getPeople(searchTerm, maxResults, "lastName", true);
   
   // create person object for each result
   for each(var personRef in personRefs)
   {
      if (logger.isLoggingEnabled())
         logger.log("found personRefs = " + personRefs);
      
      //filter out  the disabled users
      var daname = (search.findNode(personRef)).properties.userName;
 
      if(people.isAccountEnabled(daname)){
          results.push(
          {
              item: createPersonResult(search.findNode(personRef)),
              selectable: true
          });
      }
      else{
          results.push(
       {
           item: createPersonResult(search.findNode(personRef)),
           selectable: false
       });   
          if (logger.isLoggingEnabled())
              logger.log("User not added to results not enabled" + daname);
      }
   }
}




We see here that searches are currently being made using the Alfresco JavaScript API using root object 'people':

 var personRefs = people.getPeople(searchTerm, maxResults, "lastName", true);

The requirement for the customization is to change the search by limiting the results to only those users that belong to the same group as the current user.  We can again use the 'people' root-scoped object to find which groups the user belongs to:

var userGroups = people.getContainerGroups(person);

But the problem with the method getContainerGroups() is that it does not include system groups with the results.  It would be great if this method took another parameter as a flag to specify that system groups should be included in the result, but it doesn't. System groups include the Share groups created for each site for managing the four default Site roles: site manager, collaborator, contributor, and consumer.



If a user belongs to a Share site, we also want to include in the results any users which belong to any of the four Share site groups for that site. To find the additional site groups corresponding to the sites that a user belongs to, we will write a new method.  That is shown in the following code which takes a list and adds the Share site groups to the list.




function addSiteGroups(g)
{
    var s = siteService.findSites("", null, 0);
    for(var j=0; j<s.length; j++)
      {
 var perms = s[j].getSitePermissionGroups();
 for(var k=0;k<perms.length;k++)
 {
     g.push( groups.getGroupForFullAuthorityName(perms[k]).getGroupNode() );
 }
      }
  }

Here we query the site service to find all sites that the current user is able to access.  Then we get the names of the roles for the site -- there typically will be just the four we mentioned earlier. Then we get the group corresponding to each of those permissions and push the group nodes onto the list of groups:



groups.getGroupForFullAuthorityName(perms[k]).getGroupNode()

Once we have the list of groups that the user belongs to, we can make a query to find only those people that match the search criteria and also are members of those groups.

First we a build a string which contains part of the condition for the query that lists the groups that the user belongs:

 grpQuery = " AND (";
  
 // make concatenated list of groups
 for(var i=0; i<userGroups.length; i++)
   {
      if(i>0) 
      {
  grpQuery = grpQuery + " OR ";
      }
      grpQuery = grpQuery + "PARENT:\"" + userGroups[i].nodeRef.toString() + "\" ";
    }
    grpQuery = grpQuery + ")";



The query is as follows.  Note that 'filterTerm' corresponds to the text entered by the user within the UI. 'grpQuery' corresponds to the criteria for the group restriction. Note that we search for objects that are type cm:person and which do not have the cm:personDisabled flag applied.

var def = 
{ 
  query: "+TYPE:\"cm:person\" AND (-ASPECT:\"cm:personDisabled\") AND(cm:lastName:\"*" + filterTerm + "*\" OR cm:userName:\"*" + filterTerm + "*\")" + grpQuery, 
  store: "workspace://SpacesStore", 
    language: "fts-alfresco",
    page: paging
  }; 

Putting it all together, the combined changes to the Web Script are as follows:



function addSiteGroups(g)
{
 var s = siteService.findSites("", null, 0);
 for(var j=0; j<s.length; j++)
 {
  var perms = s[j].getSitePermissionGroups();
  for(var k=0;k<perms.length;k++)
  {
      g.push( groups.getGroupForFullAuthorityName(perms[k]).getGroupNode() );
  }
 }
}

// Modified by Formtek to find only group to which the user belongs
function findUsers(filterTerm, maxResults, results)
{  
 var paging = 
 { 
     maxItems: maxResults, 
     skipCount: 0 
 }; 
   
 var grpQuery = "";
 
 
 // If the user is not an admin, restrict the search
 if(!people.isAdmin(people.getPerson(person.properties["cm:userName"])))
 {
     // Find all the groups that the user is in
     var userGroups = people.getContainerGroups(person);
  addSiteGroups(userGroups);
  grpQuery = " AND (";
  
     // make concatenated list of groups
     for(var i=0; i<userGroups.length; i++)
     {
      if(i>0) 
      {
       grpQuery = grpQuery + " OR ";
      }
      grpQuery = grpQuery + "PARENT:\"" + userGroups[i].nodeRef.toString() + "\" ";
     }
     grpQuery = grpQuery + ")";
    }
 
 var def = 
 { 
   query: "+TYPE:\"cm:person\" AND -ASPECT:\"cm:personDisabled\" AND (cm:firstName:\"*" + filterTerm + "*\" OR cm:lastName:\"*" + filterTerm + "*\" OR cm:userName:\"*" + filterTerm + "*\")" + grpQuery, 
   store: "workspace://SpacesStore", 
   language: "fts-alfresco",
   page: paging
 }; 
 
 var personRefs = search.query(def); 
   
 // create person object for each result
 for each(var personRef in personRefs)
 {
    // add to results
    results.push(
    {
       item: createPersonResult(search.findNode(personRef.nodeRef)),
       selectable: true
    });
 }
}

To override the existing Web Script file, copy the original file into the extensions area and make the change to the findUsers() method as shown above here:

tomcat/shared/WEB-INF/classes/alfresco/extension/templates/webscripts/org/alfresco/repository/forms/pickerchildren.get.js.

One additional thing to notice is that filtering by group is only applied when the user is not an admin:

people.isAdmin(people.getPerson(person.properties["cm:userName"]))
 

Workflow assignments of tasks to users will now use the override method defined here and users will be filtered by group.

Wednesday, October 11, 2017

Auto-Complete in Ephesoft


Adding a semicolon separated list of values to an individual index can improve over-all accuracy while decreasing the processing time required to complete an Ephesoft batch. The auto-completion of the index field happens during the Validation process. A simple example is a field where the possible responses are ‘Positive’, ‘Negative’ or ‘No Response’. The person processing the form can simply enter a character or two that match the characters associated with the entries in the list of values. Once the corresponding value appear as the first entry in the list the user will strike the Down Arrow Key to populate the index with the selected value. A tab will take the person to the next field so they can continue to process the form.

Setup for an Auto-complete Field
First collect the values that are valid for a particular field. Now open the Doc Type in which the index value resides. Under the header ‘Field Option Values List’ enter the list of values. A semicolon is used to separate the list of values. See screenshot #1.

Screenshot #1:



Now look for the header label ‘Field Type’. Select the value ‘Combo’. See Screenshot #2. Apply your changes and you can test the results.

Screenshot #2:



Interacting With an Auto-complete Field 

Open a batch file in the Validation module that matches the Doc Type where you added your list of values. Tab down to the field where you entered your list of values. Once there enter a character or two that match the first few characters for the value you want applied to this field. Once you see your value appear at the top of the list simple hit your Down Arrow Key and this value will be used to populate this field. The tab key will take you to the next field. This simple change that can greatly decrease the processing time associated with a Doc Type within the Ephesoft application.

NOTE: The characters your entry are NOT case sensitive; a match will result even if the case of the characters do not a match.