2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
41 require_once('include/ListView/ListViewData.php');
42 require_once('include/MassUpdate.php');
44 class ListViewDisplay {
46 var $show_mass_update_form = false;
47 var $show_action_dropdown = true;
51 var $multi_select_popup;
55 var $multiSelect = true;
56 var $mailMerge = true;
57 var $should_process = true;
58 var $show_plus = false;
60 * Used in view.popup.php. Sometimes there are fields on the search form that are not referenced in the listviewdefs. If this
61 * is the case, then the filterFields will be set and the related fields will not be referenced when calling create_new_list_query.
63 var $mergeDisplayColumns = false;
64 public $actionsMenuExtraItems = array();
70 function ListViewDisplay() {
71 $this->lvd = new ListViewData();
72 $this->searchColumns = array () ;
74 function shouldProcess($moduleDir){
76 $sessionSearchQuery = "{$moduleDir}2_QUERY_QUERY";
77 if (!empty($_SESSION[$sessionSearchQuery])) {
80 if(!empty($GLOBALS['sugar_config']['save_query']) && $GLOBALS['sugar_config']['save_query'] == 'populate_only'){
81 if(empty($GLOBALS['displayListView'])
82 && (!empty($_REQUEST['clear_query'])
83 || $_REQUEST['module'] == $moduleDir
84 && ((empty($_REQUEST['query']) || $_REQUEST['query'] == 'MSI' )
86 $_SESSION['last_search_mod'] = $_REQUEST['module'] ;
87 $this->should_process = false;
91 $this->should_process = true;
97 * @param seed SugarBean Seed SugarBean to use
98 * @param file File Template file to use
99 * @param string $where
100 * @param offset:0 int offset to start at
101 * @param int:-1 $limit
102 * @param string[]:array() $filter_fields
103 * @param array:array() $params
104 * Potential $params are
105 $params['distinct'] = use distinct key word
106 $params['include_custom_fields'] = (on by default)
107 $params['massupdate'] = true by default;
108 $params['handleMassupdate'] = true by default, have massupdate.php handle massupdates?
109 * @param string:'id' $id_field
111 function setup($seed, $file, $where, $params = array(), $offset = 0, $limit = -1, $filter_fields = array(), $id_field = 'id') {
112 $this->should_process = true;
113 if(isset($seed->module_dir) && !$this->shouldProcess($seed->module_dir)){
116 if(isset($params['export'])) {
117 $this->export = $params['export'];
119 if(!empty($params['multiSelectPopup'])) {
120 $this->multi_select_popup = $params['multiSelectPopup'];
122 if(!empty($params['massupdate']) && $params['massupdate'] != false) {
123 $this->show_mass_update_form = true;
124 $this->mass = $this->getMassUpdate();
125 $this->mass->setSugarBean($seed);
126 if(!empty($params['handleMassupdate']) || !isset($params['handleMassupdate'])) {
127 $this->mass->handleMassUpdate();
132 $filter_fields = $this->setupFilterFields($filter_fields);
134 $data = $this->lvd->getListViewData($seed, $where, $offset, $limit, $filter_fields, $params, $id_field);
136 foreach($this->displayColumns as $columnName => $def)
138 $seedName = strtolower($columnName);
139 if(!empty($this->lvd->seed->field_defs[$seedName])){
140 $seedDef = $this->lvd->seed->field_defs[$seedName];
143 if(empty($this->displayColumns[$columnName]['type'])){
144 if(!empty($seedDef['type'])){
145 $this->displayColumns[$columnName]['type'] = (!empty($seedDef['custom_type']))?$seedDef['custom_type']:$seedDef['type'];
147 $this->displayColumns[$columnName]['type'] = '';
151 if(!empty($seedDef['options'])){
152 $this->displayColumns[$columnName]['options'] = $seedDef['options'];
156 if($this->displayColumns[$columnName]['type'] == 'html') {
157 $cField = $this->seed->custom_fields;
158 if(isset($cField) && isset($cField->bean->$seedName)) {
159 $seedName2 = strtoupper($columnName);
160 $htmlDisplay = html_entity_decode($cField->bean->$seedName);
162 while($count < count($data['data'])) {
163 $data['data'][$count][$seedName2] = &$htmlDisplay;
169 //Bug 40511, make sure relate fields have the correct module defined
170 if ($this->displayColumns[$columnName]['type'] == "relate" && !empty($seedDef['link']) && empty( $this->displayColumns[$columnName]['module']))
172 $link = $seedDef['link'];
173 if (!empty($this->lvd->seed->field_defs[$link]) && !empty($this->lvd->seed->field_defs[$seedDef['link']]['module']))
175 $this->displayColumns[$columnName]['module'] = $this->lvd->seed->field_defs[$seedDef['link']]['module'];
179 if (!empty($seedDef['sort_on'])) {
180 $this->displayColumns[$columnName]['orderBy'] = $seedDef['sort_on'];
184 // Merge the two arrays together, making sure the seedDef doesn't override anything explicitly set in the displayColumns array.
185 $this->displayColumns[$columnName] = $this->displayColumns[$columnName] + $seedDef;
188 //C.L. Bug 38388 - ensure that ['id'] is set for related fields
189 if(!isset($this->displayColumns[$columnName]['id']) && isset($this->displayColumns[$columnName]['id_name'])) {
190 $this->displayColumns[$columnName]['id'] = strtoupper($this->displayColumns[$columnName]['id_name']);
194 $this->process($file, $data, $seed->object_name);
198 function setupFilterFields($filter_fields = array())
200 // create filter fields based off of display columns
201 if(empty($filter_fields) || $this->mergeDisplayColumns) {
202 foreach($this->displayColumns as $columnName => $def) {
204 $filter_fields[strtolower($columnName)] = true;
206 if(isset($this->seed->field_defs[strtolower($columnName)]['type']) &&
207 strtolower($this->seed->field_defs[strtolower($columnName)]['type']) == 'currency' &&
208 isset($this->seed->field_defs['currency_id'])) {
209 $filter_fields['currency_id'] = true;
212 if(!empty($def['related_fields'])) {
213 foreach($def['related_fields'] as $field) {
214 //id column is added by query construction function. This addition creates duplicates
215 //and causes issues in oracle. #10165
216 if ($field != 'id') {
217 $filter_fields[$field] = true;
221 if (!empty($this->seed->field_defs[strtolower($columnName)]['db_concat_fields'])) {
222 foreach($this->seed->field_defs[strtolower($columnName)]['db_concat_fields'] as $index=>$field){
223 if(!isset($filter_fields[strtolower($field)]) || !$filter_fields[strtolower($field)])
225 $filter_fields[strtolower($field)] = true;
230 foreach ($this->searchColumns as $columnName => $def )
232 $filter_fields[strtolower($columnName)] = true;
237 return $filter_fields;
242 * Any additional processing
243 * @param file File template file to use
244 * @param data array row data
245 * @param html_var string html string to be passed back and forth
247 function process($file, $data, $htmlVar) {
248 $this->rowCount = count($data['data']);
249 $this->moduleString = $data['pageData']['bean']['moduleDir'] . '2_' . strtoupper($htmlVar) . '_offset';
253 * Display the listview
254 * @return string ListView contents
256 public function display()
258 if (!$this->should_process) {
263 if ($this->show_mass_update_form) {
264 $str = $this->mass->getDisplayMassUpdateForm(true, $this->multi_select_popup).$this->mass->getMassUpdateFormHeader($this->multi_select_popup);
270 * Display the select link
271 * @return string select link html
272 * @param echo Bool set true if you want it echo'd, set false to have contents returned
274 function buildSelectLink($id = 'select_link', $total=0, $pageTotal=0) {
279 if (!empty($GLOBALS['sugar_config']['disable_count_query']) && $total > $pageTotal) {
281 $this->show_plus = true;
285 $close_inline_img = SugarThemeRegistry::current()->getImage('close_inline', 'border=0', null, null, ".gif", $app_strings['LBL_CLOSEINLINE']);
286 $menuItems = "<li><a name='thispage' class='menuItem' onmouseover='hiliteItem(this,\"yes\");' onmouseout='unhiliteItem(this);' onclick='if (document.MassUpdate.select_entire_list.value==1){document.MassUpdate.select_entire_list.value=0;sListView.check_all(document.MassUpdate, \"mass[]\", true, $pageTotal)}else {sListView.check_all(document.MassUpdate, \"mass[]\", true)};' href='#'>{$app_strings['LBL_LISTVIEW_OPTION_CURRENT']} ({$pageTotal})‎</a></li>"
287 . "<li><a name='selectall' class='menuItem' onmouseover='hiliteItem(this,\"yes\");' onmouseout='unhiliteItem(this);' onclick='sListView.check_entire_list(document.MassUpdate, \"mass[]\",true,{$total});' href='#'>{$app_strings['LBL_LISTVIEW_OPTION_ENTIRE']} ({$total})‎</a></li>"
288 . "<li><a name='deselect' class='menuItem' onmouseover='hiliteItem(this,\"yes\");' onmouseout='unhiliteItem(this);' onclick='sListView.clear_all(document.MassUpdate, \"mass[]\", false);' href='#'>{$app_strings['LBL_LISTVIEW_NONE']}</a></li>";
290 $script = "<ul class='clickMenu' id='selectLink'>";
292 $script .="<input title=\"".$app_strings['LBL_SELECT_ALL_TITLE']."\" type='checkbox' class='checkbox' name='massall' id='massall' value='' onclick='sListView.check_all(document.MassUpdate, \"mass[]\", this.checked);' /><a id='$id' href='javascript: void(0);'></a>";
293 $script .="<ul class='subnav'>";
294 $script .= $menuItems;
302 * Display the actions link
304 * @param string $id link id attribute, defaults to 'actions_link'
305 * @return string HTML source
307 protected function buildActionsLink($id = 'actions_link')
310 $closeText = SugarThemeRegistry::current()->getImage('close_inline', 'border=0', null, null, ".gif", $app_strings['LBL_CLOSEINLINE']);
311 $moreDetailImage = SugarThemeRegistry::current()->getImageURL('MoreDetail.png');
312 $menuItems = array();
315 if ( ACLController::checkAccess($this->seed->module_dir,'delete',true) && $this->delete )
316 $menuItems[] = $this->buildDeleteLink();
319 $menuItems[] = $this->buildComposeEmailLink($this->data['pageData']['offsets']['total']);
321 $mass = $this->getMassUpdate();
322 $mass->setSugarBean($this->seed);
323 if ( ( ACLController::checkAccess($this->seed->module_dir,'edit',true) && ACLController::checkAccess($this->seed->module_dir,'massupdate',true) ) && $this->showMassupdateFields && $mass->doMassUpdateFieldsExistForFocus() )
324 $menuItems[] = $this->buildMassUpdateLink();
326 if ( $this->mailMerge )
327 $menuItems[] = $this->buildMergeLink();
328 if ( $this->mergeduplicates )
329 $menuItems[] = $this->buildMergeDuplicatesLink();
330 // add to target list
331 if ( $this->targetList && ACLController::checkAccess('ProspectLists','edit',true) )
332 $menuItems[] = $this->buildTargetList();
334 if ( ACLController::checkAccess($this->seed->module_dir,'export',true) && $this->export )
335 $menuItems[] = $this->buildExportLink();
337 foreach ( $this->actionsMenuExtraItems as $item )
338 $menuItems[] = $item;
340 $output = "<ul class='clickMenu fancymenu' id='selectActions' name='selectActions'>";
342 foreach($menuItems as $item)
346 $output .= "<li>$item<ul class='subnav'>";
350 $output .= "<li>$item</li>";
355 $output .= "</ul></li></ul>";
356 $output .= "<div id='selectActionsDisabled' class='selectActionsDisabled'>{$menuItems[0]}<span class='ab'></span></div>";
363 * Builds the export link
365 * @return string HTML
367 protected function buildExportLink()
370 return "<a href='javascript:void(0)' onclick=\"return sListView.send_form(true, '{$this->seed->module_dir}', 'index.php?entryPoint=export','{$app_strings['LBL_LISTVIEW_NO_SELECTED']}')\">{$app_strings['LBL_EXPORT']}</a>";
374 * Builds the massupdate link
376 * @return string HTML
378 protected function buildMassUpdateLink()
382 $onClick = "document.getElementById('massupdate_form').style.display = ''; var yLoc = YAHOO.util.Dom.getY('massupdate_form'); scroll(0,yLoc);";
383 return "<a href='javascript:void(0)' onclick=\"$onClick\">{$app_strings['LBL_MASS_UPDATE']}</a>";
388 * Builds the compose email link
390 * @return string HTML
392 protected function buildComposeEmailLink($totalCount)
394 global $app_strings,$dictionary;
396 if (!is_array($this->seed->field_defs)) {
399 $foundEmailField = false;
400 // Search for fields that look like an email address
401 foreach ($this->seed->field_defs as $field) {
402 if(isset($field['type'])&&$field['type']=='link'
403 &&isset($field['relationship'])&&isset($dictionary[$this->seed->object_name]['relationships'][$field['relationship']])
404 &&$dictionary[$this->seed->object_name]['relationships'][$field['relationship']]['rhs_module']=='EmailAddresses') {
405 $foundEmailField = true;
409 if (!$foundEmailField) {
414 $userPref = $GLOBALS['current_user']->getPreference('email_link_type');
415 $defaultPref = $GLOBALS['sugar_config']['email_default_client'];
419 $client = $defaultPref;
421 if($client == 'sugar')
422 $script = "<a href='javascript:void(0)' " .
423 'onclick="return sListView.send_form_for_emails(true, \''."Emails".'\', \'index.php?module=Emails&action=Compose&ListView=true\',\''.$app_strings['LBL_LISTVIEW_NO_SELECTED'].'\', \''.$this->seed->module_dir.'\', \''.$totalCount.'\', \''.$app_strings['LBL_LISTVIEW_LESS_THAN_TEN_SELECT'].'\')">' .
424 $app_strings['LBL_EMAIL_COMPOSE'] . '</a>';
426 $script = "<a href='javascript:void(0)' " .
427 "onclick=\"return sListView.use_external_mail_client('{$app_strings['LBL_LISTVIEW_NO_SELECTED']}', '{$_REQUEST['module']}');\">" .
428 $app_strings['LBL_EMAIL_COMPOSE'] . '</a>';
433 * Builds the delete link
435 * @return string HTML
437 protected function buildDeleteLink()
440 return "<a href='javascript:void(0)' onclick=\"return sListView.send_mass_update('selected', '{$app_strings['LBL_LISTVIEW_NO_SELECTED']}', 1)\">{$app_strings['LBL_DELETE_BUTTON_LABEL']}</a>";
443 * Display the selected object span object
445 * @return string select object span
447 function buildSelectedObjectsSpan($echo = true, $total=0) {
450 $selectedObjectSpan = "<span style='display: none;' id='selectedRecordsTop'>{$app_strings['LBL_LISTVIEW_SELECTED_OBJECTS']}<input style='border: 0px; background: transparent; font-size: inherit; color: inherit' type='text' id='selectCountTop' readonly name='selectCount[]' value='{$total}' /></span>";
452 return $selectedObjectSpan;
455 * Builds the mail merge link
456 * The link can be disabled by setting module level duplicate_merge property to false
457 * in the moudle's vardef file.
459 * @return string HTML
461 protected function buildMergeDuplicatesLink()
463 global $app_strings, $dictionary;
466 $return_string.= isset($_REQUEST['module']) ? "&return_module={$_REQUEST['module']}" : "";
467 $return_string.= isset($_REQUEST['action']) ? "&return_action={$_REQUEST['action']}" : "";
468 $return_string.= isset($_REQUEST['record']) ? "&return_id={$_REQUEST['record']}" : "";
469 //need delete and edit access.
470 if (!(ACLController::checkAccess($this->seed->module_dir, 'edit', true)) or !(ACLController::checkAccess($this->seed->module_dir, 'delete', true))) {
474 if (isset($dictionary[$this->seed->object_name]['duplicate_merge']) && $dictionary[$this->seed->object_name]['duplicate_merge']==true ) {
475 return "<a href='javascript:void(0)' ".
476 "onclick='if (sugarListView.get_checks_count()> 1) {sListView.send_form(true, \"MergeRecords\", \"index.php\", \"{$app_strings['LBL_LISTVIEW_NO_SELECTED']}\", \"{$this->seed->module_dir}\",\"$return_string\");} else {alert(\"{$app_strings['LBL_LISTVIEW_TWO_REQUIRED']}\");return false;}'>".
477 $app_strings['LBL_MERGE_DUPLICATES'].'</a>';
483 * Builds the mail merge link
485 * @return string HTML
487 protected function buildMergeLink(array $modules_array = null)
489 if ( empty($modules_array) ) {
490 require('modules/MailMerge/modules_array.php');
492 global $current_user, $app_strings;
494 $admin = new Administration();
495 $admin->retrieveSettings('system');
496 $user_merge = $current_user->getPreference('mailmerge_on');
497 $module_dir = (!empty($this->seed->module_dir) ? $this->seed->module_dir : '');
500 if ($user_merge == 'on' && isset($admin->settings['system_mailmerge_on']) && $admin->settings['system_mailmerge_on'] && !empty($modules_array[$module_dir])) {
501 return "<a href='javascript:void(0)' " .
502 'onclick="if (document.MassUpdate.select_entire_list.value==1){document.location.href=\'index.php?action=index&module=MailMerge&entire=true\'} else {return sListView.send_form(true, \'MailMerge\',\'index.php\',\''.$app_strings['LBL_LISTVIEW_NO_SELECTED'].'\');}">' .
503 $app_strings['LBL_MAILMERGE'].'</a>';
509 * Builds the add to target list link
511 * @return string HTML
513 protected function buildTargetList()
516 unset($_REQUEST[session_name()]);
517 unset($_REQUEST['PHPSESSID']);
518 $current_query_by_page = base64_encode(serialize($_REQUEST));
521 if(sugarListView.get_checks_count() < 1) {
522 alert('{$app_strings['LBL_LISTVIEW_NO_SELECTED']}');
525 if ( document.forms['targetlist_form'] ) {
526 var form = document.forms['targetlist_form'];
529 var form = document.createElement ( 'form' ) ;
530 form.setAttribute ( 'name' , 'targetlist_form' );
531 form.setAttribute ( 'method' , 'post' ) ;
532 form.setAttribute ( 'action' , 'index.php' );
533 document.body.appendChild ( form ) ;
534 if ( !form.module ) {
535 var input = document.createElement('input');
536 input.setAttribute ( 'name' , 'module' );
537 input.setAttribute ( 'value' , '{$this->seed->module_dir}' );
538 input.setAttribute ( 'type' , 'hidden' );
539 form.appendChild ( input ) ;
540 var input = document.createElement('input');
541 input.setAttribute ( 'name' , 'action' );
542 input.setAttribute ( 'value' , 'TargetListUpdate' );
543 input.setAttribute ( 'type' , 'hidden' );
544 form.appendChild ( input ) ;
547 var input = document.createElement('input');
548 input.setAttribute ( 'name' , 'uids' );
549 input.setAttribute ( 'type' , 'hidden' );
550 form.appendChild ( input ) ;
552 if ( !form.prospect_list ) {
553 var input = document.createElement('input');
554 input.setAttribute ( 'name' , 'prospect_list' );
555 input.setAttribute ( 'type' , 'hidden' );
556 form.appendChild ( input ) ;
558 if ( !form.return_module ) {
559 var input = document.createElement('input');
560 input.setAttribute ( 'name' , 'return_module' );
561 input.setAttribute ( 'type' , 'hidden' );
562 form.appendChild ( input ) ;
564 if ( !form.return_action ) {
565 var input = document.createElement('input');
566 input.setAttribute ( 'name' , 'return_action' );
567 input.setAttribute ( 'type' , 'hidden' );
568 form.appendChild ( input ) ;
570 if ( !form.select_entire_list ) {
571 var input = document.createElement('input');
572 input.setAttribute ( 'name' , 'select_entire_list' );
573 input.setAttribute ( 'value', document.MassUpdate.select_entire_list.value);
574 input.setAttribute ( 'type' , 'hidden' );
575 form.appendChild ( input ) ;
577 if ( !form.current_query_by_page ) {
578 var input = document.createElement('input');
579 input.setAttribute ( 'name' , 'current_query_by_page' );
580 input.setAttribute ( 'value', '{$current_query_by_page}' );
581 input.setAttribute ( 'type' , 'hidden' );
582 form.appendChild ( input ) ;
584 open_popup('ProspectLists','600','400','',true,false,{ 'call_back_function':'set_return_and_save_targetlist','form_name':'targetlist_form','field_to_name_array':{'id':'prospect_list'} } );
586 $js = str_replace(array("\r","\n"),'',$js);
587 return "<a href='javascript:void(0)' onclick=\"$js\">{$app_strings['LBL_ADD_TO_PROSPECT_LIST_BUTTON_LABEL']}</a>";
590 * Display the bottom of the ListView (ie MassUpdate
591 * @return string contents
593 public function displayEnd()
596 if($this->show_mass_update_form) {
597 $str .= $this->mass->getMassUpdateForm(true);
598 $str .= $this->mass->endMassUpdateForm();
605 * Display the multi select data box etc.
606 * @return string contents
608 public function getMultiSelectData()
610 $str = "<script>YAHOO.util.Event.addListener(window, \"load\", sListView.check_boxes);</script>\n";
612 $massUpdateRun = isset($_REQUEST['massupdate']) && $_REQUEST['massupdate'] == 'true';
613 $uids = empty($_REQUEST['uid']) || $massUpdateRun ? '' : $_REQUEST['uid'];
614 $select_entire_list = isset($_REQUEST['select_entire_list']) && !$massUpdateRun ? $_REQUEST['select_entire_list'] : 0;
616 $str .= "<textarea style='display: none' name='uid'>{$uids}</textarea>\n" .
617 "<input type='hidden' name='select_entire_list' value='{$select_entire_list}'>\n".
618 "<input type='hidden' name='{$this->moduleString}' value='0'>\n".
619 "<input type='hidden' name='show_plus' value='{$this->show_plus}'>\n";
624 * @return MassUpdate instance
626 protected function getMassUpdate()
628 return new MassUpdate();