MITCO Solutions |
Tahir Shazad Malik owner of MITCO Solutions. Open Source Solutions, Alfresco, ECM, Consultancy, Trainings. Check our website: http://www.mitco-solutions.com |
It’s been a while since I last posted something, that didn’t mean I wasn’t into Alfresco on the contrary I was too busy with Alfresco stuff to post ;).
So what’s this issue all about??
The issue has to do with the permissions based on roles(SiteConsumer, SiteContributer,SiteCollaborator and SiteManager) handled and configured within Sites.
Within an Enterprise organisation it’s not sufficient to group everyone within these roles. Most likely you’ll have departments with their own access rights. E.g. there will be a group Finance department with a role SiteContributer.
So before you’ll know it, the repository view will be used to set permissions. This is were the issue starts.
The following image is the default Site Manage Permissions view:
But we want to add the “Finance Department” group, so we need the Repository view “Manage Permissions”:
So the normal way setting a permission will be the following:
Which will result in the following:
Outside a site this is a proper permission-set but within a site this disables the Site Manager to have admin rights. So the Site Manager can’t even fix the issue he/she has noticed afterwards :(.
So there are 3 solutions for this:
This is the piece of code I use after an issue is submitted:
var site = space;
recurse(site.childByNamePath(“documentLibrary”), function(node) {
if (!node.inheritsPermissions()) {
logger.log(“BEFORE: ” + node.getPermissions());
node.removePermission(“SiteManager”, “GROUP_site_” + site.name + “_SiteManager”);
node.removePermission(“SiteManager”, “GROUP_” + site.name + “_SiteManager”);
node.setPermission(“SiteManager”, “GROUP_site_” + site.name + “_SiteManager”);
logger.log(“AFTER: ” + node.getPermissions());
}
});
I’ve instructed all the Site Admin’s to follow step 1 before changing permissions, but like always there are cases where users don’t follow guide lines.
—- UPDATE —-
Alfresco opened a JIRA ticket: https://issues.alfresco.com/jira/browse/ALF-16522
Like the title says we’re going to add the default Repository “Manage Permissions” action within a site.
So we’re going to add a piece of XML to the share-config-custom.xml (placed in <tomcat-dir>/shared/classes/alfresco/web-extension/).
<!— Doc libary custom —>
<config evaluator=”string-compare” condition=”DocLibActions”>
<actions>
<!— Manage permissions (repository roles) —>
<action id=”document-manage-repo-permissions” type=”pagelink” icon=”document-manage-permissions” label=”actions.document.manage-permissions”>
<param name=”page”>manage-permissions?nodeRef={node.nodeRef}</param>
<permissions>
<permission allow=”true”>ChangePermissions</permission>
</permissions>
<evaluator negate=”true”>evaluator.doclib.action.isLocked</evaluator>
</action>
<actionGroups>
<actionGroup id=”document-details”>
<action index=”290” id=”document-manage-site-permissions” label=”actions.site.manage-permissions” />
</actionGroup>
<actionGroup id=”folder-browse”>
<action index=”190” id=”document-manage-site-permissions” icon=”folder-manage-permissions” label=”actions.site.manage-permissions” />
</actionGroup>
<actionGroup id=”folder-details”>
<action index=”170” id=”document-manage-site-permissions” icon=”folder-manage-permissions” label=”actions.site.manage-permissions” />
</actionGroup>
</actionGroups>
</config>
OK, so what have I done. Nothing that fancy, just changed the evaluator which was before checking on
<evaluator negate=”true”>evaluator.doclib.action.siteBased</evaluator> and now on
<evaluator negate=”true”>evaluator.doclib.action.isLocked</evaluator> which is a pretty standard evaluator.
And the following xml is just to rename the label or else it will have the same name twice for both actions and that won’t be easy for users :).
So in a message bundle i’ve define:
actions.site.manage-permissions=Manage Roles Permissions
If one would like to rename the repository “Manager Permissions” it would be something like:
actions.folder.manage-permissions=Manager Repository Permissions
Result
I’m sure you’ve all developed a custom Share Action by taking an example from the share-extra’s Google Code project.
The first time I’ve made my own cool custom Action, I’ve used the Backup Action as an example.
Normally one can re-use all of the code, but in some cases this example isn’t sufficient. In my case I wanted to have a Notify Action which shows a user messaged that a notification has been added or been removed. So I couldn’t have 1 success message. I could use the failure message, but that wouldn’t be a nice implementation in case something really went wrong.
So I’ve changed my Action a bit to show multiple messages.
The default client JS Action looked like this:
Alfresco.doclib.Actions.prototype.onActionNotify = function DL_onActionNotify(file) {
this.modules.actions.genericAction({
success : {
message : this.msg("message.notify.success", file.displayName),
events : [ {
name : "metadataRefresh"
} ]
},
failure : {
message : this.msg("message.notify.failure", file.displayName)
},
webscript : {
name : "notify/site/{site}/{container}",
method : Alfresco.util.Ajax.POST
},
params : {
site : this.options.siteId,
container : this.options.containerId
},
config : {
requestContentType : Alfresco.util.Ajax.JSON,
dataObj : {
nodeRefs : [ file.nodeRef ]
}
}
});
};
So I needed something like:
success1 : {
message : this.msg("message.notify.success1", file.displayName),
events : [ {
name : "metadataRefresh"
} ]
}success2 : {
message : this.msg("message.notify.success2", file.displayName),
events : [ {
name : "metadataRefresh"
} ]
},
failure : {
message : this.msg("message.notify.failure", file.displayName)
},
By default this isn’t possible, so instead using the success and failure handler I needed the real AJAX CallBackHandler.
After having contact with an Alfresco Engineer, who replied on my Forum Post. I’ve come up with the following code:
Alfresco.doclib.Actions.prototype.onActionNotify = function DL_onActionNotify(file) {
this.modules.actions.genericAction({
success : {
events : [ {
name : “metadataRefresh”
} ],
callback: {
fn : function DL_oAN_success(data){
var resultJson = YAHOO.lang.JSON.parse(data.serverResponse.responseText);
var added = resultJson.results[0].added;
if (added)
Alfresco.util.PopupManager.displayMessage({
text: this.msg(“message.notify.added”, file.displayName)
});
else
Alfresco.util.PopupManager.displayMessage({
text: this.msg(“message.notify.removed”, file.displayName)
});
},
scope : this
}
},
So I’ve replaced the message block within the success event with the callback block. Now I’ve got full control of the CallBackHandler.
The message block eventually called the Alfresco.util.PopulManager.displayMessage function. So now you’ve got to do it directly.
So this fixes the client side, now we only have to push the new added property from the Repository webscript.
The default result object looks like this:
var result = {
nodeRef: null,
action: “notifyAction”,
success: true,
}
So I’ve changed it to the following to get it to work:
var result = {
action: “notifyAction”,
success: true,
added: false
}
I don’t need the nodeRef so I’ve deleted it and added my own property.
This opens a whole new world into Share for me. By using the CallBackHandler I can use any Repository property I want and evaluate on the Client Side to build my logic.
When adding a node of type cm:folder to bpm_package (in e.g. a workflow) an incorrect URL gets generated through the template packageitems.ftl
In packageitems.ftl at line 5 the following happens:
<#local documentLinkResolver>
function(item){
return Alfresco.util.siteURL(“document-details?nodeRef=” + item.nodeRef, { site: item.site });
}
</#local>
This should be:
<#local documentLinkResolver>
function(item){
if (item.isContainer)
return Alfresco.util.siteURL(“folder-details?nodeRef=” + item.nodeRef, { site: item.site });
else
return Alfresco.util.siteURL(“document-details?nodeRef=” + item.nodeRef, { site: item.site });
}
</#local>
I’ve tested it against Alfresco 3.4x and now when adding a cm:folder to the bpm_package a correct URL gets generated.
Recently a client wanted something, which couldn’t be retrieved from the normal Alfresco API. The data was present in Alfresco, I did a DB query and got the desired results, but now I wanted to present that in a Dashlet.
After doing some research, I finally found the way to do it.
I created a Java-Backed Webscript, like stated at the Alfresco Wiki.
That is quite easily done, the next step was to know which DB-class I needed to pick. I know that Alfresco uses Hibernate, so making a separate JDBC-connection is just stupid.
After struggling with different classes, I eventually found the right one. I need to extend my class from org.springframework.orm.hibernate3.support.HibernateDaoSupport
One can use HQL Queries or plain SQL.
Beginning with plain SQL I ended up doing more translations and comparisons than using HQL. It was sometimes a bit hard to creating the right Query, because for testing purposes I directly queried the DB. So re-writing SQL to HQL is something which can take a while.
This is an example class which queries directly the DB:
public class DBQuery extends HibernateDaoSupport {
public List<Object[]> getResultSet(final Date startDate, final Date endDate) {
HibernateCallback callback = new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Date startQuery = null;
Date endQuery = new Date();;
if (startDate == null){
startQuery = new Date();
Calendar cal = Calendar.getInstance();
cal.setTime(endQuery);
cal.add(Calendar.MONTH, -1);
startQuery = cal.getTime();
} else
startQuery = startDate;
if (endDate != null)
endQuery = endDate;
StringBuilder query = new StringBuilder();
query.append(“SELECT distinct date_format(aaf.date, ‘%d-%m-%Y’), aaf.userId “);
query.append(“FROM org.alfresco.repo.audit.hibernate.AuditFactImpl aaf, org.alfresco.repo.audit.hibernate.AuditSourceImpl aas “);
query.append(“WHERE aaf.auditSource = aas “)
.append(“AND aas.method = ‘authenticate’ “)
.append(“AND aas.service = ‘AuthenticationService’ “)
.append(“AND aaf.date BETWEEN :start AND :end “)
.append(“ORDER BY aaf.date DESC”);
Query queryResult = session.createQuery(query.toString());
queryResult.setParameter(“start”, startQuery);
queryResult.setParameter(“end”, endQuery);
return queryResult.list();
}
};
List<Object[]> result = (List<Object[]>) (getHibernateTemplate().execute(callback));
return result;
}
What does this query do, it retrieves the Unique Logins through the AuthenticationService, which should be enabled in the AuditConfig-Custom.xml.
So you’ve got your results in an List<Object[]>, so how can we loop through these results?
The Object[] is an array containing the results from the specified columns. So Object[0] = aaf.date and Object[1] is aaf.userId.
Map<String, Object> map = new HashMap<String, Object>();
for (Object[] objects : result) {
String date = Object[0];
String userId = Object[1];
//Do whatever you like with the results
//Eventually put the results in a Map so they can be used in the Webscript/FTL
map.put(“user”, objects[1]);
map.put(“date”, objects[0]);
}
Full code of this Java-Backed Webscript:
public class RetreiveUniqueLogin extends DeclarativeWebScript {
private DBQuery DBQuery;
private SimpleDateFormat sf = new SimpleDateFormat(“dd MM yyyy”);
protected Map<String, Object> executeImpl(WebScriptRequest req, WebScriptStatus status) {
String start = req.getParameter(“startDate”);
String end = req.getParameter(“endDate”);
Date startDate = null;
Date endDate = null;
try {
if (start != null && start.length() > 0)
startDate = sf.parse(start);
if (end != null && end.length() > 0)
endDate = sf.parse(end);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
List<Object[]> results = DBQuery.getResultSet(startDate, endDate);
Map<String, Object> map = new HashMap<String, Object>();
// loop through the results and send smart objects instead of a Object[]
for (Object[] objects : results) {
map.put(“user”, objects[1]);
map.put(“date”, objects[0]);
}
Map<String, Object> model = new HashMap<String, Object>(7, 1.0f);
model.put(“auditResults”, map);
return model;
}public DBQuery getDBQuery() {
return DBQuery;
}
public void setDBQuery(DBQuery DBQuery) {
this.DBQuery = DBQuery;
}
And we need a Freemarker Template to present the results.
<table id=”auditPreviewTable” border=”0” class=”tablesorter”>
<thead>
<tr>
<th>Date</th>
<th>User</th>
</tr>
</thead>
<#if auditResults??>
<#list auditResults as result>
<tr>
<td>${result[“date”]}</td>
<td>${result[“user”]}</td>
</tr>
</#list>
</#if>
</tbody>
</table>
And of course you need to register the Java-Backed Webscript in a context file.
!!This code is written for an Alfresco version 2.2.x, so there is a big chance some classes are changed, the Webscript class is definitely changed, but some Hibernate imports could also differ!!
MITCO has developed a new Alfresco Share Dashlet, this Dashlet is packaged in a jar file. So it can easily be deployed to your environment.
Read the Wiki for some information: http://code.google.com/p/mitco/wiki/YouTubeDashlet
Or just directly download the Dashlet: http://mitco.googlecode.com/files/youtube-site-dashlet_v1_0.jar
When I’ve have the time, I’ll go through it.
Normally the Alfresco constraints doesn’t work in an advanced workflow. This issue is stated here:https://issues.alfresco.com/jira/browse/ALF-1846
According to Alfresco it’s fixed in 3.4.2. But I have a work-around/fix for all the older implementations if it’s limited to 1 transition button.
We have a task model type:
<type name=”test:submitTask”>
<title>Submit Task</title>
<parent>bpm:workflowTask</parent>
<properties>
<property name=”test:constraintField”>
<type>d:text</type>
<mandatory>true</mandatory>
<constraints>
<constraint ref=”test:minCharConstraint” />
</constraints>
</property>
</properties>
</type>
We have a constraint:
<constraint name=”test:minCharConstraint” type=”LENGTH”>
<parameter name=”minLength”>
<value>3</value>
</parameter>
<parameter name=”maxLength”>
<value>255</value>
</parameter>
</constraint>
We have a workflow transition:
<transition name=”Next” to=”Next Info”>
<action class=”org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript”>
<script>
//Do Something or Don’t :)
</script>
</action>
</transition>
We have a webclient-config-custom:
<config evaluator=”node-type” condition=”test:submitTask”>
<property-sheet>
<show-property name=”test:constraintField”/>
</property-sheet>
</config>
This is very basic when you model with advanced workflow. So now we need to be able to constrain this “test:constraintField” so that the minimum characters should be 3.
Now if we test this, the check will be done on save changes, but not on the “Next” transition button.
To get this to work, we need to create a component generator and make some few adjustments.
Because it’s a text field, we just need to extend the TextFieldGenerator.
I’ve made the generator globally as you can see below:
public class MinCharGenerator extends TextFieldGenerator {
private String nextTransition;
private final String PREFIX_TRANSITION = “transition_”;
@Override
@SuppressWarnings(“unchecked”)
protected void setupConstraints(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem property,
PropertyDefinition propertyDef, UIComponent component) {
if (nextTransition != null && nextTransition.length() > 0)
propertySheet.setNextButtonId(PREFIX_TRANSITION + nextTransition);
if (propertySheet.inEditMode() && propertySheet.isValidationEnabled() && propertyDef != null) {
List<ConstraintDefinition> constraints = propertyDef.getConstraints();
for (ConstraintDefinition constraintDef : constraints) {
Constraint constraint = constraintDef.getConstraint();
if (constraint instanceof RegexConstraint) {
setupRegexConstraint(context, propertySheet, property, component, (RegexConstraint) constraint, true);
} else if (constraint instanceof StringLengthConstraint) {
setupStringLengthConstraint(context, propertySheet, property, component, (StringLengthConstraint) constraint, true);
} else if (constraint instanceof NumericRangeConstraint) {
setupNumericRangeConstraint(context, propertySheet, property, component, (NumericRangeConstraint) constraint, true);
}
}
}
}
public String getNextTransition() {
return nextTransition;
}
public void setNextTransition(String nextTransition) {
this.nextTransition = nextTransition;
}
Let’s look at the method setupStringLengthConstraint(..).
The important part of this, is just setting the boolean from false to true. The method is described in the JavaDoc:
/**
* Sets up a default validation rule for the string length constraint
*
* @param context FacesContext
* @param propertySheet The property sheet to add the validation rule to
* @param property The property being generated
* @param component The component representing the property
* @param constraint The constraint to setup
* @param realTimeChecking true to make the client validate as the user types
*/
So we just enabled the realTimeChecking, which is set to false by default (I don’t know why, but it’s lame). I mean the JavaScripts methods are there to validate directly, then why don’t use them???
Now if you just deploy this ComponentGenerator and enable it in the webclient-config-custom then you’ll see that the saveChanges button is disabled by default and will get enabled after the 3rd character.
But we also want to enable this validation on our Transition button. So i’ve added a parameter to our Generator to know which is the transition button.
Let’s look at the faces-config-custom.xml.
<managed-bean>
<!— Component which checks if the minimum characters are filled in —>
<managed-bean-name>
MinCharGenerator
</managed-bean-name>
<managed-bean-class>
package.MinCharGenerator
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>nextTransition</property-name>
<value>Next</value>
</managed-property>
</managed-bean>
The value in here, is the Transition name in the advanced workflow.
if (nextTransition != null && nextTransition.length() > 0)
propertySheet.setNextButtonId(PREFIX_TRANSITION + nextTransition);
The code line above, sets the transition button as a wizard next button. This won’t make sense directly, but if you look at the Javascript which is created in the class UIPropertySheet in method renderValidationScript(…).
You’ll see that by default is disables the next & finish button. The finish button —> is the “Save Changes” button and there is no next button assigned in an advanced worfklow. The next button is mostly assigned in the default wizards.
Ofcourse you could rewrite the private renderValidationScript method, but then you’re rewriting alfresco core code. With this implementation it’s just an extension to the default code.
Have fun with it and if you have any questions just contact me through my website MITCO.