ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
class.ilUserAutoComplete.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
8 {
9  const MAX_ENTRIES = 1000;
10 
11 
15  const SEARCH_TYPE_LIKE = 1;
16 
20  const SEARCH_TYPE_EQUALS = 2;
21 
26 
31 
35  private $logger = null;
36 
40  private $searchable_check = false;
41 
45  private $user_access_check = true;
46 
50  private $possible_fields = array();
51 
55  private $result_field;
56 
60  private $search_type;
61 
65  private $privacy_mode;
66 
70  private $user;
71 
72 
73  private $limit = 0;
74 
75  private $user_limitations = true;
76 
81 
85  private $more_link_available = false;
86 
90  protected $user_filter = null;
91 
95  public function __construct()
96  {
97  global $DIC;
98 
99  $this->result_field = 'login';
100 
101  $this->setSearchType(self::SEARCH_TYPE_LIKE);
102  $this->setPrivacyMode(self::PRIVACY_MODE_IGNORE_USER_SETTING);
103 
104  $this->logger = $DIC->logger()->user();
105  }
106 
110  public function respectMinimumSearchCharacterCount($a_status)
111  {
112  $this->respect_min_search_character_count = $a_status;
113  }
114 
119  {
121  }
122 
123 
133  public function addUserAccessFilterCallable(callable $user_filter)
134  {
135  $this->user_filter = $user_filter;
136  }
137 
138  public function setLimit($a_limit)
139  {
140  $this->limit = $a_limit;
141  }
142 
143  public function getLimit()
144  {
145  return $this->limit;
146  }
147 
151  public function setSearchType($search_type)
152  {
153  $this->search_type = $search_type;
154  }
155 
159  public function getSearchType()
160  {
161  return $this->search_type;
162  }
163 
167  public function setPrivacyMode($privacy_mode)
168  {
169  $this->privacy_mode = $privacy_mode;
170  }
171 
175  public function getPrivacyMode()
176  {
177  return $this->privacy_mode;
178  }
179 
183  public function setUser($user)
184  {
185  $this->user = $user;
186  }
187 
191  public function getUser()
192  {
193  return $this->user;
194  }
195 
200  public function enableFieldSearchableCheck($a_status)
201  {
202  $this->searchable_check = $a_status;
203  }
204 
210  {
212  }
213 
219  public function enableUserAccessCheck($a_status)
220  {
221  $this->user_access_check = $a_status;
222  }
223 
228  public function isUserAccessCheckEnabled()
229  {
231  }
232 
237  public function setSearchFields($a_fields)
238  {
239  $this->possible_fields = $a_fields;
240  }
241 
246  public function getSearchFields()
247  {
248  return $this->possible_fields;
249  }
250 
255  protected function getFields()
256  {
257  if (!$this->isFieldSearchableCheckEnabled()) {
258  return $this->getSearchFields();
259  }
260  $available_fields = array();
261  foreach ($this->getSearchFields() as $field) {
262  include_once 'Services/Search/classes/class.ilUserSearchOptions.php';
263  if (ilUserSearchOptions::_isEnabled($field)) {
264  $available_fields[] = $field;
265  }
266  }
267  return $available_fields;
268  }
269 
274  public function setResultField($a_field)
275  {
276  $this->result_field = $a_field;
277  }
278 
284  public function getList($a_str)
285  {
289  global $DIC;
290 
291  $ilDB = $DIC['ilDB'];
292 
293  $parsed_query = $this->parseQueryString($a_str);
294 
295  if (ilStr::strLen($parsed_query['query']) < ilQueryParser::MIN_WORD_LENGTH) {
296  $result_json['items'] = [];
297  $result_json['hasMoreResults'] = false;
298  $this->logger->debug('Autocomplete search rejected: minimum characters count.');
299  return json_encode($result_json);
300  }
301 
302 
303  $select_part = $this->getSelectPart();
304  $where_part = $this->getWherePart($parsed_query);
305  $order_by_part = $this->getOrderByPart();
306  $query = implode(" ", array(
307  'SELECT ' . $select_part,
308  'FROM ' . $this->getFromPart(),
309  $where_part ? 'WHERE ' . $where_part : '',
310  $order_by_part ? 'ORDER BY ' . $order_by_part : ''
311  ));
312 
313  $this->logger->debug('Query: ' . $query);
314 
315  $res = $ilDB->query($query);
316 
317  // add email only if it is "searchable"
318  $add_email = true;
319  include_once 'Services/Search/classes/class.ilUserSearchOptions.php';
321  $add_email = false;
322  }
323 
324  $add_second_email = true;
325  if ($this->isFieldSearchableCheckEnabled() && !ilUserSearchOptions::_isEnabled("second_email")) {
326  $add_second_email = false;
327  }
328 
329  include_once './Services/Search/classes/class.ilSearchSettings.php';
330  $max = $this->getLimit() ? $this->getLimit() : ilSearchSettings::getInstance()->getAutoCompleteLength();
331  $cnt = 0;
332  $more_results = false;
333  $result = array();
334  $recs = array();
335  $usrIds = array();
336  while (($rec = $ilDB->fetchAssoc($res)) && $cnt < ($max + 1)) {
337  if ($cnt >= $max && $this->isMoreLinkAvailable()) {
338  $more_results = true;
339  break;
340  }
341  $recs[$rec['usr_id']] = $rec;
342  $usrIds[] = $rec['usr_id'];
343  }
344  if (is_callable($this->user_filter, true, $callable_name = '')) {
345  $usrIds = call_user_func_array($this->user_filter, [$usrIds]);
346  }
347  foreach ($usrIds as $usr_id) {
348  $rec = $recs[$usr_id];
349 
350  if (self::PRIVACY_MODE_RESPECT_USER_SETTING != $this->getPrivacyMode() || in_array($rec['profile_value'], ['y','g'])) {
351  $label = $rec['lastname'] . ', ' . $rec['firstname'] . ' [' . $rec['login'] . ']';
352  } else {
353  $label = '[' . $rec['login'] . ']';
354  }
355 
356  if ($add_email && $rec['email'] && (self::PRIVACY_MODE_RESPECT_USER_SETTING != $this->getPrivacyMode() || 'y' == $rec['email_value'])) {
357  $label .= ', ' . $rec['email'];
358  }
359 
360  if ($add_second_email && $rec['second_email'] && (self::PRIVACY_MODE_RESPECT_USER_SETTING != $this->getPrivacyMode() || 'y' == $rec['second_email_value'])) {
361  $label .= ', ' . $rec['second_email'];
362  }
363 
364  $result[$cnt]['value'] = (string) $rec[$this->result_field];
365  $result[$cnt]['label'] = $label;
366  $result[$cnt]['id'] = $rec['usr_id'];
367  $cnt++;
368  }
369 
370  include_once 'Services/JSON/classes/class.ilJsonUtil.php';
371 
372  $result_json['items'] = $result;
373  $result_json['hasMoreResults'] = $more_results;
374 
375  $this->logger->dump($result_json, ilLogLevel::DEBUG);
376 
377  return ilJsonUtil::encode($result_json);
378  }
379 
383  protected function getSelectPart()
384  {
385  $fields = array(
386  'ud.usr_id',
387  'ud.login',
388  'ud.firstname',
389  'ud.lastname',
390  'ud.email',
391  'ud.second_email'
392  );
393 
394  if (self::PRIVACY_MODE_RESPECT_USER_SETTING == $this->getPrivacyMode()) {
395  $fields[] = 'profpref.value profile_value';
396  $fields[] = 'pubemail.value email_value';
397  $fields[] = 'pubsecondemail.value second_email_value';
398  }
399 
400  return implode(', ', $fields);
401  }
402 
406  protected function getFromPart()
407  {
411  global $DIC;
412 
413  $ilDB = $DIC['ilDB'];
414 
415  $joins = array();
416 
417  if (self::PRIVACY_MODE_RESPECT_USER_SETTING == $this->getPrivacyMode()) {
418  $joins[] = 'LEFT JOIN usr_pref profpref
419  ON profpref.usr_id = ud.usr_id
420  AND profpref.keyword = ' . $ilDB->quote('public_profile', 'text');
421 
422  $joins[] = 'LEFT JOIN usr_pref pubemail
423  ON pubemail.usr_id = ud.usr_id
424  AND pubemail.keyword = ' . $ilDB->quote('public_email', 'text');
425 
426  $joins[] = 'LEFT JOIN usr_pref pubsecondemail
427  ON pubsecondemail.usr_id = ud.usr_id
428  AND pubsecondemail.keyword = ' . $ilDB->quote('public_second_email', 'text');
429  }
430 
431  if ($joins) {
432  return 'usr_data ud ' . implode(' ', $joins);
433  } else {
434  return 'usr_data ud';
435  }
436  }
437 
442  protected function getWherePart(array $search_query)
443  {
448  global $DIC;
449 
450  $ilDB = $DIC['ilDB'];
451  $ilSetting = $DIC['ilSetting'];
452 
453  $outer_conditions = array();
454 
455  // In 'anonymous' context with respected user privacy, only users with globally published profiles should be found.
456  if (self::PRIVACY_MODE_RESPECT_USER_SETTING == $this->getPrivacyMode() &&
457  $this->getUser() instanceof ilObjUser &&
458  $this->getUser()->isAnonymous()
459  ) {
460  if (!$ilSetting->get('enable_global_profiles', 0)) {
461  // If 'Enable User Content Publishing' is not set in the administration, no user should be found for 'anonymous' context.
462  return '1 = 2';
463  } else {
464  // Otherwise respect the profile activation setting of every user (as a global (outer) condition in the where clause).
465  $outer_conditions[] = 'profpref.value = ' . $ilDB->quote('g', 'text');
466  }
467  }
468 
469  $outer_conditions[] = 'ud.usr_id != ' . $ilDB->quote(ANONYMOUS_USER_ID, 'integer');
470 
471  $field_conditions = array();
472  foreach ($this->getFields() as $field) {
473  $field_condition = $this->getQueryConditionByFieldAndValue($field, $search_query);
474 
475  if ('email' == $field && self::PRIVACY_MODE_RESPECT_USER_SETTING == $this->getPrivacyMode()) {
476  // If privacy should be respected, the profile setting of every user concerning the email address has to be
477  // respected (in every user context, no matter if the user is 'logged in' or 'anonymous').
478  $email_query = array();
479  $email_query[] = $field_condition;
480  $email_query[] = 'pubemail.value = ' . $ilDB->quote('y', 'text');
481  $field_conditions[] = '(' . implode(' AND ', $email_query) . ')';
482  } elseif ('second_email' == $field && self::PRIVACY_MODE_RESPECT_USER_SETTING == $this->getPrivacyMode()) {
483  // If privacy should be respected, the profile setting of every user concerning the email address has to be
484  // respected (in every user context, no matter if the user is 'logged in' or 'anonymous').
485  $email_query = array();
486  $email_query[] = $field_condition;
487  $email_query[] = 'pubsecondemail.value = ' . $ilDB->quote('y', 'text');
488  $field_conditions[] = '(' . implode(' AND ', $email_query) . ')';
489  } else {
490  $field_conditions[] = $field_condition;
491  }
492  }
493 
494  // If the current user context ist 'logged in' and privacy should be respected, all fields >>>except the login<<<
495  // should only be searchable if the users' profile is published (y oder g)
496  // In 'anonymous' context we do not need this additional conditions,
497  // because we checked the privacy setting in the condition above: profile = 'g'
498  if (self::PRIVACY_MODE_RESPECT_USER_SETTING == $this->getPrivacyMode() &&
499  $this->getUser() instanceof ilObjUser && !$this->getUser()->isAnonymous() &&
500  $field_conditions
501  ) {
502  $fields = '(' . implode(' OR ', $field_conditions) . ')';
503 
504  $field_conditions = [
505  '(' . implode(' AND ', array(
506  $fields,
507  $ilDB->in('profpref.value', array('y', 'g'), false, 'text')
508  )) . ')'
509  ];
510  }
511 
512  // The login field must be searchable regardless (for 'logged in' users) of any privacy settings.
513  // We handled the general condition for 'anonymous' context above: profile = 'g'
514  $field_conditions[] = $this->getQueryConditionByFieldAndValue('login', $search_query);
515 
516  include_once 'Services/User/classes/class.ilUserAccountSettings.php';
517  if (ilUserAccountSettings::getInstance()->isUserAccessRestricted()) {
518  include_once './Services/User/classes/class.ilUserFilter.php';
519  $outer_conditions[] = $ilDB->in('time_limit_owner', ilUserFilter::getInstance()->getFolderIds(), false, 'integer');
520  }
521 
522  if ($field_conditions) {
523  $outer_conditions[] = '(' . implode(' OR ', $field_conditions) . ')';
524  }
525 
526  include_once './Services/Search/classes/class.ilSearchSettings.php';
527  $settings = ilSearchSettings::getInstance();
528 
529  if (!$settings->isInactiveUserVisible() && $this->getUserLimitations()) {
530  $outer_conditions[] = "ud.active = " . $ilDB->quote(1, 'integer');
531  }
532 
533  if (!$settings->isLimitedUserVisible() && $this->getUserLimitations()) {
534  $unlimited = "ud.time_limit_unlimited = " . $ilDB->quote(1, 'integer');
535  $from = "ud.time_limit_from < " . $ilDB->quote(time(), 'integer');
536  $until = "ud.time_limit_until > " . $ilDB->quote(time(), 'integer');
537 
538  $outer_conditions[] = '(' . $unlimited . ' OR (' . $from . ' AND ' . $until . '))';
539  }
540 
541  return implode(' AND ', $outer_conditions);
542  }
543 
547  protected function getOrderByPart()
548  {
549  return 'login ASC';
550  }
551 
557  protected function getQueryConditionByFieldAndValue($field, $query)
558  {
562  global $DIC;
563 
564  $ilDB = $DIC['ilDB'];
565 
566  $query_strings = array($query['query']);
567 
568  if (array_key_exists($field, $query)) {
569  $query_strings = array($query[$field]);
570  } elseif (array_key_exists('parts', $query)) {
571  $query_strings = $query['parts'];
572  }
573 
574  $query_condition = '( ';
575  $num = 0;
576  foreach ($query_strings as $query_string) {
577  if ($num++ > 0) {
578  $query_condition .= ' OR ';
579  }
580  if (self::SEARCH_TYPE_LIKE == $this->getSearchType()) {
581  $query_condition .= $ilDB->like($field, 'text', $query_string . '%');
582  } else {
583  $query_condition .= $ilDB->like($field, 'text', $query_string);
584  }
585  }
586  $query_condition .= ')';
587  return $query_condition;
588  }
589 
595  public function setUserLimitations($a_limitations)
596  {
597  $this->user_limitations = (bool) $a_limitations;
598  }
599 
604  public function getUserLimitations()
605  {
607  }
608 
612  public function isMoreLinkAvailable()
613  {
615  }
616 
623  {
624  $this->more_link_available = $more_link_available;
625  }
626 
632  public function parseQueryString($a_query)
633  {
634  $query = array();
635 
636  if (!stristr($a_query, '\\')) {
637  $a_query = str_replace('%', '\%', $a_query);
638  $a_query = str_replace('_', '\_', $a_query);
639  }
640 
641  $query['query'] = trim($a_query);
642 
643  // "," means fixed search for lastname, firstname
644  if (strpos($a_query, ',')) {
645  $comma_separated = (array) explode(',', $a_query);
646 
647  if (count($comma_separated) == 2) {
648  if (trim($comma_separated[0])) {
649  $query['lastname'] = trim($comma_separated[0]);
650  }
651  if (trim($comma_separated[1])) {
652  $query['firstname'] = trim($comma_separated[1]);
653  }
654  }
655  } else {
656  $whitespace_separated = (array) explode(' ', $a_query);
657  foreach ($whitespace_separated as $part) {
658  if (trim($part)) {
659  $query['parts'][] = trim($part);
660  }
661  }
662  }
663 
664  $this->logger->dump($query, ilLogLevel::DEBUG);
665 
666  return $query;
667  }
668 }
respectMinimumSearchCharacterCount($a_status)
isFieldSearchableCheckEnabled()
Searchable check enabled.
static strLen($a_string)
Definition: class.ilStr.php:78
addUserAccessFilterCallable(callable $user_filter)
Closure for filtering users e.g $rep_search_gui->addUserAccessFilterCallable(function($user_ids) use(...
$result
global $DIC
Definition: saml.php:7
setUserLimitations($a_limitations)
allow user limitations like inactive and access limitations
setMoreLinkAvailable($more_link_available)
IMPORTANT: remember to read request parameter &#39;fetchall&#39; to use this function.
$from
static getInstance()
Singelton get instance.
user()
Definition: user.php:4
Auto completion class for user lists.
const MIN_WORD_LENGTH
Minimum of characters required for search.
getFields()
Get searchable fields.
static encode($mixed, $suppress_native=false)
enableUserAccessCheck($a_status)
Enable user access check.
foreach($_POST as $key=> $value) $res
getSearchFields()
get possible search fields
getUserLimitations()
allow user limitations like inactive and access limitations
$query
parseQueryString($a_query)
Parse query string.
setResultField($a_field)
Set result field.
isUserAccessCheckEnabled()
Check if user access check is enabled.
enableFieldSearchableCheck($a_status)
Enable the check whether the field is searchable in Administration -> Settings -> Standard Fields...
__construct()
Default constructor.
global $ilSetting
Definition: privfeed.php:17
static getInstance()
Singelton get instance.
global $ilDB
setSearchFields($a_fields)
Set searchable fields.