------------------------------------------------------------ revno: 13329 committer: Morten Olav Hansen <morte...@gmail.com> branch nick: dhis2 timestamp: Thu 2013-12-19 14:25:04 +0100 message: new system setting, allow self registration without recaptcha modified: dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/setting/DefaultSystemSettingManager.java dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AccountController.java dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/account.js dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/useraccount/account.vm dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/interceptor/SystemSettingInterceptor.java dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/java/org/hisp/dhis/settings/action/system/SetAccessSettingsAction.java dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/resources/org/hisp/dhis/settings/i18n_module.properties dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/webapp/dhis-web-maintenance-settings/systemAccessSettings.vm
-- lp:dhis2 https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk Your team DHIS 2 developers is subscribed to branch lp:dhis2. To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java 2013-12-18 15:08:00 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java 2013-12-19 13:25:04 +0000 @@ -28,15 +28,15 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.hisp.dhis.period.MonthlyPeriodType; +import org.hisp.dhis.period.QuarterlyPeriodType; +import org.hisp.dhis.period.YearlyPeriodType; + import java.io.Serializable; import java.util.Collection; import java.util.HashSet; import java.util.List; -import org.hisp.dhis.period.MonthlyPeriodType; -import org.hisp.dhis.period.QuarterlyPeriodType; -import org.hisp.dhis.period.YearlyPeriodType; - /** * @author Stian Strandli * @version $Id: SystemSettingManager.java 4910 2008-04-15 17:55:02Z larshelg $ @@ -44,7 +44,7 @@ public interface SystemSettingManager { final String ID = SystemSettingManager.class.getName(); - + final String KEY_APPLICATION_TITLE = "applicationTitle"; final String KEY_APPLICATION_INTRO = "keyApplicationIntro"; final String KEY_APPLICATION_NOTIFICATION = "keyApplicationNotification"; @@ -82,55 +82,61 @@ final String KEY_LAST_MONITORING_RUN = "keyLastMonitoringRun"; final String KEY_GOOGLE_ANALYTICS_UA = "googleAnalyticsUA"; final String KEY_CREDENTIALS_EXPIRES = "credentialsExpires"; + final String KEY_SELF_REGISTRATION_NO_RECAPTCHA = "keySelfRegistrationNoRecaptcha"; final String DEFAULT_SCHEDULE_AGGREGATE_QUERY_BUILDER_TASK_STRATEGY = "lastMonth"; final String DEFAULT_FLAG = "dhis2"; final int DEFAULT_MAX_NUMBER_OF_ATTEMPTS = 20; final int DEFAULT_TIMEFRAME_MINUTES = 1; final double DEFAULT_FACTOR_OF_DEVIATION = 2.0; - final int DEFAULT_ORGUNITGROUPSET_AGG_LEVEL = 3; + final int DEFAULT_ORGUNITGROUPSET_AGG_LEVEL = 3; final String DEFAULT_GOOGLE_MAPS_API_KEY = "ABQIAAAAut6AhySExnYIXm5s2OFIkxRKNzJ-_9njnryRTbvC6CtrS4sRvRREWnxwlZUa630pLuPf3nD9i4fq9w"; final String DEFAULT_START_MODULE = "dhis-web-dashboard-integration"; final String DEFAULT_APPLICATION_TITLE = "District Health Information Software 2"; - final int DEFAULT_EMAIL_PORT = 587; + final int DEFAULT_EMAIL_PORT = 587; final int DEFAULT_COMPLETENESS_OFFSET = 15; final String DEFAULT_TIME_FOR_SENDING_MESSAGE = "08:00"; final String DEFAULT_CACHE_STRATEGY = "CACHE_6AM_TOMORROW"; - + final String SYSPROP_PORTAL = "runningAsPortal"; - - final HashSet<String> DEFAULT_SCHEDULED_PERIOD_TYPES = new HashSet<String>() { { - add( MonthlyPeriodType.NAME ); - add( QuarterlyPeriodType.NAME ); - add( YearlyPeriodType.NAME ); - } }; - + + final HashSet<String> DEFAULT_SCHEDULED_PERIOD_TYPES = new HashSet<String>() + { + { + add( MonthlyPeriodType.NAME ); + add( QuarterlyPeriodType.NAME ); + add( YearlyPeriodType.NAME ); + } + }; + void saveSystemSetting( String name, Serializable value ); Serializable getSystemSetting( String name ); - + Serializable getSystemSetting( String name, Serializable defaultValue ); Collection<SystemSetting> getAllSystemSettings(); void deleteSystemSetting( String name ); - + List<String> getFlags(); - + String getFlagImage(); - + String getEmailHostName(); - + int getEmailPort(); - + String getEmailUsername(); - + String getEmailPassword(); - + boolean getEmailTls(); - + boolean accountRecoveryEnabled(); - + + boolean selfRegistrationNoRecaptcha(); + boolean emailEnabled(); String googleAnalyticsUA(); === modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/setting/DefaultSystemSettingManager.java' --- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/setting/DefaultSystemSettingManager.java 2013-12-18 15:08:00 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/setting/DefaultSystemSettingManager.java 2013-12-19 13:25:04 +0000 @@ -163,6 +163,12 @@ return (Boolean) getSystemSetting( KEY_ACCOUNT_RECOVERY, false ); } + @Override + public boolean selfRegistrationNoRecaptcha() + { + return (Boolean) getSystemSetting( KEY_SELF_REGISTRATION_NO_RECAPTCHA, false ); + } + public boolean emailEnabled() { return getEmailHostName() != null; === modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AccountController.java' --- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AccountController.java 2013-12-19 12:07:47 +0000 +++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AccountController.java 2013-12-19 13:25:04 +0000 @@ -187,8 +187,8 @@ @RequestParam String email, @RequestParam String phoneNumber, @RequestParam String employer, - @RequestParam( value = "recaptcha_challenge_field" ) String recapChallenge, - @RequestParam( value = "recaptcha_response_field" ) String recapResponse, + @RequestParam( value = "recaptcha_challenge_field", required = false ) String recapChallenge, + @RequestParam( value = "recaptcha_response_field", required = false ) String recapResponse, HttpServletRequest request, HttpServletResponse response ) { @@ -274,40 +274,43 @@ return "Employer is not specified or invalid"; } - if ( recapChallenge == null ) - { - response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); - return "Recaptcha challenge must be specified"; - } - - if ( recapResponse == null ) - { - response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); - return "Recaptcha response must be specified"; - } - - // --------------------------------------------------------------------- - // Check result from API, return 500 if not - // --------------------------------------------------------------------- - - String[] results = checkRecaptcha( KEY, request.getRemoteAddr(), recapChallenge, recapResponse ); - - if ( results == null || results.length == 0 ) - { - response.setStatus( HttpServletResponse.SC_INTERNAL_SERVER_ERROR ); - return "Captcha could not be verified due to a server error"; - } - - // --------------------------------------------------------------------- - // Check if verification was successful, return 400 if not - // --------------------------------------------------------------------- - - if ( !TRUE.equalsIgnoreCase( results[0] ) ) - { - log.info( "Recaptcha failed with code: " + (results.length > 0 ? results[1] : "") ); - - response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); - return "The characters you entered did not match the word verification, try again"; + if ( !systemSettingManager.selfRegistrationNoRecaptcha() ) + { + if ( recapChallenge == null ) + { + response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); + return "Recaptcha challenge must be specified"; + } + + if ( recapResponse == null ) + { + response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); + return "Recaptcha response must be specified"; + } + + // --------------------------------------------------------------------- + // Check result from API, return 500 if not + // --------------------------------------------------------------------- + + String[] results = checkRecaptcha( KEY, request.getRemoteAddr(), recapChallenge, recapResponse ); + + if ( results == null || results.length == 0 ) + { + response.setStatus( HttpServletResponse.SC_INTERNAL_SERVER_ERROR ); + return "Captcha could not be verified due to a server error"; + } + + // --------------------------------------------------------------------- + // Check if verification was successful, return 400 if not + // --------------------------------------------------------------------- + + if ( !TRUE.equalsIgnoreCase( results[0] ) ) + { + log.info( "Recaptcha failed with code: " + (results.length > 0 ? results[1] : "") ); + + response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); + return "The characters you entered did not match the word verification, try again"; + } } // --------------------------------------------------------------------- === modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/account.js' --- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/account.js 2013-10-10 15:49:20 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/account.js 2013-12-19 13:25:04 +0000 @@ -1,116 +1,121 @@ var validationRules = { - rules: { - firstName: { - required: true, - rangelength: [ 2, 80 ] - }, - surname: { - required: true, - rangelength: [ 2, 80 ] - }, - username: { - required: true, - rangelength: [ 4, 80 ], - remote: "../../api/account/username" - }, - password: { - required: true, - rangelength: [ 8, 80 ], - password: true, - notequalto : "#username", - }, - retypePassword: { - required: true, - equalTo: "#password", - }, - email: { - required: true, - email: true, - rangelength: [ 4, 80 ] - }, - phoneNumber: { - required: true, - rangelength: [ 6, 30 ] - }, - employer: { - required: true, - rangelength: [ 2, 80 ] - } - } + rules: { + firstName: { + required: true, + rangelength: [ 2, 80 ] + }, + surname: { + required: true, + rangelength: [ 2, 80 ] + }, + username: { + required: true, + rangelength: [ 4, 80 ], + remote: "../../api/account/username" + }, + password: { + required: true, + rangelength: [ 8, 80 ], + password: true, + notequalto: "#username", + }, + retypePassword: { + required: true, + equalTo: "#password", + }, + email: { + required: true, + email: true, + rangelength: [ 4, 80 ] + }, + phoneNumber: { + required: true, + rangelength: [ 6, 30 ] + }, + employer: { + required: true, + rangelength: [ 2, 80 ] + } + } }; var login = {}; login.localeKey = "dhis2.locale.ui"; -$( document ).ready( function() { +$(document).ready(function() { var locale = localStorage[login.localeKey]; - - if ( undefined !== locale && locale ) - { - login.changeLocale( locale ); - } - - Recaptcha.create( "6LcM6tcSAAAAANwYsFp--0SYtcnze_WdYn8XwMMk", "recaptchaDiv", { - theme: "white" - } ); - - $( "#accountForm" ).validate( { - rules: validationRules.rules, - submitHandler: accountSubmitHandler, - errorPlacement: function( error, element ) { - element.parent( "td" ).append( "<br>" ).append( error ); - } - } ); -} ); - -function accountSubmitHandler() -{ - if ( $.trim( $( "#recaptcha_challenge_field" ).val() ).length == 0 || - $.trim( $( "#recaptcha_response_field" ).val() ).length == 0 ) - { - $( "#messageSpan" ).show().text( "Please enter a value for the word verification above" ); - return false; - } - - $( "#submitButton" ).attr( "disabled", "disabled" ); - - $.ajax( { - url: "../../api/account", - data: $( "#accountForm" ).serialize(), - type: "post", - success: function( data ) { - window.location.href = "../../dhis-web-commons-about/redirect.action"; - }, - error: function( jqXHR, textStatus, errorThrown ) { - $( "#messageSpan" ).show().text( jqXHR.responseText ); - Recaptcha.reload(); - $( "#submitButton" ).removeAttr( "disabled" ); - } - } ); -} - -function reloadRecaptcha() -{ - Recaptcha.reload(); -} - -login.changeLocale = function( locale ) -{ - $.get( 'accountStrings.action?loc=' + locale, function( json ) { - $('#create_new_account').html( json.create_new_account ); - $('#label_firstName').html( json.name ); - $('#firstName').attr( "placeholder", json.first_name ); - $('#surname').attr( "placeholder", json.last_name ); - $('#label_username').html( json.user_name ); - $('#label_password').html( json.password ); - $('#label_retypePassword').html( json.confirm_password ); - $('#label_email').html( json.email ); - $('#label_mobile_phone').html( json.mobile_phone ); - $('#label_employer').html( json.employer ); - $('#label_recaptchaDiv').html( json.prove_not_robot ); - $('#cant_read_words').html( json.cant_read_words ); - $('#submitButton').val( json.create ); - } ); + + if( undefined !== locale && locale ) { + login.changeLocale(locale); + } + + if( recaptchaEnabled() ) { + Recaptcha.create("6LcM6tcSAAAAANwYsFp--0SYtcnze_WdYn8XwMMk", "recaptchaDiv", { + theme: "white" + }); + } + + $("#accountForm").validate({ + rules: validationRules.rules, + submitHandler: accountSubmitHandler, + errorPlacement: function( error, element ) { + element.parent("td").append("<br>").append(error); + } + }); +}); + +function accountSubmitHandler() { + if( recaptchaEnabled() ) { + if( $.trim($("#recaptcha_challenge_field").val()).length == 0 || + $.trim($("#recaptcha_response_field").val()).length == 0 ) { + $("#messageSpan").show().text("Please enter a value for the word verification above"); + return false; + } + } + + $("#submitButton").attr("disabled", "disabled"); + + $.ajax({ + url: "../../api/account", + data: $("#accountForm").serialize(), + type: "post", + success: function( data ) { + window.location.href = "../../dhis-web-commons-about/redirect.action"; + }, + error: function( jqXHR, textStatus, errorThrown ) { + $("#messageSpan").show().text(jqXHR.responseText); + reloadRecaptcha(); + $("#submitButton").removeAttr("disabled"); + } + }); +} + +function recaptchaEnabled() { + return typeof Recaptcha !== 'undefined'; +} + +function reloadRecaptcha() { + if( recaptchaEnabled() ) { + Recaptcha.reload(); + } +} + +login.changeLocale = function( locale ) { + $.get('accountStrings.action?loc=' + locale, function( json ) { + $('#create_new_account').html(json.create_new_account); + $('#label_firstName').html(json.name); + $('#firstName').attr("placeholder", json.first_name); + $('#surname').attr("placeholder", json.last_name); + $('#label_username').html(json.user_name); + $('#label_password').html(json.password); + $('#label_retypePassword').html(json.confirm_password); + $('#label_email').html(json.email); + $('#label_mobile_phone').html(json.mobile_phone); + $('#label_employer').html(json.employer); + $('#label_recaptchaDiv').html(json.prove_not_robot); + $('#cant_read_words').html(json.cant_read_words); + $('#submitButton').val(json.create); + }); } === modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/useraccount/account.vm' --- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/useraccount/account.vm 2013-10-10 15:49:20 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/useraccount/account.vm 2013-12-19 13:25:04 +0000 @@ -7,7 +7,9 @@ <script type="text/javascript" src="../javascripts/jQuery/jquery.validate.ext.js"></script> <script type="text/javascript" src="../javascripts/useraccount/account.js"></script> <script type="text/javascript" src="../i18nJavaScript.action"></script> + #if( !$keySelfRegistrationNoRecaptcha ) <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script> + #end <link type="text/css" rel="stylesheet" href="../css/account.css"> </head> <body> @@ -54,6 +56,7 @@ <td><label id="label_employer" for="employer">$i18n.getString( "employer" )</label></td> <td style="padding-bottom: 5px"><input type="text" id="employer" name="employer"></td> </tr> + #if( !$keySelfRegistrationNoRecaptcha ) <tr> <td><label id="label_recaptchaDiv">$i18n.getString( "prove_you_are_not_a_robot" )</label></td> <td style="padding-bottom: 5px"><div id="recaptchaDiv"></div></td> @@ -62,13 +65,14 @@ <td></td> <td><a id="cant_read_words" href="javascript:reloadRecaptcha()">$i18n.getString( "cant_read_the_words_try_another_one" )</a></span></td> </tr> + #end <tr> <td></td> - <td><label id="messageSpan" class="error" style="display:none"></label></td> + <td><label id="messageSpan" class="error" style="display:none"></label></td> </tr> <tr> <td></td> - <td><input id="submitButton" type="submit" value="$i18n.getString( 'create' )" style="width:10em"></td> + <td><input id="submitButton" type="submit" value="$i18n.getString( 'create' )" style="width:10em"></td> </tr> </table> === modified file 'dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/interceptor/SystemSettingInterceptor.java' --- dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/interceptor/SystemSettingInterceptor.java 2013-12-18 15:08:00 +0000 +++ dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/interceptor/SystemSettingInterceptor.java 2013-12-19 13:25:04 +0000 @@ -98,6 +98,7 @@ map.put( KEY_APP_BASE_URL, systemSettingManager.getSystemSetting( KEY_APP_BASE_URL ) ); map.put( KEY_GOOGLE_ANALYTICS_UA, systemSettingManager.getSystemSetting( KEY_GOOGLE_ANALYTICS_UA, "" ) ); map.put( KEY_CREDENTIALS_EXPIRES, systemSettingManager.credentialsExpires() ); + map.put( KEY_SELF_REGISTRATION_NO_RECAPTCHA, systemSettingManager.selfRegistrationNoRecaptcha() ); map.put( SYSPROP_PORTAL, defaultIfEmpty( System.getProperty( SYSPROP_PORTAL ), String.valueOf( false ) ) ); === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/java/org/hisp/dhis/settings/action/system/SetAccessSettingsAction.java' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/java/org/hisp/dhis/settings/action/system/SetAccessSettingsAction.java 2013-12-18 15:08:00 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/java/org/hisp/dhis/settings/action/system/SetAccessSettingsAction.java 2013-12-19 13:25:04 +0000 @@ -39,8 +39,7 @@ import org.hisp.dhis.user.UserService; import org.springframework.beans.factory.annotation.Autowired; -import static org.hisp.dhis.setting.SystemSettingManager.KEY_ACCOUNT_RECOVERY; -import static org.hisp.dhis.setting.SystemSettingManager.KEY_CREDENTIALS_EXPIRES; +import static org.hisp.dhis.setting.SystemSettingManager.*; /** * @author Lars Helge Overland @@ -78,6 +77,13 @@ this.selfRegistrationOrgUnit = selfRegistrationOrgUnit; } + private Boolean selfRegistrationNoRecaptcha; + + public void setSelfRegistrationNoRecaptcha( Boolean selfRegistrationNoRecaptcha ) + { + this.selfRegistrationNoRecaptcha = selfRegistrationNoRecaptcha; + } + private Boolean accountRecovery; public void setAccountRecovery( Boolean accountRecovery ) @@ -135,6 +141,7 @@ configurationService.setConfiguration( config ); systemSettingManager.saveSystemSetting( KEY_ACCOUNT_RECOVERY, accountRecovery ); + systemSettingManager.saveSystemSetting( KEY_SELF_REGISTRATION_NO_RECAPTCHA, selfRegistrationNoRecaptcha ); if ( credentialsExpires != null ) { === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/resources/org/hisp/dhis/settings/i18n_module.properties' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/resources/org/hisp/dhis/settings/i18n_module.properties 2013-12-18 17:45:28 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/resources/org/hisp/dhis/settings/i18n_module.properties 2013-12-19 13:25:04 +0000 @@ -51,6 +51,7 @@ application_introduction=Application introduction access_settings=Access settings do_not_allow_self_registration=Do not allow self registration +do_not_use_recaptcha_for_self_registration=Do not use recaptcha for self registration self_registration_account_user_role=Self registration account user role self_registration_account_organisation_unit=Self registration account organisation unit access=Access === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/webapp/dhis-web-maintenance-settings/systemAccessSettings.vm' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/webapp/dhis-web-maintenance-settings/systemAccessSettings.vm 2013-12-18 15:08:00 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/webapp/dhis-web-maintenance-settings/systemAccessSettings.vm 2013-12-19 13:25:04 +0000 @@ -4,6 +4,7 @@ jQuery.postUTF8( "setSystemAccessSettings.action", { selfRegistrationRole: jQuery( "#selfRegistrationRole" ).val(), selfRegistrationOrgUnit: jQuery( "#selfRegistrationOrgUnit" ).val(), + selfRegistrationNoRecaptcha: jQuery( '#selfRegistrationNoRecaptcha' ).is( ':checked' ), accountRecovery: jQuery( '#accountRecovery' ).is( ':checked' ), credentialsExpires: jQuery( '#credentialsExpires' ).val() }, function( json ) { @@ -32,6 +33,11 @@ </select> </div> +<div class="setting"> + <input type="checkbox" id="selfRegistrationNoRecaptcha" name="selfRegistrationNoRecaptcha"#if( $keySelfRegistrationNoRecaptcha ) checked="checked"#end> + <label for="selfRegistrationNoRecaptcha">$i18n.getString( "do_not_use_recaptcha_for_self_registration" )</label> +</div> + <div class="settingLabel">$i18n.getString( "self_registration_account_organisation_unit" )</div> <div class="setting">
_______________________________________________ Mailing list: https://launchpad.net/~dhis2-devs Post to : dhis2-devs@lists.launchpad.net Unsubscribe : https://launchpad.net/~dhis2-devs More help : https://help.launchpad.net/ListHelp