User Tools

Developer Tips and Tricks

You have to force the state in DetailView and CallRelatedList Single Pane forced on module patch

This is obsolete, you do this now with global variables.

--- a/CallRelatedList.php
+++ b/CallRelatedList.php
@@ -16,7 +16,7 @@
 $action = vtlib_purify($_REQUEST['action']);
 $record = vtlib_purify($_REQUEST['record']);
 $isduplicate = vtlib_purify($_REQUEST['isDuplicate']);
+$singlepane_view = 'true';
 if($singlepane_view == 'true' && $action == 'CallRelatedList') {
 } else {
--- a/DetailView.php
+++ b/DetailView.php
@@ -71,6 +71,7 @@
 $smarty->assign('IS_REL_LIST', isPresentRelatedLists($currentModule));
+$singlepane_view = 'true';
 $smarty->assign('SinglePane_View', $singlepane_view);
 if($singlepane_view == 'true') {

Look for this field meta data in the database table vtiger_field and change the value of the column generatedtype to 2.

This is obsolete, you do this inserting information in the database and creating the function in a separate file. Ask on gitter.

with global variables.

The next code adds a substring function and should suffice to show how to add more funtions. Please share if you create any.

Index: modules/com_vtiger_workflow/expression_engine/
--- modules/com_vtiger_workflow/expression_engine/	(revisión: 938)
+++ modules/com_vtiger_workflow/expression_engine/	(revisión: 939)
@@ -41,7 +41,7 @@
 	function expressionFunctions() {
 		return array('concat' => 'concat(a,b)', 'time_diffdays' => 'time_diffdays(a,b)', 'time_diff' => 'time_diff(a,b)',
 			'add_days' => 'add_days(datefield, noofdays)', 'sub_days' => 'sub_days(datefield, noofdays)',
-			'get_date' => "get_date('today')");
+			'get_date' => "get_date('today')", 'substring' => "substring(stringfield,start,end)");
Index: modules/com_vtiger_workflow/expression_engine/
--- modules/com_vtiger_workflow/expression_engine/	(revisión: 938)
+++ modules/com_vtiger_workflow/expression_engine/	(revisión: 939)
@@ -144,6 +144,15 @@
+function __vt_substring($arr) {
+	if (count($arr)<2 or count($arr)>3) return $arr[0];
+	if (count($arr)==2) {
+		return substr($arr[0],$arr[1]);
+	} else {
+		return substr($arr[0],$arr[1],$arr[2]);
+	}
 /** END * */
 class VTFieldExpressionEvaluater{
 	function __construct($expr){
@@ -165,7 +174,8 @@
  	            'time_diffdays' => '__vt_time_diffdays',
 				'add_days' => '__vt_add_days',
 				'sub_days' => '__vt_sub_days',
-				'get_date' => '__vt_get_date'
+				'get_date' => '__vt_get_date',
+				'substring' => '__vt_substring'
 		$this->operations = array_merge($this->functions, $this->operators);

You have to apply this next code change.

diff --git a/include/js/general.js b/include/js/general.js
index d79cc33..c5eae5d 100755
--- a/include/js/general.js
+++ b/include/js/general.js
@@ -4066,6 +4066,7 @@ function submitFormForActionWithConfirmation(formName, action, confirmationMsg)
 function submitFormForAction(formName, action) {
 	var form = document.forms[formName];
 	if (!form) return false;
 	form.action.value = action;
 	return true;

Note that you may have to make a similar change in some other parts of the code, I ONLY did a quick test to answer the forum question.

See this page for tips on playing with PDF output: PDF Formatting

This patch accomplishes this functionality. You don't have to collect the currency symbol, just save the value and format like with the php function “number_format” to get the value with the correct decimals.

Index: modules/Reports/ReportUtils.php
--- modules/Reports/ReportUtils.php     (revisión: 5892)
+++ modules/Reports/ReportUtils.php     (revisión: 5964)
@@ -75,11 +75,10 @@
                        $cur_sym_rate = getCurrencySymbolandCRate($currency_id);
                        if($value!='') {
                                $formattedCurrencyValue = CurrencyField::convertToUserFormat($currency_value, null, true);
-                               $fieldvalue = CurrencyField::appendCurrencySymbol($formattedCurrencyValue, $cur_sym_rate['symbol']);
+                               $fieldvalue = number_format($formattedCurrencyValue,2,'.','');
                } else {
-                       $currencyField = new CurrencyField($value);
-                       $fieldvalue = $currencyField->getDisplayValue();
+                       $fieldvalue = number_format($value,2,'.','');

        } elseif ($dbField->name == "PurchaseOrder_Currency" || $dbField->name == "SalesOrder_Currency"

Each module has defined it's default column for sorting. This must be changed in the code in the module's main script. For example, for Accounts we can find:

  //Added these variables which are used as default order by and sortorder in ListView
  var $default_order_by = 'accountname';
  var $default_sort_order = 'ASC';

where we can change the variable $default_order_by to any valid column of the account

For this to work we must set the variable 'LISTVIEW_DEFAULT_SORTING' to true in the file config.performance.php

Go to the database and change the value of vtiger_field.presence to

  • 0: always active, cannot be modified in layout editor
  • 1: inactive
  • 2: active and editable

This is a very useful trick when we lose the user_privileges directory or the admin user files and can't login.

cd <application root directory>
php -r "require_once 'include/utils/UserInfoUtil.php'; RecalculateSharingRules();"

This should work although I have my doubts about this functionality because I understand that you may want to associate the closed won opportunity to the quote. NOTE that the patch affects Quotes and SalesOrders.

diff --git a/Popup.php b/Popup.php
index 16e7048..c6faf32 100644
--- a/Popup.php
+++ b/Popup.php
@@ -237,6 +237,9 @@ else
 		$url_string .='&recordid='.vtlib_purify($_REQUEST['recordid']);
         	$where_relquery = getRelCheckquery($currentModule,$_REQUEST['return_module'],$_REQUEST['recordid']);
+	if ($currentModule=='Potentials' and (isset($_REQUEST['restrictclosed']) and $_REQUEST['restrictclosed']=='1')) {
+		$where_relquery = " and sales_stage not like 'Closed%' ";
+	}
 	if(isset($_REQUEST['relmod_id']) || isset($_REQUEST['fromPotential']))
 		if($_REQUEST['relmod_id'] !='')
diff --git a/include/js/general.js b/include/js/general.js
index d0feb0a..72db333 100755
--- a/include/js/general.js
+++ b/include/js/general.js
@@ -2379,9 +2379,9 @@ function selectPotential()
 		parent_module = 'Contacts';
 	if(record_id != '')
 //to select Quote Popup
 function selectQuote()

This is very easy with vtlib. Simply get an instance of the field you want to modify and use the setRelatedModules() method. Have a look at the code below:

// Turn on debugging level
$Vtiger_Utils_Log = true;
$modname = 'Potentials';
$module = Vtiger_Module::getInstance($modname);
if($module) {
	$field = Vtiger_Field::getInstance('related_to',$module);
	echo "<br><b>Modified Field on $modname module.</b><br>";
} else {
	echo "<b>Failed to find $modname module.</b><br>";

You have to do this from database.

  • First you have to know the fieldname, in this case it is “rel_id”
  • Go to vtiger_field table and search this field to get the fieldid, for example: 1994
  • After that you have to got to vtiger_fieldmodulerel, search this fieldid and you will see something like the next image, where you have to change the sequence to order the values:

Use the Debug_Report_Query Global variable

Use the Debug_ListView_Query Global variable

For bulk operation script setting $VTIGER_BULK_SAVE_MODE to true - turns off the Evening API.

The import module set variable $VTIGER_BULK_SAVE_MODE = true , to not load the events to execute workflows. When you need to execute workflows when import records, you can apply the next patch. This patch force the validation to isBulkSaveMode, with this the workflows always will be load in any part of code.

After your importation, remember to undo the patch.

diff --git a/data/CRMEntity.php b/data/CRMEntity.php
index 30b7c2e..c737722 100755
--- a/data/CRMEntity.php
+++ b/data/CRMEntity.php
@@ -48,6 +48,7 @@ class CRMEntity {
         * to improve performance.
        static function isBulkSaveMode() {
+               return false;
                global $VTIGER_BULK_SAVE_MODE;
                if (isset($VTIGER_BULK_SAVE_MODE) && $VTIGER_BULK_SAVE_MODE) {
                        return true;

Instead of calling save(), use saveentity(), that will not trigger the workflows again.

The answer to this is not only for workflow custom functions but in general whenever we need to save a record without launching workflow or events.

I would suggest that you add your function and libraries in the event/custom code that you create. For example, suppose that you need to include some google drive library and some new functions to the save event of a document. You decide to add a vtiger.aftersave event. In the file where your new functionality resides you will add the library include at the beginning and your functions inside the new class so you don't need to modify the common utils base code. Something like this:

require_once('google drive library');
class YourCustomEventHandlerClass extends VTEventHandler {
    function YourNewFunction() {
    function handleEvent($eventName, $entityData) {
      global $log, $adb;

It seems that your database can't setup the foreign key constraint for some reason. The correct path would be to review your data and make sure it is correct. In other words, the SQL must work, if it doesn't there is some inconsistency in your FAQ table. As an extreme case you can eliminate the constraint, the program will work the same and you will have custom field support on the FAQ module, simply the information in those custom fields will not be deleted when you eliminate a FAQ. Here is the change:

diff --git a/build/changeSets/cffaq.php b/build/changeSets/cffaq.php
index 3a10292..cf4896a 100644
--- a/build/changeSets/cffaq.php
+++ b/build/changeSets/cffaq.php
@@ -23,8 +23,7 @@ class cffaq extends cbupdaterWorker {
                } else {
                        $this->ExecuteQuery("CREATE TABLE IF NOT EXISTS vtiger_faqcf (
                                                faqid int(19),
-                                               PRIMARY KEY (faqid),
-                                               CONSTRAINT fk_1_vtiger_faqcf FOREIGN KEY (faqid) REFERENCES vtiger_faq(id) ON DELETE CASCADE
+                                               PRIMARY KEY (faqid)
                                                ) ENGINE=InnoDB DEFAULT CHARSET=utf8", array());
                        $this->sendMsg('Changeset '.get_class($this).' applied!');

I encountered a race condition using the 2-steps login process provided by vtiger's webservices. Say that you have a multi-process / multi-threaded application using vtiger's webservices. Each thread/process has to call getchallenge+login before proceeding in doing what they have to do (that's what doLogin() in vtwsclib does).

The problem lies in the getchallenge phase. include/Webservices/AuthToken.php does

delete from vtiger_ws_userauthtoken where userid=?

for the current user. This instruction can be executed in between the 2 getchallenge+login steps of another webservice thread/process, therefore destroying its chance of doing a correct login.

Maybe the solution would be for the getchallenge step to detect if there is already an existing (not expired) outstanding getchallenge request for the current user and return that.

trac #6500

Index: trunk/include/Webservices/AuthToken.php
--- trunk/include/Webservices/AuthToken.php	(revision 7)
+++ trunk/include/Webservices/AuthToken.php	(working copy)
@@ -19,6 +19,14 @@
 		$servertime = time();
 		$expireTime = time()+(60*5);
+		$sql = "select userid,token,expiretime, count(*) as count from vtiger_ws_userauthtoken group by userid=?";
+		$res = $adb->pquery($sql,array($userid,$servertime));
+		$result = $adb->fetchByAssoc($res);
+		if ($result['count']>=1) {
+			return array("token"=>$result["token"],"serverTime"=>$servertime,"expireTime"=>$result["expiretime"]);
+		}
 		$sql = "delete from vtiger_ws_userauthtoken where userid=?";

The following URL format will suppress the header and footer information.


Add this code to Save.php of the Invoice

$source_id = $_REQUEST['return_id'];
$source_module = $_REQUEST['return_module'];
$_REQUEST['return_module'] = 'Invoice';
$_REQUEST['return_action'] = 'DetailView';

so it ends up looking like this:

$source_id = $_REQUEST['return_id'];
$source_module = $_REQUEST['return_module'];
$_REQUEST['return_module'] = 'Invoice';
$_REQUEST['return_action'] = 'DetailView';

Thanks Luke :-)

  • Use different base tables for each module's specific data and have a common related table to store the common data.
  • Use a common base table and have different related tables for the specifics.

As long as the field is mapped with target-table or column through meta-data and follows the Entity module convention system would continue to work.

Shared table poses a challenge when different module records enable edits of fields.

It would also come in the way of performance (specifically query cache invalidation).

If modules are from different publishers then sharing table is not a good idea, as the control or convention can be too tedious to maintain/manage.

Go to the Business Actions module and add a LISTVIEWBASIC button with this action:


coreBOS Documentación