Pages

Thursday, May 21, 2015

SugarCRM Reference: Dissecting the NavBar

A common challenge for Sugar developers is often times less about the development of a solution, but instead, finding the starting point to apply it. For example, should we wish to customize something in the subpanels area, do we modify the subpanels layout controller, or the subpanel-list metadata? Similar questions apply to various areas of Sugar.

To alleviate this problem, it is helpful to dissect the various sections that make up a typical page in Sugar and determine which components are used to construct it. In this case, we will focus on the NavBar, found at the top of Sugar.

The diagram below indicates the names of the various Sidecar components used to make the NavBar:

NavBar Analysis

I hope it helps and stay tuned for future diagrams!

Friday, May 1, 2015

SugarCRM Customization: Custom Upload Directories

I always enjoy finding gems within SugarCRM, little known features that end up opening up a number of doors for further and even more intriguing customizations. A while back, a discussion with a colleague led me to one such discovery.

Those of you making use of documents and other file attachments in Sugar might find it of help to know that said attachments can be stored on Amazon S3 storage in place of the default upload directory. Support for S3 is built-in functionality and can be enabled via some configuration changes.

More intriguing, however, is the fact that this functionality leverages one of those gems I reference and you can take advantage of it as well. The ability to upload attachments to an S3 bucket is accomplished through the extension of the UploadStream class found in Sugar 6.6 and higher.

But if S3 support does not appeal to you, perhaps you might be interested in a slightly different extension of UploadStream.

One of the main problems of the default behavior for attachments in Sugar is that all files end up in the same folder (i.e. <sugar>/upload). Over time, the contents of this folder can become rather difficult to manage. To simplify things, we could organize its contents based on the creation date of the database record associated with the file. For example, MyFile.doc, added to Sugar on April 27, 2015 would be stored as <sugar>/upload/2015/04/27/<SomeGUID>. This approach makes the functionality much more scalable and is easily implemented.

Here is the code that allows us to do that:

<?php
/* Author: Angel Magaña -- cheleguanaco@cheleguanaco.com
* File: ./custom/include/CustomSplitAttachFolder.php
*
* UploadStream extension to add use of date named
* subfolders to ./upload directory.
*
* e.g. ./upload/2015/04/02/<MyApril2nd2015File.docRepresentedAsGUID>
*
* Enabled via: $sugar_config['upload_wrapper_class'] = 'CustomSplitAttachFolder';
*/
class CustomSplitAttachFolder extends UploadStream
{
public function __construct()
{
parent::__construct();
}
/**
* Get upload directory
* @return string
*/
public static function getDir()
{
if(empty(parent::$upload_dir)) {
parent::$upload_dir = rtrim($GLOBALS['sugar_config']['upload_dir'], '/\\') . self::getSubDir();
if(empty(parent::$upload_dir)) {
parent::$upload_dir = 'upload' . self::getSubDir();
}
if(!file_exists(parent::$upload_dir)) {
sugar_mkdir(parent::$upload_dir, 0755, true);
}
}
return parent::$upload_dir;
}
public static function getSubDir()
{
$subdir = '';
$id = $GLOBALS['_REQUEST']['id'];
if (empty($id))
{
$subdir = gmdate('/Y/m/d/');
}
elseif (!empty($id))
{
$bean_type = $GLOBALS['_REQUEST']['type'];
if ($bean_type == 'Documents')
{
$bean_type = 'DocumentRevisions';
}
$bean = BeanFactory::getBean($bean_type, $id);
$dt = strtotime($bean->date_entered);
$subdir = gmdate('/Y/m/d/', $dt);
}
return $subdir;
}
/**
* Get real FS path of the upload stream file
* Non-static version for overrides
* @param string $path Upload stream path (with upload://)
* @return string FS path
*/
public function getFSPath($path, $mode = 0)
{
$path = substr($path, strlen(parent::STREAM_NAME)+3); // cut off upload://
$path = str_replace("\\", "/", $path); // canonicalize path
if($path == ".." || substr($path, 0, 3) == "../" || substr($path, -3, 3) == "/.." || strstr($path, "/../")) {
return null;
}
if ($mode == 0)
{
$path = self::getDir() . self::getSubDir() . $path;
}
elseif ($mode == 1)
{
$path = self::getDir() . $path;
}
return $path;
}
//Here we open stream to download an attachment
public function stream_open($path, $mode)
{
$fullpath = $this->getFSPath($path, 1);
if(empty($fullpath)) return false;
if($mode == 'r') {
$this->fp = fopen($fullpath, $mode);
} else {
// if we will be writing, try to transparently create the directory
$this->fp = @fopen($fullpath, $mode);
if(!$this->fp && !file_exists(dirname($fullpath))) {
mkdir(dirname($fullpath), 0755, true);
$this->fp = fopen($fullpath, $mode);
}
}
return !empty($this->fp);
}
public function url_stat($path, $flags)
{
return @stat($this->getFSPath($path, 1));
}
}