How to add cc emails to new email

Hello,

  I need to find a way to include the people that are CC'ed in an email to appear when they email form the record view.

They then click the plus btn to generate a new email and this div appears to do that.

How would I start the process of automatically adding the CC'ed peoples to this view?

So to clarify I want the people that were CC'ed before in the case's history to automatically appear when clicking the plus btn and the user won't need to do it manually then.

Best Regards

James Palmisano

  • I had a similar requirement, the problem is that when you clcick the + on the Email subpanel on Cases you don't know WHICH email you are replying to.

    I solved the issue by creating a "reply all" option in rowactions ( on each line of the Email Subpanel ), by selecting that reply you know which email you are replying to.

    It was not easy.

    In

    custom/modules/Emails/clients/base/views/<your cases subpanel view>/<your cases subpanel view>.php

    I added three custom email actions: reply, reply all and forward (opendetails ia a custom view of the email thread)

    They behave as you would expect in an email client.

      'rowactions' => array (
        'actions' => array (
          array(
            'type' => 'rowaction',
            'css_class' => 'btn',
            'event' => 'list:customemailreply:fire',
            'tooltip' => 'Reply to email',
            'icon' => 'fa-reply',
            'acl_action' => 'create',
          ),
          array(
            'type' => 'rowaction',
            'event' => 'list:customemailreplyall:fire',
            'label'=>'LBL_REPLY_ALL',
            'tooltip' => 'Reply All',
            'icon' => 'fa-mail-reply-all',
            'acl_action' => 'create',
          ),
          array(
            'type' => 'rowaction',
            'event' => 'list:customemailforward:fire',
            'label'=>'LBL_FORWARD',
            'tooltip' => 'forward',
            'icon' => 'fa-mail-forward',
            'acl_action' => 'create',
          ),
    
          array(
            'type' => 'rowaction',
            'event' => 'list:opendetails:fire',
            'label' => 'LBL_OPEN_EMAIL_DETAILS',
            'tooltip' => 'Read email',
            'acl_action' => 'create',
          ),
        ),
    

     

    In the controller for the same view:

    custom/modules/Emails/clients/base/views/<your cases subpanel view>/<your cases subpanel view>.js

    I defined the actions to be taken when each of the events is fired.

    ({
      extendsFrom:'SubpanelListView',
      initialize: function(options){
        this._super('initialize', [options]);
        this.context.on('list:customemailreply:fire',this.customReplyClicked);
        this.context.on('list:customemailreplyall:fire',this.customReplyAllClicked);
        this.context.on('list:customemailforward:fire',this.customForwardClicked);
        this.context.on('list:opendetails:fire',this.opendetailsClicked);
      },
      opendetailsClicked: function(emailModel){
        //show progress message
        app.alert.show('loading-details', {
          level: 'info',
          messages: 'Loading details...',
          autoClose: false
        });
        var url = app.api.buildURL('getCustomEmailDetails/'+emailModel.get('id'));
        app.api.call('read', url, null, {
          success: _.bind(function(data) {
            console.log(data);
            app.alert.dismiss('loading-details');
            app.drawer.open({
              layout:'custom-email-detail',
              context:{
                emailDetails: data,
              }
            });
          })
        });
      },
      customForwardClicked: function(emailModel){
        var caseModel = this.get('parentModel'),
            caseId = caseModel.get('id'),
            emailType = emailModel.get('status');
        if(emailType == 'draft'){
          emailModel.set('isNotEmpty', true);
          //editing a draft
          //open the drawer with the email  as was when last saved
          app.drawer.open({
            layout:'compose',
            context:{
              create: false,
              model: emailModel,
              module: 'Emails',
            }
          },this);
        }else{
          var emailId = emailModel.get('id'),
              messageType = 'forward',//internal, reply, replyAll, forward or new
              url = app.api.buildURL('Emails/getCustomCompose/'+messageType+'/'+caseId+'/'+emailId);
          //new forwardpre-populate some information
          App.api.call('GET', url, '',{
            success: _.bind(function(o){
              app.drawer.open({
                layout:'compose',
                context:{
                  create: true,
                  prepopulate: o ,
                  module:'Emails',
                }
              });
            }, this),
            error: _.bind(function(o){
              console.log(o);
            }, this),
          });
        }
      },
      customReplyAllClicked: function(emailModel){
        var caseModel = this.get('parentModel'),
            caseId = caseModel.get('id'),
            emailType = emailModel.get('status');
        if(emailType == 'draft'){
          emailModel.set('isNotEmpty', true);
          //editing a draft
          //open the drawer with the email  as was when last saved
          app.drawer.open({
            layout:'compose',
            context:{
              create: false,
              model: emailModel,
              module: 'Emails',
            }
          },this);
        }else{
          var emailId = emailModel.get('id'),
              messageType = 'replyAll',//internal, reply, replyAll or new
              url = app.api.buildURL('Emails/getCustomCompose/'+messageType+'/'+caseId+'/'+emailId);
          //new reply pre-populate some information
          App.api.call('GET', url, '',{
            success: _.bind(function(o){
              app.drawer.open({
                layout:'compose',
                context:{
                  create: true,
                  prepopulate: o ,
                  module:'Emails',
                }
              });
            }, this),
            error: _.bind(function(o){
              console.log(o);
            }, this),
          });
        }
      },
      customReplyClicked: function(emailModel){
        var caseModel = this.get('parentModel'),
            caseId = caseModel.get('id'),
            emailType = emailModel.get('status');
        if(emailType == 'draft'){
          emailModel.set('isNotEmpty', true);
          //editing a draft
          //open the drawer with the email  as was when last saved
          app.drawer.open({
            layout:'compose',
            context:{
              create: false,
              model: emailModel,
              module: 'Emails',
            }
          },this);
        }else{
          var emailId = emailModel.get('id'),
              messageType = 'reply',//internal, reply, replyAll or new
              url = app.api.buildURL('Emails/getCustomCompose/'+messageType+'/'+caseId+'/'+emailId);
          //new reply pre-populate some information
          App.api.call('GET', url, '',{
            success: _.bind(function(o){
              app.drawer.open({
                layout:'compose',
                context:{
                  create: true,
                  prepopulate: o ,
                  module:'Emails',
                }
              });
            }, this),
            error: _.bind(function(o){
              console.log(o);
            }, this),
          });
        }
      }
    });
    

    As you can see the actions use a CustomCompose, which is the custom API that takes care of setting the necessary variables for the drawer to prepopulate with the necessary information

    in custom/modules/Emails/clients/base/api/getCustomComposeApi.php

    (I have some additional custom compose here for panel-top emails actions such as Internal Compose, and Custom Compose which are panel-top actions that are not specific to an email).

    <?php
    class getCustomComposeApi extends SugarApi
    {
      public function registerApiRest(){
        return array(
          'getCustomCompose' => array(
            // What type of HTTP request to match against, we support GET/PUT/POST/DELETE
            'reqType' => 'GET',
            // This is the path you are hoping to match, it also accepts wildcards of ? and <module>
            'path' => array('Emails', 'getCustomCompose', '?','?','?'),
            // These take elements from the path and use them to populate $args
            'pathVars' => array('','','type','caseId', 'emailId'),
            // This is the method name in this class that the url maps to
            'method' => 'getCustomCompose',
            // The shortHelp is vital, without it you will not see your endpoint in the /help
            'shortHelp' => 'Generates options for Email Reply button on subpanel in cases module',
            // The longHelp points to an HTML file and will be there on /help for people to expand and show
            'longHelp' => '',
          ),
        );
      }
    
      /**
       * Generate the Custom compose data package consumed by the quick compose screens.
       *
       * @param Array $data
       * @param $data['email_id'] email The BeanID for the Email we are replying to
       * @param $data['parent_id'] is assumed to be a Case ID
       * @param $type internal = send to Engineers, new = send to user, use case data likely no email to reply to, reply = copy original email .
       */
      function getCustomCompose($api, $args){
        global $current_user, $app_list_strings;
        $data = array();
        $data['email_id']= !empty($args['emailId'])?$args['emailId']:'';
        $data['parent_id']=$args['caseId'];
        $type = $args['type'];
        $toAddresses = array();
        $ccAddresses = array();
        //need to retrieve the full case bean, not all the bean values are pulled in the "bean"
        $case = BeanFactory::retrieveBean('Cases',$data['parent_id']);
        $parent_id = $case->id;
        $parent_type = 'Cases';
        if($type== 'new'){
          $GLOBALS['log']->debug("CustomCompose:: New");
          //NEW EMAIL to Customers
          //send the email TO all contacts on the case, eliminate duplicates
          //default in the from address, this is needed especially for cases from forms that don't have
          //any matching contacts
          $rel = 'contacts';
          $case->load_relationship($rel);
          foreach($case->$rel->getBeans() as $contact){
            $sea = BeanFactory::newBean('EmailAddresses');
            $email_addr = $sea->getPrimaryAddress($contact);
            $toAddresses[]=array(
              'email'=>$email_addr,
              'module'=> 'Contacts',
              'name'=>$contact->full_name
            );
          }
          //add the address from the case if valid
          //sugar should take care not to send duplicates 
          if(isset($case->case_from_addr_c) && filter_var($case->case_from_addr_c, FILTER_VALIDATE_EMAIL)){
            $toAddresses[]=array(
              'email'=>$case->case_from_addr_c,
              'module'=> '',
              'name'=>$case->case_from_addr_c
            );
          }
          //set up the BODY of the email as a copy of the case description
          $preBodyHTML = "<div><br><br><br> <hr></div>" . "<i><small>On: " . $case->date_entered . " " . $case->case_from_addr_c . " submitted the following Case:</small></i><br />";
          $order   = array("\r\n", "\n", "\r");
          $replace = '<br />';
          //default the body to the description of the case
          //in case we use this from panel-top
          $body = str_replace($order, $replace,$case->description);
          if(strpos($case->name, str_replace('%1',$case->case_number,$case->getEmailSubjectMacro()))===FALSE){
            //set the SUBJECT with the case macro valid for all compose types
            $subject=str_replace('%1', $case->case_number, $case->getEmailSubjectMacro() . " ". $case->name);
          }else{
             $subject = $case->name;
          }
          if(!preg_match('/^(re:)+/i', $subject)) {
            $subject = "RE: {$subject} ";
          }
        }elseif($type == 'internal'){
          $GLOBALS['log']->debug("CustomCompose:: internal");
           //INTERNAL EMAIL to Case Engineers
           //send the email TO all engineers on the case, eliminate duplicates
           $rel = 'cases_users_1';
           $case->load_relationship($rel);
           $toAddresses = array();
           foreach($case->$rel->getBeans() as $user){
             $sea = BeanFactory::newBean('EmailAddresses');
             $email_addr = $sea->getPrimaryAddress($user);
             if(!empty($email_addr)){
               $GLOBALS['log']->debug("add user to To address:" . $email_addr);
               $toAddresses[]=array(
                 'email'=>$email_addr,
                 'module'=> 'Users',
                 'name'=>$user->user_name
               );
             }
           }
           //get assigned user info
           if(!empty($case->assigned_user_id)){
             $auser = BeanFactory::retrieveBean('Users', $case->assigned_user_id);
             //add the assigned user to the To
             //the To cannot be empty, if there is no one the system will add the original customer
             $sea = BeanFactory::newBean('EmailAddresses');;
             $aemail_addr = $sea->getPrimaryAddress($auser);
             $GLOBALS['log']->debug("add user to To address:" . $aemail_addr);
             if(!empty($aemail_addr)){
               $toAddresses[]=array(
                 'email'=>$aemail_addr,
                 'module'=> 'Users',
                 'name'=>$auser->user_name
               );
             }
           }
           // add current user
           $cea = BeanFactory::newBean('EmailAddresses');;
           $cemail_addr = $cea->getPrimaryAddress($current_user);
           $GLOBALS['log']->debug("add user to To address:" . $cemail_addr);
           if(!empty($cemail_addr)){
              $toAddresses[]=array(
                'email'=>$cemail_addr,
                'module'=> 'Users',
                'name'=>$current_user->user_name
              );
           }
           if (empty($auser)) $auser = $current_user;
           //get primary contact info for body
           $contact = BeanFactory::retrieveBean('Contacts',$case->contact_id_c);
           //get Account info for body
           $account = BeanFactory::retrieveBean('Accounts',$case->account_id);
           $order   = array("\r\n", "\n", "\r");
           $replace = '<br />';
           $preBodyHTML =<<<BODY
    <pre>
    ================================================
         This is a posting from Technical Support
    ================================================
             Engineer: {$auser->first_name} {$auser->last_name}
                        speaking for...
             Customer: {$contact->full_name}
         Organization: {$account->name}
              Product: {$app_list_strings['case_product_dd'][$case->case_product_c]}
              Version: {$case->case_product_version_c}
             Platform: {$app_list_strings['product_platforms_list_DD'][$case->case_platform_c]}
        Date Received: {$case->date_entered}
    
    Technical Summary:
    
            {$case->case_summary_c}
    
          Description:
    
            {$case->description}
    ------------------------------------------------
    </pre>
    BODY;
          $body = '';
          //set the SUBJECT with the case macro valid for all compose types
          if(strpos($case->name, str_replace('%1',$case->case_number,$case->getEmailSubjectMacro()))===FALSE){
            //set the SUBJECT with the case macro valid for all compose types
            $subject=str_replace('%1', $case->case_number, $case->getEmailSubjectMacro() . " ". $case->name);
          }else{
             $subject = $case->name;
          }
          if(!preg_match('/^(re:)+/i', $subject)) {
            $subject = "RE: {$subject} ";
          }
        }elseif($type == 'reply' && !empty($data['email_id'])){
          $GLOBALS['log']->debug("CustomCompose:: as Reply to emailID " .$data['email_id']);
          //assume it's a reply message TO the original sender of the email in question
          //or a draft
          $ie = BeanFactory::retrieveBean('Emails',$data['email_id']);
          if($ie->type == 'draft'){
            //return the draft as is
            return;
          }else{
            //not a draft, composing a reply
            //set the recipient as the reply-to or from of the original message
            //unless it's from the queue
            if((!empty($ie->reply_to_email) && $ie->reply_to_email!=$case->case_queue_c) ||
               (!empty($ie->from_addr) && $ie->from_addr !=$case->case_queue_c)){
              $email_addr = !empty($ie->reply_to_email)?$ie->reply_to_email:$ie->from_addr;
            }else{
              $email_addr = $ie->to_addrs;
            }
            $GLOBALS['log']->debug('$email_addr = ' . $email_addr);
            $name = !empty($ie->reply_to_addr)?$ie->reply_to_addr:$email_addr;
            $GLOBALS['log']->debug('$name = ' . $name);
            if(!empty($email_addr)){
              $toAddresses[]=array(
                'email'=>$email_addr,
                'module'=> '',
                'name'=>$name,
              );
            }
            //include Cc'd addresses?
    
            //include Case Contacts
    /*
            $case = BeanFactory::retrieveBean('Cases',$data['parent_id']);
            $parent_id = $case->id;
            $parent_type = 'Cases';
            $rel = 'contacts';
            $case->load_relationship($rel);
            foreach($case->$rel->getBeans() as $contact){
              $sea = BeanFactory::newBean('EmailAddresses');;
              $email_addr = $sea->getPrimaryAddress($contact);
              $toAddresses[]=array(
                'email'=>$email_addr,
                'module'=> 'Contacts',
                'name'=>$contact->full_name
              );
            }
    */
            //use the BODY of the original message in the email
            $description = '';
            if(!empty($ie->description)){
              $description = $ie->description;
            }elseif(!empty($ie->description_html)){
              $description = $ie->description_html;
            }
            $GLOBALS['log']->debug('description = ' . $description);
            $preBodyHTML = " <div><br><br><br><hr></div>";
            $order   = array("\r\n", "\n", "\r");
            $replace = '<br />';
            $body = str_replace($order, $replace,$description);
            //the email should come FROM the queue, not the individual
            //we override the default in custom/modules/Emails/clients/base/api/sender/sender.js
            if(strpos($ie->name, str_replace('%1',$case->case_number,$case->getEmailSubjectMacro()))===FALSE){
              $subject=str_replace('%1', $case->case_number, $case->getEmailSubjectMacro() . " ". $ie->name);
            }else{
              $subject=$ie->name;
            }
            if(!preg_match('/^(re:)+/i', $subject)) {
              $subject = "RE: {$subject} ";
            }
          }
        }elseif($type == 'replyAll' && !empty($data['email_id'])){
          $GLOBALS['log']->debug("CustomCompose:: as Reply to emailID " .$data['email_id']);
          //assume it's a reply message TO the original sender of the email in question
          //or a draft
          $ie = BeanFactory::retrieveBean('Emails',$data['email_id']);
          if($ie->type == 'draft'){
            //return the draft as is
            return;
          }else{
            //not a draft, composing a reply
            //set the recipient as the reply-to or from of the original message
            $email_addr = !empty($ie->reply_to_email)?$ie->reply_to_email:$ie->from_addr;
            $GLOBALS['log']->debug('$email_addr = ' . $email_addr);
            $name = !empty($ie->reply_to_addr)?$ie->reply_to_addr:$email_addr;
            $GLOBALS['log']->debug('$name = ' . $name);
            if(!empty($email_addr)){
              $toAddresses[]=array(
                'email'=>$email_addr,
                'module'=> '',
                'name'=>$name,
              );
            }
    
            //include Cc
            $email_addr_cc = !empty($ie->cc_addrs)?$ie->cc_addrs:'';
            $GLOBALS['log']->debug('$email_addr_cc = ' . $email_addr_cc);
            $name = !empty($ie->cc_addrs)?$ie->cc_addrs:'';
            $GLOBALS['log']->debug('$name = ' . $name);
            if(!empty($email_addr_cc)){
              $ccAddresses[]=array(
                'email'=>$email_addr,
                'module'=> '',
                'name'=>$name,
              );
            }
    
    
            //use the BODY of the original message in the email
            $description = '';
            if(!empty($ie->description)){
              $description = $ie->description;
            }elseif(!empty($ie->description_html)){
              $description = $ie->description_html;
            }
            $GLOBALS['log']->debug('description = ' . $description);
            $preBodyHTML = " <div><br><br><br><hr></div>";
            $order   = array("\r\n", "\n", "\r");
            $replace = '<br />';
            $body = str_replace($order, $replace,$description);
            //the email should come FROM the queue, not the individual
            //we override the default in custom/modules/Emails/clients/base/api/sender/sender.js
            if(strpos($ie->name, str_replace('%1',$case->case_number,$case->getEmailSubjectMacro()))===FALSE){
              $subject=str_replace('%1', $case->case_number, $case->getEmailSubjectMacro() . " ". $ie->name);
            }else{
              $subject=$ie->name;
            }
            if(!preg_match('/^(re:)+/i', $subject)) {
              $subject = "RE: {$subject} ";
            }
          }
    
        }elseif($type == 'forward' && !empty($data['email_id'])){
          $GLOBALS['log']->debug("CustomCompose:: as Forward emailID " .$data['email_id']);
          $ie = BeanFactory::retrieveBean('Emails',$data['email_id']);
          if($ie->type == 'draft'){
            //return the draft as is
            return;
          }else{
            //not a draft, composing a forward
            //no recipient therefore set to current user or the system will default customer
            // add current user
            $cea = BeanFactory::newBean('EmailAddresses');;
            $cemail_addr = $cea->getPrimaryAddress($current_user);
            $GLOBALS['log']->debug("add user to To address:" . $cemail_addr);
            if(!empty($cemail_addr)){
               $toAddresses[]=array(
                 'email'=>$cemail_addr,
                 'module'=> 'Users',
                 'name'=>$current_user->user_name
               );
            }
            //use the BODY of the original message in the email
            $description = '';
            if(!empty($ie->description)){
              $description = $ie->description;
            }elseif(!empty($ie->description_html)){
              $description = $ie->description_html;
            }
            $GLOBALS['log']->debug('description = ' . $description);
            $ie = BeanFactory::retrieveBean('Emails',$data['email_id']);
    
            $preBodyHTML =<<<FWD
    <pre>
    ---------- Forwarded message ----------
    From: {$ie->from_addr}
    Date: {$ie->date_sent} (UTC)
    Subject: {$ie->name}
    To: {$ie->to_addrs}
    </pre>
    FWD;
            $order   = array("\r\n", "\n", "\r");
            $replace = '<br />';
            $body = str_replace($order, $replace,$description);
            //the email should come FROM the queue, not the individual
            //we override the default in custom/modules/Emails/clients/base/api/sender/sender.js
            if(strpos($ie->name, str_replace('%1',$case->case_number,$case->getEmailSubjectMacro()))===FALSE){
              $subject=str_replace('%1', $case->case_number, $case->getEmailSubjectMacro() . " ". $ie->name);
            }else{
              $subject=$ie->name;
            }
            if(!preg_match('/^(fwd:)+/i', $subject)) {
              $subject = "Fwd: {$subject} ";
            }
          }
    
        }
    
        //build the return the package
        $composePackage = array(
          'to_addresses' => $toAddresses,
          'cc_addresses' => $ccAddresses,
          'parent_type'      => 'Cases',
          'parent_id'        => $case->id,
          'parent_name'    => $case->name,
          'subject'  => $subject,
          'html_body'        => $preBodyHTML . $body,
          'body'             => $preBodyHTML . $body,
          'attachments'      => array(),
          //'email_id'       => (isset($email_id)?$email_id:''),
          //'email_config'   => $fromAddress,
        );
        //the compose needs some additional information to get the preview 
        if($type == 'reply') $composePackage['related'] = array('parent_id'=>$case->id, 'parent_type'=>'Cases');
        foreach ($composePackage as $key => $singleCompose)
        {
          if (is_string($singleCompose)) $composePackage[$key] = str_replace(" ", " ", from_html($singleCompose));
        }
        return ($composePackage);
      }
    }
    

    Note that we also override the sender of the email to be the queue - we don't send out Case emails from individuals but queues:

    custom/modules/Emails/clients/base/fields/sender/sender.js (I have the wrong path in the code comments above line 347)

    Note that it uses the "case_queue_c" field in Cases (a custom field) that is populated with the email address of the case queue in a logic hook when the Case is created (another adventure, see: https://community.sugarcrm.com/message/81897#comment-81897 )

    ({
        fieldTag: 'input.select2',
        initialize: function(options) {
            _.bindAll(this);
            app.view.Field.prototype.initialize.call(this, options);
            this.endpoint = this.def.endpoint;
        },
        _render: function() {
            var result = app.view.Field.prototype._render.call(this);
            if (this.tplName === 'edit') {
                var action = (this.endpoint.action) ? this.endpoint.action : null,
                    attributes = (this.endpoint.attributes) ? this.endpoint.attributes : null,
                    params = (this.endpoint.params) ? this.endpoint.params : null,
                    myURL = app.api.buildURL(this.endpoint.module, action, attributes, params);
                app.api.call('GET', myURL, null, {
                    success: this.populateValues,
                    error:   function(error) {
                        app.alert.show('server-error', {
                            level: 'error',
                            messages: 'ERR_GENERIC_SERVER_ERROR'
                        });
                        app.error.handleHttpError(error);
                    }
                });
            }
            return result;
        },
        populateValues: function(results) {
            var self = this,
                defaultResult,
                defaultValue = {},
                parentMod = this.context.parent,
                parentModule = parentMod.get('module'),
                queue = ('('+parentMod.get('model').get('case_queue_c')+')').replace(/[.*+?^${}()|[\]\\]/g, "\\$&");;
            if (this.disposed === true) {
                return; //if field is already disposed, bail out
            }
            if (!_.isEmpty(results)) {
                if(parentModule == 'Cases' && typeof(queue)!= 'undefined'){
                  var pattern = new RegExp('.*'+queue+'.*');
                  defaultResult = _.find(results, function(result) {
                    return result.display.match(pattern);
                  });
                }else{
                  defaultResult = _.find(results, function(result) {
                    return result.default;
                  });
                }
                defaultValue = (defaultResult) ? defaultResult : results[0];
                if (!this.model.has(this.name)) {
                    this.model.set(this.name, defaultValue.id);
                }
            }
            var format = function(item) {
                return item.display;
            };
            this.$(this.fieldTag).select2({
                data:{ results: results, text: 'display' },
                formatSelection: format,
                formatResult: format,
                width: '100%',
                placeholder: app.lang.get('LBL_SELECT_FROM_SENDER', this.module),
                initSelection: function(el, callback) {
                    if (!_.isEmpty(defaultValue)) {
                          callback(defaultValue);
                    }
                }
            }).on("change", function(e) {
                if (self.model.get(self.name) !== e.val) {
                    self.model.set(self.name, e.val, {silent: true});
                }
            });
    
            this.$(".select2-container").addClass("tleft");
        },
    
      /**
       * {@inheritdoc}
       *
       * We need this empty so it won't affect refresh the select2 plugin
       */
      bindDomChange: function() {
      }
    })
    

    HTH, comments welcome.
    FrancescaS

  • This is the first answer that I have received about sugar that is extremely useful.  Thank you so much for your comment!  I have not implemented what you suggested yet, but I will let you know what happens.

  • Question Regarding : "In

    custom/modules/Emails/clients/base/views/<your cases subpanel view>/<your cases subpanel view>.php

    I added three custom email actions: reply, reply all and forward (opendetails ia a custom view of the email thread)

    They behave as you would expect in an email client."

    I do not have directory so I am assuming that I create it. Does the name of the file matter or is it just that it needs to be the same as the directory above it?

    So will this php file by itself edit the view so after I do a rebuild I will see buttons in the subpanel row for emails?  If you can provide screenshot of finished result that would help me visualize what does what.

  • I put the suggested code snippets into my project and did a quick rebuild, but I don't see any changes?

    As you can tell I am still new to this new way of creating views.  I'm even more confused at which file you know sugar is using.  How do you know these things!?

  • It matters.

    The best way is to modify the subpanel from Cases in studio it will create all the dirs for you.

    And Yes the filename for the php needs to match the dir.

  • Hi James,

    I know you are eager to get your hands dirty with Sugar customizations.  But have done any of the developer training courses on Sugar University?

    Sugar is a big application, so it can be hard to get started if you haven't had some training. The Developer Essentials class will help orient you on the Sugar application architecture as well as the fundamental techniques for customizing and extending Sugar (including how files are named and where they should be placed).

    Sugar Development - Essentials - SugarCRM

    It also gives you a solid basis for you to pursue Sugar Developer Specialist certification if you haven't earned that already.

    App Ecosystem @ SugarCRM

  • Hi Matt, 

    Thank you to you and your team for all support so far.  I am familiar with those videos and have watched most of them but it is hard to put some of the things into practice.  I imagine with a little more time and practice I will get it. 

  • I didn't realize you're a beginner in SugarCRM. This is probably not the best one to get started on.... it's quite complex and you need to be able to distinguish what is my own custom code and what is stock.

    Just for clarification, I don't work for SugarCRM.

    FrancescaS

  • well I wish you did so you could answer more of my questions.