From 559f9d94759f0e2c28ee4e788ba4dbf4bf051496 Mon Sep 17 00:00:00 2001 From: Benjamin Renard Date: Mon, 15 Jun 2020 10:40:48 +0200 Subject: [PATCH] Add autocompleter for CLI command search --- src/includes/class/class.LScli.php | 63 ++++++++++++- src/includes/class/class.LSsearch.php | 118 ++++++++++++++++++++++++- src/includes/class/class.LSsession.php | 14 +-- 3 files changed, 186 insertions(+), 9 deletions(-) diff --git a/src/includes/class/class.LScli.php b/src/includes/class/class.LScli.php index 7142cfc6..9acdbd0c 100644 --- a/src/includes/class/class.LScli.php +++ b/src/includes/class/class.LScli.php @@ -250,9 +250,7 @@ class LScli extends LSlog_staticLoggerClass { // Connect to LDAP server (if command need) if (self :: $commands[$command]['need_ldap_con']) { - if (!class_exists('LSldap') || !LSldap :: isConnected()) - if (!LSsession :: LSldapConnect()) - self :: log_fatal('Fail to connect to LDAP server.'); + self :: need_ldap_con(); } // Run command @@ -272,6 +270,19 @@ class LScli extends LSlog_staticLoggerClass { return false; } + /** + * Start LDAP connection (if not already connected) + * + * @retval void + **/ + public static function need_ldap_con() { + // Connect to LDAP server (if not already the case) + if (!class_exists('LSldap') || !LSldap :: isConnected()) { + if (!LSsession :: LSldapConnect()) + self :: log_fatal('Fail to connect to LDAP server.'); + } + } + /** * Run external command * @@ -543,6 +554,52 @@ class LScli extends LSlog_staticLoggerClass { return $matched_opts; } + /** + * Autocomplete integer option + * + * @param[in] $prefix string Option prefix (optional, default=empty string) + * + * @retval array List of available options + **/ + public static function autocomplete_int($prefix='') { + $opts = array(); + for ($i=0; $i < 10; $i++) { + $opts[] = "$prefix$i"; + } + return $opts; + } + + /** + * Autocomplete LSobject type option + * + * @param[in] $prefix string Option prefix (optional, default=empty string) + * + * @retval array List of available options + **/ + public static function autocomplete_LSobject_types($prefix='') { + $types = LSconfig :: get('LSaccess', array(), null, LSsession :: $ldapServer); + $subdn_config = LSconfig :: get('subDn', null, null, LSsession :: $ldapServer); + if (is_array($subdn_config)) { + foreach ($subdn_config as $key => $value) { + if (!is_array($value)) continue; + if ($key == 'LSobject') { + if (isset($value['LSobjects']) && is_array($value['LSobjects'])) + foreach ($value['LSobjects'] as $type) + if (!in_array($type, $types)) + $types[] = $type; + } + else { + foreach ($value as $objConfig) + if (is_array($objConfig) && isset($objConfig['LSobjets']) && is_array($objConfig['LSobjects'])) + foreach ($objConfig['LSobjects'] as $type) + if (!in_array($type, $types)) + $types[] = $type; + } + } + } + return self :: autocomplete_opts($types, $prefix, false); + } + } /* diff --git a/src/includes/class/class.LSsearch.php b/src/includes/class/class.LSsearch.php index ca40b872..d4e1f99a 100644 --- a/src/includes/class/class.LSsearch.php +++ b/src/includes/class/class.LSsearch.php @@ -1523,6 +1523,120 @@ class LSsearch { echo "Page ".($page['nb']+1)." on ".$page['nbPages']."\n"; return true; } + + /** + * Args autocompleter for CLI command search + * + * @param[in] $command_args array List of already typed words of the command + * @param[in] $comp_word_num int The command word number to autocomplete + * @param[in] $comp_word string The command word to autocomplete state + * @param[in] $opts array List of global available options + * + * @retval array List of available options for the word to autocomplete + **/ + public static function cli_search_args_autocompleter($command_args, $comp_word_num, $comp_word, $opts) { + $command_opts = array ( + '-f', '--filter', + '-b', '--basedn', + '--subdn', + '-s', '--scope', + '-l', '--limit', + '-a', '--approx', + '-r', '--recursive', + '--sort-by', + '-R', '--reverse', + '--sort-limit', + '--display-subdn', + '--display-format', + '-N', '--nb-obj-by-page', + '-W', '--without-cache', + '-e', '--extra-columns', + '-p', '--page', + ); + + // Detect positional args + $objType = null; + $objType_arg_num = null; + $patterns = array(); + $extra_columns = false; + for ($i=0; $i < count($command_args); $i++) { + if (!in_array($command_args[$i], $command_opts) || in_array($command_args[$i], $opts)) { + // If object type not defined + if (is_null($objType)) { + // Check object type exists + $objTypes = LScli :: autocomplete_LSobject_types($command_args[$i]); + + // Load it if exist and not trying to complete it + if (in_array($command_args[$i], $objTypes) && $i != $comp_word_num) { + LSsession :: loadLSobject($command_args[$i], false); + } + + // Defined it + $objType = $command_args[$i]; + $objType_arg_num = $i; + } + else + $patterns[] = $command_args[$i]; + } + else { + switch ($command_args[$i]) { + case '-e': + case '--extra-columns': + $extra_columns = true; + LSlog :: debug('Extra columns enabled'); + break; + } + } + } + + // Handle completion of args value + LSlog :: debug("Last complete word = '".$command_args[$comp_word_num-1]."'"); + switch ($command_args[$comp_word_num-1]) { + case '--subdn': + LScli :: need_ldap_con(); + $subDns = LSsession :: getSubDnLdapServer(); + if (is_array($subDns)) { + $subDns = array_keys($subDns); + LSlog :: debug('List of available subDns: '.implode(', ', $subDns)); + } + else + $subDns = array(); + return LScli :: autocomplete_opts($subDns, $comp_word); + case '-s': + case '--scope': + return LScli :: autocomplete_opts(array('sub', 'one', 'base'), $comp_word); + case '-f': + case '--filter': + case '-b': + case '--basedn': + // This args need string value that can't be autocomplete: stop autocompletion + return array(); + case '-l': + case '--limit': + case '--sort-limit': + case '-N': + case '--nb-obj-by-page': + case '-p': + case '--page': + return LScli :: autocomplete_int($comp_word); + case '--sort-by': + $bys = array('displayName', 'subDn'); + if ($objType && $extra_columns) { + $extraDisplayedColumns = LSconfig::get("LSobjects.$objType.LSsearch.extraDisplayedColumns", array()); + if (is_array($extraDisplayedColumns)) + $bys = array_merge($bys, array_keys($extraDisplayedColumns)); + } + LSlog :: debug('Available sort-bys clauses: '.implode(', ', $bys)); + return LScli :: autocomplete_opts($bys, $comp_word); + } + $opts = array_merge($opts, $command_opts); + + // If objType not already choiced (or currently autocomplete), add LSobject types to available options + if (!$objType || $objType_arg_num == $comp_word_num) + $opts = array_merge($opts, LScli :: autocomplete_LSobject_types($comp_word)); + + return LScli :: autocomplete_opts($opts, $comp_word); + } } /** @@ -1608,5 +1722,7 @@ LScli :: add_command( ' - -W|--without-cache : Disable cache', ' - -e|--extra-columns : Display extra columns', ' - -p|--page : page number to show (starting by 1, default: first one)', - ) + ), + true, + array('LSsearch', 'cli_search_args_autocompleter'), ); diff --git a/src/includes/class/class.LSsession.php b/src/includes/class/class.LSsession.php index a865abfc..7b4cc3c0 100644 --- a/src/includes/class/class.LSsession.php +++ b/src/includes/class/class.LSsession.php @@ -403,13 +403,14 @@ class LSsession { } /** - * Chargement d'un object LdapSaisie + * Load LSobject type * - * @param[in] $object Nom de l'objet à charger + * @param[in] $object string Name of the LSobject type + * @param[in] $warn boolean Set to false to avoid warning in case of loading error (optional, default: true) * - * @retval boolean true si le chargement a réussi, false sinon. + * @retval boolean True if LSobject type loaded, false otherwise */ - public static function loadLSobject($object) { + public static function loadLSobject($object, $warn=true) { if(class_exists($object)) { return true; } @@ -445,7 +446,7 @@ class LSsession { } } } - if ($error) { + if ($error && $warn) { LSerror :: addErrorCode('LSsession_04',$object); return; } @@ -1158,6 +1159,9 @@ class LSsession { * @retval boolean True sinon false. */ public static function LSldapConnect() { + if (!self :: $ldapServer && !self :: setLdapServer(0)) { + return; + } if (self :: $ldapServer) { self :: includeFile(LSconfig :: get('NetLDAP2'), true); if (!self :: loadLSclass('LSldap')) {