]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SearchForm/SugarSpot.php
Release 6.5.0
[Github/sugarcrm.git] / include / SearchForm / SugarSpot.php
1 <?php
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.
6  * 
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.
13  * 
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
17  * details.
18  * 
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
22  * 02110-1301 USA.
23  * 
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.
26  * 
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.
30  * 
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  ********************************************************************************/
37
38
39 /**
40  * Global search
41  * @api
42  */
43 class SugarSpot
44 {
45     protected $module = "";
46
47     /**
48      * @param string $current_module
49      */
50     public function __construct($current_module = "")
51     {
52         $this->module = $current_module;
53     }
54         /**
55      * searchAndDisplay
56      *
57          * Performs the search and returns the HTML widget containing the results
58          *
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
63      *
64      * @deprecated deprecated since 6.5
65          */
66         public function searchAndDisplay($query, $modules, $offset=-1)
67         {
68         $query_encoded = urlencode($query);
69         $formattedResults = $this->formatSearchResultsToDisplay($query, $modules, $offset);
70         $displayMoreForModule = $formattedResults['displayMoreForModule'];
71         $displayResults = $formattedResults['displayResults'];
72
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'))
81         {
82             $template = 'custom/include/SearchForm/tpls/SugarSpot.tpl';
83         }
84         return $ss->fetch($template);
85         }
86
87
88     protected function formatSearchResultsToDisplay($query, $modules, $offset=-1)
89     {
90         $results = $this->_performSearch($query, $modules, $offset);
91         $displayResults = array();
92         $displayMoreForModule = array();
93         //$actions=0;
94         foreach($results as $m=>$data)
95         {
96             if(empty($data['data']))
97             {
98                 continue;
99             }
100
101             $countRemaining = $data['pageData']['offsets']['total'] - count($data['data']);
102             if($offset > 0)
103             {
104                 $countRemaining -= $offset;
105             }
106
107             if($countRemaining > 0)
108             {
109                 $displayMoreForModule[$m] = array('query'=>$query,
110                     'offset'=>$data['pageData']['offsets']['next']++,
111                     'countRemaining'=>$countRemaining);
112             }
113
114             foreach($data['data'] as $row)
115             {
116                 $name = '';
117
118                 //Determine a name to use
119                 if(!empty($row['NAME']))
120                 {
121                     $name = $row['NAME'];
122                 }
123                 else if(!empty($row['DOCUMENT_NAME']))
124                 {
125                     $name = $row['DOCUMENT_NAME'];
126                 }
127                 else
128                 {
129                     $foundName = '';
130                     foreach($row as $k=>$v)
131                     {
132                         if(strpos($k, 'NAME') !== false)
133                         {
134                             if(!empty($row[$k]))
135                             {
136                                 $name = $v;
137                                 break;
138                             }
139                             else if(empty($foundName))
140                             {
141                                 $foundName = $v;
142                             }
143                         }
144                     }
145
146                     if(empty($name))
147                     {
148                         $name = $foundName;
149                     }
150                 }
151
152                 $displayResults[$m][$row['ID']] = $name;
153             }
154         }
155
156         return array('displayResults' => $displayResults, 'displayMoreForModule' => $displayMoreForModule);
157     }
158         /**
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)
162          *
163          * @param  $moduleName String name of module to retrieve SearchFields entries for
164          * @return array of SearchFields
165          */
166         protected static function getSearchFields( $moduleName )
167         {
168                 $searchFields = array();
169
170                 if(file_exists("modules/{$moduleName}/metadata/SearchFields.php"))
171                 {
172                     require("modules/{$moduleName}/metadata/SearchFields.php");
173                 }
174
175                 if(file_exists("custom/modules/{$moduleName}/metadata/SearchFields.php"))
176                 {
177                     require("custom/modules/{$moduleName}/metadata/SearchFields.php");
178                 }
179
180                 return $searchFields;
181         }
182
183
184         /**
185          * Get count from query
186          * @param SugarBean $seed
187          * @param string $main_query
188          */
189         protected function _getCount($seed, $main_query)
190         {
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;
194         }
195
196     /**
197      * Determine which modules should be searched against.
198      *
199      * @return array
200      */
201     protected function getSearchModules()
202     {
203         $usa = new UnifiedSearchAdvanced();
204         $unified_search_modules_display = $usa->getUnifiedSearchModulesDisplay();
205
206         // load the list of unified search enabled modules
207         $modules = array();
208
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');
211
212         if(!empty($users_modules))
213         {
214             // use user's previous selections
215             foreach ($users_modules as $key => $value )
216             {
217                 if (isset($unified_search_modules_display[$key]) && !empty($unified_search_modules_display[$key]['visible']))
218                 {
219                     $modules[$key] = $key;
220                 }
221             }
222         }
223         else
224         {
225             foreach($unified_search_modules_display as $key=>$data)
226             {
227                 if (!empty($data['visible']))
228                 {
229                     $modules[$key] = $key;
230                 }
231             }
232         }
233         // make sure the current module appears first in the list
234         if(isset($modules[$this->module]))
235         {
236             unset($modules[$this->module]);
237             $modules = array_merge(array($this->module=>$this->module),$modules);
238         }
239
240         return $modules;
241     }
242
243     /**
244      * Perform a search
245      *
246      * @param $query string what we are searching for
247      * @param $offset int search result offset
248      * @return array
249      */
250     public function search($query, $offset = -1, $limit = 20, $options = array())
251     {
252         if( isset($options['modules']) && !empty($options['modules']) )
253             $modules = $options['modules'];
254         else
255             $modules = $this->getSearchModules();
256
257         return $this->_performSearch($query, $modules, $offset, $limit);
258
259     }
260         /**
261      * _performSearch
262      *
263          * Performs the search from the global search field.
264          *
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
269          * @return array
270          */
271     protected function _performSearch($query, $modules, $offset = -1, $limit = 20)
272     {
273         if(empty($query)) return array();
274         $primary_module='';
275         $results = array();
276         require_once 'include/SearchForm/SearchForm2.php' ;
277         $where = '';
278         $searchEmail = preg_match('/^([^%]|%)*@([^%]|%)*$/', $query);
279
280         // bug49650 - strip out asterisks from query in case
281         // user thinks asterisk is a wildcard value
282         $query = str_replace( '*' , '' , $query );
283         
284         $limit = !empty($GLOBALS['sugar_config']['max_spotresults_initial']) ? $GLOBALS['sugar_config']['max_spotresults_initial'] : 5;
285                 if($offset !== -1){
286                         $limit = !empty($GLOBALS['sugar_config']['max_spotresults_more']) ? $GLOBALS['sugar_config']['max_spotresults_more'] : 20;
287                 }
288         $totalCounted = empty($GLOBALS['sugar_config']['disable_count_query']);
289
290
291         foreach($modules as $moduleName)
292         {
293             if (empty($primary_module))
294             {
295                 $primary_module=$moduleName;
296             }
297
298             $searchFields = SugarSpot::getSearchFields($moduleName);
299
300             if (empty($searchFields[$moduleName]))
301             {
302                 continue;
303             }
304
305             $class = $GLOBALS['beanList'][$moduleName];
306             $return_fields = array();
307             $seed = new $class();
308             if(!$seed->ACLAccess('ListView')) continue;
309
310             if ($class == 'aCase')
311             {
312                 $class = 'Case';
313             }
314
315             foreach($searchFields[$moduleName] as $k=>$v)
316             {
317                 $keep = false;
318                 $searchFields[$moduleName][$k]['value'] = $query;
319                 if(!empty($searchFields[$moduleName][$k]['force_unifiedsearch']))
320                 {
321                     continue;
322                 }
323
324                                 if(!empty($GLOBALS['dictionary'][$class]['unified_search'])){
325
326                                         if(empty($GLOBALS['dictionary'][$class]['fields'][$k]['unified_search'])){
327
328                         if(isset($searchFields[$moduleName][$k]['db_field']))
329                         {
330                             foreach($searchFields[$moduleName][$k]['db_field'] as $field)
331                             {
332                                 if(!empty($GLOBALS['dictionary'][$class]['fields'][$field]['unified_search']))
333                                 {
334                                     if(isset($GLOBALS['dictionary'][$class]['fields'][$field]['type']))
335                                     {
336                                         if(!$this->filterSearchType($GLOBALS['dictionary'][$class]['fields'][$field]['type'], $query))
337                                         {
338                                             unset($searchFields[$moduleName][$k]);
339                                             continue;
340                                         }
341                                     }
342
343                                     $keep = true;
344                                                                 }
345                                                         } //foreach
346                                                 }
347                         # Bug 42961 Spot search for custom fields
348                         if (!$keep && (isset($v['force_unifiedsearch']) == false || $v['force_unifiedsearch'] != true))
349                         {
350                                                         if(strpos($k,'email') === false || !$searchEmail) {
351                                                                 unset($searchFields[$moduleName][$k]);
352                                                         }
353                                                 }
354                                         }else{
355                                             if($GLOBALS['dictionary'][$class]['fields'][$k]['type'] == 'int' && !is_numeric($query)) {
356                                                 unset($searchFields[$moduleName][$k]);
357                                             }
358                                         }
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)
362                                         {
363                                            unset($searchFields[$moduleName][$k]);
364                                         }
365                                 }else if(!$this->filterSearchType($GLOBALS['dictionary'][$class]['fields'][$k]['type'], $query)){
366                     unset($searchFields[$moduleName][$k]);
367                                 }
368                         } //foreach
369
370             //If no search field criteria matched then continue to next module
371                         if (empty($searchFields[$moduleName]))
372             {
373                 continue;
374             }
375
376             if (empty($searchFields[$moduleName])) continue;
377
378             if(isset($seed->field_defs['name']))
379             {
380                 $return_fields['name'] = $seed->field_defs['name'];
381             }
382
383             foreach($seed->field_defs as $k => $v)
384             {
385                 if(isset($seed->field_defs[$k]['type']) && ($seed->field_defs[$k]['type'] == 'name') && !isset($return_fields[$k]))
386                 {
387                     $return_fields[$k] = $seed->field_defs[$k];
388                 }
389             }
390
391             if(!isset($return_fields['name']))
392             {
393                 // if we couldn't find any name fields, try search fields that have name in it
394                 foreach($searchFields[$moduleName] as $k => $v)
395                 {
396                     if(strpos($k, 'name') != -1 && isset($seed->field_defs[$k]) && !isset($seed->field_defs[$k]['source']))
397                     {
398                         $return_fields[$k] = $seed->field_defs[$k];
399                         break;
400                     }
401                 }
402             }
403
404             if(!isset($return_fields['name']))
405             {
406                 // last resort - any fields that have 'name' in their name
407                 foreach($seed->field_defs as $k => $v)
408                 {
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];
412                         break;
413                     }
414                 }
415             }
416
417             if(!isset($return_fields['name']))
418             {
419                 // FAIL: couldn't find id & name for the module
420                 $GLOBALS['log']->error("Unable to find name for module $moduleName");
421                 continue;
422             }
423
424             if(isset($return_fields['name']['fields']))
425             {
426                 // some names are composite
427                 foreach($return_fields['name']['fields'] as $field)
428                 {
429                     $return_fields[$field] = $seed->field_defs[$field];
430                 }
431             }
432
433
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() ;
437
438             if(empty($where_clauses))
439             {
440                 continue;
441             }
442             if(count($where_clauses) > 1)
443             {
444                 $query_parts =  array();
445
446                 $ret_array_start = $seed->create_new_list_query('', '', $return_fields, array(), 0, '', true, $seed, true);
447                 $search_keys = array_keys($searchFields[$moduleName]);
448
449                 foreach($where_clauses as $n => $clause)
450                 {
451                     $allfields = $return_fields;
452                     $skey = $search_keys[$n];
453                     if(isset($seed->field_defs[$skey]))
454                     {
455                         // Joins for foreign fields aren't produced unless the field is in result, hence the merge
456                         $allfields[$skey] = $seed->field_defs[$skey];
457                     }
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'];
460                 }
461                 $main_query = "(".join(") UNION (", $query_parts).")";
462             }
463             else
464             {
465                 foreach($searchFields[$moduleName] as $k=>$v)
466                 {
467                     if(isset($seed->field_defs[$k]))
468                     {
469                         $return_fields[$k] = $seed->field_defs[$k];
470                     }
471                 }
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'];
474             }
475
476             $totalCount = null;
477             if($limit < -1)
478             {
479                 $result = $seed->db->query($main_query);
480             }
481             else
482             {
483                 if($limit == -1)
484                 {
485                     $limit = $GLOBALS['sugar_config']['list_max_entries_per_page'];
486                 }
487
488                 if($offset == 'end')
489                 {
490                     $totalCount = $this->_getCount($seed, $main_query);
491                     if($totalCount)
492                     {
493                         $offset = (floor(($totalCount -1) / $limit)) * $limit;
494                     } else
495                     {
496                         $offset = 0;
497                     }
498                 }
499                 $result = $seed->db->limitQuery($main_query, $offset, $limit + 1);
500             }
501
502             $data = array();
503             $count = 0;
504             while($count < $limit && ($row = $seed->db->fetchByAssoc($result)))
505             {
506                 $temp = clone $seed;
507                 $temp->setupCustomFields($temp->module_dir);
508                 $temp->loadFromRow($row);
509                 $data[] = $temp->get_list_view_data($return_fields);
510                 $count++;
511             }
512
513             $nextOffset = -1;
514             $prevOffset = -1;
515             $endOffset = -1;
516
517             if($count >= $limit)
518             {
519                 $nextOffset = $offset + $limit;
520             }
521
522             if($offset > 0)
523             {
524                 $prevOffset = $offset - $limit;
525                 if($prevOffset < 0) $prevOffset = 0;
526             }
527
528             if( $count >= $limit && $totalCounted)
529             {
530                 if(!isset($totalCount))
531                 {
532                     $totalCount  = $this->_getCount($seed, $main_query);
533                 }
534             } else
535             {
536                 $totalCount = $count + $offset;
537             }
538
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);
541
542             $results[$moduleName] = array("data" => $data, "pageData" => $pageData);
543         }
544         return $results;
545     }
546
547
548         /**
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.
551      */
552     protected function _searchKeys($item1, $key, $patterns)
553     {
554         //make the module name singular....
555         if ($patterns[1][strlen($patterns[1])-1] == 's')
556         {
557             $patterns[1]=substr($patterns[1],0,(strlen($patterns[1])-1));
558         }
559
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)
563         {
564             $GLOBALS['matching_keys']= array_merge(array(array('NAME'=>$key, 'ID'=>$key, 'VALUE'=>$item1)),$GLOBALS['matching_keys']);
565         }
566         else
567         {
568             if ($pattern_exists !== false)
569             {
570                 $GLOBALS['matching_keys'][]=array('NAME'=>$key, 'ID'=>$key, 'VALUE'=>$item1);
571             }
572         }
573     }
574
575
576     /**
577      * filterSearchType
578      *
579      * This is a private function to determine if the search type field should be filtered out based on the query string value
580      * 
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
584      */
585     protected function filterSearchType($type, $query)
586     {
587         switch($type)
588         {
589             case 'id':
590             case 'date':
591             case 'datetime':
592             case 'bool':
593                 return false;
594                 break;
595             case 'int':
596                 if(!is_numeric($query)) {
597                    return false;
598                 }
599                 break;
600             case 'phone':
601                 //For a phone search we require at least three digits
602                 if(!preg_match('/[0-9]{3,}/', $query))
603                 {
604                     return false;
605                 }
606             case 'decimal':
607             case 'float':
608                 if(!preg_match('/[0-9]/', $query))
609                 {
610                    return false;
611                 }
612                 break;
613         }
614         return true;
615     }
616
617 }