<?php
/*******************************************************************************
 * Copyright (C) 2007 Easter-eggs
 * https://ldapsaisie.org
 *
 * Author: See AUTHORS file in top-level directory.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

******************************************************************************/

LSsession :: loadLSclass('LSlog_staticLoggerClass');

class LSlang extends LSlog_staticLoggerClass {


  /**
   * Current lang
   * @see LSlang::setLocale()
   * @see LSlang::getLang()
   * @var string|null
   */
  private static $lang = NULL;

  /**
   * Current encoding
   * @see LSlang::setLocale()
   * @see LSlang::getEncoding()
   * @var string|null
   */
  private static $encoding = NULL;

  /**
   * Define current locale (and encoding)
   *
   * @param string|null $lang     The lang (optional, default: default current LDAP
   *                                  server lang, or default lang)
   * @param string|null $encoding The encoding (optional, default: default current LDAP
   *                                  server encoding, or default encoding)
   *
   * @return void
   */
   public static function setLocale($lang=null, $encoding=null) {
     // Handle $lang parameter
     if (is_null($lang)) {
       if (isset($_REQUEST['lang'])) {
         $lang = $_REQUEST['lang'];
       }
       elseif (isset($_SESSION['LSlang'])) {
         $lang = $_SESSION['LSlang'];
       }
       elseif (isset(LSsession :: $ldapServer['lang'])) {
         $lang = LSsession :: $ldapServer['lang'];
       }
       else {
         $lang = LSconfig :: get('lang');
       }
     }

     // Handle $encoding parameter
     if (is_null($encoding)) {
       if (isset($_REQUEST['encoding'])) {
         $encoding = $_REQUEST['encoding'];
       }
       elseif (isset($_SESSION['LSencoding'])) {
         $encoding = $_SESSION['LSencoding'];
       }
       elseif (isset(LSsession :: $ldapServer['encoding'])) {
         $encoding = LSsession :: $ldapServer['encoding'];
       }
       else {
         $encoding = LSconfig :: get('encoding');
       }
     }

     // Set session and self variables
     $_SESSION['LSlang'] = self :: $lang = $lang;
     $_SESSION['LSencoding'] = self :: $encoding = $encoding;

     // Check
     if (self :: localeExist($lang, $encoding)) {
       self :: log_trace("setLocale($lang, $encoding): local '$lang.$encoding' exist, use it");
       if ($encoding) {
         $lang .= '.'.$encoding;
       }
       // Gettext firstly look the LANGUAGE env variable, so set it
       putenv("LANGUAGE=$lang");

       // Set the locale
       if (setlocale(LC_ALL, $lang) === false)
         self :: log_error("An error occured setting locale to '$lang'");

       // Configure and set the text domain
       $fullpath = bindtextdomain(LS_TEXT_DOMAIN, LS_I18N_DIR_PATH);
       self :: log_trace("setLocale($lang, $encoding): Text domain fullpath is '$fullpath'.");
       self :: log_trace("setLocale($lang, $encoding): Text domain is : ".textdomain(LS_TEXT_DOMAIN));

       // Include local translation file
       $lang_file = LS_I18N_DIR.'/'.$lang.'/lang.php';
       if (LSsession :: includeFile($lang_file, false, false))
         self :: log_trace("setLocale($lang, $encoding): lang file '$lang_file' loaded.");
       else
         self :: log_trace("setLocale($lang, $encoding): no lang file found ($lang_file).");

       // Include other local translation file(s)
       foreach(array(LS_I18N_DIR_PATH.'/'.$lang, LS_ROOT_DIR.'/'.LS_LOCAL_DIR.'/'.LS_I18N_DIR.'/'.$lang) as $lang_dir) {
         self :: log_trace("setLocale($lang, $encoding): lookup for translation file in '$lang_dir'");
         if (is_dir($lang_dir)) {
           foreach (listFiles($lang_dir, '/^lang\..+\.php$/') as $file) {
             $path = "$lang_dir/$file";
             self :: log_trace("setLocale($lang, $encoding): load translation file '$path'");
             include($path);
           }
         }
       }
     }
     else {
       if ($encoding && $lang) $lang .= '.'.$encoding;
       self :: log_error("The local '$lang' does not exists, use default one.");
     }
   }

  /**
   * Return list of available languages
   *
   * @param string|null $encoding  Specify encoding for lang selection. If null, use self :: encoding value,
   *                                    if false, do not filter on encoding, otherwise filter available lang for
   *                                    specified encoding (optional, default: null)
   * @param bool $with_encoding         Return available lang list with encoding (optional, default: false)
   *
   * @return array List of available languages.
   **/
   public static function getLangList($encoding=null, $with_encoding=false) {
     if (is_null($encoding))
      $encoding = self :: $encoding;
     if ($with_encoding)
       $list = array('en_US.UTF8');
     else
       $list = array('en_US');
     if ($encoding) {
       if ($with_encoding)
        $regex = '/^([a-zA-Z_]*\.'.$encoding.')$/';
      else
        $regex = '/^([a-zA-Z_]*)\.'.$encoding.'$/';
     }
     else {
       if ($with_encoding)
        $regex = '/^([a-zA-Z_]+\.[a-zA-Z0-9\-]+)$/';
       else
        $regex = '/^([a-zA-Z_]+)\.[a-zA-Z0-9\-]+$/';
     }
     self :: log_trace("getLangList(".varDump($encoding).", $with_encoding) : regex='$regex'");
     foreach(array(LS_I18N_DIR_PATH, LS_ROOT_DIR.'/'.LS_LOCAL_DIR.'/'.LS_I18N_DIR) as $lang_dir) {
       if (!is_dir($lang_dir))
       continue;
       if ($handle = opendir($lang_dir)) {
         while (false !== ($file = readdir($handle))) {
           if(is_dir("$lang_dir/$file")) {
             if (preg_match($regex, $file, $regs)) {
               if (!in_array($regs[1], $list)) {
                 $list[]=$regs[1];
               }
             }
           }
         }
       }
     }
     return $list;
   }

  /**
   * Return current language
   *
   * @param boolean $short If true, only return the two first characters of the language
   *                    (For instance, 'fr' for 'fr_FR')
   *
   * @return string The current language (ex: fr_FR, or fr if $short==true)
   **/
   public static function getLang($short=false) {
     if ($short) {
       return strtolower(substr(self :: $lang, 0, 2));
     }
     return self :: $lang;
   }

  /**
   * Return current encoding
   *
   * @return string The current encoding (ex: UTF8)
   **/
   public static function getEncoding() {
     return self :: $encoding;
   }

  /**
   * Check a locale exists
   *
   * @param string $lang The language (ex: fr_FR)
   * @param string $encoding The encoding (ex: UTF8)
   *
   * @return boolean True if the locale is available, False otherwise
   **/
   public static function localeExist($lang, $encoding) {
     if ( !$lang && !$encoding ) {
       return false;
     }
     $locale=$lang.(($encoding)?'.'.$encoding:'');
     if ($locale == 'en_US.UTF8') {
       return true;
     }
     foreach(array(LS_I18N_DIR_PATH, LS_ROOT_DIR.'/'.LS_LOCAL_DIR.'/'.LS_I18N_DIR) as $lang_dir)
       if (is_dir("$lang_dir/$locale"))
         return true;
     return false;
   }

}

/*
 ***********************************************
 * Generate translation file CLI methods
 *
 * Only load in CLI context
 ***********************************************
 */
if (php_sapi_name() != "cli") return true;

global $LSlang_cli_logger, $available_onlys, $available_withouts;

/**
 * Convert relative path to absolute
 * @param  string $path The path to convert
 * @return string The converted absolute path
 */
function _cli_relative2absolute_path($path) {
  if ($path[0] == '/')
    return $path;
  global $curdir;
  return realpath($curdir)."/".$path;
}

/**
 * Convert absolute path to relative
 * @param  string $path The path to convert
 * @return string The converted relative path
 */
function _cli_absolute2relative_path($path) {
  if ($path[0] == '/')
    $path = realpath($path);
  if (substr($path, 0, strlen(LS_ROOT_DIR)) == LS_ROOT_DIR)
    return substr($path, strlen(LS_ROOT_DIR)+1);
  return $path;
}

/**
 * CLI Helper to ask user to translate a string
 * @param  string $context The context of the string to translate
 * @param  string $msg The string to convert
 * @return string|true The translated message string, true if $interactive_exit enabled
 */
function _cli_interactive_ask($context, $msg) {
  global $copyoriginalvalue, $interactive_exit;

  if ($interactive_exit) {
    if ($copyoriginalvalue)
      return $msg;
    return true;
  }

  // Format question
  $question ="\"$msg\"\n\n => "._("Please enter translated string");
  $empty_action = (
    $copyoriginalvalue?
    _("or leave empty to copy original message"):
    _("or leave empty to pass")
  );
  $question .= " (i";
  if (!$copyoriginalvalue)
    $question .= "/c";
  $question .= "/e/q/? $empty_action): ";

  while (true) {
    if ($context)
      fwrite(STDERR, "\n# $context\n");
    fwrite(STDERR, $question);
    $in = trim(fgets(STDIN));
    switch($in) {
      case 'q': // Exit interactive mode
      case 'Q':
        $interactive_exit = true;
        return True;
      case 'i': // Ignore
      case 'I':
        return True;
      case 'c':
      case 'C': // Copy
        if (!$copyoriginalvalue)
          return $msg;
      case 'e':
      case 'E': // Open editor
        $tmp_file = tempnam(sys_get_temp_dir(), 'ldapsaisie_translation');
        if ($tmp_file === false) {
          fwrite(STDERR, _('Fail to create temporary file.'));
          break;
        }
        $fd = fopen($tmp_file, 'w');
        if ($fd === false) {
          fwrite(STDERR, _('Fail to open temporary file.'));
          break;
        }
        $nb_lines = 1;
        if ($context) {
          fwrite($fd, "# $context\n");
          $nb_lines++;
        }
        $lines = explode("\n", $msg);
        $nb_lines += count($lines);
        fwrite($fd, "# ".implode("\n# ", $lines)."\n\n");
        fclose($fd);
        system("editor +$nb_lines $tmp_file > `tty`", $exit_code);
        $result = array();
        foreach(file($tmp_file) as $line)
          if (!preg_match('/^# /', $line) || $result)
            $result[] = preg_replace("/\n$/", "", $line);
        unlink($tmp_file);
        return implode("\n", $result);
      case '?': // Help message
        fwrite(STDERR, _("Available choices:\n"));
        fwrite(STDERR, _(" - i: ignore this message\n"));
        if (!$copyoriginalvalue)
          fwrite(STDERR, _(" - c: copy original message\n"));
        fwrite(STDERR, _(" - e: translate message in text editor\n"));
        fwrite(STDERR, _(" - q: quit interactive mode and ignore all following untranslated messages\n"));
        fwrite(STDERR, _(" - ?: Show this message\n"));
        if ($copyoriginalvalue)
          fwrite(STDERR, _("Or leave empty to copy original message.\n"));
        else
          fwrite(STDERR, _("Or leave empty to pass.\n"));
        break;
      case "": // Empty
        // On copy orignal value mode, return $msg
        if ($copyoriginalvalue)
          return $msg;
        // Otherwise, leave translation empty
        return "";
      default:
        // Return user input
        return $in;
    }
  }
  // Supposed to never happen
  return true;
}

/**
 * CLI helper to add string to translate
 * @param string $msg The string to translate
 * @param string|null $context The context of the string to translate
 * @return void
 */
function _cli_add_str_to_translate($msg, $context=null) {
  global $LSlang_cli_logger, $lang, $data, $translations, $interactive, $interactive_exit, $copyoriginalvalue, $format;
  $LSlang_cli_logger -> trace("_cli_add_str_to_translate($msg, $context)");
  if ($msg == '')
    return;
  if (!is_null($lang) && _($msg) != "$msg")
    return;

  // It is a LSformat string with a simple substitution (for instance: "%{test}")?
  if (preg_match("/^%[{(][^{(]+[})]$/", $msg)) {
    $LSlang_cli_logger -> trace(
      "_cli_add_str_to_translate($msg, $context): LSformat string with a simple substitution, ".
      "ignore it"
    );
    return;
  }

  // Message already exists ?
  if (array_key_exists($msg, $data)) {
    if ($context && !in_array($context, $data[$msg]['contexts']))
      $data[$msg]['contexts'][] = $context;
    return;
  }

  // Handle translation
  $translation = "";
  if (array_key_exists($msg, $translations)) {
    $translation = $translations[$msg];
  }
  elseif (!is_null($lang) && _($msg) != $msg) {
    $translation = _($msg);
  }
  elseif ($interactive && $format != 'pot') {
    $translation = _cli_interactive_ask($context, $msg);
    if (!is_string($translation))
      return;
  }
  $data[$msg] = array (
    'translation' => $translation,
    'contexts' => ($context?array($context):array()),
  );
}

/**
 * CLI helper to add string to translate from LSconfig
 * @param string $pattern   The LSconfig parameter pattern used to find strings to translate
 * @param string $what      Where to find string to translate. Possible values:
 *                          - key : The LSconfig parameter key
 *                          - value : The LSconfig parameter value
 * @param array  $excludes  Array of string to exclude
 * @return void
 */
function _cli_add_str_to_translate_from_LSconfig($pattern, $what='value', $excludes=array()) {
  global $LSlang_cli_logger;
  $LSlang_cli_logger -> trace("_cli_add_str_to_translate_from_LSconfig($pattern, array(".implode(',', $excludes)."))");
  $keys = LSconfig :: getMatchingKeys($pattern);
  $LSlang_cli_logger -> trace("addFromLSconfig : ".count($keys)." matching key(s)");
  foreach ($keys as $key => $value) {
    $LSlang_cli_logger -> trace("addFromLSconfig : $key = ".varDump($value));
    if ($what == 'key') {
      // Get the last key parts as value and all other as key
      $key_parts = explode('.', $key);
      $value = $key_parts[count($key_parts)-1];
      $key = implode('.', array_slice($key_parts, 0, count($key_parts)-1));
    }
    if (!in_array($value, $excludes) && is_string($value))
      _cli_add_str_to_translate($value, $key);
  }
}

/**
 * CLI helper to add string to translate from list of possible values retreive from LSconfig
 * @param string  $context   The context == prefix of the LSconfig parameter key to retreive possible values
 * @param array<string>  $withouts FIXME
 * @param integer $level     The level of the possible value (used to identify recursion level)
 * @return void
 */
function _cli_add_possible_values_from_LSconfig($context, $withouts, $level=0) {
  global $LSlang_cli_logger;
  $LSlang_cli_logger -> trace("_cli_add_possible_values_from_LSconfig($context)");
  if (in_array('select-list', $withouts))
    return;
  if (!LSconfig :: get("$context.translate_labels", True, "bool"))
    return;
  foreach(LSconfig :: get("$context.possible_values", array()) as $pkey => $plabel) {
    if (is_array($plabel)) {
      // Sub possible values
      // Check level
      if ($level > 1) {
        $LSlang_cli_logger -> warning(
          "_cli_add_possible_values_from_LSconfig($context): Level to hight to handle sub possible values of $context.possible_values.$pkey"
        );
        return;
      }
      _cli_add_str_to_translate_from_LSconfig("$context.possible_values.$pkey.label");
      $LSlang_cli_logger -> trace("_cli_add_possible_values_from_LSconfig($context): handle sub possible values of $context.possible_values.$pkey");
      _cli_add_possible_values_from_LSconfig("$context.possible_values.$pkey", $withouts, $level+1);
    }
    else {
      switch ($pkey) {
        case 'OTHER_OBJECT':
          $LSlang_cli_logger -> trace("_cli_add_possible_values_from_LSconfig($context): ignore $context.possible_values.$pkey (OTHER_OBJECT)");
          break;
        case 'OTHER_ATTRIBUTE':
          if (is_array($plabel)) {
            if (isset($plabel['json_component_key']))
              _cli_add_str_to_translate_from_LSconfig("$context.possible_values.OTHER_ATTRIBUTE.json_component_label");
            else
              _cli_add_str_to_translate_from_LSconfig("$context.possible_values.OTHER_ATTRIBUTE.*");
          }
          else {
            $LSlang_cli_logger -> warning("_cli_add_possible_values_from_LSconfig($context): invalid $context.possible_values.OTHER_ATTRIBUTE config => Must be an array.");
          }
          break;
        default:
          _cli_add_str_to_translate($plabel, "$context.possible_values.$pkey");
          break;
      }
    }
  }
}

/**
 * CLI helper to parse a template file to locate strings to translate
 * @param  string $file  The file path
 * @return void
 */
function _cli_parse_template_file($file) {
  global $LSlang_cli_logger;
  $LSlang_cli_logger -> debug("Looking for string to translate in '$file' template file");
  foreach(file($file) as $line_count => $line) {
    $line_count ++;
    if (preg_match_all('/\{ *tr +.*msg=(["\'])(?P<msg>[^"}]+)\1.*\}/', $line, $matches)) {
      foreach($matches["msg"] as $t) {
        $t = preg_replace('/[\'"]\|escape\:.*$/', '', $t);
        $LSlang_cli_logger -> trace("  - \"$t\" # Line $line_count");
        _cli_add_str_to_translate($t, _cli_absolute2relative_path($file).":$line_count");
      }
    }
  }
  $LSlang_cli_logger -> trace("_cli_parse_template_file($file) : done.");
}

/**
 * CLI helper to find and parse all template files of the specified directory
 * to locate strings to translate
 * @param  string $dir  The directory path
 * @return void
 */
function _cli_find_and_parse_template_file($dir) {
  global $LSlang_cli_logger;
  if (is_dir($dir)) {
    $LSlang_cli_logger -> debug("Looking for string to translate in template directory '$dir'");
    if ($dh = opendir($dir)) {
      while (($file = readdir($dh)) !== false) {
        if ($file=='.' || $file=='..') continue;
        if (is_dir("$dir/$file")) {
          _cli_find_and_parse_template_file("$dir/$file");
        }
        elseif (is_file($dir."/".$file) && preg_match('/\.tpl$/',$file)) {
          _cli_parse_template_file("$dir/$file");
        }
      }
      closedir($dh);
    }
  }
}

/**
 * CLI helper to parse a PHP file to locate strings to translate
 * @param  string $file  The file path
 * @return void
 */
function _cli_parse_php_file($file) {
  global $LSlang_cli_logger;
  $LSlang_cli_logger -> debug("Looking for string to translate in '$file' PHP file");
  $quote = '';
  $res = '';
  $comment = null;
  $line_break_continue = false;
  $inside_keyword = false;
  $inside_comment = false;
  $end_comment_pos = false;
  $keyword = null;
  foreach(file($file) as $line_count => $line) {
    $line = preg_replace("/\n$/", "", $line);
    $line_count++;
    $LSlang_cli_logger -> trace("Handle line #$line_count of '$file' PHP file : '$line'");
    $offset = 0;
    while (
      $line_break_continue
      || (!$inside_comment && preg_match('#^([ \t])*(//|/\*)#', substr($line, $offset), $comment))
      || ($inside_comment && $end_comment_pos = strpos(substr($line, $offset), '*/'))
      || preg_match('/[^a-zA-Z0-9\'\"]_{1,3}\(/', substr($line, $offset), $keyword, PREG_OFFSET_CAPTURE)
    ) {
      if ($line_break_continue) {
        $LSlang_cli_logger -> trace("$file:$line_count: continue from previous line after a line break");
        $line_break_continue = false;
        $offset = 0;
        $inside_keyword = true;
      }
      elseif ($comment) {
        $pos = $offset + strlen($comment[1]);
        if ($comment[2] == '//') {
          $LSlang_cli_logger -> trace(
            "$file:$line_count: entering comment detected '".$comment[2]."' at position #$pos, ".
            "ignore the line ('$line', offset=$offset)");
          break;
        }
        $LSlang_cli_logger -> trace(
          "$file:$line_count: entering comment detected '".$comment[2]."' at position #$pos ".
          "('$line', offset=$offset)");
        $inside_comment = true;
        $offset = $pos + strlen($comment[2]);
        $comment = null;
      }
      elseif ($inside_comment) {
        if ($end_comment_pos) {
          $pos = $offset + $end_comment_pos;
          $LSlang_cli_logger -> trace(
            "$file:$line_count: end of comment detected at position #$pos, continue ".
            "('$line', offset=$offset)");
          $offset = $pos;
          $end_comment_pos = null;
          $inside_comment = false;
          continue;
        }
        if ($keyword) {
          $pos = $offset + $keyword[0][1];
          $LSlang_cli_logger -> trace(
            "$file:$line_count: detect keyword '".substr($keyword[0][0], 1).")' at position #$pos,".
            " but inside comment => ignore it ('$line', offset=$offset)");
          $offset += strlen($keyword[0][0]) + $keyword[0][1];
        }
        else {
          $LSlang_cli_logger -> fatal(
            "$file:$line_count: not supported case (inside comment & !keyword), stop");
          return;
        }
      }
      else if ($keyword) {
        $pos = $offset + $keyword[0][1];
        $LSlang_cli_logger -> trace(
          "$file:$line_count: detect keyword '".substr($keyword[0][0], 1).")' at position #$pos ".
          "('$line', offset=$offset)");
        $offset += strlen($keyword[0][0]) + $keyword[0][1];
        $closed = false;
        $inside_keyword = true;
      }
      else {
        $LSlang_cli_logger -> fatal(
          "$file:$line_count: not supported case, stop");
        return;
      }
      $ignore = false;
      $concatenation_need = false;
      for ($i=$offset; $i<strlen($line); $i++) {
        $offset = $i; // Always increase offset to avoid infinity-loop
        if ($inside_comment) {
          if (substr($line, $i, 2) == '*/') {
            $LSlang_cli_logger -> trace(
              "$file:$line_count: End of comment detected at position #$i ('$line')");
            $inside_comment = false;
          }
          $i++;
        }
        elseif (empty($quote)) {
          // Quote char not detected : try to detect it
          if ($line[$i]=='\\' || $line[$i]==" " || $line[$i]=="\t") {
            // Space or escape char : pass
            $i++;
          }
          elseif (in_array($line[$i], array('"', "'")) && !$concatenation_need) {
            // Quote detected
            $LSlang_cli_logger -> trace(
              "$file:$line_count: Quote (".$line[$i].") detected at position #$i ('$line')");
            $quote=$line[$i];
          }
          elseif ($line[$i]=='$') {
            // Variable translation not possible or end function call detected
            $LSlang_cli_logger -> trace(
              "$file:$line_count: Variable translation detected at position #$i, ignore it ".
              "('$line')");
            $ignore = true;
            break;
          }
          elseif ($line[$i]==')' && $inside_keyword) {
            // End function call detected
            $LSlang_cli_logger -> trace(
              "$file:$line_count: End function call detected at position #$i ('$line')");
            $inside_keyword = false;
            break;
          }
          elseif ($line[$i] == '.' && $concatenation_need) {
            // Concatenation char detected
            $LSlang_cli_logger -> trace(
              "$file:$line_count: Concatenation char detected at position #$i ('$line')");
            $concatenation_need = false;
          }
          elseif (preg_match('/[A-Za-z]/', $line[$i])) {
            // Constant or function call detected
            $LSlang_cli_logger -> trace(
              "$file:$line_count: Constant or function call detected at position #$i, ignore it ('$line')");
            $ignore = true;
            break;
          }
          else {
            // Unknown case : continue
            $LSlang_cli_logger -> trace(
              "$file:$line_count: Unknown case before quote at position #$i ('".$line[$i]."'), ".
              "ignore it and continue ('$line')");
            $i++;
          }
        }
        elseif ($quote) {
          // Quote char already detected : try to detect end quote char
          if ($line[$i] == '\\') {
            // Escape char detected : keep it and the following one
            $LSlang_cli_logger -> trace(
              "$file:$line_count: Escape char detected inside quote at position #$i, keep it and ".
              "the following one ('$line')");
            $res .= $line[$i];
            $i++;
            $res .= $line[$i];
          }
          elseif ($line[$i] == $quote) {
            // End quote char detected : reset quote and set concatenation as need
            $LSlang_cli_logger -> trace(
              "$file:$line_count: End quote char detected at position #$i, reset quote and set ".
              "concatenation as need ('$line')");
            $quote = '';
            $concatenation_need = true;
          }
          else {
            // End quote char not detected : append current char to result
            $res .= $line[$i];
          }
        }
      }
      // Include detected string if not empty and quote char was detected and reseted
      if ($inside_comment) {
        $LSlang_cli_logger -> trace(
          "$file:$line_count: line break detected inside comment ('$line')");
      }
      elseif (!empty($res) && empty($quote) && !$inside_keyword) {
        _cli_add_str_to_translate($res, _cli_absolute2relative_path($file).":$line_count");
        $res = '';
      }
      else if (!$ignore) {
        $LSlang_cli_logger -> trace(
          "$file:$line_count: line break detected inside keyword, continue on next line ('$line')");
        $line_break_continue = true;
        if ($quote)
          $res .= "\n";
        break;
      }
    }
  }
}

/**
 * CLI helper to find and parse all PHP files of the specified directory
 * to locate strings to translate
 * @param  string $dir  The directory path
 * @return void
 */
function _cli_find_and_parse_php_file($dir, $filename_regex=null, $recursive=false, $ignore_dirs=null) {
  global $LSlang_cli_logger;
  $filename_regex = $filename_regex?$filename_regex:'/^(.+)\.php$/';
  if (is_dir($dir)) {
    $LSlang_cli_logger -> debug("Looking for string to translate in PHP files of directory '$dir'");
    if ($dh = opendir($dir)) {
      while (($file = readdir($dh)) !== false) {
        if ($file == '.' || $file == '..') continue;
        if (is_link("$dir/$file")) continue;
        if (
          is_dir("$dir/$file")
          && $recursive
          && (!is_array($ignore_dirs) || !in_array("$dir/$file", $ignore_dirs))
        ) {
          _cli_find_and_parse_php_file("$dir/$file", $filename_regex, true, $ignore_dirs);
        }
        elseif (is_file($dir."/".$file) && preg_match($filename_regex, $file)) {
          _cli_parse_php_file("$dir/$file");
        }
      }
      closedir($dh);
    }
  }
}

/**
 * CLI helper to generate output PHP translation file
 * @param  resource $fd The file descriptor where to write to generated output
 * @return void
 */
function _cli_output_php($fd) {
  global $LSlang_cli_logger, $additionalfileformat, $data, $copyoriginalvalue, $keep_unused, $translations;
  fwrite($fd, "<?php\n\n");

  if (!$additionalfileformat) fwrite($fd, "\$GLOBALS['LSlang'] = array (\n");

  foreach($data as $key => $key_data) {
    if ($copyoriginalvalue && $key_data['translation'] == "") {
      $val = $key;
    }
    else
      $val = $key_data['translation'];
    $key=str_replace('"','\\"',$key);
    $val=str_replace('"','\\"',$val);
    foreach ($key_data['contexts'] as $context)
      fwrite($fd, "\n# $context");
    if ($additionalfileformat) {
      fwrite($fd, "\n\$GLOBALS['LSlang'][\"$key\"] = \"$val\";\n");
    }
    else {
      fwrite($fd, "\n\"$key\" =>\n  \"$val\",\n");
    }
  }

  // Handle keep unused mode
  if ($keep_unused) {
    $unused_msgs = array();
    foreach ($translations as $msg => $trans)
      if (!array_key_exists($msg, $data))
        $unused_msgs[$msg] = $trans;
    if ($unused_msgs) {
      fwrite($fd, "\n\n");
      fwrite($fd, "######################################################################\n");
      fwrite($fd, "#                     Unused translations keeped                     #\n");
      fwrite($fd, "######################################################################\n");
      foreach($unused_msgs as $key => $val) {
        $key=str_replace('"','\\"',$key);
        $val=str_replace('"','\\"',$val);
        if ($additionalfileformat) {
          fwrite($fd, "\n\$GLOBALS['LSlang'][\"$key\"] = \"$val\";\n");
        }
        else {
          fwrite($fd, "\n\"$key\" =>\n  \"$val\",\n");
        }
      }
    }
  }

  if (!$additionalfileformat) fwrite($fd, "\n);\n");
}

/**
 * CLI helper to clean a orignal or translated message before writing it in a POT file
 * @param  string $val The string to clean
 * @return string The cleaned string
 */
function _cli_clean_for_pot_file($val) {
  $val = preg_replace('/([^\\\\])"/', '$1\"', $val);
  return str_replace("\n", '\n', $val);
}

/**
 * CLI helper to generate output POT translation file
 * @param  resource $fd The file descriptor where to write to generated output
 * @return void
 */
function _cli_output_pot($fd) {
  global $LSlang_cli_logger, $data, $copyoriginalvalue;
  foreach($data as $key => $key_data) {
    if ($copyoriginalvalue && $key_data['translation'] == "") {
      $val = $key;
    }
    else
      $val = $key_data['translation'];
    foreach ($key_data['contexts'] as $context)
      fwrite($fd, "#: $context\n");
    $key = _cli_clean_for_pot_file($key);
    $val = _cli_clean_for_pot_file($val);
    fwrite($fd, "msgid \"$key\"\nmsgstr \"$val\"\n\n");
  }
}

$available_onlys = array("config", "templates", "addons", "auth_methods", "includes", "php");
$available_withouts = array_merge($available_onlys, array("select-list"));


/**
 * CLI generate_lang_file command
 *
 * @param string $command_args Command arguments
 *
 * @return boolean True on success, false otherwise
 **/
function cli_generate_lang_file($command_args) {
  // Use global variables to share it with sub-functions
  global $LSlang_cli_logger, $available_onlys, $available_withouts, $data, $translations, $interactive,
  $interactive_exit, $copyoriginalvalue, $format, $curdir, $additionalfileformat, $copyoriginalvalue,
  $keep_unused, $lang;

  // Initialize logger (if not already initialized by another CLI command)
  if (!isset($LSlang_cli_logger))
    $LSlang_cli_logger = LSlog :: get_logger('generate_lang_file');

  // Store existing translations
  $translations = array();
  // Store output translations
  $data = array();

  // Parameters
  $only = null;
  $withouts = array();
  $include_upstream = false;
  $copyoriginalvalue = False;
  $interactive = False;
  $interactive_exit = False; // Exit flag set when user type 'q'
  $output = False;
  $additionalfileformat = False;
  $keep_unused = False;
  $fix_utf8 = False;
  $lang = null;
  $encoding = null;
  $available_formats = array('php', 'pot');
  $format=$available_formats[0];

  $debug = false;
  $load_files = array();

  // Change directory
  $curdir = getcwd();
  chdir(dirname(__FILE__).'/../');

  for ($i=0; $i < count($command_args); $i++) {
    switch ($command_args[$i]) {
      case '--without':
      case '-W':
        $i++;
        $without = strtolower($command_args[$i]);
        if (!in_array($without, $available_withouts))
          LScli :: usage(_("Invalid -W/--without parameter. Must be one of the following values: %s.'"), implode("','", $available_withouts));
        elseif ($only)
          LScli :: usage(_("You could not use -W/--without parameter combined with -O/--only parameter."));
        $withouts[] = $without;
        break;

      case '--only':
      case '-O':
        $i++;
        if ($only)
          LScli :: usage(_("You could specify only one -O/--only parameter."));
        $only = strtolower($command_args[$i]);
        if (!in_array($only, $available_onlys))
          LScli :: usage(_("Invalid -O/--only parameter. Must be one of the following values: '%s'."), implode("', '", $available_onlys));
        elseif ($withouts)
          LScli :: usage(_("You could not use -W/--without parameter combined with -O/--only parameter."));
        break;

      case '-I':
      case '--include-upstream':
        $include_upstream=True;
        break;

      case '--copy-original-value':
      case '-c':
        $copyoriginalvalue=True;
        break;

      case '--interactive':
      case '-i':
        $interactive=True;
        break;

      case '--additional-file-format':
      case '-a':
        $additionalfileformat=True;
        break;

      case '--lang':
      case '-l':
        $i++;
        $parse_lang = explode('.', $command_args[$i]);
        if (count($parse_lang) == 2) {
          $lang = $parse_lang[0];
          $encoding = $parse_lang[1];
        }
        else {
          LScli :: usage(_("Invalid -l/--lang parameter. Must be compose in format : [lang].[encoding]"));
        }
        break;

      case '--output':
      case '-o':
        $i++;
        $output = $command_args[$i];
        break;

      case '--format':
      case '-f':
        $i++;
        $format = strtolower($command_args[$i]);
        if (!in_array($format, $available_formats)) {
          LScli :: usage(_("Invalid -f/--format parameter. Must be one of the following values: %s.'"), implode("','", $available_formats));
        }
        break;

      case '--debug':
      case '-d':
        $debug = true;
        break;

      case '--keep-unused':
      case '-K':
        $keep_unused = true;
        break;

      case '--fix-utf8':
      case '-F':
        $fix_utf8 = true;
        break;

      default:
        $path = _cli_relative2absolute_path($command_args[$i]);
        if (is_file($path))
          $load_files[] = $path;
        else
          LScli :: usage(_("%s: Invalid parameter or lang file to load."), $command_args[$i]);
    }
  }



  // In fix-utf8 mode, load ForceUT8/Encoding lib
  if ($fix_utf8)
    LSsession :: includeFile(LS_LIB_DIR . "ForceUTF8/Encoding.php");

  // Load translation files
  foreach($load_files as $path) {
    $LSlang_cli_logger -> debug("Load $path lang file");
    @include($path);
    foreach($GLOBALS['LSlang'] as $msg => $trans) {
      if ($fix_utf8)
        $translations[\ForceUTF8\Encoding::fixUTF8($msg)] = \ForceUTF8\Encoding::fixUTF8($trans);
      else
        $translations[$msg] = $trans;
    }
  }

  // Initialize session
  LSlang :: setLocale($lang, $encoding);

  // Load lang string if lang was specify
  if ($lang && $encoding && isset($GLOBALS['LSlang']) && is_array($GLOBALS['LSlang'])) {
    foreach($GLOBALS['LSlang'] as $msg => $trans) {
      if ($fix_utf8)
        $translations[\ForceUTF8\Encoding::fixUTF8($msg)] = \ForceUTF8\Encoding::fixUTF8($trans);
      else
        $translations[$msg] = $trans;
    }
  }

  /*
   * Manage configuration parameters
   */
  if (!in_array('config', $withouts) && (!$only || $only == 'config')) {
    // LDAP Servers
    $objects = array();
    $LSlang_cli_logger -> info("Looking for string to translate configuration of LDAP servers");
    foreach(LSconfig :: keys('ldap_servers') as $ldap_server_id) {
      $LSlang_cli_logger -> debug("Looking for string to translate configuration of LDAP server #$ldap_server_id");
      _cli_add_str_to_translate_from_LSconfig("ldap_servers.$ldap_server_id.name");
      _cli_add_str_to_translate_from_LSconfig("ldap_servers.$ldap_server_id.subDnLabel");
      _cli_add_str_to_translate_from_LSconfig("ldap_servers.$ldap_server_id.recoverPassword.recoveryHashMail.subject");
      _cli_add_str_to_translate_from_LSconfig("ldap_servers.$ldap_server_id.recoverPassword.recoveryHashMail.msg");
      _cli_add_str_to_translate_from_LSconfig("ldap_servers.$ldap_server_id.recoverPassword.newPasswordMail.subject");
      _cli_add_str_to_translate_from_LSconfig("ldap_servers.$ldap_server_id.recoverPassword.newPasswordMail.msg");
      _cli_add_str_to_translate_from_LSconfig("ldap_servers.$ldap_server_id.subDn.*", 'key', array("LSobject"));
      _cli_add_str_to_translate_from_LSconfig("ldap_servers.$ldap_server_id.LSprofiles.*.label");

      // LSaccess
      foreach (LSconfig :: get("ldap_servers.$ldap_server_id.LSaccess", array()) as $LSobject) {
        if (is_string($LSobject) && !in_array($LSobject, $objects) && LSsession :: loadLSobject($LSobject)) {
          $objects[] = $LSobject;
        }
      }

      // Sub DN LSobjects
      foreach (LSconfig :: getMatchingKeys("ldap_servers.$ldap_server_id.subDn.*.LSobjects.*") as $LSobject)
        if (is_string($LSobject) && !in_array($LSobject, $objects) && LSsession :: loadLSobject($LSobject))
          $objects[] = $LSobject;

    }

    $LSlang_cli_logger -> debug('LSobjects list : '.implode(', ', $objects));

    // LSobject
    foreach($objects as $obj) {
      $LSlang_cli_logger -> info("Looking for string to translate configuration of object type $obj");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.label");

      // LSrelation
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSrelation.*.label");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSrelation.*.emptyText");

      // Custom Actions
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.customActions.*.label");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.customActions.*.helpInfo");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.customActions.*.question_format");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.customActions.*.onSuccessMsgFormat");

      // LSform
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSform.layout.*.label");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSform.dataEntryForm.*.label");

      // LSioFormat
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.ioFormat.*.label");

      // LSsearch
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSsearch.predefinedFilters.*");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSsearch.extraDisplayedColumns.*.label");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSsearch.extraDisplayedColumns.*.LSformat");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSsearch.extraDisplayedColumns.*.alternativeLSformats.*");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSsearch.extraDisplayedColumns.*.formaterLSformat");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSsearch.customActions.*.label");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSsearch.customActions.*.question_format");
      _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.LSsearch.customActions.*.onSuccessMsgFormat");

      // Attributes
      foreach(LSconfig :: keys("LSobjects.$obj.attrs") as $attr) {
        _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.label");
        _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.help_info");
        _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.no_value_label");
        _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.check_data.*.msg");
        _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.validation.*.msg");

        // HTML Options
        $html_type = LSconfig :: get("LSobjects.$obj.attrs.$attr.html_type");
        switch($html_type) {
          case 'boolean':
            _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.true_label");
            _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.false_label");
            break;
          case 'jsonCompositeAttribute':
            $components = LSconfig :: keys("LSobjects.$obj.attrs.$attr.html_options.components");
            foreach($components as $c) {
              _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.components.$c.label");
              _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.components.$c.help_info");
              _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.components.$c.check_data.*.msg");

              if (LSconfig :: get("LSobjects.$obj.attrs.$attr.html_options.components.$c.type") == 'select_list')
                _cli_add_possible_values_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.components.$c.options", $withouts);
            }
            break;
          case 'labeledValue':
            if (LSconfig :: get("LSobjects.$obj.attrs.$attr.html_options.translate_labels", True, "bool"))
              _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.labels.*");
            break;
          case 'password':
            _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.mail.subject");
            _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.mail.msg");
            _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.confirmChangeQuestion");
            break;
          case 'select_list':
          case 'select_box':
            _cli_add_possible_values_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options", $withouts);
            break;
          case 'valueWithUnit':
            _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.units.*");
            break;
          case 'date':
            _cli_add_str_to_translate_from_LSconfig("LSobjects.$obj.attrs.$attr.html_options.special_values.*");
            break;
        }
      }
    }
  }

  /*
   * Manage template file
   */
  if (!in_array('templates', $withouts) && (!$only || $only == 'templates')) {
    $LSlang_cli_logger -> info("Looking for string to translate in templates file");
    if ($include_upstream) _cli_find_and_parse_template_file(LS_ROOT_DIR.'/'.LS_TEMPLATES_DIR);
    _cli_find_and_parse_template_file(LS_ROOT_DIR.'/'.LS_LOCAL_DIR.LS_TEMPLATES_DIR);
  }

  /*
  * Manage custom PHP code/config files
   */

  // Manage includes files
  if (!in_array('includes', $withouts) && (!$only || $only == 'includes')) {
    // Note: Upstream code most only use gettext translation, do not handle it here
    if ($include_upstream) _cli_find_and_parse_php_file(LS_ROOT_DIR.'/'.LS_INCLUDE_DIR);
    _cli_find_and_parse_php_file(LS_ROOT_DIR.'/'.LS_LOCAL_DIR.LS_INCLUDE_DIR);
    if ($include_upstream) _cli_find_and_parse_php_file(LS_ROOT_DIR.'/'.LS_CLASS_DIR, '/^class\.(.+)\.php$/');
    _cli_find_and_parse_php_file(LS_ROOT_DIR.'/'.LS_LOCAL_DIR.LS_CLASS_DIR, '/^class\.(.+)\.php$/');
  }

  // Manage addons files
  if (!in_array('addons', $withouts) && (!$only || $only == 'addons')) {
    $LSlang_cli_logger -> info("Looking for string to translate in LSaddons PHP code");
    if ($include_upstream) _cli_find_and_parse_php_file(LS_ROOT_DIR.'/'.LS_ADDONS_DIR, '/^LSaddons\.(.+)\.php$/');
    _cli_find_and_parse_php_file(LS_ROOT_DIR.'/'.LS_LOCAL_DIR.LS_ADDONS_DIR, '/^LSaddons\.(.+)\.php$/');
    $LSlang_cli_logger -> info("Looking for string to translate in LSaddons configuration files");
    if ($include_upstream) _cli_find_and_parse_php_file(LS_ROOT_DIR.'/'.LS_CONF_DIR.'/LSaddons', '/^config\.LSaddons\.(.+)\.php$$/');
    _cli_find_and_parse_php_file(LS_ROOT_DIR.'/'.LS_LOCAL_DIR.LS_CONF_DIR.'/LSaddons', '/^config\.LSaddons\.(.+)\.php$$/');
  }

  // Manage auth methods files
  if (!in_array('auth_methods', $withouts) && (!$only || $only == 'auth_methods')) {
    $LSlang_cli_logger -> info("Looking for string to translate in LSauthMethods configuration files");
    if ($include_upstream) _cli_find_and_parse_php_file(LS_ROOT_DIR.'/'.LS_CONF_DIR.'/LSauth', '/^config\.(.+)\.php$$/');
    _cli_find_and_parse_php_file(LS_ROOT_DIR.'/'.LS_LOCAL_DIR.LS_CONF_DIR.'/LSauth', '/^config\.(.+)\.php$$/');
  }

  // Manage all PHP files
  if ($only == 'php') {
    $LSlang_cli_logger -> info("Looking for string to translate in PHP files");
    _cli_find_and_parse_php_file(LS_ROOT_DIR, null, true, array(LS_ROOT_DIR.'/'.LS_LOCAL_DIR));
  }

  // Sort resulting strings
  ksort($data);

  /*
   * Handle output file format
   */

  // Determine where to write result
  if ($output) {
    $output = _cli_relative2absolute_path($output);
    $LSlang_cli_logger -> info("Write result in output file ($output)");
    $fd = null;
    try {
      $LSlang_cli_logger -> debug("Open output file ($output)");
      $fd = fopen($output, 'w');
    }
    catch(Exception $e) {
      $LSlang_cli_logger -> error('Error occured opening output file : '.$e->getMessage(), "\n");
    }
    if (!$fd) {
      $LSlang_cli_logger -> error("Use stdout out instead.\n");
      $fd = STDOUT;
      $output = false;
    }
  }
  else
    $fd = STDOUT;

  // Generate output
  $LSlang_cli_logger -> debug("Output format : $format");
  switch($format) {
    case 'pot':
      _cli_output_pot($fd);
      break;
    case 'php':
    default:
      _cli_output_php($fd);
      break;
  }

  // Close output file (is specified)
  if ($output && $fd != STDOUT) {
    $LSlang_cli_logger -> debug("Close output file ($output)");
    fclose($fd);
  }

  return true;
}

/**
 * Args autocompleter for CLI command generate_lang_file
 *
 * @param array $comp_words List of already typed words of the command
 * @param int $comp_word_num The command word number to autocomplete
 * @param string $comp_word The command word to autocomplete
 * @param array<string> $opts List of global available options
 *
 * @return array<string> List of available options for the word to autocomplete
 **/
function cli_generate_lang_file_args_autocompleter($comp_words, $comp_word_num, $comp_word, $opts) {
  global $available_withouts, $available_onlys;
  switch ($comp_words[$comp_word_num-1]) {
    case '-W':
    case '--without':
      return LScli :: autocomplete_opts($available_withouts, $comp_word);
      break;
    case '-O':
    case '--only':
      return LScli :: autocomplete_opts($available_onlys, $comp_word);
      break;
    case '-l':
    case '--lang':
      return LScli :: autocomplete_opts(LSlang :: getLangList(false, true), $comp_word);
      break;
    case '-o':
    case '--output':
      return array();
      break;
    case '-f':
    case '--format':
      return LScli :: autocomplete_opts(array('php', 'pot'), $comp_word);
      break;
  }
  $opts = array_merge(
    $opts,
    array (
      '-W', '--without',
      '-O', '--only',
      '-c', '--copy-original-value',
      '-i', '--interactive',
      '-a', '--additional-file-format',
      '-l', '--lang',
      '-o', '--output',
      '-f', '--format',
      '-I', '--include-upstream',
      '-K', '--keep-unused',
      '-F', '--fix-utf8',
    )
  );
  return LScli :: autocomplete_opts($opts, $comp_word);
}

LScli :: add_command(
  'generate_lang_file',
  'cli_generate_lang_file',
  ___('Generate lang.php file'),
  '-l [lang] [-o output.file] [file1] [file2] [-h] [options]',
  array(
    ___(
"  -W/--without                Disable specified messages. Must be one of
                              the following values:"),
    "                               - ".implode("\n                               - ", $available_withouts),
___(
"  -O/--only                   Only handle specified messages. Must be one
                              of the following values :"),
    "                               - ".implode("\n                               - ", $available_onlys),
___(
"  -I/--include-upstream       Include upstream code to message lookup
  -c/--copy-original-value    Copy original value as translated value when
                              no translated value exists
  -i/--interactive            Interactive mode : ask user to enter
                              translated on each translation needed
  -a/--additional-file-format Additional file format output
  -l/--lang                   Language of the translation
                              Format: [lang].[encoding]
  -o/--output                 Output file (default: stdout)
  -f/--format                 Output file format : php or pot
                              (default: php)
  -K/--keep-unused            Keep unused translations in resulting file
  -F/--fix-utf8               Try to load and fix broken UTF-8 characters
                              in existing lang files.")
  ),
  false,  // This command does not need LDAP connection
  'cli_generate_lang_file_args_autocompleter'
);

/**
 * CLI generate_ldapsaisie_pot command
 *
 * @param string $command_args Command arguments
 *
 * @return boolean True on success, false otherwise
 **/
function cli_generate_ldapsaisie_pot($command_args) {
  global $LSlang_cli_logger;

  // Parameters
  $use_internal_parser = false;

  for ($i=0; $i < count($command_args); $i++) {
    switch ($command_args[$i]) {
      case '--internal':
      case '-I':
        $use_internal_parser = true;
        break;

      default:
        LScli :: usage(_("%s: Invalid parameter."), $command_args[$i]);
    }
  }

  // Initialize logger (if not already initialized by another CLI command)
  if (!isset($LSlang_cli_logger))
    $LSlang_cli_logger = LSlog :: get_logger('generate_ldapsaisie_pot');

  // Clean php file in tmp directory
  if (is_dir(LS_TMP_DIR_PATH)) {
    foreach(listFiles(LS_TMP_DIR_PATH, '/\.php$/') as $file) {
      $tmp_file = LS_TMP_DIR_PATH.$file;
      $LSlang_cli_logger -> debug("Remove temporary file '$tmp_file'");
      if (!unlink($tmp_file)) {
        $LSlang_cli_logger -> fatal("Fail to delete temporary file '$tmp_file'.");
      }
    }
  }

  // List PHP files to parse
  $php_files = LScli :: run_external_command(
    array('find', '-name', "'*.php'"),
    null,   // no STDIN data
    false,   // do not escape command args (already done)
    LS_ROOT_DIR // run in LdapSaisie root directory to retreive relative paths
  );
  if (!is_array($php_files) || $php_files[0] != 0) {
    $LSlang_cli_logger -> fatal("Fail to list PHP files.");
    return false;
  }

  // Extract messages from LdapSaisie PHP files
  if ($use_internal_parser) {
    // Using internal parser
    $result = LScli :: run_command(
      'generate_lang_file',
      array (
        "-o", LS_I18N_DIR_PATH."/ldapsaisie-main.pot",
        "-f", "pot",
        "--only", "php",
      ),
      false // do not exit
    );
    if (!$result)
      $LSlang_cli_logger -> fatal("Fail to extract messages from PHP files using generate_lang_file command.");
  }
  else {
    // Using xgettext
    $result = LScli :: run_external_command(
      array(
        "xgettext",
        "--from-code utf-8",
        "--language=PHP",
        "-o", LS_I18N_DIR_PATH."/ldapsaisie-main.pot",      // Output
        "--omit-header",                                    // No POT header
        "--keyword=__",                                     // Handle custom __() translation function
        "--keyword=___",                                    // Handle custom ___() translation function
        "--files=-"                                         // Read files to parse from STDIN
      ),
      $php_files[1],                                        // Pass PHP files list via STDIN
      true,                                                 // Escape parameters
      LS_ROOT_DIR                                           // Run in LdapSaisie root directory
    );
    if (!is_array($result) || $result[0] != 0)
      $LSlang_cli_logger -> fatal("Fail to extract messages from PHP files using xgettext.");
  }

  // Extract other messages from LdapSaisie templates files
  $result = LScli :: run_command(
    'generate_lang_file',
    array (
      "-o", LS_I18N_DIR_PATH."/ldapsaisie-templates.pot",
      "-f", "pot",
      "--only", "templates",
      "--include-upstream",
    ),
    false // do not exit
  );
  if (!$result)
    $LSlang_cli_logger -> fatal("Fail to extract messages from template files using generate_lang_file command.");

  // Merge previous results in ldapsaisie.pot file using msgcat
  $result = LScli :: run_external_command(array(
    'msgcat',
    LS_I18N_DIR_PATH."/ldapsaisie-main.pot",
    LS_I18N_DIR_PATH."/ldapsaisie-templates.pot",
    "-o", LS_I18N_DIR_PATH."/ldapsaisie.pot",
  ));
  if (!is_array($result) || $result[0] != 0)
    $LSlang_cli_logger -> fatal("Fail to merge messages using msgcat.");

  return true;
}
LScli :: add_command(
  'generate_ldapsaisie_pot',
  'cli_generate_ldapsaisie_pot',
  ___('Generate POT files:'),
  null,
  array(
    ___("This command generate 3 POT files:"),
    " - ".LS_I18N_DIR_PATH."/ldapsaisie-main.pot",
    ___("   => contains messages from PHP files"),
    " - ".LS_I18N_DIR_PATH."/ldapsaisie-templates.pot",
    ___("   => contains messages from templates files"),
    " - ".LS_I18N_DIR_PATH."/ldapsaisie.pot",
    ___("   => contains all messages"),
    null,
___(
"Note: Use -I / --internal parameter to use internal parser instead of
xgettext for PHP files."),
  ),
  false   // This command does not need LDAP connection
);
