Upgrade Safe way to modify Save.php behavior

When Gantt Charts and Project Templates were introduced, SuiteCRM changed the default behavior for viewing Projects to the GanttChart view instead of the DetailView. To modify this behavior back to DetailView, I had to make several changes to address this. One was in /modules/Project/tpls/ListViewGeneric.tpl, however, this file seems to no longer exist, and I’m still getting to DetailView from ListView (not sure why or how, and not immediately concerned by it). Global search also seems to be going to DetailView by default (not sure how or why and not immediately concerned by it). But, when editing a project and then saving it, I’m ending up back at the Gantt Chart.

To originally fix this, I made the following change to /modules/Project/Save.php (which obviously isn’t upgrade safe):


if ($sugarbean->is_template){
    header("Location: index.php?action=ProjectTemplatesDetailView&module=Project&record=$return_id&return_module=Project&return_action=ProjectTemplatesEditView");
}
else{
	//customize default retrun view to make it to redirect to GanttChart view
	// sieberta changed this back to DetailView, the old version is still shown commented out...
	$_REQUEST['return_url'] = "index.php?module=Project&action=DetailView&record=" . $return_id;
	// $_REQUEST['return_url'] = "index.php?module=Project&action=view_GanttChart&project_id=" . $return_id;
    handleRedirect($return_id,'Project');
}

I’m curious if anyone can tell me how to make this change upgrade safe.

To show it a little more concisely, this:

$_REQUEST['return_url'] = "index.php?module=Project&action=view_GanttChart&project_id=" . $return_id;

was changed to this:

$_REQUEST['return_url'] = "index.php?module=Project&action=DetailView&record=" . $return_id;

Oh, it appears I also changed /modules/Project/metadata/editviewdefs.php

<?php
$viewdefs ['Project'] = 
array (
  'EditView' => 
  array (
    'templateMeta' => 
    array (
      'maxColumns' => '2',
      'widths' => 
      array (
        0 => 
        array (
          'label' => '10',
          'field' => '30',
        ),
        1 => 
        array (
          'label' => '10',
          'field' => '30',
        ),
      ),
      'form' => 
      array (
        'hidden' => '<input type="hidden" name="is_template" value="{$is_template}" />',
        'headerTpl' => 'modules/Project/tpls/header.tpl',
        'footerTpl' => 'modules/Project/tpls/footer.tpl',
        'buttons' => 
        array (
          0 => 
          array (
            'customCode' => '<input title="{$APP.LBL_SAVE_BUTTON_TITLE}" id ="SAVE_HEADER" accessKey="{$APP.LBL_SAVE_BUTTON_KEY}" class="button primary" onclick="SUGAR.projects.fill_invitees();document.EditView.action.value=\'Save\'; document.EditView.return_action.value=\'view_GanttChart\'; {if isset($smarty.request.isDuplicate) && $smarty.request.isDuplicate eq "true"}document.EditView.return_id.value=\'\'; {/if} formSubmitCheck();"type="button" name="button" value="{$APP.LBL_SAVE_BUTTON_LABEL}">',
          ),
          1 => 
          array (
            'customCode' => '{if !empty($smarty.request.return_action) && $smarty.request.return_action == "ProjectTemplatesDetailView" && (!empty($fields.id.value) || !empty($smarty.request.return_id)) }<input title="{$APP.LBL_CANCEL_BUTTON_TITLE}" accessKey="{$APP.LBL_CANCEL_BUTTON_KEY}" class="button" onclick="this.form.action.value=\'ProjectTemplatesDetailView\'; this.form.module.value=\'{$smarty.request.return_module}\'; this.form.record.value=\'{$smarty.request.return_id}\';" type="submit" name="button" value="{$APP.LBL_CANCEL_BUTTON_LABEL}" id="CANCEL{$place}"> {elseif !empty($smarty.request.return_action) && $smarty.request.return_action == "DetailView" && (!empty($fields.id.value) || !empty($smarty.request.return_id)) }<input title="{$APP.LBL_CANCEL_BUTTON_TITLE}" accessKey="{$APP.LBL_CANCEL_BUTTON_KEY}" class="button" onclick="this.form.action.value=\'DetailView\'; this.form.module.value=\'{$smarty.request.return_module}\'; this.form.record.value=\'{$smarty.request.return_id}\';" type="submit" name="button" value="{$APP.LBL_CANCEL_BUTTON_LABEL}" id="CANCEL{$place}"> {elseif $is_template}<input title="{$APP.LBL_CANCEL_BUTTON_TITLE}" accessKey="{$APP.LBL_CANCEL_BUTTON_KEY}" class="button" onclick="this.form.action.value=\'ProjectTemplatesListView\'; this.form.module.value=\'{$smarty.request.return_module}\'; this.form.record.value=\'{$smarty.request.return_id}\';" type="submit" name="button" value="{$APP.LBL_CANCEL_BUTTON_LABEL}" id="CANCEL{$place}"> {else}<input title="{$APP.LBL_CANCEL_BUTTON_TITLE}" accessKey="{$APP.LBL_CANCEL_BUTTON_KEY}" class="button" onclick="this.form.action.value=\'index\'; this.form.module.value=\'{$smarty.request.return_module}\'; this.form.record.value=\'{$smarty.request.return_id}\';" type="submit" name="button" value="{$APP.LBL_CANCEL_BUTTON_LABEL}" id="CANCEL{$place}"> {/if}',
          ),
        ),
        'buttons_footer' => 
        array (
          0 => 
          array (

HERE IS THE CHANGE:


		  // sieberta Changed to DetailView... not sure this one is necessary
            'customCode' => '<input title="{$APP.LBL_SAVE_BUTTON_TITLE}" id ="SAVE_HEADER" accessKey="{$APP.LBL_SAVE_BUTTON_KEY}" class="button primary" onclick="SUGAR.projects.fill_invitees();document.EditView.action.value=\'Save\'; document.EditView.return_action.value=\'DetailView\'; {if isset($smarty.request.isDuplicate) && $smarty.request.isDuplicate eq "true"}document.EditView.return_id.value=\'\'; {/if} formSubmitCheck();"type="button" name="button" value="{$APP.LBL_SAVE_BUTTON_LABEL}">',		  
//            'customCode' => '<input title="{$APP.LBL_SAVE_BUTTON_TITLE}" id ="SAVE_HEADER" accessKey="{$APP.LBL_SAVE_BUTTON_KEY}" class="button primary" onclick="SUGAR.projects.fill_invitees();document.EditView.action.value=\'Save\'; document.EditView.return_action.value=\'view_GanttChart\'; {if isset($smarty.request.isDuplicate) && $smarty.request.isDuplicate eq "true"}document.EditView.return_id.value=\'\'; {/if} formSubmitCheck();"type="button" name="button" value="{$APP.LBL_SAVE_BUTTON_LABEL}">',

          ),
          1 => 
          array (
            'customCode' => '{if !empty($smarty.request.return_action) && $smarty.request.return_action == "ProjectTemplatesDetailView" && (!empty($fields.id.value) || !empty($smarty.request.return_id)) }<input title="{$APP.LBL_CANCEL_BUTTON_TITLE}" accessKey="{$APP.LBL_CANCEL_BUTTON_KEY}" class="button" onclick="this.form.action.value=\'ProjectTemplatesDetailView\'; this.form.module.value=\'{$smarty.request.return_module}\'; this.form.record.value=\'{$smarty.request.return_id}\';" type="submit" name="button" value="{$APP.LBL_CANCEL_BUTTON_LABEL}" id="CANCEL{$place}"> {elseif !empty($smarty.request.return_action) && $smarty.request.return_action == "DetailView" && (!empty($fields.id.value) || !empty($smarty.request.return_id)) }<input title="{$APP.LBL_CANCEL_BUTTON_TITLE}" accessKey="{$APP.LBL_CANCEL_BUTTON_KEY}" class="button" onclick="this.form.action.value=\'DetailView\'; this.form.module.value=\'{$smarty.request.return_module}\'; this.form.record.value=\'{$smarty.request.return_id}\';" type="submit" name="button" value="{$APP.LBL_CANCEL_BUTTON_LABEL}" id="CANCEL{$place}"> {elseif $is_template}<input title="{$APP.LBL_CANCEL_BUTTON_TITLE}" accessKey="{$APP.LBL_CANCEL_BUTTON_KEY}" class="button" onclick="this.form.action.value=\'ProjectTemplatesListView\'; this.form.module.value=\'{$smarty.request.return_module}\'; this.form.record.value=\'{$smarty.request.return_id}\';" type="submit" name="button" value="{$APP.LBL_CANCEL_BUTTON_LABEL}" id="CANCEL{$place}"> {else}<input title="{$APP.LBL_CANCEL_BUTTON_TITLE}" accessKey="{$APP.LBL_CANCEL_BUTTON_KEY}" class="button" onclick="this.form.action.value=\'index\'; this.form.module.value=\'{$smarty.request.return_module}\'; this.form.record.value=\'{$smarty.request.return_id}\';" type="submit" name="button" value="{$APP.LBL_CANCEL_BUTTON_LABEL}" id="CANCEL{$place}"> {/if}',
          ),
        ),
      ),
      'javascript' => '<script type="text/javascript">{$JSON_CONFIG_JAVASCRIPT}</script>
		{sugar_getscript file="cache/include/javascript/sugar_grp_project.js"}
		<script>toggle_portal_flag();function toggle_portal_flag()  {ldelim} {$TOGGLE_JS} {rdelim} 
		function formSubmitCheck(){ldelim}if(check_form(\'EditView\')){ldelim}document.EditView.submit();{rdelim}{rdelim}</script>',
      'useTabs' => false,
      'tabDefs' => 
      array (
        'LBL_PROJECT_INFORMATION' => 
        array (
          'newTab' => false,
          'panelDefault' => 'expanded',
        ),
      ),
      'syncDetailEditViews' => true,
    ),
    'panels' => 
    array (
      'lbl_project_information' => 
      array (
        0 => 
        array (
          0 => 'name',
          1 => 'status',
        ),
        1 => 
        array (
          0 => 'estimated_start_date',
          1 => 'priority',
        ),
        2 => 
        array (
          0 => 'estimated_end_date',
          1 => 'override_business_hours',
        ),
        3 => 
        array (
          0 => 'assigned_user_name',
          1 => 
          array (
            'name' => 'am_projecttemplates_project_1_name',
          ),
        ),
      ),
    ),
  ),
);
?>

sieberta

I assume you tried the obvious, creating a copy in

custom/modules/Project/Save.php

and it didn’t work?

I couldn’t find any place where this file is included, I think it just gets called directly from the URL because it is the action name. I didn’t try debugging through that call, that might bring some more information. Do you know what is the exact URL that gets called when going into that code?

pgr,

I did not try that. I’ve been using SuiteCRM for 4 years, and making slight modifications and bug fixes for that duration. That said, only relatively recently have I gained more education on upgrade-safe paths to customization and bug fixes… and sometimes the path to that is more obvious than others.

I’m not necessarily sure how to ‘debug through that call’. I would mention that my sourcecode that I edit/play with is located on my local Windows 10 PC (no PHP installeD) and my development and production instances are located in a VPS with InMotion. My point being, I’m not sure if that limits my debug options. I understand PHP pretty well, but I’m still probably considered a relatively novice developer.

And no, I don’t know what the exact URL is that gets called when going into that code.

sieberta

Ok, so

  1. Try copying files you need to edit into their corresponding place in “custom”, that works well in 80% of the cases. Some of the others can be fixed as needed, so that upgrade-safe customization becomes possible.

  2. You can set up remote debugging by installing XDEBUG on the server, and having a capable IDE (I use PHP Storm, Eclipse, etc) in your Windows local. This can be troublesome to set up (you also need a way to share the source files between the two machines), but once you have that up and running, your PHP skills and your SuiteCRM abilities are in a whole new league. You can dive into the code and follow the way things are happening, check variables, etc. There are many online tutorials about setting up XDEBUG and remote debugging.

  3. I mean if you know where to click to run that code, we should be able to find out. Is it just editing a Project and then hitting the Save button?

[quote=“pgr” post=74357]Ok, so

  1. Try copying files you need to edit into their corresponding place in “custom”, that works well in 80% of the cases. Some of the others can be fixed as needed, so that upgrade-safe customization becomes possible.
    [/quote]
    This seems to have worked. I left a ‘normal’ Save.php file in the projects folder, and put my modified version in the custom folder and rebuilt.

Cool. Thanks for the tip. I will have to check this out. I use PHP Storm as well as Notepad++ (which I guess isn’t really an IDE). When you say 'share the source files between the two machines, do you mean not have a local copy and a cloud copy, just do everything from the cloud copy? I think I might be able to accomplish that with WebDav, but PHP Storm will create some useless files/folders in the cloud in that instance.

Yes, just editing a project and then clicking ‘save’ calls this code.

Thanks for the help!

  1. B-)

  2. No longer an issue, because, 1. is solved

  3. When remote debugging, the remote needs to have the source PHP in order to execute it. The local needs to have the source in order to show it to you in the IDE and to understand the data it is getting from XDEBUG. These two sources need to match, of course.

There are several ways to keep them in sync.

a) The simplest is when you’re doing read-only debugging, you just copy the entire source once and never sync it again, unless you change the code on the server and copy the changes over. You don’t ever edit the code in the IDE, or if you do, you copy things manually over to the other machine.

b) Of course you will soon get tired of a) and you’ll try something automatic. Look for settings in PHP Storm designed for this, probably the simplest is SSH (which also includes file copying abilities, scp) and PHP Storm will take care of everything for you. Look for “Debug configurations” and then something like “PHP Remote”.

With b) you can edit the code, save, and run the new version immediately.

1 Like

I got xdebugger with remote debugging setup (finally). This was not an easy task (for me). Thank you for your help, I’m finding it tremendously useful.

sieberta

It’s a game-changer, I’m glad you got it working.

Just so you feel better, it’s never an easy task for me either, even after the first time… when I should already know what needs to be done.

Just out of curiosity, which IDE are you using? On Windows or Linux?

I use JetBrains PHPNuke on Windows for development. I have 7 instances of SuiteCRM on the same server in the Cloud on Linux… one of which is production, and the others that are for development. Why 7?

1 - Production
2-6 - Various Devs, such as attempting an upgrade to find bugs, or possibly the older version to see if that bug existed before production was upgraded
7 - Clean install - This is a clean install of whatever version I’m running, that wasn’t upgraded several times like production was, so I can find differences in code, and see if Bugs only exist in my upgrades, or in the clean code as well.

sieberta

1 Like

You mean PHP Storm, not PHP Nuke, right? :slight_smile:

You are absolutely correct.

I’ve tried something similar with my AOS_Products_Quotes.php to tweak the save_lines function. I copied it to it’s folder under /custom/ but it doesn’t seem to be getting called.
I put a call to error_log() in both files to see which one is getting used and I’m only seeing the default file.

I wonder if this is the right approach or if it would be better to overwrite the function somehow. I was looking into ActionReMap but I couldn’t find anything explaining how to begin that.