Quantcast
Channel: Sugar 7 – Sugar Developer Blog – SugarCRM
Viewing all 93 articles
Browse latest View live

Adding a floating frame into Sugar

$
0
0

Sugar’s Single Page Architecture

Sugar relies on a single page architecture (SPA) for the Sidecar framework. But when a user navigates across the Sugar application (for example, when switching to another Sugar module), while the page is not refreshed, you will find that the majority of the HTML on the page is still re-rendered based upon the newly selected layout. This is done to not only change the style of the view (List View → Record View) but also to update the context for the dashboard panel.

But not everything changes – the footer and header of the application remain fixed so they can serve as navigational anchors for Sugar users. This is an important use case but there are certainly others.

Telephony integration scenarios

A common set of integrations that are built for Sugar involve integrating a phone system for use by customer service organizations. This can vary from simple “click to dial” softphone functionality to full blown multi-channel call center capability where an agent handles phone, SMS, and e-mail inquiries at the same time.

A typical in-bound call process follows:

Step 1) A customer calls support desk with an inquiry

Step 2) The call is routed to an available support agent desktop

Step 3) The agent desktop displays current customer information

Step 4) The service agent handles customer inquiry

Step 5) The interaction details are logged to the customer record

CTI_Image.png

A typical telephony integration workflow

While the call is active, it is typically necessary to have a persistent display of the call’s status and caller information. The service agent may need to be able to review CRM data across multiple modules without losing the call’s context.

This need is not addressed by customizing standard Sidecar Record or List layouts because they aren’t fixed. An alternative could be to open a pop-up windows but that is a pretty crappy user experience.

Pop-up_ads.jpg

There are better alternatives. Users expect to be able to work within the Sugar application and this can be accommodated by adding a floating frame to the Sugar user interface that will serve our needs.

Floating Div Action Building Block

We have created a package that provides a prescribed pattern for handling persistent content within the Sugar UI using a floating frame. This frame can be configured to iframe web content from an external system or as a basis for a native Sidecar component. There are multiple telephony solutions in the Sugar ecosystem that use variations on this approach.

cti_example_screenshot

We will dig into some of the code below.

Footer contribution via Sidecar Extension

We can add the Call button seen in the screenshot above using a Sidecar Extension for a new custom Sidecar View. We will talk about this new click-to-call view (ClickToCallView) later.

custom/Extension/application/Ext/clients/base/layouts/footer/addClickToCallAction.php

<?php

// Copyright 2016 SugarCRM Inc.  Licensed by SugarCRM under the Apache 2.0 license.

//  Append new View to Footer layout's list of components

$viewdefs['base']['layout']['footer']['components'][] = array (
'view' => 'click-to-call',
);

The ClickToCallView toggle button

The toggle button that appears in footer is implemented as part of our new custom ClickToCallView within custom/clients/base/views/click-to-call/click-to-call.js and custom/clients/base/views/click-to-call/click-to-call.hbs. Since this Sidecar View is attached to the footer, it will be persistent as the user navigates. We can conveniently use it to manage the lifecycle of our “popup” frame to ensure it is only loaded once. Instead of removing the pop-up frame completely when a user closes it, we will simply hide it from view so that it does not need to be recreated later.

...
    events: {
        //On click of our "button" element
        'click [data-action=open_phone]': 'togglePopup',
    },

    // tagName attribute is inherited from Backbone.js.
    // We set it to "span" instead of default "div" so that our "button" element is displayed inline.
    tagName: "span",
    // Used to keep track of Popup since it is not attached to this View's DOM
    $popup: undefined,
    /**
     * Toggle the display of the popup.  Called when the phone icon is pressed in the footer of the page.
     */
    togglePopup: function () {
        //Toggle active status on button in footer
        var $button = this.$('[data-action="open_phone"]');
        $button.toggleClass('active');
        //Create popup if necessary, otherwise just toggle the hidden class to hide/show.
        if (!this.$popup) {
            this._createPopup();
        } else {
            this.$popup.toggleClass('hidden');
        }
    },
...
{{! Copyright 2016 SugarCRM Inc.  Licensed by SugarCRM under the Apache 2.0 license. }}

{{!
    Define HTML for our new button.  We will mimic the style of other buttons
    in the footer so we remain consistent.
}}
<button data-action="open_phone" class="btn btn-invisible" aria-label="{{str "Call"}}" role="link" type="button">
    <i class="fa fa-phone icon-phone"></i><span class="action-label"> {{str "Call"}}</span>
</button>

The floating frame

Sugar does not provide a popup frame component out of the box, so we can create one from scratch with our own Handlebars templates for the HTML and necessary CSS. We also use the Draggable jQuery UI plug-in that is already included with Sidecar to allow users to reposition the frame as they want.

Our JavaScript code will lazy load in the CSS to the HEAD section of the page and append our popup frame to the main content DIV element when the Call button is toggled the very first time.

custom/clients/base/views/click-to-call/click-to-call.js

...
    /**
     * Used to create Popup as needed. Avoid calling this directly, should only need to be called once.
     * @private
     */
    _createPopup: function () {
        var popupCss = app.template.get("click-to-call.popup-css");
        // We need to load some custom CSS, this is an easy way to do it without having to edit custom.less
        $('head').append(popupCss());
        var popup = app.template.get("click-to-call.popup")(this);
        // Add to main content pane of screen
        $('#sidecar').append(popup);
        this.$popup = $('#sidecar').find('div.cti-popup');
        // Hide pop up on click of X (close button)
        this.$popup.find('[data-action=close]').click(_.bind(this._closePopup, this));
        // Make pop up draggable using existing jQuery UI plug-in
        this.$popup.draggable();
    },
...

custom/clients/base/views/click-to-call/popup.hbs

{{! Copyright 2016 SugarCRM Inc.  Licensed by SugarCRM under the Apache 2.0 license. }}
<div class="cti-popup">
<div class="cti-popup-header">
        <strong>CTI Window</strong>
        <a class="cti-close pull-right" data-action="close"><i class="fa fa-times fa-large"></i></a></div>
<div class="cti-popup-content">
        <iframe src="{{meta.iframeSrc}}"></iframe></div>
</div>

custom/clients/base/views/click-to-call/popup-css.hbs

{{! Copyright 2016 SugarCRM Inc.  Licensed by SugarCRM under the Apache 2.0 license. }}
<style type="text/css">
    .cti-popup {
        position: fixed;
        top: 50%;
        left: 50%;
        z-index: 1050;
        max-height: 500px;
        background-color: #f6f6f6;
        border: 1px solid #cfcfcf;
        *border: 1px solid #999;
        /* IE6-7 */

        -webkit-border-radius: 4px;
        -moz-border-radius: 4px;
        border-radius: 4px;
        -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
        -moz-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
        -webkit-background-clip: padding-box;
        -moz-background-clip: padding-box;
        background-clip: padding-box;
    }

    .cti-popup-header {
        background-color: #f6f6f6;
        padding: 10px 15px;
        border-bottom: 1px solid #f5f5f5;
    }
    .cti-close{
        position: relative;
        top: -2px;
    }
</style>

Full example available in Github

You can find the full example as a module loadable package you can build yourself on Github. Just find the Floating Div Action package that is part of the SugarCRM Building Blocks repository. As of today, I have tested this package with Sugar 7.6 and Sugar 7.7.

Let me know what you think!



Security changes coming in Sugar 7.8

$
0
0

Important security changes in Sugar 7.8

As we near the release of Sugar 7.8, we wanted to update the Sugar Developer community on a couple of important security changes that are coming in this release.

The oauth_token URL parameter is disabled by default

A convenience feature that allowed an OAuth 2.0 access token to be passed via the oauth_token URL parameter instead of using the OAuth-Token HTTP header is no longer supported in Sugar 7.8. The examples in the Sugar Developer Guide always used the OAuth-Token HTTP header – but a few people had discovered they could pass the oauth_token as a URL parameter on their own.

If you have been using it, you should know that this feature has been disabled by default due to security concerns. The practice of passing and accepting session identifiers via URL parameters is against OWASP guidelines.

For reference: Session Management Cheat Sheet – OWASP

This example is no longer allowed by default

GET /sugar/rest/v10/Contacts?oauth_token={GUID} HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Cache-Control: no-cache

This example continues to work and is preferred

GET /sugar/rest/v10/Contacts HTTP/1.1
Host: localhost:8080
Content-Type: application/json
OAuth-Token: {GUID}
Cache-Control: no-cache

If you want to enable this feature again, then you can use a new SugarConfig setting called allow_oauth_via_get. When the config setting is true, this will permit the oauth_token URL parameter to be used to pass access tokens.

$sugar_config['allow_oauth_via_get'] = true;

CSRF Tokens are now enforced by default

In a previous post on CSRF tokens in Sugar 7.7, we indicated that CSRF authentication would be enabled by default in future Sugar versions.

Well in Sugar 7.8, the $sugar_config[‘csrf’][‘soft_fail_form’] setting will default to false and the $sugar_config[‘csrf’][‘opt_in’] setting has been removed. This means that CSRF tokens are now enabled by default as of Sugar 7.8

For more details on Sugar’s CSRF implementation, please review the original post on this blog.

HTML forms used in BWC modules need to be authenticated in order to continue to function. If necessary, Sugar Developers can temporarily set the following configuration variable to disable enforcement of CSRF tokens.

$sugar_config['csrf']['soft_fail_form'] = true;

The above setting will generate FATAL messages in the sugarcrm.log file whenever a CSRF authentication failure occurs.

Sugar Developers and Administrators should resist the temptation to use soft fail mode in production instances.


How to add a client idle timeout to Sugar 7.x

$
0
0

Here is another guest post from Shijin Krishna from BHEA Technologies.

When a user logs into Sugar 7, an OAuth access token (with a 1 hour timeout by default) and a refresh token (with a 2 week timeout by default) are returned. When the access token expires, Sugar will automatically retrieve another access token as long as the refresh token is valid. This allows a user to use a Sugar browser tab for days on end without having to log back in.

Automated notification requests are made to the server on the user’s behalf at a default interval of every 5 minutes. These requests will allow the current session to remain active without actual user input. So tracking user activity by adjusting access token and refresh token expiry time or tracking network activity alone is not a good idea.

In this blog we are going to explore a way to track a user’s true idle time based on actual user interface activity. For example, one or more of clicks, typing, mouse movements etc. To track a user’s idle time we will use the JQuery IdleTimeout plugin.

This allow us to configure reasonable settings for the OAuth access token and refresh token to allow Lotus Notes, Outlook and other plugins to function for a longer period without needing to login again, while continuing to reliably enforce idle logout on the web client.

We will also learn to configure maximum idle time crossing the same will log the user out from Sugar automatically.

Step 1) Add JQuery IdleTimeout plug-in to Sugar JS

Create a new JS Grouping Extension file in the following path.
custom/Extension/application/Ext/JSGroupings/idleTimer.php

<?php
// Copyright Shijin Krishna. This work is licensed under an Apache 2.0 license.
$js_groupings[] = $sugar_grp_sidecar = array_merge($sugar_grp_sidecar, array(
        'custom/include/javascript/store.min.js' => 'include/javascript/sugar_sidecar.min.js',
        'custom/include/javascript/jquery-idleTimeout.min.js' => 'include/javascript/sugar_sidecar.min.js',
        'custom/include/javascript/idleTimer.js' => 'include/javascript/sugar_sidecar.min.js',
    )
);
  • jquery-idleTimeout.min.js – Contains the source code for JQuery IdleTimeout plugin.
  • store.min.js – Required dependency for the plugin.
  • idleTimer.js – We will talk about this little later.

Add the IdleTimeout plug-in and store.js at following paths:

custom/include/javascript/jquery-idleTimeout.min.js
custom/include/javascript/store.min.js

Step 2) Start the idle timer

We will start tracking users inactivity time once the app:sync:complete event is triggered. The JQuery Idle Timeout plugin comes with a set of configurable parameters which will allow us to define the maximum idle time, callback to execute when the idle time reaches the maximum limit, etc. Please click here to view more public configuration variables.

custom/include/javascript/idleTimer.js

/**
* Idle time logout
* Copyright Shijin Krishna. This work is licensed under an Apache 2.0 license.
* Date 11/29/2016
*
* */
(function(app){
app.events.on('app:sync:complete',function(){
$(document).idleTimeout({
redirectUrl:'#logout', //redirect url
idleTimeLimit: app.config.max_idle_time || 600, // 'No activity' time limit in seconds. 600 = 10 Minutes
idleCheckHeartbeat: 10, // Frequency to check for idle timeouts in seconds
// optional custom callback to perform before logout
customCallback: function(){
app.logger.error("Logging out user after maximum idle time:" + app.config.max_idle_time); // this method will destroy user's session and log user out
// Due to bug with customCallbacks with idleTimeout jQuery plug-in,
// We must reload document to remove idleTimeout from page until user logs in again
window.location.reload();
},
enableDialog: false
});
});
})(SUGAR.App);

Step 3) Configuring the max idle time

By default our timer will consider ten minutes as the maximum idle time. But this can be configured by adding a new parameter ‘max_idle_time‘ to the config_override.php file which is available under sugar root directory.

config_override.php

<?php
// Copyright Shijin Krishna. This work is licensed under an Apache 2.0 license.
$sugar_config['additional_js_config']['max_idle_time'] = 1800;

Step 4) Rebuild Extensions & Configuration

Finally, you will need to run Quick Repair & Rebuild, Rebuild GS Grouping Files and Rebuild Config File in order to build your new extensions and configuration. You will also need to do a hard refresh of the browser page in order to load the updated JavaScript files.

screen-shot-2016-12-02-at-11-15-41-am

Nothing fancy but you can see the logout occurring in the console log.


Here are 3 new resources for developing on Sugar 7.8

$
0
0

You may have seen that that Sugar 7.8 has now been released! We have introduced some important platform updates in this release. Have you read the developer release notes yet? Here are three additional and essential resources for you to use before you embark on new Sugar 7.8 projects.

Sugar 7.8 Migration Guide

The Migration Guide for Sugar 7.8 is an essential resource for any Sugar Developer upgrading a customer from Sugar 7.7.x. The guide will help you identify code and platform changes that you need to make in order to ensure that you can upgrade your code and customers successfully.

Sugar 7.8 Unit Tests

The Sugar Unit Tests repository has been updated with our unit test suites for Sugar 7.8. This is an essential resource for ensuring that your Sugar 7.8 custom code is working correctly and hasn’t introduced regressions within Sugar core code. Remember that you need to sign up for access to the Sugar test repositories.

Sugar 7.8 Overview for Sugar Developers

We delivered a recorded webinar that is now available in the Developer space in the Sugar Community. This is a great way to get immediate understanding about the new platform features and changes that were introduced in Sugar 7.8. The presentation slides are also available in the community.


Helpful utility functions for notifying users about inbound emails

$
0
0

Here is another Francesca Shiekh guest post! She is a Sugar Developer from Wolfram Research.

We had the need to notify the assigned user when an email was received that was related to a Case record. To make our application more flexible we extended this concept to be reusable for email received that was related to any module. We then further broke out the notification functions to be reusable in other scenarios.

Notifying an Assigned User that an Email was received

The following concept can be used to notify the Assigned user on a Case, Contact, Account, or basically any module that has related Emails.

For example, suppose that we wish to notify the Assigned User that an email has been logged on a Contact record assigned to them.

Detecting inbound Emails using an after_relationship_add logic hook

First, we can add an after_relationship_add logic hook on Contacts:

In ./custom/modules/Contacts/logic_hooks.php

<?php
$hook_version = 1;
$hook_array = Array();
$hook_array['after_relationship_add'] = Array();
$hook_array['after_relationship_add'][] = Array(1, 'email_contact_relationship', 'custom/modules/Contacts/email_contact_handling.php','email_contact_handling', 'update_on_relationship'); ?>

The logic hook checks if the related record is an Email then calls a custom utility function to handle the notification.

./custom/modules/Contacts/email_contact_handling.php

class email_contact_handling
{
   // Email RELATED to a Contact
   function update_on_relationship ($bean,$event,$arguments){
        require_once('custom/entry_points/Utils/notifyAssignedUser.php'); //choose your own path for your custom utility functions
        //bean = Contact
        //related_module = Emails
        if ($arguments['related_module'] =='Emails'){ //check that you are indeed relating an Email to this Contact
           notifyAssignedUser($bean, $arguments['related_id']); //related_id is the email id in this case
        }
    }
 }

Notifying an Assigned User that an Email was received

We can also define a reusable utility function to notify the Assigned User. We choose to store our reusable functions in in ./custom/entry_points/Utils/ but you can put yours inside another custom directory.

In /custom/entry_points/Utils/notifyAssignedUser.php

<?php
//given a bean and an Email id notify the
//Assigned user on the bean that the email was received.
function notifyAssignedUser($bean, $email_id){
$email = BeanFactory::retrieveBean('Emails',$email_id);
$module = $bean->module_name;
    $u = new User();
    $u->retrieve($c->assigned_user_id);
    $user_email = $u->emailAddress->getPrimaryAddress($u);
    if (filter_var($user_email, FILTER_VALIDATE_EMAIL)) {
      include_once('custom/entry_points/Utils/sendEmail.php');
      $email_text = !empty($email->description_html)?$email->description_html:$email->description;
      $EmailBody=<<<BODY New email received for $bean->name

Assigned to: $u->user_name

From: {$email->from_addr_name} ( {$email->from_addr} )

To: {$email->to_addrs}

Cc: {$email->cc_addrs}

Subject: $email->name

See: {$sugar_config['site_url']}/#{$module}/{$bean->id}

{$email_text}
BODY;
      $to_address = $u->emailAddress->getPrimaryAddress($u);
      $subject = "New Email received for $c->name";
      if(!empty($to_address)&&$u->user_name!='tsreception'){
        sendEmail($to_address, '<your sugar email address>', $from, $subject,$EmailBody);
      }
    }
  return;
}
?>

Notifying a Group of Users based on criteria from the Users module

You can also choose to retrieve a group of users based on some criteria for User records.

For example, we have a custom field support_group_c which we use for group notifications on Case records.

<?php
// Copyright 2016 Wolfram Research, Inc.

// Given a support group and a Case bean, then notify the support group users that the case was created.
function notify_support_group_users($c, $support_group, $email=null){
$GLOBALS['log']->debug("Inbound Email: Prepare to notify " . $support_group);
  require_once('include/SugarQuery/SugarQuery.php');
  $sugarQuery = new SugarQuery();
  $sugarQuery->select(array('id'));
  $bean = BeanFactory::newBean('Users');
  $sugarQuery->from($bean, array('team_security' => false));
  $sugarQuery->where()->equals('support_group_c', $support_group);
  $result = $sugarQuery->execute();
  $GLOBALS['log']->debug("Inbound Email: Prepare to notify " . print_r($result,true));
  foreach($result as $user){
    $GLOBALS['log']->debug("Inbound Email: Prepare to notify " . print_r($user,true));
    $user = BeanFactory::getBean('Users', $user['id']);
    $user_email = $user->emailAddress->getPrimaryAddress($user);
    if (filter_var($user_email, FILTER_VALIDATE_EMAIL)) {
      $user_emails[] = $user_email;
    }
  }
  if(!empty($user_emails)){
    $GLOBALS['log']->debug("Inbound Email: Will notify " . print_r($user_emails,true));
    include_once('custom/entry_points/Utils/send_group_email.php');
    global $sugar_config;
    $toAddresses = implode(';',$user_emails);
    if(!empty($email)){
        $email_text = !empty($email->description_html)?$email->description_html:$email->description;
        $EmailBody=<<<BODY New email on [CASE:{$c->case_number}] $c->name

Current Case Status: $c->status

Assigned to: $user->user_name

From: {$email->from_addr_name} ( {$email->from_addr} )

To: {$email->to_addrs}

Cc: {$email->cc_addrs}

Subject: $email->name

See: {$sugar_config['site_url']}/#Cases/{$c->id}

{$email_text}
BODY;
    }else{
        $EmailBody=<<<BODY New Correspondence on  Case: [CASE:{$c->case_number}] $c->name

See: {$sugar_config['site_url']}/#Cases/{$c->id}
BODY;
    }
    // sendEmail($ToEmailAdd, $FromEmailAdd, $FromEmailName, $EmailSubject, $EmailBody)
    // defined in custom/entry_points/send_email.php
    $GLOBALS['log']->debug("Inbound Email: Will notify " . $toAddresses);
    if($c->status == 'New'){
      $subject = "New Case Created [CASE:{$c->case_number}] $c->name";
      $from = 'SugarCRM Case New';
    }else{
      $subject = "New Correspondence on Case [CASE:{$c->case_number}] $c->name";
      $from = 'SugarCRM Case Update';
    }
    if(!empty($toAddresses))  sendGroupEmail($toAddresses, 'sugarCRM@wolfram.com', $from, $subject,$EmailBody);
  }
  return;
}
?>

Sending an Email to a given set of addresses, one per addressee

Finally, we have a sendEmail() utility function.

./custom/entry_points/Utils/sendEmail.php

<?php
// Copyright 2016 Wolfram Research, Inc.

// Function to send Email message
// one email per recipient
function sendEmail($ToEmailAdd, $FromEmailAdd, $FromEmailName, $EmailSubject, $EmailBody) {
global $sugar_config;
$GLOBALS['log']->debug('PREPARE EMAIL to ' . print_r($ToEmailAdd, true));
  require_once ('modules/Emails/Email.php');
  if(is_array($ToEmailAdd)){
    $To = $ToEmailAdd;
  }else{
    $To = explode(';',$ToEmailAdd);
  }
  foreach ($To as $to_addr){
    $GLOBALS['log']->debug('PREPARE EMAIL TO:' . $to_addr);
    if (filter_var($to_addr, FILTER_VALIDATE_EMAIL)){
      try{
        $phpMailer = MailerFactory::getSystemDefaultMailer();
        $mailTransmissionProtocol = $phpMailer->getMailTransmissionProtocol();
        $FromEmailIdentity = new EmailIdentity($FromEmailAdd, $FromEmailName);
        $header_array = array(
          'From'=>$FromEmailIdentity,
          'ReplyTo'=>'',
          'Sender'=>$FromEmailIdentity, //mandatory
          'Subject'=>$EmailSubject,
        );
        $phpMailer->constructHeaders($header_array);
        $phpMailer->addRecipientsTo(new EmailIdentity($to_addr, $to_addr));
        $phpMailer->setHtmlBody($EmailBody);
        $phpMailer->send();
      }catch(MailerException $me) {
        $message = $me->getMessage();
        $GLOBALS["log"]->warn(
          "SendEmail: error sending e-mail (method: {$mailTransmissionProtocol}), (error: {$message})"
        );
      }
    }
  }
  return;
}

Send Email to a set of addresses as multiple To and/or CC

We can also modify the sendEmail() function above to send one email to the whole group with multiple recipients instead of individual emails.

<?php
// Copyright 2016 Wolfram Research, Inc.
//Function to send Email message
//Multiple recipients => multiple addresses in the To, and/or Cc

function sendGroupEmail($ToEmailAdd, $FromEmailAdd, $FromEmailName, $EmailSubject, $EmailBody, $CcEmailAdd = array()) {
  include_once('modules/Mailer/MailerFactory.php');
  global $sugar_config;
  $ToAddrs = array();
  $CcAddrs = array();
  if(is_array($ToEmailAdd)){
    $To = $ToEmailAdd;
  }else{
    $To = explode(';',$ToEmailAdd);
  }
  if(is_array($CcEmailAdd)){
    $Cc = $CcEmailAdd;
  }else{
    $Cc = explode(';',$CcEmailAdd);
  }
  try{
    $phpMailer = MailerFactory::getSystemDefaultMailer();
    $mailTransmissionProtocol = $phpMailer->getMailTransmissionProtocol();
    $FromEmailIdentity = new EmailIdentity($FromEmailAdd, $FromEmailName);
    $headers = new EmailHeaders();
    $header_array = array(
      'From'=>$FromEmailIdentity,
      'ReplyTo'=>'',
      'Sender'=>$FromEmailIdentity, //mandatory
      'Subject'=>$EmailSubject,
    );
    $headers->buildFromArray($header_array);
    $phpMailer->setHeaders($headers);
    //$phpMailer->setHtmlBody($EmailBody);
    $phpMailer->setTextBody($EmailBody);

    foreach($To as $to_addr){
      //note should be new EmailIdentity(<email_address>, <name>)
      //we don't have a name for these
      $phpMailer->addRecipientsTo(new EmailIdentity($to_addr, $to_addr));
    }
    foreach($Cc as $cc_addr){
      //note should be new EmailIdentity(<email_address>, <name>)
      //we don't have a name for these
      $phpMailer->addRecipientsCc(new EmailIdentity($cc_addr, $cc_addr));
    }
    $phpMailer->send();
  }catch(MailerException $me) {
    $message = $me->getMessage();
    $GLOBALS["log"]->warn(
      "SendGroupEmail: error sending e-mail (method: {$mailTransmissionProtocol}), (error: {$message})"
    );
  }
  return;
}
?>

 


How to configure SAML SSO for Sugar

$
0
0

Many customers want to configure Sugar for Single Sign On (SSO). Well Sugar supports Security Assertion Markup Language (SAML) so this must be easy, right? But the devil is always in the details.

Each SAML identity provider behaves a little differently. Each of these systems has different terminology and methods for configuration and may use different default settings. Some of these important configuration settings can make the difference between a successful SSO implementation and a tire fire. For example, are users provisioned Just-In-Time or will they be provisioned manually? Did you know that Sugar uses the e-mail address as the SAML application username format?

Below are instructions for configuring SAML SSO with a couple of common identity providers.

Okta

One of our Solution Architects, Enrico Simonetti, wrote a good summary of how to configure SAML authentication for Sugar using Okta as the identity provider. Okta is convenient for trying out SSO because they have a developer program you can join. Enrico also covers a few tips and details that can trip up any SAML implementation.

Please visit Enrico’s post called SSO Authentication on SugarCRM with SAML for more details including screen shots and even code examples.

Active Directory Federation Service

The most common system that we get questions about is Microsoft’s Active Directory Federation Service (ADFS). ADFS is pretty complicated so there are several steps that you need to follow to get it done right.

We recently publish a SugarCRM Knowledge Base article called Configuring SSO With Active Directory’s ADFS. It was written by Lars Blockken, one of our Senior Technical Account Managers, and in it he walks you through each of these steps in detail along with screenshots. It will have you up and running on ADFS in no time!


How to build Chart layouts for Sugar 7

$
0
0

This blog will the first in a two part series on building Charts components for Sugar 7. This post is targeted at beginner to intermediate Sugar Developers who want to learn how to build their first Chart component.

This post assumes some basic knowledge of Sugar 7 development, Sugar 7 administration, JavaScript, and PHP.  This information should be useful to anyone who has an interest in Sugar 7 development.

The examples in this post were created on an out-of-the-box installation of Sugar Professional 7.8.0.0.  But this technique should work on any on-premise Sugar 7 instance.

Introduction

You may have noticed that a number of out of the box dashlets and views contain various fancy charts and visualizations.  This is possible because Sugar has a charting component build into it.  You can make use of this to display charts within your own custom dashlets, views or layouts.

In this post, we will focus on the “LineChart” type. There are other chart types that use different data formats and chart options but the general techniques covered here will work for all chart types.  These examples were implemented in a basic custom view but they will also work within dashlets.

NVD3 and Sugar’s Charts

To provide the ability to display charts easily, Sugar uses a highly customized version of NVD3. The basic structure of our version is the same as NVD3 but the details are different so take some time to explore the chart examples in the source code at /sugarcrm/styleguide/content/charts/. Sugar plans to fully migrate to Sucrose Charts which is SugarCRM’s official fork of the NVD3 libraries in an upcoming release. It is worth taking some time to explore Sucrose Charts documentation as well. It includes a number of detailed examples of what charts are possible as well as the data models used to produce them.

Basic Requirements for a Sugar Chart

Within a Sugar view, all charts have the same general requirements. The examples here use the LineChart chart type but these steps apply to any chart you may want to create.

  1. Create the container for your Chart in your Handlebars template
    • Create a DIV element with a child SVG element inside your Handlebars template
  2. Implement the Chart plug-in within your JavaScript controller
    • Include the “Chart” Sidecar plug-in
    • Instantiate the chart model for your view in the initialize() method
    • Collect data in the right format for your chart type
    • Invoke the chart model via the chart call() method

The example below will make this clear.

A Basic Line Chart Example

We can create a custom Sidecar layout under the Accounts module called single-chart-layout which, in turn, includes a custom Sidecar view for Accounts module called single-chart-view. While we are using Accounts module here, this example could be adapted to run on any module.

First, we will create our custom layout.

custom/modules/Accounts/clients/base/layouts/single-chart-layout/single-chart-layout.php

<?php
$viewdefs['Accounts']['base']['layout']['single-chart-layout'] =
array( 'type' => 'simple',
    'components' => array(
        array(
            'view' => 'single-chart-view',
        ),
    ),
);

As you can see from the layout, we reference “single-chart-view” so we should create this next.

custom/modules/Accounts/clients/base/views/single-chart-view/single-chart-view.php

<?php
$viewdefs['Accounts']['base']['view']['single-chart-view'] =
array( 'title' => 'Chart example',
    'config' => "",
);

Now we can set up our chart within the single-chart-view component.

Set up your Handlebars template

Next, we create our Handlebars template.

single-chart-view.hbs

<div class="single-chart-view">
<svg></svg></div>

Include the “Chart” plugin

When creating single-chart-view.js the first thing to do is to include the “Chart” plugin:

({
    plugins: ['Chart'],
    className: 'single-chart-view',
    chartData: {},
    total: 0,
    initialize: function (options) {
    ...
})

Instantiate the chart model for your View in initialize() method

The Chart plug-in will rely on the view’s “chart” property.  We need to set up that property to point to a chart object.  In our example, we will tell it to point to a Line Chart.  To do that we can call the nv.models.lineChart() method which returns a line chart object.  For the options available for the Line Chart and others, you will need to consult NVD3 and Sucrose Charts documentation listed above.

For our first example, we will create a very simple line chart that displays a title.  You will notice that we attach “x” and “y” callbacks to the new Line Chart object.  These methods tell the chart object where to get the X and Y axis data for the line chart.  In our case the “x” callback retrieves from the “widget_points” property and the “y” callback retrieves from the “num_widgets” property. There is nothing magical about those names, of course. I made them deliberately arbitrary to illustrate that they can be anything you want.

({
    ...
    initialize: function (options) {
        this._super('initialize', [options]);
        this.chart = nv.models.lineChart()
            .x(function (d) {
                return d.widget_points;  // We get the X data points from 'widget_points'
            })
            .y(function (d) {
                return d.num_widgets;  // We get the Y data points from 'num_widgets'
            })
            .showTitle(true)
            .tooltips(false);
    },
    ...
})

Populate your data

If a loadData() method exists, then it will be executed when the View is rendered. This is when you retrieve data to be used in a chart. If you attach the data object to the View controller then it will be available whenever needed. Here we set it to a property called “chartData“.

The format of the object is crucial. Use this format to working with Line Charts.

({
  ...
    loadData: function() {
        this.chartData = {
                data: [
                    {
                        key: "Blue Stuff",
                        values: [
                            {
                                widget_points: 1, num_widgets: 10
                            },
                            {
                                widget_points: 2, num_widgets: 9
                            },
                            {
                                widget_points: 3, num_widgets: 8
                            },
                            {
                                widget_points: 4, num_widgets: 7
                            },
                            {
                                widget_points: 5, num_widgets: 6
                            },
                        ],
                        color: "#0000ff"
                    },
                    {
                        key: "Red Stuff",
                        values: [
                            {
                                widget_points: 1, num_widgets: 1
                            },
                            {
                                widget_points: 2, num_widgets: 2
                            },
                            {
                                widget_points: 3, num_widgets: 3
                            },
                            {
                                widget_points: 4, num_widgets: 4
                            },
                            {
                                widget_points: 5, num_widgets: 5
                            },
                        ],
                        color: "#ff0000"
                    },
                ],
                properties: {
                    title: 'Example Chart Data'
                }
            };

        this.total = 1;
    }
 ...
 })

You should also notice that at the end of the method is the line:

this.total = 1;

When rendering the view, this value is tested to see if data is available prior to rendering chart. With a hard coded example this does not matter much but when you are making an AJAX call for data then it matters since the response returns asynchronously. Other chart types use “total” differently but line charts simply evaluate if it is set to a non-zero value.

Invoke the chart model via the chart call() method

When rendering, the loadData() method is called and then later the renderChart() method is called, if it is defined. This method is used to draw the chart on the screen. We do this by using the built-in d3 call() method.

({
  ...
      renderChart: function () {
        if (!this.isChartReady()) {
            return;
        }

        d3.select(this.el).select('svg')
            .datum(this.chartData)
            .transition().duration(500)
            .call(this.chart);

        this.chart_loaded = _.isFunction(this.chart.update);
    }
  ...
})

This method first calls isChartReady() to see whether the chart is ready to display or not. Recall the this.total value we set in loadData()?  That’s what this method is examining, among other things, to figure out whether it is appropriate to now draw the chart.

At the end of this method we set this.chart_loaded, indicating that the chart has indeed been drawn.

There are several parts to that long call() method chain that are important to understand. First, the string argument to select() is a CSS selector which points to the SVG element in our HTML where the chart will be drawn.

Second, the argument to the datum() method is the object we populated in the loadData() method. It is looking for the appropriately formatted data object.

Third, we attach whatever features of the d3 object we want. In this case, we set transitions to half a second (500 milliseconds). You can experiment with different values there to see what happens.

Finally, we pass the chart property of the view to the call() method. At that point the chart will be drawn.

To put it all together, here is the complete JavaScript controller:

({
    plugins: ['Chart'],
    className: 'single-chart-view',
    chartData: {},
    total: 0,
    initialize: function (options) {
        this._super('initialize', [options]);
        this.chart = nv.models.lineChart()
            .x(function (d) {
                return d.widget_points;
            })
            .y(function (d) {
                return d.num_widgets;
            })
            .showTitle(true)
            .tooltips(false);
    },
    loadData: function() {
        this.chartData = {
                data: [
                    {
                        key: "Blue Stuff",
                        values: [
                            {
                                widget_points: 1, num_widgets: 10
                            },
                            {
                                widget_points: 2, num_widgets: 9
                            },
                            {
                                widget_points: 3, num_widgets: 8
                            },
                            {
                                widget_points: 4, num_widgets: 7
                            },
                            {
                                widget_points: 5, num_widgets: 6
                            },
                        ],
                        color: "#0000ff"
                    },
                    {
                        key: "Red Stuff",
                        values: [
                            {
                                widget_points: 1, num_widgets: 1
                            },
                            {
                                widget_points: 2, num_widgets: 2
                            },
                            {
                                widget_points: 3, num_widgets: 3
                            },
                            {
                                widget_points: 4, num_widgets: 4
                            },
                            {
                                widget_points: 5, num_widgets: 5
                            },
                        ],
                        color: "#ff0000"
                    },
                ],
                properties: {
                    title: 'Example Chart Data'
                }
            };

        this.total = 1;
    },
    renderChart: function () {
        if (!this.isChartReady()) {
            return;
        }

        d3.select(this.el).select('svg')
            .datum(this.chartData)
            .transition().duration(500)
            .call(this.chart);

        this.chart_loaded = _.isFunction(this.chart.update);
    }
})

Run a Quick Repair & Rebuild when finished. If you put the view in the layout described above, then you can see it by navigating to the following URL.

http://{your sugar server}/#Accounts/layout/single-chart-layout

and something like the following will appear:

single-chart-view.png

Look at that beautiful chart!

You, of course, are not limited in number of lines, colors that are used, or even to line charts alone. Users love charts, so I recommend spending some time to experiment with Sugar’s chart engine to see what they can do for you.


How to filter on related fields using Filter API

$
0
0

This post originally appeared on the SynoLab blog hosted by Synolia, an Elite SugarCRM Partner. Yann Bergès describes how you can use a relate filter with the Filter API. He also explores how Sugar does it as well as identifying a drawback to be considered when using this feature.

We all know the moment when you are roaming through source code to find something for a particular purpose and you come across that feature you didn’t expect but you absolutely want to test. This is how I came into the related link filter feature. What do I mean by related link filter? It is a derivative way to filter related data on a One-to-Many relationship by specifying a link name and a target field:

I want all Contacts filtered on their related Account with « Industry » value « Electronics » (use « one » side of the relationship)
I want all Accounts filtered on their related Contacts with « Title » value « President » (use « many » side of the relationship)

This is an advanced use of Sugar 7 Filter API, if you never used it before, have a look at this documentation for detailed information about how filters work:
– SugarCRM Cookbook – The School of REST – Part 3
– Sugar 7.8 Developer Guide – Architecture – Filters
Examples and tests have been made with a Sugar instance PRO 7.8.0.0

How does it work?

First of all, we have to look at the source code that manages this capability to understand how we will build our filter.

clients/base/api/FilterApi.php

<?php
...
protected static function verifyField(SugarQuery $q, $field)
{
    $ret = array();
    if (strpos($field, '.')) {
        // It looks like it's a related field that it's searching by
        list($linkName, $field) = explode('.', $field);

        $q->from->load_relationship($linkName);
        if(empty($q->from->$linkName)) {
            throw new SugarApiExceptionInvalidParameter("Invalid link $linkName for field $field");
        }

        if($q->from->$linkName->getType() == "many") {
            // FIXME TY-1192: we have a problem here: we should allow 'many' links for related to match against
            // parent object but allowing 'many' in  other links may lead to duplicates. So for now we allow 'many'
            // but we should figure out how to find if 'many' is permittable or not.
            // throw new SugarApiExceptionInvalidParameter("Cannot use condition against multi-link $linkName");
        }

        $join = $q->join($linkName, array('joinType' => 'LEFT'));
        $table = $join->joinName();
        $ret['field'] = "$table.$field";

        $bean = $q->getTableBean($table);
        if (empty($bean))
            $bean = $q->getTableBean($linkName);
        if (empty($bean) && $q->getFromBean() && $q->getFromBean()->$linkName)
            $bean = BeanFactory::getBean($q->getFromBean()->$linkName->getRelatedModuleName());
        if(empty($bean)) {
            throw new SugarApiExceptionInvalidParameter("Cannot use condition against $linkName - unknown module");
        }

    } else {
        $bean = $q->from;
    }
    ...
}
...

The method verifyField has a specific behavior for field names having a dot character inside. The field name passed is parsed has « link ». »field » to match an existing link on the current module requested. There are 2 things to remember here: using any related field of a link is allowed (so not only « name » or « id » as vardef definition have for « relate » type fields, e.g. « account_id » and « account_name ») and using « many » side of a link is tolerated.

We will come back later on the FIXME, let’s practice now how to fill these conditions for filtering results.

How to use relate filter with FilterApi ?

For my examples, I used the HTTP client provided by Postman Chrome Extension and the demo dataset for Sugar PRO 7.8.0.0.

My first need: I wanted all Contacts filtered on their related Account with « Industry » value « Electronics ». Let’s have a look on how to implement it with a relate filter.

sugar_filter_2 - synolab

The second test is with the « many » side of a relationship: I want all Accounts filtered on their related Contacts with « Title » value « President »

sugar_filter_1 - synolab

Do not forget that such filter can be combined with other ones with « $and » and « $or » constructions or you can use more complex logic with operators provided for targeted field types (« $in », « $starts », …).

So what happens when using a relate filter on the « many » side ?

Adding related data augments the query to join additional tables. As highlighted in the FIXME comment, the « many » side of a relationship can produce duplicate entries in the results. Actually, Sugar could remove these duplicates manually, but it will have a cost and it will not prevent huge transfer of result set data from the database. So before executing query, the generated SugarQuery is inspected and modified to include a DISTINCT option in order to reduce data result size to unique values:

MySQL

SELECT DISTINCT accounts.name name, accounts.id id, accounts.date_modified date_modified, accounts.assigned_user_id assigned_user_id, accounts.created_by created_by
FROM accounts
INNER JOIN(
    SELECT tst.team_set_id FROM team_sets_teams tst INNER JOIN team_memberships team_memberships ON tst.team_id = team_memberships.team_id AND team_memberships.user_id = 'seed_will_id'
    AND team_memberships.deleted = 0 GROUP BY tst.team_set_id
) accounts_tf ON accounts_tf.team_set_id = accounts.team_set_id
LEFT JOIN accounts_contacts jt1_accounts_contacts ON(accounts.id = jt1_accounts_contacts.account_id AND jt1_accounts_contacts.deleted = 0)
LEFT JOIN contacts jt0_contacts ON(jt0_contacts.id = jt1_accounts_contacts.contact_id AND jt0_contacts.deleted = 0 AND(jt0_contacts.team_set_id IN(
    SELECT tst.team_set_id FROM team_sets_teams tst INNER JOIN team_memberships team_membershipsjt0_contacts ON tst.team_id = team_membershipsjt0_contacts.team_id AND team_membershipsjt0_contacts.user_id = 'seed_will_id'
    AND team_membershipsjt0_contacts.deleted = 0)))
WHERE accounts.deleted = 0 AND jt0_contacts.title = 'President'
ORDER BY accounts.date_modified DESC, accounts.id DESC
LIMIT 0, 21

Let me highlight some source code that will illustrate what Sugar does:

data/SugarBean.php

<?php
...
function fetchFromQuery(SugarQuery $query, array $fields = array(), array $options = array()) {
    ...
    if ($this->queryProducesDuplicates($query)) {
        $this->fixQuery($query);
    }
    ...
}
...
protected function queryProducesDuplicates(SugarQuery $query)
{
    foreach ($query->join as $join) {
        if ($join->linkName) {
            $seed = $query->from;
            $linkName = $join->linkName;
            if ($seed->load_relationship($linkName)) {
                /** @var Link2 $link */
                $link = $seed->$linkName;
                if ($link->getType() === REL_TYPE_MANY) {
                    $relationship = $link->getRelationshipObject();
                    if (empty($relationship->primaryOnly)) {
                        return true;
                    }
                }
            }
        }
    }

    return false;
}
...
protected function fixQuery(SugarQuery $query)
{
    foreach ($query->select->select as $field) {
        if ($field->table) {
            $bean = $query->getTableBean($field->table);
            if (!$bean) {
                $bean = $query->from;
            }
            $def = $bean->getFieldDefinition($field->field);
            $type = $this->db->getFieldType($def);
            if ($this->db->isTextType($type)) {
                $GLOBALS['log']->warn('Unable to fix the query containing text field');
                return;
            }
        }
    }

    $query->distinct(true);
}
...

Using DISTINCT is not always a good way to prevent duplicate results, especially when there is not enough memory allocated to the database server or too many rows to compute. If you run into this problem, then you will have to optimize your SugarQuery or simply find another way to get the results you need.

Conclusion

Keep in mind that using this feature can generate heavy load on your database server, especially if you have no idea of what is going on behind your query. Also, since it is not a public method, we can’t guess if SugarCRM is going to make some updates on the verifyField method to handle the « many » side differently (there is explicitly the commented line throw new SugarApiExceptionInvalidParameter(“Cannot use condition against multi-link $linkName”);).

Sugar 7 provides many tools to deal with problematic relationships. So keep into digging some of their great features for more efficient results or just to satisfy your own curiosity.



The Pragmatic Sugar Developer

$
0
0

To be honest, I do not get to sling code all that often anymore. But I do spend a lot of time thinking about software even when I have no real hand (or keyboard) in creating it. I also talk to people about software a lot – software design, solution architecture, teams, and development processes. This means my growth today as a software professional comes primarily from the wisdom of others. And this was probably always true.

I have started to read more books about software and most recently that meant reading The Pragmatic Programmer. For those who do not know it, it is a classic software engineering book that was released in 1999. In a fast moving industry, what could you possible learn from a 17+ year old technology book? Well if you read it (again) then you may be surprised. The Pragmatic Programmer is not about programming – it is about programming processes, practices, and code craftsmanship. While you may not agree with everything you find in it, some parts feel out of date, it remains overall a very worthy book. I recommend it.

Something that books do for me is to put names on some concepts that I acquired from first-hand experience. It has also fully explained others I have known from having heard them tossed over cubicle walls or conference room tables over the years. Terms like Reversibility, Orthogonality, and many others represent concepts that should guide any Sugar Developer who sits down to her daily work of customizing, integrating, and extending the Sugar platform.

So in the spirit of The Pragmatic Programmer, here is my take on what some of the lessons taught in the Pragmatic Programmer mean to a Sugar Developer.

For a “cliff notes” version of the Pragmatic Programmer, you can find a quick reference guides and other summaries online.

The Pragmatic Sugar Developer

This blog post will help you become a better Sugar Developer. Apologies to Andrew Hunt and David Thomas who are the authors of The Pragmatic Programmer.

Orthogonality of Sugar customizations

The Pragmatic Sugar Developer designs Sugar customizations to be as orthogonal to Sugar core code as possible. Keeping customizations independent and loosely coupled from the Sugar platform means that Sugar version upgrades will rarely introduce side effects or breakages. The reverse is also true, which means the Developer can make changes to her customizations that will not cause side effects or breakages in the Sugar platform either.

For example, overriding core Sugar code (like SugarBean.php) using the custom directory to implement a customization is decidedly non-orthogonal with respect to the Sugar platform. Any change in the core file will impact the override. However, customizations implemented using an Extension such as a Logic Hook, a custom Sidecar Dashlet, or using the REST API would be orthogonal to the Sugar platform. As Sugar platform evolves, these orthogonal customizations will be better protected from side effects.

Sugar core code and the DRY Principle

DRY stands for Don’t Repeat Yourself. Every piece of knowledge should have an single, unambiguous, and authoritative representation in the system.

This means that a Pragmatic Sugar Developer will not blindly copy and paste core code into the custom directory. Developers should be calling APIs and not copying them. Core code should never be repeated under the custom directory where it will not be updated during upgrades.

Reversibility

The Pragmatic Sugar Developer knows that customers/stakeholders/management will often change their mind. This means that the Developer will ensure that design decisions are easily reversible in the future. The long term success of her project will likely depend on it.

Today, a customer is deploying on MySQL but later they might want to use Oracle instead. That custom chart was in a Sidecar Dashlet today may need to be moved into a custom layout tomorrow. Pragmatic Sugar Developers ensure that the right set of abstractions are in place to minimize the amount of rework associated with these change requests. Any required irreversible (final) decisions are postponed or delayed as long as possible.

Deconstruct user workflows to leverage concurrency

A Pragmatic Sugar Developer understands how to analyze user workflows and business processes to identify tasks that can be run independently and therefore concurrently. Implementing a giant workflow in one place can easily result in a solution that is a mess. Complex single threaded processes take a long time to run and lock up resources in the mean time like the user’s UI, memory, etc.

Performance can be improved by scheduling these tasks to be run asynchronously using the Sugar Job Queue or on different web servers for horizontal scaling. Additionally, breaking down a large workflow into independent tasks allows for each of them to be implemented more simply and maintained more easily.

Use Sidecar to separate views from models

The Pragmatic Sugar Developer understands the difference between the views and models when building Sidecar user interfaces. They create Sidecar Views that focus on representing models in the UI and keeping them synchronized. Sidecar allows Developers to build more flexible and adaptable user interfaces.

A Pragmatic Sugar Developer works with the Sidecar framework and not around it in order to drop in foreign frameworks (ex. to incorporate a flavor of the week JavaScript framework) or techniques (ex. fetching Sugar data using jqXHRs instead of using Beans or BeanCollections). Using the native Sidecar framework allows you to minimize complexity and maintain cohesion with the rest of Sugar platform.

Design to Test using Sugar Test Tools

The Pragmatic Sugar Developer designs her tests before writing any code and includes that additional QA scope in project plans. The Developer also designs her code with the intention to make it easy to test.

Pragmatic Sugar Developers benefit from using Sugar Test Tools instead of needing to build new test frameworks from scratch.

Dig for requirements for customer success (and your own)

Developers know that sometimes customers and stakeholders can provide unreliable or incomplete information. So Pragmatic Sugar Developers do not gather requirements, they dig for them.

An experienced Developer knows Sugar better and has done more CRM projects than her customers. She uses her experience to ask lots of questions to test assumptions and uncover new requirements that need to be addressed. Very importantly, customers are often focused on explaining their current systems and processes – Developers should challenge their customers to improve processes and not implement sub-optimal or broken processes on a brand new system.

For example, has her customer thought about mobile use cases? Will they want e-mail notifications for certain actions and, if so, what should the e-mail templates look like? Is there customer data outside the CRM today that could be integrated so that users do not need to switch between systems? Is the right data captured in the CRM to support the customer’s reporting requirements?


How to build Chart Layouts for Sugar 7 – Part 2

$
0
0

In our last post we covered the basics of adding a custom chart layout. Today’s post will build on that example to cover some of the more advanced configurations that are possible using charts in Sugar.  Like the previous post, this is targeted at a beginner to intermediate skilled Sugar Developer who is interested in building custom charts.

Multiple Charts On the Same View

Previously we explored how to display a single chart on a view. Displaying more than a single chart on the view is also very easy too.

In order to add a second chart, you may be tempted to create another chart object in the initialize() method but that is not necessarily how Sugar Charts works.  The “chart” property in the view controller is a Chart factory. Chart factories will not affect how your view is rendered unless you do something with them. You can use the same factory’s call() method to construct the same style of chart multiple times.

Example

We will create a custom layout and view exactly the way we did as before in the single chart example.  We will call this new example the “multi-chart-view”.

Set up the Handlebars template

To display multiple charts you need to provide multiple locations for the charts to exist. These locations are represented below by the div and the enclosed svg elements below. It is convenient to allow each chart div to have a unique id.

<div id="example-chart1">
    <svg></svg></div>
<div id="example-chart2">
    <svg></svg></div>
<div id="example-chart3">
    <svg></svg></div>

Populate data for multiple views

In our JavaScript code, we will obviously need to provide data for each chart. Here we create three separate properties on the view controller:  “bluered”, “purpleteal”, and “yellowgray”.  You’ll notice that each chart uses the same format as before.

({
...
    loadData: function() {
        this.bluered = {
            data: [
                {
                    key: "Blue Stuff",
                    values: [
                        {
                            x: 1, y: 10
                        },
                        {
                            x: 2, y: 9
                        },
                        {
                            x: 3, y: 8
                        },
                        {
                            x: 4, y: 7
                        },
                        {
                            x: 5, y: 6
                        },
                    ],
                    color: "#0000ff"
                },
                {
                    key: "Red Stuff",
                    values: [
                        {
                            x: 1, y: 1
                        },
                        {
                            x: 2, y: 2
                        },
                        {
                            x: 3, y: 3
                        },
                        {
                            x: 4, y: 4
                        },
                        {
                            x: 5, y: 5
                        },
                    ],
                    color: "#ff0000"
                },
            ],
            properties: {
                title: 'First Chart Data'
            }
        };
        this.purpleteal =             {
            data: [
                {
                    key: "Purple Stuff",
                    values: [
                        {
                            x: 1, y: 10
                        },
                        {
                            x: 2, y: 9
                        },
                        {
                            x: 3, y: 8
                        },
                        {
                            x: 4, y: 7
                        },
                        {
                            x: 5, y: 6
                        },
                    ],
                    color: "#ff00ff"
                },
                {
                    key: "Teal Stuff",
                    values: [
                        {
                            x: 1, y: 1
                        },
                        {
                            x: 2, y: 2
                        },
                        {
                            x: 3, y: 3
                        },
                        {
                            x: 4, y: 4
                        },
                        {
                            x: 5, y: 5
                        },
                    ],
                    color: "#00ffff"
                },
            ],
            properties: {
                title: 'Second Chart Data'
            }
        };
        this.yellowgray = {
            data: [
                {
                    key: "Yellow Stuff",
                    values: [
                        {
                            x: 1, y: 10
                        },
                        {
                            x: 2, y: 9
                        },
                        {
                            x: 3, y: 8
                        },
                        {
                            x: 4, y: 7
                        },
                        {
                            x: 5, y: 6
                        },
                    ],
                    color: "#ffff00"
                },
                {
                    key: "Gray Stuff",
                    values: [
                        {
                            x: 1, y: 1
                        },
                        {
                            x: 2, y: 2
                        },
                        {
                            x: 3, y: 3
                        },
                        {
                            x: 4, y: 4
                        },
                        {
                            x: 5, y: 5
                        },
                    ],
                    color: "#888888"
                },
            ],
            properties: {
                title: 'Third Chart Data'
            }
        }

        this.total = 1;
    },
 ...
 })

Call the Chart factory to display each chart

Now we make three separate calls to display each of the charts we want to display. You will notice that the unique ids we added to the div elements in the Handlebars template allow us to easily select each chart location. We also pass each a data object that we created above.

({
...
    renderChart: function () {
        if (!this.isChartReady()) {
            return;
        }

        d3.select(this.el).select('#example-chart1 svg')
            .datum(this.bluered)
            .transition().duration(500)
            .call(this.chart);
        d3.select(this.el).select('#example-chart2 svg')
            .datum(this.purpleteal)
            .transition().duration(500)
            .call(this.chart);
        d3.select(this.el).select('#example-chart3 svg')
            .datum(this.yellowgray)
            .transition().duration(500)
            .call(this.chart);

        this.chart_loaded = _.isFunction(this.chart.update);
    }
})

With this code in place, you can run a Quick Repair & Rebuild and navigate to the following URL.

http://{your sugar server}/#Accounts/layout/multi-chart-layout

You should then see each of your charts in the layout:

multi-chart-view.png

Any Number of Charts from an API Call

While we have shown how to get basic charts working, what we have done so far does not reflect the real world implementations very well.  You do not always know how many charts you need to display and you pretty much never are working with static data. Typically you will make a HTTP call to some REST API to provide data to display. Depending on the nature of that data you may want to display a variable number of charts.

In this example, we provide an example that makes a call to an API which returns a variable amount of data, therefore requiring a variable number of charts, so we can explore how to handle this.

Set up an API to call

For our example we will create a toy API for us to call.  It is simplistic in that it returns data in the exact format the LineChart expects.  In the real world you might have to transform the data in JavaScript a bit first. However, this will illustrate the important principles involved.

This API will be invoked using a simple HTTP GET to

http://{your sugar server}/rest/v10/Accounts/get_line_chart_data

which will return a randomly generated set of data for multiple charts with multiple lines. You can play with the numbers at the top of the method to give you different results.

custom/modules/Accounts/clients/base/api/chartInfoAPI.php

<?php if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point'); }
require_once 'clients/base/api/ModuleApi.php';
class chartInfoAPI extends ModuleApi {
public function registerApiRest() {
return array(
'webhook' => array(
                'reqType' => 'GET',
                'path' => array('Accounts', 'get_line_chart_data'),
                'pathVars' => array('module', 'action'),
                'method' => 'getLineChartInfo',
                'shortHelp' => 'This endpoint retrieves line chart information in the proper format',
                'longHelp' => '',
            ),
        );
    }

    /**
     * This method generates data for a line chart example and returns it in the following LineChart format:
     * array(
     *     array(
     *         'data' => array(
     *             array(
     *                 'key' => "Data set the first", // Title of this data set
     *                 'values' => array(
     *                     array(
     *                         'x' => {some number},
     *                         'y' => {some other number},
     *                     ),
     *                     ...
     *                 ),
     *                 'color' => "#ff0000", // Any color you want, of course
     *             ),
     *             ...
     *         ),
     *         'properties' => array(
     *             'title' => "Title of this chart",
     *         ),
     *     ),
     *     ...
     * )
     */
    public function getLineChartInfo($api, $args) {
        $out_data_arr = array();

        $num_charts = 3; // Total number of charts to display
        $num_lines = 2; // Number of lines per chart to display
        $num_data_points = 5; // Number of data points per line to display

        $color_map = array(
            'silver',
            'gray',
            'black',
            'red',
            'maroon',
            'yellow',
            'olive',
            'lime',
            'green',
            'aqua',
            'teal',
            'blue',
            'navy',
            'fuchsia',
            'purple',
        );

        for ($i = 0; $i < $num_charts; $i++) {             $tmp_chart_arr = array(                 'data' => array(),
                'properties' => array(
                    'title' => "Chart #" . ($i + 1),
                ),
            );
            for ($j = 0; $j < $num_lines; $j++) {                 // Pick a color for the line                 $line_color = $color_map[rand(0, count($color_map) - 1)];                 $tmp_line_arr = array(                     'key' => ucfirst($line_color) . " Data",
                    'values' => array(),
                    'color' => $line_color,
                );
                for ($k = 1; $k <= $num_data_points; $k++) {                     $tmp_data_point = array(                         'x' => $k,
                        'y' => rand(0, 10),
                    );
                    $tmp_line_arr['values'][] = $tmp_data_point;
                }
                $tmp_chart_arr['data'][] = $tmp_line_arr;
            }
            $out_data_arr[] = $tmp_chart_arr;
        }

        return $out_data_arr;
    }
}

Implement the Handlebars template

Since we have no idea how many charts we are going to need to display we cannot just set up div and svg elements ahead of time like we did for the previous example. Instead we will just have a generic div into which we’ll  add further div and svg elements dynamically within the Javascript below.

<div id="chart-section"></div>

Implement the loadData() method

Now we need to actually call our custom API. Here we call the API via Sugar’s built in app.api.call() method which takes care of access token management automatically. We take the results from the API call and assign them to the chartDataArr array for use during render. Take note of the assignment to self.total. Here it matters where this happens because it should not be set until the data is available and charts can be rendered.

({
    ...
    loadData: function() {
        var self = this;
        var url = app.api.buildURL('Accounts/get_line_chart_data');
        app.api.call('read', url, null, {
            success: function (response) {
                _.each(response, function (data, key) {
                    self.chartDataArr.push(data);
                });
                if (self.chartDataArr.length > 0) {
                    self.total = 1;
                } else {
                    self.errorMsg = "There is no chart information available";
                }

                self.render();
            },
            error: _.bind(function () {
                this.errorMsg = "Error encountered retrieving chart information";
            }, this)
        });
    },
    ...
})

Implement the renderChart() method

The renderChart() method gets a little more complex here. We iterate through the set of arrays we stored in chartDataArr in the loadData() method, each of which represents a chart to display. First we add a new div and svg element, with a unique identifier, in which to display the chart. Then we make the call to the call() method to actually display the chart to the new elements.

({
...
      renderChart: function () {
        if (!this.isChartReady()) {
            return;
        }

        var self = this;
        var id_val = "";
        var selector_str = "";
        _.each(this.chartDataArr, function (data, key) {
            id_val = 'example_chart' + key;
            selector_str = '#' + id_val + ' svg';
            $("#chart-section").append('
<div id="' + id_val + '"><svg></svg></div>
');
            d3.select(self.el).select(selector_str)
                .datum(data)
                .transition().duration(500)
                .call(self.chart);
        });

        this.chart_loaded = _.isFunction(this.chart.update);
    }
})

Here’s the JavaScript when you put it all together

({
    plugins: ['Chart'],
    className: 'api-chart-view',
    chartDataArr: [],
    total: 0,
    initialize: function (options) {
        this._super('initialize', [options]);
        this.chart = nv.models.lineChart()
            .x(function (d) {
                return d.x;
            })
            .y(function (d) {
                return d.y;
            })
            .showTitle(true)
            .tooltips(false);
    },
    loadData: function() {
        var self = this;
        var url = app.api.buildURL('Accounts/get_line_chart_data');
        app.api.call('read', url, null, {
            success: function (response) {
                _.each(response, function (data, key) {
                    self.chartDataArr.push(data);
                });
                if (self.chartDataArr.length > 0) {
                    self.total = 1;
                } else {
                    self.errorMsg = "There is no chart information available";
                }

                self.render();
            },
            error: _.bind(function () {
                this.errorMsg = "Error encountered retrieving chart information";
            }, this)
        });
    },
    renderChart: function () {
        if (!this.isChartReady()) {
            return;
        }

        var self = this;
        var id_val = "";
        var selector_str = "";
        _.each(this.chartDataArr, function (data, key) {
            id_val = 'example_chart' + key;
            selector_str = '#' + id_val + ' svg';
            $("#chart-section").append('
<div id="' + id_val + '"><svg></svg></div>
');
            d3.select(self.el).select(selector_str)
                .datum(data)
                .transition().duration(500)
                .call(self.chart);
        });

        this.chart_loaded = _.isFunction(this.chart.update);
    }
})

When everything is in place, do a Quick Repair & Rebuild and go to

http://{your sugar server}/#Accounts/layout/api-chart-layout

That should show you something like this

api-chart-view.png

Final Thoughts

The examples here are very simple. There are many chart types to choose from and many options for each chart type. Hopefully using these tutorials, the resources mentioned above, and some trial and error, you will then be able to build custom charts in your Sugar customizations.


Big things happening with SugarCRM Mobile

$
0
0

SugarCRM recently released versions 4.1.0 and 5.0.0 of SugarCRM Mobile and version 1.2.0 of the Mobile Application Configurator Service (MACS). These releases represent a significant technology evolution for our Mobile products that is worthy of a summary of the benefits to the Sugar Developer community.

Even more exciting, we are also getting tantalizingly close to general availability for the SugarCRM Mobile SDK!

Enhanced Offline mode in SugarCRM Mobile 4.1.0

Who likes waiting? Nobody! Well our previous offline storage would make the user wait after login while the app would download records so they were available offline. We did not want users waiting too long so we limited the offline storage to a modest 100 records per module. This allowed the Mobile app to be available to users quickly but not instantly.

This changed with SugarCRM Mobile 4.1.0 because we improved how offline record storage works CRM records are now (asynchronously) downloaded into offline storage behind the scenes whenever the Mobile app is in use. This allows the user to be productive with the Mobile app immediately after login even while offline storage is being loaded up. We also increased the offline storage limit to 1000+ records per module because the time taken to download thousands of records over a slow cellular network was no longer a concern.

Since we drastically increased the download size, we also added an option to download records over Wifi only. That should help anyone worried about blowing up their data plans!

New SugarCRM Mobile UX in 5.0.0!

Mobile 5.0 NewDetailView

As part of the SugarCRM Mobile 5.0.0 release, we introduced a redesigned user experience. We reduced the number of clicks needed to reach information, for example, by redesigning the record details view to show more information on the initial screen in a Details tab. We also improved navigation and re-ordered tabs and elements in the user interface based upon customer feedback.

From administrative perspective, we have added AppConfig standard which makes it easier to manage the app using Enterprise Mobile Management (EMM) solutions. This included the ability to preset Sugar instance URLs to allow for easier deployment of the Mobile app to end users.

Mobile 5.0.0 also simplified the login page by moving configuration settings into a separate screen and added support for Apple Touch ID among other changes.

Updates to Mobile Application Configuration Service (MACS)

MACS allows Sugar Partners and Customers to create re-branded binaries of the SugarCRM Mobile app that gives them full control over how they distribute and deploy the mobile application.

Every time SugarCRM releases a new version of our Mobile app, it is also added to MACS. We also recently added the ability to cancel a build as well as delete old ones. This will help keep your list of built Apps pristine as newer versions of Mobile app are added to MACS.

More details on Mobile SDK!

We have talked about the Mobile SDK before and I am excited to say that we are making great progress. The SugarCRM Mobile SDK is currently in a limited beta with a few key Sugar Partners and customers and we look forward to making it generally available very soon.

The first generally available release of the Mobile SDK will be based on SugarCRM Mobile 5.0.0.

MobileSDKPortal.png

Sneak peak of the Mobile SDK portal

Some resources you can expect when the Mobile SDK is released is a Getting Started Guide, detailed Mobile API documentation, a Mobile developer community, and eventually a Mobile Styleguide. You will be able to reach all these resources from the same Mobile Tools portal that you use for Mobile Application Configuration Service (MACS).

The Mobile Tools portal is available to Sugar Partners and Sugar Enterprise customers. So if that means YOU then watch that space! This will be where you will get your hands on the SDK.


Use of prepared statements in Sugar 7.9

$
0
0

What are Prepared Statements?

Prepared Statements, also known as parameterized statements, is a database feature that allows the same or similar queries to be executed with more efficiency and greater security. It has also been a common Sugar platform feature request for some time.

A prepared statement looks something like this:

SELECT * FROM table WHERE id = ?

As you can see, a prepared statement is basically a SQL template that allows you to identify parameters that can be bound later. The database engine can parse, optimize, and cache this statement without executing it.

This reduces the overhead associated with parsing complex queries that are used frequently by applications like Sugar. For example, you can imagine that List View queries would benefit from prepared statements since they are often complex and executed each time a list is displayed, searched, filtered, or paginated. With prepared statements, the database will do less work each time one of these actions is repeated.

Another key strength of prepared statements is that it helps prevent SQL injection vulnerabilities. Parameters are expected to be constant values (strings, integers, etc.) and not SQL. So if an attacker managed to bind raw SQL as a parameter to a prepared statement it will not be interpreted as SQL. Attack defeated!

Database Administrators (DBAs) like prepared statements too because it tends to give them more control over how these queries are executed and cached by backend database engines. In the hands of a good DBA, prepared statements allows an application to be better tuned for high performance.

Changes in Sugar 7.9.0

Sugar 7.9.0 will be available in the next few weeks at the time of this writing. In Sugar 7.9.0, most queries that Sugar executes are now parameterized. Some new Query APIs have been added to support prepared statements as well.

The most important change is that we have adopted parts of Doctrine’s Database Abstraction Layer, especially the QueryBuilder class, for working with prepared statements.

db

DBManager

The DBManager class will use Doctrine QueryBuilder for building INSERT and UPDATE queries.

SugarQuery

The SugarQuery class will use Doctrine QueryBuilder for building SELECT queries.

SugarBean

The SugarBean class will continue to use DBManager class for saving all fields.

Things to watch out for in Sugar 7.9.0

There are a few things that Sugar Developers need to know as they prepare their code customizations for Sugar 7.9.0.

DBManager and SugarQuery API changes

As documented in the Sugar 7.7.1 Release Notes, many DBManager APIs and some SugarQuery APIs were deprecated as part of our plans to add prepared statement support to Sugar. These deprecated APIs have been removed in Sugar 7.9.0. If you haven’t already, you must migrate your custom code that uses these APIs to alternative APIs prior to Sugar 7.9.0 upgrades.

The following deprecated PHP classes and methods have been removed in this Sugar release.

SugarQuery_Builder_Delete
SugarQuery_Builder_Insert
SugarQuery_Builder_Update
SugarQuery_Builder_Literal
SugarQuery_Compiler
SugarQuery::joinRaw()
SugarQuery::compileSql()
DBManager::delete()
DBManager::retrieve()
DBManager::insertSQL()
DBManager::updateSQL()
DBManager::deleteSQL()
DBManager::retrieveSQL()
DBManager::preparedQuery()
DBManager::pQuery()
DBManager::prepareQuery()
DBManager::prepareTypeData()
DBManager::prepareStatement()

The $execute parameter on DBManager::insertParams() and DBManager::updateParams() has also been removed.

SugarQuery::compileSql()

SugarQuery::compileSql() was commonly used to debug the raw SQL built using SugarQuery APIs. SugarQuery::compileSql() was deprecated in Sugar 7.7.x and is no longer supported in Sugar 7.9.0. Because SugarQuery now uses prepared statements, it no longer compiles a complete SQL statement by itself. Remember that parameterized queries are assembled and executed within the DB engine. So you will need to separately fetch the parameterized SQL and the parameters. From this information, you can determine how the query will be executed.

For example,

$compiled = $query->compile(); // create compiled prepared statement
$compiled->getSQL(); // fetches parameterized SQL
$compiled->getParameters(); // fetches parameters

The $compiled->getSQL() will return SQL with placeholders instead of parameters:

SELECT * FROM users WHERE id=?

The $compiled->getParameters() will return an array of parameters:

['ec2f4abb-b6b9-3d49-0382-5730e67c116c']

How to use Prepared Statements in Sugar 7.9.0

If you already use SugarQuery or SugarBean then congratulations! Your code customizations will automatically benefit from prepared statements. We have made changes to both of these interfaces to ensure that they use prepared statements. The underlying behavior is transparent to custom code.

However, if you need more finesse with your queries then we will explore how to use prepared statements using new DBManager and Doctrine QueryBuilder APIs.

SELECT queries

For simple static SELECT queries, the changes are pretty straight forward.

Before:

$query = 'SELECT * FROM table WHERE id = ' . $this->db->quoted($id);
$db->query($query);

After:

$query = 'SELECT * FROM table WHERE id = ?';
$conn = $db->getConnection();
$stmt = $conn->executeQuery($query, array($id));

In the case that query logic is variable or conditionally built then it makes sense to use Doctrine QueryBuilder directly.

Before:

$query = 'SELECT * FROM table';
if ($status !== null) {
    $query .= ' WHERE status = ' . $this->db->quoted($status);
}
$db->query($query);

After:

$builder = $db->getConnection()->createQueryBuilder();
$builder->select('*')->from('table');
if ($status !== null) {
    $builder->where(
        'status = ' . $builder->createPositionalParameter($status))
    );
}
$builder->execute();

INSERT queries

INSERT queries can be easily performed using DBManager class.

Before:

$query = 'INSERT INTO table (foo, bar) VALUES ("foo", "bar")';
$db->query($query);

After:

$fieldDefs = $GLOBALS['dictionary']['table']['fields'];
$db->insertParams('table', $fieldDefs, array(
    'foo' => 'foo',
    'bar' => 'bar',
));

UPDATE queries

When updating records with known IDs or a set of records with simple filtering criteria, then DBManager can be used:

Before:

$query = 'UPDATE table SET foo = "bar" WHERE id = ' . $db->quoted($id);
$db->query($query);

After:

$fieldDefs = $GLOBALS['dictionary']['table']['fields'];
$db->updateParams('table', $fieldDefs, array(
 'foo' => 'bar',
), array(
 'id' => $id,
), );

For more complex criteria or when column values contain expressions or references to other fields in the table then Doctrine QueryBuilder can be used.

Before:

$query = 'UPDATE table SET foo = "bar" WHERE foo = "foo" OR foo IS NULL';
$db->execute($query);

After:

$query = 'UPDATE table SET foo = ? WHERE foo = ? OR foo IS NULL';
$conn = $db->getConnection();
$stmt = $conn->executeQuery($query, array('bar', 'foo'));

Fill out the UnCon 2017 Survey!

$
0
0

Where will you be on September 26th at 1:00pm Pacific Time? If you are a Sugar Developer then you better be in San Francisco, the City By the Bay, at the Hilton Union Square!

The annual UnCon is the top rated event at SugarCon because of the in-depth technical content, knowledgable speakers, and our enthusiastic developer and admin community.  This is a unique event where you get an incredible level of access to the development teams and decision makers that build our products and platforms.

Essential to the success of UnCon is input from the Sugar Developer and Administrator community! We are just over 90 days away so planning is well underway. Now is the time to provide your input on the content presented at UnCon.

Please fill out this very short on question survey. This will help us focus the event on the types of content that you want to see.

UnCon 2017 Theme Survey

 

Fill out the UnCon 2017 Theme Survey!

 

UnCon will be bigger than ever

We will be growing UnCon massively this year with a longer running time, more activities, and more space than ever before. This will allow you more time with the SugarCRM experts in Product Engineering, Product Manage

ment, and other technical staff from across the company.

Never attended before?

Check out the UnCon archive where you can find all the available content from the last two years. Some popular past presentations and workshops have been on topics ranging from introductory sessions on debugging Sugar, Elastic

search, Sugar CLI, and Mobile SDK to deep dives into Sidecar and Advanced Workflow. All those topics and more can be found in the archive.

Other ways to get involved

  1. RSVP to the event in the UnCon community to let everyone know you will be there. Separate registration to SugarCon is required to attend.
  2. Share additional feedback in the UnCon community as discussions or ideas.
  3. Stay tuned for more updates! Follow the UnCon space in the Sugar Community.

follow_in_jive.png

See you in San Francisco!

header


Customer improves Sugar On-Site performance by 2x using CloudFlare CDN

$
0
0

Note from the SugarCRM team

This post represents an individual customer’s experience with using CloudFlare CDN. The results are pretty exciting! But here is a word of warning.

SugarCRM does not officially support use of a Content Delivery Networks (CDNs). CDNs alter the behavior and content of HTTP requests which can cause bugs and prevent the propagation of administrative (ex. Studio) and code changes. The author has provided a process that works for them but may not work for everyone.

Sugar Support will not help you with CDN configuration. If you run into problems with Sugar using a CDN that you cannot resolve on your own then you should discontinue use of the CDN.

The following post was written by Sugar Customer Mark Anderson. It was originally posted on his personal blog and is reproduced here in a lightly edited form.

The CloudFlare content delivery network (CDN) is an inexpensive but powerful way to optimize the performance of a Sugar on-premise deployment. This post looks at how a free CloudFlare account could double the performance of a Sugar On-Site instance.

The experiment started with a review of the performance of a Sugar deployment, using GTMetrix to establish baseline performance. The ultimate goal was to improve performance. We wanted to explore all options since our initial assumption was that better performance would require a costly server upgrade.

Step 1: Establish Baseline Performance

GTMetrix is a fantastic online tool for measuring the performance of a website. It lets you test a page from a number of servers spread across the world (Dallas, Hong Kong, London, Mumbai, Sydney, Sao Paulo and Vancouver) in any one of Firefox, Chrome Desktop or Chrome Mobile. It then presents you with both PageSpeed and YSlow scores and offers suggestions on how a page can be optimized.

optimize-SugarCRM-cloudflare-cdn-01-gtmetrix.jpg

Setting up the test in GTMetrix is straightforward but a little more complex than testing a simple web page that is accessible by the public. The first step is to click the small gray “Analysis Options” button, just underneath the larger blue “Analyze” button on the right hand side.

The URL is the page you want to test, in this case the list view of the Sugar Accounts module. The location setting is important, different locations will perform very differently so you likely want to test from a few (but make sure you only compare results from the same location).

Unless your Sugar users are working with a particular browser the most important thing is to perform all tests using the same browser (the “Using” setting) so you can compare results.

For this test it is also essential that you enter a valid username and password so that GTMetrix can log into the account.

optimize-SugarCRM-cloudflare-cdn-02-gtmetrix-test-results.jpg

So… mixed results:

Bad News

  • The tests where it does poorly (gzip compression, minifying HTML, etc) it does really poorly.

Good News

  • Sugar gets great scores on most of the tests!
  • It is likely that CloudFlare (if it works) can make huge performance improvements.

More tests of other Sugar pages from different locations show similar results.

optimize-SugarCRM-cloudflare-cdn-03-more-gtmetrix-performance-tests.jpg

We have had great results using CloudFlare with our public facing sites but had never considered using it with our Sugar instance. The test results suggest a CDN like CloudFlare could greatly improve Sugar performance, but there was no supporting information available on the internet and so we had no expectation it would work.

Step 2: Enable CloudFlare

Our next step was to enable CloudFlare for the Sugar On-Site instance. In our case this was very easy because Sugar is hosted on a sub-domain of a domain that is already set up in CloudFlare. We just have to turn CloudFlare on in the DNS section of the CloudFlare admin page for that domain.

When a subdomain is not enabled in CloudFlare it looks like this:

optimize-SugarCRM-cloudflare-cdn-04-cdn_disabled.jpg

Just click on the cloud icon and it will change color like this…

optimize-SugarCRM-cloudflare-cdn-05-enable-cdn.jpg

… indicating that CloudFlare is now providing both DNS and HTTP proxy for that sub-domain.

If you are setting up CloudFlare for the first time for a new domain it will be slightly more work but manageable and absolutely worth it for the performance gains.

Step 3: Wait…

Having made changes to the DNS settings we now needed to wait for them to propagate. This usually takes less than 10 minutes.

Step 4: Debug

In our case we knew that the DNS settings had propagated because our Sugar instance stopped working – no Sugar pages were available – and we were worried that using CloudFlare was not working.

Fortunately an excellent technician caught the problem! This particular Sugar instance used a non-standard port for HTTP traffic. This port was one that CloudFlare does not support.

It turns out CloudFlare can only proxy traffic going over specific ports. The supported ports are listed below.

HTTP HTTPS
80 443
8080 2053
8880 2083
2052 2087
2082 2096
2086 8443
2095

The fix was relatively simple; we just changed the Sugar instance to use a supported port.

It is not likely you will encounter this issue but if you do lose access to your Sugar instance after enabling CloudFlare then this is a good place to start looking – just make sure that you are using the supported ports.

After making the change… success! We were able to again access Sugar but through the CloudFlare content delivery network.

Step 5: CloudFlare Settings

One of the great things about CloudFlare is that it takes very little tweaking to get great results. But there are a few settings that you should confirm.

Start in the Speed settings of CloudFlare admin for the domain:

optimize-SugarCRM-cloudflare-cdn-06-minify-settings.jpg

Remember that a big part of the problem in the initial GTMetrix report was minification, so make sure all three of these are checked.

Farther down the Speed page are the settings for the Rocket Loader feature. They should look like this:

optimize-SugarCRM-cloudflare-cdn-07-rocket-loader-settings.jpg

Next up are the caching settings (choose “Caching” from the icons arranged across the top of the CloudFlare dashboard…

optimize-SugarCRM-cloudflare-cdn-08-caching-settings.jpg

The Caching Level should be set to standard but the really important setting here is Browser Cache Expiration. Try setting it for 1 day.

That’s it for CloudFlare settings.

Step 6: Initial Testing

The next step was to use Sugar, clicking to navigate between modules, to test the application and make sure that everything worked as expected.

Some improvements, like magnification and gzip, were experienced right away but there was some mystery about how CloudFlare actually builds the cache. We assumed that using the CRM application like this encouraged CloudFlare to fill up the cache. We also used a proxy service to browse the instance from other locations to help propagate the changes to other CloudFlare servers.

This may not have been needed but we were only testing and very little effort was required to test from different geographical locations.

Step 7: Performance Testing

Now the interesting part – we looked to see what (if any) performance gains we had accomplished. We did all the same tests again in GTMetrix and compared the results. The old test, before CloudFlare, appear at the top of the list and the newer results appear at the bottom.

optimize-SugarCRM-cloudflare-cdn-09-performance-improvements.jpg

The improvements are remarkable! PageSpeed scores go from straight F’s at 26% to straight A’s at 99%. YSlow scores improve from straight B’s at 85% to straight A’s at 98%.

The times are even more interesting than the scores:

Test Before CloudFlare After CloudFlare Time saved (s) Savings (%) Increase
Home (CA) 2.7s 1.3s 1.4s 52% 2.08x
Home (UK) 2.5s 1.6s 0.9s 36% 1.56x
Home (US) 2.5s 1.1s 1.4s 56% 2.27x
Account List (AU) 5.0s 3.2s 1.8s 36% 1.56x
Account List (UK) 4.2s 1.6s 2.6s 62% 2.63x
Account List (US) 1.7s 1.1s 0.6s 35% 1.55x
Account Record (AU) 5.4s 3.2s 2.2s 41% 1.69x
Account Record (CA) 2.5s 1.3s 1.2s 48% 1.92x
Account Record (UK) 4.2s 3.0s 1.2s 29% 1.40x
Account Record (US) 1.6s 1.2s 0.4s 25% 1.33x
Total Time 32.3s 18.6s 13.7s
Averages 3.23s 1.86s 1.37s 42% 1.74x


Highlights:

  • Average page load time was reduced by almost 50% (over 1 second)
  • Speed increased an average of 1.74x after introducing CloudFlare CDN
  • Best gain was a saving of 2.6 seconds for a 2.63x increase in speed
  • Smallest gain was still just under half a second for 25% savings

It is also worth using a great feature in GTMetrix that lets you compare two or more reports. Here it is used to compare the before and after performance of the Sugar home page from Dallas TX. This is likely the most relevant test in our case – most users are in the US and this is the first page they load each day.

optimize-SugarCRM-cloudflare-cdn-10-performance-comparison.jpg

Propagating Sugar changes to the CDN

A variety of changes on the server side can require the CDN to be purged to ensure changes are propagated to clients. For example, most changes done via the Administration panel like Sugar Studio or the deployment of code customizations like a new Sugar Dashlet or integration package will require caches to be purged.

We generally do any work in a development instance on a subdomain that does not use the the CDN feature in CloudFlare. This can be easily configured in the CloudFlare DNS settings, you just click the orange cloud for the A record associated with the specific sub-domain and when it turns grey then that indicates the CDN is disabled.

When making changes to the production instance, we just flip CloudFlare into development mode (in Quick Actions on Overview panel), which turns off the CDN and any caching temporarily. We can then deploy our changes to the Sugar application. The CDN remains off until you manually turn it back on or two hours pass. Either way caches are rebuilt when the CDN comes back on.

If we expect development work to take any more than thirty minutes then we completely disable the CDN for the production subdomain until we are ready.

If there are any lingering doubts, then we go to Caching > Purge > Purge Everything.

Conclusions

There were significant improvements in every single test and overall performance nearly doubled. This is a great result especially when the minimal costs are considered.

What were the costs? Making the change, debugging the port issue and all of the testing and reporting took us under three hours. In this case a CloudFlare Pro plan ($20/mo) was already in place for the domain, so there was absolutely no additional cost incurred there.

If you don’t have CloudFlare already, then the free CloudFlare starter plan could provide the same benefit.

All in all this was a great investment for us, we significantly improved the speed and performance of our Sugar on-site instance at very little cost.


Upcoming Sugar Developer classes by Sugar University

$
0
0

You may have noticed that we made changes to Sugar University over the past few months. We introduced new online role based learning pathways for Administrators, Developers, and Solution Architects. This will help you locate the training you need to get your job done and hone the skills you need to be certified. All the online learning pathways are free!

We have also added some classes to the calendar that Sugar Developers should definitely check out!

New Virtual Classes

We have introduced a new form of training called a Virtual Class. This is a short form of live training on specific topics that can last anywhere from 2 to 4 hours. The cost for these shorter forms of classes have been adjusted accordingly.

Most of the classes are focused on Sugar Administrator topics today like Sugar Studio, Sugar Logic, Record & Module Level Security, and Advanced Workflow. A firm understanding of Sugar Administration capabilities is an important skill for a Sugar Developer and I think these classes are an easy and cost effective way to refresh and advance these skills.

Upcoming Live Sugar Developer Training

Make sure you take advantage of these multi-day live training opportunities for Sugar Developers at any level. These classes are being run the week of August 14th at SugarCRM headquarters in Cupertino, CA.

Get trained and certified at SugarCon!

If you are coming to SugarCon then you should not overlook the opportunity to get trained and certified!

When you register for SugarCon, make sure to include the Training option. We will be providing special 1-day versions of the Sugar Development Essentials and Advanced classes on Monday, September 25th at the Hilton Union Square in San Francisco, CA.

Getting trained on Monday will ensure they are primed and ready to get hands on with SugarCRM Engineers and fellow Sugar Developers at UnCon on Tuesday and Wednesday! It is also a great way to help practice for Sugar Developer and Solution Architect certification exams. Exams are offered for free on Monday afternoon and Thursday morning of SugarCon. See the full SugarCon agenda for more details.

I hope to see you there!

 

 

 

 



Sign up for Sugar Developer News!

$
0
0

Want to make sure you are included in upcoming Developer Webinars, Newsletters, and other developer updates straight from SugarCRM? Don’t miss out. Sign up to get Sugar Developer News today!


Triggering Advanced Workflow processes in PHP customizations

$
0
0

An Advanced Workflow process can only be triggered once per PHP process or HTTP request. This is intended to prevent Sugar Administrators from defining infinitely looping processes. (A real catastrophe!) But what does this mean for PHP customizations?

Assume that you have an Advanced Workflow process enabled for the Contacts module that performs an update on this Contact each time it is saved. If you have an Accounts after_save logic hook that performs an update on each related Contact SugarBean then the process will only run against the first related Contact. Any other related Contact that gets saved during your logic hook execution will not have a process run.

This affects not just logic hooks but any other class of PHP customization such as custom API endpoints or jobs.

Workaround

If you really need to run that process more than once in the same request, here is a workaround:

use Sugarcrm\Sugarcrm\ProcessManager\Registry;
...
Registry\Registry::getInstance()->drop('triggered_starts');

Calling this method will clear the internal Advanced Workflow registry that keeps track of the triggered process starts. After calling this method, the same process can then be triggered again inside the same PHP process or HTTP request.

Careful use of this method can make sure that PHP customizations play nicely with processes defined in Advanced Workflow.


Using e-mail fields correctly in Sugar

$
0
0

Here is an important message from David Wheeler, a long time Software Engineer and Architect at SugarCRM, about using e-mail fields correctly.

E-mail handling is core to CRM software. Almost everyone we know uses multiple e-mail addresses every single day for both personal or work purposes. So it goes without saying that managing a person’s multiple e-mail addresses correctly is essential in your Sugar customizations and integrations.

History of Sugar E-Mail fields

Several years ago, Sugar changed from using email# named text fields (like email1, email2, etc.) to using an e-mail relationship. This was done to better handle multiple e-mails, multiple relationships, and e-mail attributes like opt in or invalid.

However, use of the email1 field remains particularly persistent. We observe many examples of custom code (and some core code) that still use the old email# fields. This is probably because it is convenient to use the email1 field like a regular text field.

But this is out of date, inaccurate, deprecated, and subject to removal in upcoming Sugar releases.

Below we will describe the proper method for using e-mail fields within Sugar customizations and integrations.

Sugar Metadata

You should reference the “email” field instead of “email#”.

For record views, this will load a “email” type field widget with all related e-mail addresses included.

Email field example

Example of e-mail field in Record view

For list views, instead of all e-mail addresses only the primary e-mail address will be displayed.

e-mail list view screenshot

Example of e-mail field in List view

Sugar PHP code

Instead of

$bean->email#

use

$bean->emailAddress->addresses

which references an array of e-mail addresses.

To determine the primary e-mail, you can iterate over the addresses array to find where the primary_address attribute is true.

foreach ($bean->emailAddresses->addresses as $address) {
    if ($address->primary_address == true) {
        // Found primary e-mail
    }
}

v10 REST API

When selecting a record’s email field in a GET request, it will return a JSON array of all associated e-mail addresses.

When using a PUT request to update an e-mail address on a record, provide the complete e-mail address array.

For example,

"email": [
    {
        "email_address": "judith.banks@yahoo.com",
        "primary_address": true,
        "reply_to_address": false,
        "invalid_email": false,
        "opt_out": false
    },
    {
        "email_address": "jbanks@hotmail.com",
        "primary_address": false,
        "reply_to_address": false,
        "invalid_email": false,
        "opt_out": true
    }
],

If you leave off an e-mail address in a PUT request then this will be removed during the update to the record.

Sidecar code

For Sidecar code, you should not reference fields like email1, etc, when working with Beans or other models. You should be referencing the email field instead.

model.get("email1") --> model.get("email")

This will return a JavaScript array in the same format as returned by the REST API (above). This array of e-mails is typically iterated through for display in custom Handlebars templates.

PDF Templates

This change also applies to PDF templates. Here is the correct way to reference the primary e-mail address from a PDF template.

{$field.email1} --> {$fields.email_addresses_primary.email_address}

Other locations

You may still see email1 field referenced in e-mail templates used with Advanced Workflow or Campaigns module. Don’t count on those sticking around. Please, use the email field instead.

 


Hello from SugarCRM’s newest Developer Advocate!

$
0
0

Hey there, Sugar Developers!

I’m SugarCRM’s newest Developer Advocate, and I wanted to take a moment to introduce myself.  My name is Lauren Schaefer. Since getting my bachelor’s and master’s degrees in Computer Science at North Carolina State (go pack!), I’ve been slowly migrating north over the years, and I currently live in Pennsylvania.

I love developing web interfaces in JavaScript. That moment when you finally get an interface pixel perfect after tinkering for hours is so rewarding.

I believe that technology should be easy to use, and I love helping people get started with technology that is new to them. I’m excited about making the process of becoming a Sugar developer even easier as well as introducing the community to the latest and greatest features and customizations as they become available.

One important part of my job is listening to what you, members of the Sugar developer community, have to say and bringing that feedback back to the engineering team. I’m looking forward to chatting with you in the Sugar Developer Community as well as in person at events like SugarCon.

Some facts about me…  I’m obsessed with the color pink. I’m a sucker for reality tv shows like Dancing with the Stars as well as PBS dramas like Victoria. I love to travel and have visited all seven continents (Antarctica is truly amazing!).

I’m really excited about meeting so many of you at SugarCon in a little over a month! Here’s why:

See you in September!

Lauren


The perfect way for beginners to learn about Sugar

$
0
0

Are you learning about Sugar for the first time?  Or maybe, it has been a while, and you want to see how the Sugar platform has evolved since the Community Edition days?

Watch the video below to learn why SugarCon and UnCon is the perfect way to get started with building on and integrating with Sugar.


Viewing all 93 articles
Browse latest View live