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 ********************************************************************************/
45 protected $module = "";
48 * @param string $current_module
50 public function __construct($current_module = "")
52 $this->module = $current_module;
57 * Performs the search and returns the HTML widget containing the results
59 * @param $query string what we are searching for
60 * @param $modules array modules we are searching in
61 * @param $offset int search result offset
62 * @return string HTML code containing results
64 * @deprecated deprecated since 6.5
66 public function searchAndDisplay($query, $modules, $offset=-1)
68 $query_encoded = urlencode($query);
69 $formattedResults = $this->formatSearchResultsToDisplay($query, $modules, $offset);
70 $displayMoreForModule = $formattedResults['displayMoreForModule'];
71 $displayResults = $formattedResults['displayResults'];
73 $ss = new Sugar_Smarty();
74 $ss->assign('displayResults', $displayResults);
75 $ss->assign('displayMoreForModule', $displayMoreForModule);
76 $ss->assign('appStrings', $GLOBALS['app_strings']);
77 $ss->assign('appListStrings', $GLOBALS['app_list_strings']);
78 $ss->assign('queryEncoded', $query_encoded);
79 $template = 'include/SearchForm/tpls/SugarSpot.tpl';
80 if(file_exists('custom/include/SearchForm/tpls/SugarSpot.tpl'))
82 $template = 'custom/include/SearchForm/tpls/SugarSpot.tpl';
84 return $ss->fetch($template);
88 protected function formatSearchResultsToDisplay($query, $modules, $offset=-1)
90 $results = $this->_performSearch($query, $modules, $offset);
91 $displayResults = array();
92 $displayMoreForModule = array();
94 foreach($results as $m=>$data)
96 if(empty($data['data']))
101 $countRemaining = $data['pageData']['offsets']['total'] - count($data['data']);
104 $countRemaining -= $offset;
107 if($countRemaining > 0)
109 $displayMoreForModule[$m] = array('query'=>$query,
110 'offset'=>$data['pageData']['offsets']['next']++,
111 'countRemaining'=>$countRemaining);
114 foreach($data['data'] as $row)
118 //Determine a name to use
119 if(!empty($row['NAME']))
121 $name = $row['NAME'];
123 else if(!empty($row['DOCUMENT_NAME']))
125 $name = $row['DOCUMENT_NAME'];
130 foreach($row as $k=>$v)
132 if(strpos($k, 'NAME') !== false)
139 else if(empty($foundName))
152 $displayResults[$m][$row['ID']] = $name;
156 return array('displayResults' => $displayResults, 'displayMoreForModule' => $displayMoreForModule);
159 * Returns the array containing the $searchFields for a module. This function
160 * first checks the default installation directories for the SearchFields.php file and then
161 * loads any custom definition (if found)
163 * @param $moduleName String name of module to retrieve SearchFields entries for
164 * @return array of SearchFields
166 protected static function getSearchFields( $moduleName )
168 $searchFields = array();
170 if(file_exists("modules/{$moduleName}/metadata/SearchFields.php"))
172 require("modules/{$moduleName}/metadata/SearchFields.php");
175 if(file_exists("custom/modules/{$moduleName}/metadata/SearchFields.php"))
177 require("custom/modules/{$moduleName}/metadata/SearchFields.php");
180 return $searchFields;
185 * Get count from query
186 * @param SugarBean $seed
187 * @param string $main_query
189 protected function _getCount($seed, $main_query)
191 $result = $seed->db->query("SELECT COUNT(*) as c FROM ($main_query) main");
192 $row = $seed->db->fetchByAssoc($result);
193 return isset($row['c'])?$row['c']:0;
197 * Determine which modules should be searched against.
201 protected function getSearchModules()
203 $usa = new UnifiedSearchAdvanced();
204 $unified_search_modules_display = $usa->getUnifiedSearchModulesDisplay();
206 // load the list of unified search enabled modules
209 //check to see if the user has customized the list of modules available to search
210 $users_modules = $GLOBALS['current_user']->getPreference('globalSearch', 'search');
212 if(!empty($users_modules))
214 // use user's previous selections
215 foreach ($users_modules as $key => $value )
217 if (isset($unified_search_modules_display[$key]) && !empty($unified_search_modules_display[$key]['visible']))
219 $modules[$key] = $key;
225 foreach($unified_search_modules_display as $key=>$data)
227 if (!empty($data['visible']))
229 $modules[$key] = $key;
233 // make sure the current module appears first in the list
234 if(isset($modules[$this->module]))
236 unset($modules[$this->module]);
237 $modules = array_merge(array($this->module=>$this->module),$modules);
246 * @param $query string what we are searching for
247 * @param $offset int search result offset
250 public function search($query, $offset = -1, $limit = 20, $options = array())
252 if( isset($options['modules']) && !empty($options['modules']) )
253 $modules = $options['modules'];
255 $modules = $this->getSearchModules();
257 return $this->_performSearch($query, $modules, $offset, $limit);
263 * Performs the search from the global search field.
265 * @param $query string what we are searching for
266 * @param $modules array modules we are searching in
267 * @param $offset int search result offset
268 * @param $limit int search limit
271 protected function _performSearch($query, $modules, $offset = -1, $limit = 20)
273 if(empty($query)) return array();
276 require_once 'include/SearchForm/SearchForm2.php' ;
278 $searchEmail = preg_match('/^([^%]|%)*@([^%]|%)*$/', $query);
280 // bug49650 - strip out asterisks from query in case
281 // user thinks asterisk is a wildcard value
282 $query = str_replace( '*' , '' , $query );
284 $limit = !empty($GLOBALS['sugar_config']['max_spotresults_initial']) ? $GLOBALS['sugar_config']['max_spotresults_initial'] : 5;
286 $limit = !empty($GLOBALS['sugar_config']['max_spotresults_more']) ? $GLOBALS['sugar_config']['max_spotresults_more'] : 20;
288 $totalCounted = empty($GLOBALS['sugar_config']['disable_count_query']);
291 foreach($modules as $moduleName)
293 if (empty($primary_module))
295 $primary_module=$moduleName;
298 $searchFields = SugarSpot::getSearchFields($moduleName);
300 if (empty($searchFields[$moduleName]))
305 $class = $GLOBALS['beanList'][$moduleName];
306 $return_fields = array();
307 $seed = new $class();
308 if(!$seed->ACLAccess('ListView')) continue;
310 if ($class == 'aCase')
315 foreach($searchFields[$moduleName] as $k=>$v)
318 $searchFields[$moduleName][$k]['value'] = $query;
319 if(!empty($searchFields[$moduleName][$k]['force_unifiedsearch']))
324 if(!empty($GLOBALS['dictionary'][$class]['unified_search'])){
326 if(empty($GLOBALS['dictionary'][$class]['fields'][$k]['unified_search'])){
328 if(isset($searchFields[$moduleName][$k]['db_field']))
330 foreach($searchFields[$moduleName][$k]['db_field'] as $field)
332 if(!empty($GLOBALS['dictionary'][$class]['fields'][$field]['unified_search']))
334 if(isset($GLOBALS['dictionary'][$class]['fields'][$field]['type']))
336 if(!$this->filterSearchType($GLOBALS['dictionary'][$class]['fields'][$field]['type'], $query))
338 unset($searchFields[$moduleName][$k]);
347 # Bug 42961 Spot search for custom fields
348 if (!$keep && (isset($v['force_unifiedsearch']) == false || $v['force_unifiedsearch'] != true))
350 if(strpos($k,'email') === false || !$searchEmail) {
351 unset($searchFields[$moduleName][$k]);
355 if($GLOBALS['dictionary'][$class]['fields'][$k]['type'] == 'int' && !is_numeric($query)) {
356 unset($searchFields[$moduleName][$k]);
359 }else if(empty($GLOBALS['dictionary'][$class]['fields'][$k]) ){
360 //If module did not have unified_search defined, then check the exception for an email search before we unset
361 if(strpos($k,'email') === false || !$searchEmail)
363 unset($searchFields[$moduleName][$k]);
365 }else if(!$this->filterSearchType($GLOBALS['dictionary'][$class]['fields'][$k]['type'], $query)){
366 unset($searchFields[$moduleName][$k]);
370 //If no search field criteria matched then continue to next module
371 if (empty($searchFields[$moduleName]))
376 if (empty($searchFields[$moduleName])) continue;
378 if(isset($seed->field_defs['name']))
380 $return_fields['name'] = $seed->field_defs['name'];
383 foreach($seed->field_defs as $k => $v)
385 if(isset($seed->field_defs[$k]['type']) && ($seed->field_defs[$k]['type'] == 'name') && !isset($return_fields[$k]))
387 $return_fields[$k] = $seed->field_defs[$k];
391 if(!isset($return_fields['name']))
393 // if we couldn't find any name fields, try search fields that have name in it
394 foreach($searchFields[$moduleName] as $k => $v)
396 if(strpos($k, 'name') != -1 && isset($seed->field_defs[$k]) && !isset($seed->field_defs[$k]['source']))
398 $return_fields[$k] = $seed->field_defs[$k];
404 if(!isset($return_fields['name']))
406 // last resort - any fields that have 'name' in their name
407 foreach($seed->field_defs as $k => $v)
409 if(strpos($k, 'name') != -1 && isset($seed->field_defs[$k])
410 && !isset($seed->field_defs[$k]['source'])) {
411 $return_fields[$k] = $seed->field_defs[$k];
417 if(!isset($return_fields['name']))
419 // FAIL: couldn't find id & name for the module
420 $GLOBALS['log']->error("Unable to find name for module $moduleName");
424 if(isset($return_fields['name']['fields']))
426 // some names are composite
427 foreach($return_fields['name']['fields'] as $field)
429 $return_fields[$field] = $seed->field_defs[$field];
434 $searchForm = new SearchForm ( $seed, $moduleName ) ;
435 $searchForm->setup (array ( $moduleName => array() ) , $searchFields , '' , 'saved_views' /* hack to avoid setup doing further unwanted processing */ ) ;
436 $where_clauses = $searchForm->generateSearchWhere() ;
438 if(empty($where_clauses))
442 if(count($where_clauses) > 1)
444 $query_parts = array();
446 $ret_array_start = $seed->create_new_list_query('', '', $return_fields, array(), 0, '', true, $seed, true);
447 $search_keys = array_keys($searchFields[$moduleName]);
449 foreach($where_clauses as $n => $clause)
451 $allfields = $return_fields;
452 $skey = $search_keys[$n];
453 if(isset($seed->field_defs[$skey]))
455 // Joins for foreign fields aren't produced unless the field is in result, hence the merge
456 $allfields[$skey] = $seed->field_defs[$skey];
458 $ret_array = $seed->create_new_list_query('', $clause, $allfields, array(), 0, '', true, $seed, true);
459 $query_parts[] = $ret_array_start['select'] . $ret_array['from'] . $ret_array['where'] . $ret_array['order_by'];
461 $main_query = "(".join(") UNION (", $query_parts).")";
465 foreach($searchFields[$moduleName] as $k=>$v)
467 if(isset($seed->field_defs[$k]))
469 $return_fields[$k] = $seed->field_defs[$k];
472 $ret_array = $seed->create_new_list_query('', $where_clauses[0], $return_fields, array(), 0, '', true, $seed, true);
473 $main_query = $ret_array['select'] . $ret_array['from'] . $ret_array['where'] . $ret_array['order_by'];
479 $result = $seed->db->query($main_query);
485 $limit = $GLOBALS['sugar_config']['list_max_entries_per_page'];
490 $totalCount = $this->_getCount($seed, $main_query);
493 $offset = (floor(($totalCount -1) / $limit)) * $limit;
499 $result = $seed->db->limitQuery($main_query, $offset, $limit + 1);
504 while($count < $limit && ($row = $seed->db->fetchByAssoc($result)))
507 $temp->setupCustomFields($temp->module_dir);
508 $temp->loadFromRow($row);
509 $data[] = $temp->get_list_view_data($return_fields);
519 $nextOffset = $offset + $limit;
524 $prevOffset = $offset - $limit;
525 if($prevOffset < 0) $prevOffset = 0;
528 if( $count >= $limit && $totalCounted)
530 if(!isset($totalCount))
532 $totalCount = $this->_getCount($seed, $main_query);
536 $totalCount = $count + $offset;
539 $pageData['offsets'] = array( 'current'=>$offset, 'next'=>$nextOffset, 'prev'=>$prevOffset, 'end'=>$endOffset, 'total'=>$totalCount, 'totalCounted'=>$totalCounted);
540 $pageData['bean'] = array('objectName' => $seed->object_name, 'moduleDir' => $seed->module_dir);
542 $results[$moduleName] = array("data" => $data, "pageData" => $pageData);
549 * Function used to walk the array and find keys that map the queried string.
550 * if both the pattern and module name is found the promote the string to thet top.
552 protected function _searchKeys($item1, $key, $patterns)
554 //make the module name singular....
555 if ($patterns[1][strlen($patterns[1])-1] == 's')
557 $patterns[1]=substr($patterns[1],0,(strlen($patterns[1])-1));
560 $module_exists = stripos($key,$patterns[1]); //primary module name.
561 $pattern_exists = stripos($key,$patterns[0]); //pattern provided by the user.
562 if ($module_exists !== false and $pattern_exists !== false)
564 $GLOBALS['matching_keys']= array_merge(array(array('NAME'=>$key, 'ID'=>$key, 'VALUE'=>$item1)),$GLOBALS['matching_keys']);
568 if ($pattern_exists !== false)
570 $GLOBALS['matching_keys'][]=array('NAME'=>$key, 'ID'=>$key, 'VALUE'=>$item1);
579 * This is a private function to determine if the search type field should be filtered out based on the query string value
581 * @param String $type The string value of the field type (e.g. phone, date, datetime, int, etc.)
582 * @param String $query The search string value sent from the global search
583 * @return boolean True if the search type fits the query string value; false otherwise
585 protected function filterSearchType($type, $query)
596 if(!is_numeric($query)) {
601 //For a phone search we require at least three digits
602 if(!preg_match('/[0-9]{3,}/', $query))
608 if(!preg_match('/[0-9]/', $query))