Skip navigation
All Places > Developer > Blog > 2020 > February
2020

Developer

February 2020 Previous month Next month

We have 4 different sets of servers on one of my SugarCRM Enterprise installs.  We also have 4 people who do development, QA or Admin work so it is vitally important that everyone is aware of what server they are on.  We all move among the servers all day and sometimes we forget which server we are on.  We don't want a developer testing something or adding/deleting records in Production or a QA engineer to test something on a development server.  I tried a few different options, mostly coloring the tabs and while this worked for a short while we found ourselves ignoring the colors after a week or two.  I needed something that was more in your face.  So I added a watermark to the code.

Watermark

 

You cant ignore that.  It is only seen by the 4 people in question and all other users see nothing.  It appears on every page, custom modules, admin screen, everywhere.  All I did was add a custom base.js file at custom/clients/base/layouts/base/base.js that looks like this

/*
* Your installation or use of this SugarCRM file is subject to the applicable
* terms available at
* http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
* If you do not agree to all of the applicable terms or do not have the
* authority to bind the entity as an authorized representative, then do not
* install or use this SugarCRM file.
*
* Copyright (C) SugarCRM Inc. All rights reserved.
*/

/**
* The Base Layout that all Layouts should extend from before extending
* {@link #View.Layout}.
*
* Use this controller to specify your customizations for the Base platform.
* This should contain any special override that only applies to Base platform
* and not to Sidecar's library.
*
* Any Layout in a module can skip the default fallback and extend this one
* directly. In your `BaseModuleMyLayout` component that lives in the file
* `modules/<module>/clients/base/layouts/my-layout/my-layout.js`, you can
* directly extend the `BaseLayout` skipping the normal extend flow which will
* extend automatically from `BaseMyLayout` that might live in
* `clients/base/layouts/my-layout/my-layout.js`. Simply define your controller
* with:
*
* ```
* ({
*     extendsFrom: 'BaseLayout',
*     // ...
* })
* ```
*
* This controller exists to force the component to be created and not fallback
* to the default flow (which happens when the component isn't found).
*
* @class View.Layouts.Base.BaseLayout
* @alias SUGAR.App.view.layouts.BaseBaseLayout
* @extends View.Layout
*/

({
    /**
     * The Base Layout will always clear any tooltips after `render` or `dispose`.
     */

    initialize: function() {
        //watermark code 1.0
        //We have 4 users that need to see the watermark, we hide it for everyone else
        //  I might change this to use the department field on the user in the future but
        //  this works for now.
        let userID = app.user.id;
        if (userID === '20821fb3-541e-2d6e-1b89-56b8c6d65ce9' ||
            userID === 'b8f60911-ead1-31d9-1709-56cb52895212' ||
            userID === '0b069d40-2b7f-11e7-b49b-52d504b662cb' ||
            userID === '1cf7b3d3-73d0-73d9-9cbd-5527de354afc') {
            let url = window.location.href;
            //I test for the different URLs of the different machines.  If none are matched I assume
            // its a local copy on the users machine
            if (url.indexOf('production') !== -1) {
                $('head').append('<link rel="stylesheet" type="text/css" href="custom/themes/default/css/prod.css">');
            } else if (url.indexOf('qa') !== -1) {
                $('head').append('<link rel="stylesheet" type="text/css" href="custom/themes/default/css/dive.css">');
            } else if (url.indexOf('development') !== -1) {
                $('head').append('<link rel="stylesheet" type="text/css" href="custom/themes/default/css/dev.css">');
            } else {
                $('head').append('<link rel="stylesheet" type="text/css" href="custom/themes/default/css/local.css">');
            }
        }


        this._super('initialize', arguments);
        if (app.tooltip) {
            this.on('render', app.tooltip.clear);
        }
    }
})

 

This adds a CSS file from the custom/themes/default/css directory to the page depending on its URL.  You would have to modify it to use your URL naming scheme.  You would need to create 1 CSS file for each of your server types.  The CSS it loads looks like this

 

custom/themes/default/css/local.css

html:after {
    /* common custom values */
    content: "LOCAL"; /* your site name */
    font-size: 225px; /* font size */
    color: rgba(0, 0, 50, .1);
    /* alpha, could be even rgba(0,0,0,.05) */

    /* rest of the logic */
    z-index: 9999;
    cursor: default;
    display: block;
    position: fixed;
    top: 33%;
    right: 0;
    bottom: 0;
    left: 15%;
    font-family: sans-serif;
    font-weight: bold;
    font-style: italic;
    text-align: center;
    line-height: 100%;

    /* not sure about who implemented what ..
      ... so bring it all */

    -webkit-pointer-events: none;
    -moz-pointer-events: none;
    -ms-pointer-events: none;
    -o-pointer-events: none;
    pointer-events: none;

    -webkit-transform: rotate(-40deg);
    -moz-transform: rotate(-40deg);
    -ms-transform: rotate(-40deg);
    -o-transform: rotate(-40deg);
    transform: rotate(-40deg);

    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    user-select: none;
}

 

And that's all there is to it.  This seems like a tiny thing but it has been the second most useful customization I have yet made to our system.

Code Completion

I have finally gotten around to improving my development environment and I chose to start with code completion. I use phpStorm but I think everything here would apply to any editor out there. When Sugar went to the BeanFactory to instantiate a bean, the code completion stopped working. If you define your bean class in the DocBlock or in an inline phpDocumentor tag, some of the functionality returns but not all. Custom fields and relationships are never available as they are never added to the public variable list on the class. To demonstrate what I am talking about this is how a DocBlock can handle code completion:
Code Completion Example

 

You see that the 'name' field is not available until after I tell the editor that $bean represents the Call class.  But, even then the custom fields that I have added to the Calls module are not available - only the stock ones. Of course you cannot define the $bean of every function as some function accept data from several modules.  It might be a Call one time and a Meeting another.  In these cases you can define the $bean as a SugarBean instead of a Call or Meeting and get some functionality out of it.

 

An inline tag works in a similar way and is used to define the class of a BeanFactory call.
Inline Tag Example
Again, the fields are not shown until after I tell the editor that $quoteBean represents the IN_Orders class.  After that, all the stock fields and relationships (and functions I guess) are available to you.

 

So, that's step one. I have to get better about documenting my functions and using inline tags to decorate my BeanFactory calls. Not hard I guess, but that is only getting me half my code completion options. I need my custom fields on that list. The code completion options are taken from the classes and the variables in them. So somehow we have to get the custom fields that were added to the modules onto the classes...

 

I went through about 10 different methods before I settled on 2. One is effortless but means you have to update stock files. The other is more coding work but uses custom files.  I use the first option as updating my stock files in this way is not a big deal.  My .gitignore file keeps them from getting added to my commits. So, the changes only exist on my development files and the utility I use to add them has a clean up option. We will get to that later but rest assured, unless you are actually editing your live files directly (don't do that) you will be fine with option one.

 

The Editor Helper Tool
The Editor Helper Tool

This tool is available for download from this blog post and is licensed under the MIT license. It allows you to update your class files all at once and has an option to remove everything it added if that becomes necessary. When you run it (It adds itself to the Admin menu) in either the 'Normal' or 'Custom' modes the first thing it does is remove any code it added on the previous run. Then it looks at every module for custom fields and adds them to the class files. So, if you do a git clone or a version upgrade, you can just go to the admin menu and run this tool. It will set everything up again no matter what.

 

So, if you run it in the 'Normal' mode, the Accounts class file (modules/Accounts/Accounts.php) will end up looking like this:

Accounts Class File

 

Now the class file has all my custom fields inserted into it and code completion will work for all fields - stock and custom. This is safe for me to do on my system as it only exists on my development box. My .gitignore will prevent me from ever creating a commit that would add these changes to production code as they are not needed to actually run Sugar and are only there to make the editor work better. Whenever I add or remove custom fields I can just re-run this tool and it will update the classes for me. In this mode I wouldn't have to change my coding style at all. I can just do things like this to make sure both $bean and $masterAccount are seen as Accounts in the code.

     /**
      * @param Account $bean
      * @param string $events
      * @param array $arguments
      */

     private function beforeSavePartner($bean, $events, $arguments): void
        {
           if (!empty($bean->parent_id)) {
              /** @var Account $masterAccount */
              $masterAccount = BeanFactory::getBean('Accounts', $bean->parent_id, IGNORE_TEAMS);
              if (!empty($masterAccount->dealerID) {
                  $bean->masterid_c = $masterAccount->dealerid_c;
              } else {
                  $bean->masterid_c = '';
                  $bean->parent_id = null;
              }
           } else {
              $bean->masterid_c = '';
           }
}

 

If I ran it in 'Custom' mode I would get a file created in 'custom/modules/Accounts/customAccounts.php' that looks like this:

Custom Accounts Class

 

In this scenario I would have to alter my .gitignore to keep these files from being committed to production code.  Then I would have to alter my code like in the following example. You should notice that in the DocBlock I had to define $bean as being either an Account or a customAccount.  Otherwise, PHP would complain.  Since $masterAccount is all mine, I can just define it as  customAccount.

     /**
      * @param Account|customAccount $bean
      * @param string $events
      * @param array $arguments
      */

     private function beforeSavePartner($bean, $events, $arguments): void
        {
           if (!empty($bean->parent_id)) {
              /** @var customAccount $masterAccount */
              $masterAccount = BeanFactory::getBean('Accounts', $bean->parent_id, IGNORE_TEAMS);
              if (!empty($masterAccount->dealerID) {
                  $bean->masterid_c = $masterAccount->dealerid_c;
              } else {
                  $bean->masterid_c = '';
                  $bean->parent_id = null;
              }
           } else {
              $bean->masterid_c = '';
           }
}


PHPStorm Live Templates

I know I said at the top that all of this would be compatible with all editors but I just wanted to share this one PHPStorm thing as I use it in relation to this all the time. I created a 'Live Template' for BeanFactory calls so I would not have to type the BeanFactory call AND the inline tag every time. I just type BF<tab> and then fill in the form like this:

Live Template

 

This can be created in your PHPStorm like this. Just open preferences and search for 'live' and you will see Live Templates, select that and then add a template to the PHP section.

Live Template Setup 1

 

Then you have to set up the variables for the template. This is done by clicking on the 'Edit variables' button and filling out the form there.

Live Templates Setup 2

 

Is any of this necessary to customize Sugar? No, not really. But, it has made coding for Sugar much easier for me. I hope it can do the same for you.