]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/Home/QuickSearch.php
Release 6.5.15
[Github/sugarcrm.git] / modules / Home / QuickSearch.php
1 <?php
2 /*********************************************************************************
3  * SugarCRM Community Edition is a customer relationship management program developed by
4  * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
5  * 
6  * This program is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Affero General Public License version 3 as published by the
8  * Free Software Foundation with the addition of the following permission added
9  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
11  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12  * 
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
16  * details.
17  * 
18  * You should have received a copy of the GNU Affero General Public License along with
19  * this program; if not, see http://www.gnu.org/licenses or write to the Free
20  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21  * 02110-1301 USA.
22  * 
23  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
24  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
25  * 
26  * The interactive user interfaces in modified source and object code versions
27  * of this program must display Appropriate Legal Notices, as required under
28  * Section 5 of the GNU Affero General Public License version 3.
29  * 
30  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
31  * these Appropriate Legal Notices must retain the display of the "Powered by
32  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
33  * technical reasons, the Appropriate Legal Notices must display the words
34  * "Powered by SugarCRM".
35  ********************************************************************************/
36
37
38 require_once('include/SugarObjects/templates/person/Person.php');
39 require_once('include/MVC/SugarModule.php');
40
41 /**
42  * quicksearchQuery class, handles AJAX calls from quicksearch.js
43  *
44  * @copyright  2004-2007 SugarCRM Inc.
45  * @license    http://www.sugarcrm.com/crm/products/sugar-professional-eula.html  SugarCRM Professional End User License
46  * @since      Class available since Release 4.5.1
47  */
48 class quicksearchQuery
49 {
50     /**
51      * Condition operators
52      * @var string
53      */
54     const CONDITION_CONTAINS    = 'contains';
55     const CONDITION_LIKE_CUSTOM = 'like_custom';
56     const CONDITION_EQUAL       = 'equal';
57
58     protected $extra_where;
59
60     /**
61      * Query a module for a list of items
62      *
63      * @param array $args
64      * example for querying Account module with 'a':
65      * array ('modules' => array('Accounts'), // module to use
66      *        'field_list' => array('name', 'id'), // fields to select
67      *        'group' => 'or', // how the conditions should be combined
68      *        'conditions' => array(array( // array of where conditions to use
69      *                              'name' => 'name', // field
70      *                              'op' => 'like_custom', // operation
71      *                              'end' => '%', // end of the query
72      *                              'value' => 'a',  // query value
73      *                              )
74      *                        ),
75      *        'order' => 'name', // order by
76      *        'limit' => '30', // limit, number of records to return
77      *       )
78      * @return array list of elements returned
79      */
80     public function query($args)
81     {
82         $args = $this->prepareArguments($args);
83         $args = $this->updateQueryArguments($args);
84         $data = $this->getRawResults($args);
85
86         return $this->getFormattedJsonResults($data, $args);
87     }
88
89     /**
90      * get_contact_array
91      *
92      */
93     public function get_contact_array($args)
94     {
95         $args    = $this->prepareArguments($args);
96         $args    = $this->updateContactArrayArguments($args);
97         $data    = $this->getRawResults($args);
98         $results = $this->prepareResults($data, $args);
99
100         return $this->getFilteredJsonResults($results);
101     }
102
103     /**
104      * Returns the list of users, faster than using query method for Users module
105      *
106      * @param array $args arguments used to construct query, see query() for example
107      * @return array list of users returned
108      */
109     public function get_user_array($args)
110     {
111         $condition = $args['conditions'][0]['value'];
112         $results   = $this->getUserResults($condition);
113
114         return $this->getJsonEncodedData($results);
115     }
116
117
118     /**
119      * Returns search results from external API
120      *
121      * @param array $args
122      * @return array
123      */
124     public function externalApi($args)
125     {
126         require_once('include/externalAPI/ExternalAPIFactory.php');
127         $data = array();
128         try {
129             $api = ExternalAPIFactory::loadAPI($args['api']);
130             $data['fields']     = $api->searchDoc($_REQUEST['query']);
131             $data['totalCount'] = count($data['fields']);
132         } catch(Exception $ex) {
133             $GLOBALS['log']->error($ex->getMessage());
134         }
135
136         return $this->getJsonEncodedData($data);
137     }
138
139
140     /**
141      * Internal function to construct where clauses
142      *
143      * @param Object $focus
144      * @param array $args
145      * @return string
146      */
147     protected function constructWhere($focus, $args)
148     {
149         global $db, $locale, $current_user;
150
151         $table = $focus->getTableName();
152         if (!empty($table)) {
153             $table_prefix = $db->getValidDBName($table).".";
154         } else {
155             $table_prefix = '';
156         }
157         $conditionArray = array();
158
159         if (!isset($args['conditions']) || !is_array($args['conditions'])) {
160             $args['conditions'] = array();
161         }
162
163         foreach($args['conditions'] as $condition)
164         {
165             if (isset($condition['op'])) {
166                 $operator = $condition['op'];
167             } else {
168                 $operator = null;
169             }
170
171             switch ($operator)
172             {
173                 case self::CONDITION_CONTAINS:
174                     array_push(
175                         $conditionArray,
176                         sprintf(
177                             "%s like '%%%s%%'",
178                             $table_prefix . $db->getValidDBName($condition['name']),
179                             $db->quote($condition['value']
180                     )));
181                     break;
182
183                 case self::CONDITION_LIKE_CUSTOM:
184                     $like = '';
185                     if (!empty($condition['begin'])) {
186                         $like .= $db->quote($condition['begin']);
187                     }
188                     $like .= $db->quote($condition['value']);
189
190                     if (!empty($condition['end'])) {
191                         $like .= $db->quote($condition['end']);
192                     }
193
194                     if ($focus instanceof Person){
195                         $nameFormat = $locale->getLocaleFormatMacro($current_user);
196
197                         if (strpos($nameFormat,'l') > strpos($nameFormat,'f')) {
198                             array_push(
199                                 $conditionArray,
200                                 $db->concat($table, array('first_name','last_name')) . " like '$like'"
201                             );
202                         } else {
203                             array_push(
204                                 $conditionArray,
205                                 $db->concat($table, array('last_name','first_name')) . " like '$like'"
206                             );
207                         }
208                     }
209                     else {
210                         array_push(
211                             $conditionArray,
212                             $table_prefix . $db->getValidDBName($condition['name']) . sprintf(" like '%s'", $like)
213                         );
214                     }
215                     break;
216
217                 case self::CONDITION_EQUAL:
218                     if ($condition['value']) {
219                         array_push(
220                             $conditionArray,
221                             sprintf("(%s = '%s')", $db->getValidDBName($condition['name']), $db->quote($condition['value']))
222                             );
223                     }
224                     break;
225
226                 default:
227                     array_push(
228                         $conditionArray,
229                         $table_prefix.$db->getValidDBName($condition['name']) . sprintf(" like '%s%%'", $db->quote($condition['value']))
230                     );
231             }
232         }
233
234         $whereClauseArray = array();
235         if (!empty($conditionArray)) {
236             $whereClauseArray[] = sprintf('(%s)', implode(" {$args['group']} ", $conditionArray));
237         }
238         if(!empty($this->extra_where)) {
239             $whereClauseArray[] = "({$this->extra_where})";
240         }
241
242         if ($table == 'users') {
243             $whereClauseArray[] = "users.status='Active'";
244         }
245
246         return implode(' AND ', $whereClauseArray);
247     }
248
249     /**
250      * Returns formatted data
251      *
252      * @param array $results
253      * @param array $args
254      * @return array
255      */
256     protected function formatResults($results, $args)
257     {
258         global $sugar_config;
259
260         $app_list_strings = null;
261         $data['totalCount'] = count($results);
262         $data['fields']     = array();
263
264         for ($i = 0; $i < count($results); $i++) {
265             $data['fields'][$i] = array();
266             $data['fields'][$i]['module'] = $results[$i]->object_name;
267
268             //C.L.: Bug 43395 - For Quicksearch, do not return values with salutation and title formatting
269             if($results[$i] instanceof Person)
270             {
271                 $results[$i]->createLocaleFormattedName = false;
272             }
273             $listData = $results[$i]->get_list_view_data();
274
275             foreach ($args['field_list'] as $field) {
276                 // handle enums
277                 if ((isset($results[$i]->field_name_map[$field]['type']) && $results[$i]->field_name_map[$field]['type'] == 'enum')
278                     || (isset($results[$i]->field_name_map[$field]['custom_type']) && $results[$i]->field_name_map[$field]['custom_type'] == 'enum')) {
279
280                     // get fields to match enum vals
281                     if(empty($app_list_strings)) {
282                         if(isset($_SESSION['authenticated_user_language']) && $_SESSION['authenticated_user_language'] != '') $current_language = $_SESSION['authenticated_user_language'];
283                         else $current_language = $sugar_config['default_language'];
284                         $app_list_strings = return_app_list_strings_language($current_language);
285                     }
286
287                     // match enum vals to text vals in language pack for return
288                     if(!empty($app_list_strings[$results[$i]->field_name_map[$field]['options']])) {
289                         $results[$i]->$field = $app_list_strings[$results[$i]->field_name_map[$field]['options']][$results[$i]->$field];
290                     }
291                 }
292
293
294                 if (isset($listData[$field])) {
295                     $data['fields'][$i][$field] = $listData[$field];
296                 } else if (isset($results[$i]->$field)) {
297                     $data['fields'][$i][$field] = $results[$i]->$field;
298                 } else {
299                     $data['fields'][$i][$field] = '';
300                 }
301             }
302         }
303
304         if (is_array($data['fields'])) {
305             foreach ($data['fields'] as $i => $recordIn) {
306                 if (!is_array($recordIn)) {
307                     continue;
308                 }
309
310                 foreach ($recordIn as $col => $dataIn) {
311                     if (!is_scalar($dataIn)) {
312                         continue;
313                     }
314
315                     $data['fields'][$i][$col] = html_entity_decode($dataIn, ENT_QUOTES, 'UTF-8');
316                 }
317             }
318         }
319
320         return $data;
321     }
322
323     /**
324      * Filter duplicate results from the list
325      *
326      * @param array $list
327      * @return  array
328      */
329     protected function filterResults($list)
330     {
331         $fieldsFiltered = array();
332         foreach ($list['fields'] as $field) {
333             $found = false;
334             foreach ($fieldsFiltered as $item) {
335                 if ($item === $field) {
336                     $found = true;
337                     break;
338                 }
339             }
340
341             if (!$found) {
342                 $fieldsFiltered[] = $field;
343             }
344         }
345
346         $list['totalCount'] = count($fieldsFiltered);
347         $list['fields']     = $fieldsFiltered;
348
349         return $list;
350     }
351
352     /**
353      * Returns raw search results. Filters should be applied later.
354      *
355      * @param array $args
356      * @param boolean $singleSelect
357      * @return array
358      */
359     protected function getRawResults($args, $singleSelect = false)
360     {
361         $orderBy = !empty($args['order']) ? $args['order'] : '';
362         $limit   = !empty($args['limit']) ? intval($args['limit']) : '';
363         $data    = array();
364
365         foreach ($args['modules'] as $module) {
366             $focus = SugarModule::get($module)->loadBean();
367
368             $orderBy = $focus->db->getValidDBName(($args['order_by_name'] && $focus instanceof Person && $args['order'] == 'name') ? 'last_name' : $orderBy);
369
370             if ($focus->ACLAccess('ListView', true)) {
371                 $where = $this->constructWhere($focus, $args);
372                 $data  = $this->updateData($data, $focus, $orderBy, $where, $limit, $singleSelect);
373             }
374         }
375
376
377         return $data;
378     }
379
380     /**
381      * Returns search results with all fixes applied
382      *
383      * @param array $data
384      * @param array $args
385      * @return array
386      */
387     protected function prepareResults($data, $args)
388     {
389         $results['totalCount'] = $count = count($data);
390         $results['fields']     = array();
391
392         for ($i = 0; $i < $count; $i++) {
393             $field = array();
394             $field['module'] = $data[$i]->object_name;
395
396             $field = $this->overrideContactId($field, $data[$i], $args);
397             $field = $this->updateContactName($field, $args);
398
399             $results['fields'][$i] = $this->prepareField($field, $args);
400         }
401
402         return $results;
403     }
404
405     /**
406      * Returns user search results
407      *
408      * @param string $condition
409      * @return array
410      */
411     protected function getUserResults($condition)
412     {
413         $users = $this->getUserArray($condition);
414
415         $results = array();
416         $results['totalCount'] = count($users);
417         $results['fields']     = array();
418
419         foreach ($users as $id => $name) {
420             array_push(
421                 $results['fields'],
422                 array(
423                     'id' => (string) $id,
424                     'user_name' => $name,
425                     'module' => 'Users'
426             ));
427         }
428
429         return $results;
430     }
431
432     /**
433      * Merges current module search results to given list and returns it
434      *
435      * @param array $data
436      * @param SugarBean $focus
437      * @param string $orderBy
438      * @param string $where
439      * @param string $limit
440      * @param boolean $singleSelect
441      * @return array
442      */
443     protected function updateData($data, $focus, $orderBy, $where, $limit, $singleSelect = false)
444     {
445         $result = $focus->get_list($orderBy, $where, 0, $limit, -1, 0, $singleSelect);
446
447         return array_merge($data, $result['list']);
448     }
449
450     /**
451      * Updates search result with proper contact name
452      *
453      * @param array $result
454      * @param array $args
455      * @return string
456      */
457     protected function updateContactName($result, $args)
458     {
459         global $locale;
460
461         $result[$args['field_list'][0]] = $locale->getLocaleFormattedName(
462             $result['first_name'],
463             $result['last_name'],
464             $result['salutation']
465         );
466
467         return $result;
468     }
469
470     /**
471      * Overrides contact_id and reports_to_id params (to 'id')
472      *
473      * @param array $result
474      * @param object $data
475      * @param array $args
476      * @return array
477      */
478     protected function overrideContactId($result, $data, $args)
479     {
480         foreach ($args['field_list'] as $field) {
481             $result[$field] = (preg_match('/reports_to_id$/s',$field)
482                                || preg_match('/contact_id$/s',$field))
483                 ? $data->id // "reports_to_id" to "id"
484                 : $data->$field;
485         }
486
487         return $result;
488     }
489
490     /**
491      * Returns prepared arguments. Should be redefined in child classes.
492      *
493      * @param array $arguments
494      * @return array
495      */
496     protected function prepareArguments($args)
497     {
498         global $sugar_config;
499
500         // override query limits
501         if (isset($args['limit']) && $sugar_config['list_max_entries_per_page'] < ($args['limit'] + 1)) {
502             $sugar_config['list_max_entries_per_page'] = ($args['limit'] + 1);
503         }
504
505         $defaults = array(
506             'order_by_name' => false,
507         );
508         $this->extra_where = '';
509
510         // Sanitize group
511         /* BUG: 52684 properly check for 'and' jeff@neposystems.com */
512         if(!empty($args['group'])  && strcasecmp($args['group'], 'and') == 0) {
513             $args['group'] = 'AND';
514         } else {
515             $args['group'] = 'OR';
516         }
517
518         return array_merge($defaults, $args);
519     }
520
521     /**
522      * Returns prepared field array. Should be redefined in child classes.
523      *
524      * @param array $field
525      * @param array $args
526      * @return array
527      */
528     protected function prepareField($field, $args)
529     {
530         return $field;
531     }
532
533     /**
534      * Returns user array
535      *
536      * @param string $condition
537      * @return array
538      */
539     protected function getUserArray($condition)
540     {
541         return (showFullName())
542             // utils.php, if system is configured to show full name
543             ? getUserArrayFromFullName($condition, true)
544             : get_user_array(false, 'Active', '', false, $condition,' AND portal_only=0 ',false);
545     }
546
547     /**
548      * Returns additional where condition for non private teams and removes arguments that have been replaced with
549      * custom where clauses
550      *
551      * @param array $args
552      * @return string
553      */
554     protected function getNonPrivateTeamsWhere(&$args)
555     {
556         global $db;
557
558         $where = array();
559         $teams_filtered = false;
560
561         if (isset($args['conditions']) && is_array($args['conditions'])) {
562             foreach ($args['conditions'] as $i => $condition) {
563                 if (isset($condition['name'], $condition['value'])) {
564                     switch($condition['name']) {
565                         case 'name':
566                             $where[] = sprintf(
567                                 "(teams.name like '%s%%' OR teams.name_2 like '%s%%')",
568                                 $db->quote($condition['value']),
569                                 $db->quote($condition['value'])
570                             );
571                             unset($args['conditions'][$i]);
572                             break;
573                         case 'user_id':
574                             $where[] = sprintf(
575                                 "teams.id IN (SELECT team_id FROM team_memberships WHERE user_id = '%s' AND deleted = 0)",
576                                 $db->quote($condition['value'])
577                             );
578                             unset($args['conditions'][$i]);
579                             $teams_filtered = true;
580                     }
581                 }
582             }
583         }
584
585         if (!$teams_filtered) {
586             $where[] ='teams.private = 0';
587         }
588
589         return implode(' AND ', $where);
590     }
591
592     /**
593      * Returns JSON encoded data
594      *
595      * @param array $data
596      * @return string
597      */
598     protected function getJsonEncodedData($data)
599     {
600         $json = getJSONobj();
601
602         return $json->encodeReal($data);
603     }
604
605     /**
606      * Returns formatted JSON encoded search results
607      *
608      * @param array $args
609      * @param array $results
610      * @return string
611      */
612     protected function getFormattedJsonResults($results, $args)
613     {
614         $results = $this->formatResults($results, $args);
615
616         return $this->getJsonEncodedData($results);
617     }
618
619     /**
620      * Returns filtered JSON encoded search results
621      *
622      * @param array $results
623      * @return string
624      */
625     protected function getFilteredJsonResults($results)
626     {
627         $results = $this->filterResults($results);
628
629         return $this->getJsonEncodedData($results);
630     }
631
632     /**
633      * Returns updated arguments array
634      *
635      * @param array $args
636      * @return array
637      */
638     protected function updateQueryArguments($args)
639     {
640         $args['order_by_name'] = true;
641
642         return $args;
643     }
644
645     /**
646      * Returns updated arguments array for contact query
647      *
648      * @param array $args
649      * @return array
650      */
651     protected function updateContactArrayArguments($args)
652     {
653         return $args;
654     }
655
656     /**
657      * Returns updated arguments array for team query
658      *
659      * @param array $args
660      * @return array
661      */
662     protected function updateTeamArrayArguments($args)
663     {
664         $this->extra_where = $this->getNonPrivateTeamsWhere($args);
665         $args['modules'] = array('Teams');
666
667         return $args;
668     }
669 }