Let the platform do the work

Extending Sugar Logic

Overview

How to write custom Sugar Logic functions.

Writing a Custom Formula Function

The most important feature of Sugar Logic is that it is simply and easily extendable. Both custom formula functions and custom actions can be added in an upgrade-safe manner to allow almost any custom logic to be added to Sugar. Custom functions will be stored in ./custom/include/Expressions/Expression/{Type}/{Function_Name}.php.

The first step in writing a custom function is to decide what category the function falls under. Take, for example, a function for calculating the factorial of a number. In this case, we will be returning a number so we will create a file in ./custom/include/Expressions/Expression/Numeric/ called FactorialExpression.php. In the new PHP file we just created, we will define a class called FactorialExpression that will extend NumericExpression. All formula functions must follow the format {functionName}Expression.php and the class name must match the file name.

Next, we need to decide what parameters the function will accept. In this case, we need take in a single parameter, the number to return the factorial of. Since this class will be a sub-class of NumericExpression, it by default accepts only numeric types and we do not need to worry about specifying the type requirement.

Next, we must define the logic behind evaluating this expression. So we must override the abstract evaluate() function. The parameters can be accessed by calling an internal function getParameters() which returns the parameters passed into this object. So, with all this information, we can go ahead and write the code for the function.

Note: For the custom function to appear in Studio after completing these steps, you must compile your code by running the Rebuild Sugar Logic Functions job in Admin > Repair and then clear your browser cache.

  <?php

require_once 'include/Expressions/Expression/Numeric/NumericExpression.php';

class FactorialExpression extends NumericExpression
{
    function evaluate()
    {
        $params = $this->getParameters();
        // params is an Expression object, so evaluate it
        // to get its numerical value
        $number = $params->evaluate();
        // exception handling
        if ( ! is_int( $number ) )
        {
            throw new Exception("factorial: Only accepts integers");
        }

        if ( $number < 0 )
        {
            throw new Exception("factorial: The number must be positive");
        }

        // special case 0! = 1
        if ( $number == 0 ) return 1;

        // calculate the factorial
        $factorial = 1;
        for ( $i = 2 ; $i <= $number ; $i ++ )
        {
            $factorial = $factorial * $i;
        }

        return $factorial;
     }

     // Define the javascript version of the function
     static function getJSEvaluate()
     {
        return <<<EOQ
        var params = this.getParameters();
        var number = params.evaluate();
        // reg-exp integer test
        if ( ! /^\d*$/.test(number) )
        throw "factorial: Only accepts integers";

        if ( number < 0 )
        throw "factorial: The number must be postitive";
        // special case, 0! = 1
        if ( number == 0 ) return 1;
        // compute factorial
        var factorial = 1;

        for ( var i = 2 ; i <= number ; i ++ )
        factorial = factorial * i;

        return factorial;
EOQ;
        }

        function getParameterCount()
        {
            return 1; // we only accept a single parameter
        }

        static function getOperationName()
        {
            return "factorial";
            // our function can be called by 'factorial'
        }
    }

?>

One of the key features of Sugar Logic is that the functions should be defined in both PHP and JavaScript and have the same functionality under both circumstances. As you can see above, the getJSEvaluate() method should return the JavaScript equivalent of your evaluate() method. The JavaScript code is compiled and assembled for you after you run the "Rebuild Sugar Logic Functions" script through Admin > Repair.

Writing a Custom Action

Using custom actions, you can easily create reusable custom logic or integrations that can include user-editable logic using the Sugar Formula Engine. Custom actions will be stored in ./custom/include/Expressions/Actions/{ActionName}.php. Actions files must end in Action.php and the class defined in the file must match the file name and extend the AbstractAction class. The basic functions that must be defined are fire, getDefinition, getActionName, getJavascriptClass, and getJavscriptFire. Unlike functions, there is no requirement that an action works exactly the same for both server and client side as this is not always sensible or feasible.

A simple action could be "WarningAction" that shows an alert warning the user that something may be wrong and logs a message to the ./sugarcrm.log file if triggered on the server side. It will take in a message as a formula so that the message can be customized at runtime. We would do this by creating a PHP file in ./custom/include/Expressions/Actions/WarningAction.php as shown below:

./custom/include/Expressions/Actions/WarningAction.php

  <?php

require_once("include/Expressions/Actions/AbstractAction.php");

class WarningAction extends AbstractAction    {

    protected $messageExp = "";

    /**
    * Returns the javascript class equavalent to this php class
    * @return string javascript.
    */
    static function getJavascriptClass()
    {
        return <<<EOQ

        SUGAR.forms.WarningAction = function(message)
        {
            this.messageExp = message;
        };

        //Use the sugar extend function to extend AbstractAction
        SUGAR.util.extend(SUGAR.forms.WarningAction, SUGAR.forms.AbstractAction,
        {
            //javascript exection code
            exec : function()
            {
            //assume the message is a formula
            var msg = SUGAR.forms.evalVariableExpression(this.messageExp);
            alert(msg.evaluate());
            }
        });
    EOQ;
    }

    /**
    * Returns the javascript code to generate this actions equivalent.
    * @return string javascript.
    */

    function getJavascriptFire()
    {
        return "new SUGAR.forms.WarningAction('{$this->messageExp}')";
    }

    /**
    * Applies the Action to the target.
    * @param SugarBean $target
    */
    function fire(&$target)
    {
        //Parse the message formula and log it to fatal.
        $expr = Parser::replaceVariables($this->messageExp, $target);
        $result = Parser::evaluate($expr)->evaluate();
        $GLOBALS['log']->warn($result);
    }

    /**
    * Returns the definition of this action in array format.
    */
    function getDefinition()
    {
        return array("message" => $this->messageExp);
    }

    /**
    * Returns the short name used when defining dependencies that use this action.
    */
    static function getActionName()
    {
        return "Warn";
    }
}

?>