Hello.
Unfortunately I have uncovered a fatal flaw in my approach. The problem is that the “before_save” hook is called after any relation records have been inserted. This means that after the exception has been thrown, although the actual record is not saved, there are still orphan relation records left behind.
As such I have had to take a different approach. The first set of steps apply to the application as a whole, so need to be put somewhere under custom/Extension/application
- Create a new class that extends Exception so that the field name can be specified:
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class WF1_FieldValidationException extends Exception {
private $fieldName;
public function __construct($fieldName, $message, $code = 0, Exception $previous = null) {
parent::__construct($message, $code, $previous);
$this->fieldName = $fieldName;
}
public function getFieldName() {
return $this->fieldName;
}
}
- Create a class that extends Basic that overrides the save() method to call a new hook, called “validate_before_save”, then run the parent save() method:
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class WF1_Basic extends Basic {
public function save($check_notify = false) {
$this->call_custom_logic("validate_before_save");
parent::save($check_notify);
}
}
- Create a class that extends SugarController and overrides the action_save() method to handle exceptions, causing the edit view to be displayed. It also stores the name of the field that failed validation in the request, for later use:
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
require_once('.../WF1_FieldValidationException.php');
class WF1_Controller extends SugarController
{
public function action_save()
{
try {
parent::action_save();
} catch (WF1_FieldValidationException $fvex) {
$this->action = 'EditView';
$this->view = 'edit';
$_REQUEST['action'] = 'EditView';
$_REQUEST[$fvex->getFieldName() . 'Error'] = $fvex->getMessage();
} catch (Exception $ex) {
$this->action = 'EditView';
$this->view = 'edit';
$_REQUEST['action'] = 'EditView';
SugarApplication::appendErrorMessage($ex->getMessage());
}
}
}
The following steps must be implemented for each module that you want to do server-side validation with:
- Change the module bean to extend the custom Basic bean created above, e.g. modules/<module_name>/<module_name>.php, change:
class <module_name> extends Basic
to
require_once('.../WF1_Basic.php');
class <module_name> extends WF1_Basic
- Implement your validation logic hook, throwing a WF1_FieldValidationException on a validation error:
.
.
.
throw new WF1_FieldValidationException("<field_name>", "<error_message>");
.
.
.
- Register the logic hook, in custom/Extension/modules/<module_name>/Ext/LogicHooks:
<?php
$hook_version = 1;
$hook_array = Array();
$hook_array['validate_before_save'][] = Array(
1,
'<operation_name>',
'.../<logic_hook_file_name>',
'<logic_hook_class_name>',
'<logic_hook_method_name>'
);
Note that the hook name is ‘validate_before_save’.
- Create a custom controller that extends the WF1_Controller class, called custom/modules/<module_name>/controller.php:
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
require_once('.../WF1_Controller.php');
class Custom<module_name>Controller extends WF1_Controller
{
}
Note that this is deliberately empty, since the parent class overrides the action_save() method
- Display the error message by adding custom code to the modules/<module_name>/metadata/editviewdefs.php, changing:
.
.
.
array (
0 => 'name',
.
.
.
to
.
.
.
0 => array(
'<field_name>' => '<field_name>',
'customCode' => '{if !empty($smarty.request.<field_name>Error)}{literal}<script type="text/javascript">$(window).bind("load", function() { add_error_style("EditView", "<field_name>", "{/literal}{$smarty.request.<field_name>Error}{literal}");})</script>{/literal}{/if}',
'customCodeRenderField' => true,
),
.
.
.
- Disable the “quick create” screen" in custom/Extension/modules/<parent_module>/Ext/Layoutdefs/<module_name>_<parent_module_name>.php by changing:
.
.
.
'top_buttons' =>
array (
0 =>
array (
'widget_class' => 'SubPanelTopButtonQuickCreate',
),
.
.
.
to
.
.
.
'top_buttons' =>
array (
0 =>
array (
'widget_class' => 'SubPanelTopCreateButton',
),
.
.
.
The upshot of all this, is that when you edit your record and submit it, if the record fails validation, you will be taken to the edit form, with the field that failed validation highlighted and the error message shown below it, just like client-side validation.
Hopefully this helps someone else in the future.
Regards,
Carl