#!/usr/bin/php -q
<?PHP
// Copyright 2005-2023, Lime Technology
// License: GPLv2 only
//
// Program updates made by Bergware International (April 2020)
// Program updates made by Bergware International (June 2022)

$usage = <<<EOF
Process language files.

Usage: language install LANGUAGE-FILE
  install a language

Usage: language [attribute name] LANGUAGE-FILE
  obtain an attribute value

Usage: language check LANGUAGE
  check and output the latest version of the language

Usage: language update LANGUAGE
  update the language

Usage: language remove LANGUAGE
  remove the language

Usage: language checkall
  check the latest version of all installed languages

Usage: language updateall
  update all installed languages

EOF;

// Error code to description (wget)
// ref: https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html
//
function error_desc($code) {
  switch($code) {
    case 0: return 'No errors';
    case -1: return 'Generic error';
    case 1: return 'Generic error';
    case 2: return 'Parse error';
    case 3: return 'File I/O error';
    case 4: return 'Network failure';
    case 5: return 'SSL verification failure';
    case 6: return 'Username/password authentication failure';
    case 7: return 'Protocol errors';
    case 8: return 'Invalid URL / Server error response';
    default: return 'Error code '.$code;
  }
}

// Signal DONE to caller
//
function done($code) {
  global $nchan;
  if ($nchan) write('_DONE_','');
  exit($code);
}

// Function to write either to console (echo) or nchan (curl)
// Default output is console, use optional parameter "nchan" to write to nchan instead
//
function write(...$messages){
  global $nchan;
  if ($nchan) {
    $com = curl_init();
    curl_setopt_array($com,[
      CURLOPT_URL => 'http://localhost/pub/plugins?buffer_length=1',
      CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
      CURLOPT_POST => 1,
      CURLOPT_RETURNTRANSFER => true
    ]);
    foreach ($messages as $message) {
      curl_setopt($com, CURLOPT_POSTFIELDS, $message);
      curl_exec($com);
    }
    curl_close($com);
  } else {
    foreach ($messages as $message) echo $message;
  }
}

// Run command and obtain output
//
function run($command) {
  $run = popen($command,'r');
  while (!feof($run)) write(fgets($run));
  return pclose($run);
}

// Run hooked scripts before correct execution of "method"
// method = install, update, remove, check
// hook programs receives three parameters: type=language and method and language-name
//
function pre_hooks() {
  global $method, $name;
  $language = (pathinfo($name)['extension']??'')=='xml' ? $name : "lang-$name.xml";
  $hooks = "/usr/local/emhttp/plugins/dynamix.plugin.manager/pre-hooks";
  foreach (glob("$hooks/*") as $hook) if (is_executable($hook)) {
    write("Executing hook script: ".basename($hook)."\n");
    run("$hook language $method $language");
  }
}

// Run hooked scripts after successful or failed completion of "method"
// method = install, update, remove, check
// hook programs receives four parameters: type=language and method and language-name and error (empty if none)
//
function post_hooks($error='') {
  global $method, $name;
  $language = (pathinfo($name)['extension']??'')=='xml' ? $name : "lang-$name.xml";
  $hooks = "/usr/local/emhttp/plugins/dynamix.plugin.manager/post-hooks";
  foreach (glob("$hooks/*") as $hook) if (is_executable($hook)) {
    write("Executing hook script: ".basename($hook)."\n");
    run("$hook language $method $language $error");
  }
}

// Download a file from a URL.
// Returns TRUE if success else FALSE and fills in error.
//
function download($url, $name, &$error) {
  $plg = basename($url);
  if ($file = popen("wget --compression=auto --no-cache --progress=dot -O $name $url 2>&1", 'r')) {
    write("language: downloading: $plg ...\r");
    $level = -1;
    while (!feof($file)) {
      if (preg_match('/\d+%/', fgets($file), $matches)) {
        $percentage = substr($matches[0],0,-1);
        if ($percentage > $level) {
          write("language: downloading: $plg ... $percentage%\r");
          $level = $percentage;
        }
      }
    }
    if (($perror = pclose($file)) == 0) {
      write("language: downloading: $plg ... done\n");
      return true;
    } else {
      $error = "$plg download failure (".error_desc($perror).")";
      return false;
    }
  } else {
    $error = "$plg failed to open";
    return false;
  }
}

// Deal with logging message.
//
function logger($message) {
  shell_exec("logger $message");
}

// Interpret a language file
// Returns TRUE if success, else FALSE and fills in error string.
//
function language($method, $xml_file, &$error) {
  global $docroot, $boot, $plugins, $tmp;

  // parse language XML file
  $xml = file_exists($xml_file) ? @simplexml_load_file($xml_file,NULL,LIBXML_NOCDATA) : false;
  if ($xml === false) {
    $error = "XML file doesn't exist or xml parse error";
    return false;
  }
  switch ($method) {
  case 'install':
    $url = $xml->LanguageURL;
    $name = $xml->LanguagePack;
    $save = "$boot/dynamix/lang-$name.zip";
    if (!file_exists($save)) {
      if ($url) {
        if (!download($url, $save, $error)) {
          @unlink($save);
          return false;
        }
      } else {
        $error = "missing URL";
        return false;
      }
    }
    $path = "$docroot/languages/$name";
    exec("mkdir -p $path");
    @unlink("$docroot/webGui/javascript/translate.$name.js");
    foreach (glob("$path/*.dot",GLOB_NOSORT) as $dot_file) unlink($dot_file);
    exec("unzip -qqLjo -d $path $save", $dummy, $err);
    if ($err > 1) {
      @unlink($save);
      exec("rm -rf $path");
      $error = "unzip failed. Error code $err";
      return false;
    }
    return true;
  case 'remove':
    $name = $xml->LanguagePack;
    if ($name) {
      $path = "$docroot/languages/$name";
      exec("rm -rf $path");
      @unlink("$docroot/webGui/javascript/translate.$name.js");
      @unlink("$boot/lang-$name.xml");
      @unlink("$plugins/lang-$name.xml");
      @unlink("$tmp/lang-$name.xml");
      @unlink("$boot/dynamix/lang-$name.zip");
      return true;
    } else {
      $error = "missing language pack";
      return false;
    }
  case 'dump':
    // dump file: debugging
    write(print_r($xml,true));
    return true;
  default:
    // return single attribute
    $error = "$method attribute not present";
    return $xml->$method ?: false;
  }
}

$docroot = '/usr/local/emhttp';
$boot    = '/boot/config/plugins';
$plugins = '/var/log/plugins';
$tmp     = '/tmp/plugins';
$method  = $argv[1];
$nchan   = $argv[$argc-1] == 'nchan'; // console or nchan output

// MAIN - single argument
if ($argc < 2) {
  write($usage);
  done(1);
}

// language checkall
// check all installed languages
//
if ($method == 'checkall') {
  write("language: checking all language packs\n");
  foreach (glob("$plugins/lang-*.xml", GLOB_NOSORT) as $link) {
    $lang_file = @readlink($link);
    if ($lang_file === false) continue;
    if (language('LanguageURL', $lang_file, $error) === false) continue;
    $name = str_replace('lang-', '', basename($lang_file, '.xml'));
    $lang = language('Language', $lang_file, $error) ?: $name;
    write("language: checking $lang language pack ...\n");
    exec(realpath($argv[0])." check $name >/dev/null");
  }
  write("language: checking finished.\n");
  done(0);
}

// language updateall
// update all installed languages
//
if ($method == 'updateall') {
  write("language: updating all language packs\n");
  foreach (glob("$plugins/lang-*.xml", GLOB_NOSORT) as $link) {
    $lang_file = @readlink($link);
    if ($lang_file === false) continue;
    if (language('LanguageURL', $lang_file, $error) === false) continue;
    $version = language('Version', $lang_file, $error);
    $name = str_replace('lang-', '', basename($lang_file, '.xml'));
    $lang = language('Language', $lang_file, $error) ?: $name;
    $latest = language('Version', "$tmp/lang-$name.xml", $error);
    // update only when newer
    if (strcmp($latest, $version) > 0) {
      write("language: updating $lang language pack ...\n");
      exec(realpath($argv[0])." update $name >/dev/null");
    }
  }
  write("language: updating finished.\n");
  done(0);
}

// MAIN - two arguments
if ($argc < 3) {
  write($usage);
  done(1);
}

// language install [language_file]
//
if ($method == 'install') {
  $argv[2] = preg_replace('#[\x00-\x1F\x80-\xFF]#', '', $argv[2]);
  $name = basename($argv[2]);
  write("language: installing language pack\n");
  // check for URL
  if (preg_match('#^https?://#',$argv[2])) {
    $langURL = $argv[2];
    $xml_file = "$tmp/$name";
    write("language: downloading: $name\n");
    if (!download($langURL, $xml_file, $error)) {
      write("language: $error\n");
      @unlink($xml_file);
      done(1);
    }
  } else {
    $xml_file = realpath($argv[2]);
  }
  $link_file = "$plugins/$name";
  $lang_file = "$boot/$name";
  @unlink($link_file);
  // run hook scripts for pre processing
  pre_hooks();
  if (language('install', $xml_file, $error) === false) {
    write("language: $error\n");
    // run hook scripts for post processing
    post_hooks($error);
    done(1);
  }
  $lang = language('Language', $xml_file, $error) ?: substr($name,0,-4);
  copy($xml_file, $lang_file);
  symlink($lang_file, $link_file);
  write("language: $lang language pack installed\n");
  logger("language: $lang language pack installed");
  // run hook scripts for post processing
  post_hooks();
  done(0);
}

// language check [language]
//
if ($method == 'check') {
  $name = $argv[2];
  write("language: checking language pack\n");
  $link_file = "$plugins/lang-$name.xml";
  $lang_file = @readlink($link_file);
  if ($lang_file === false) {
    write("language: $name language pack not installed\n");
    done(1);
  }
  $templateURL = language('TemplateURL', $lang_file, $error);
  if ($templateURL === false) {
    write("language: $error\n");
    done(1);
  }
  $xml_file = "$tmp/lang-$name.xml";
  if (!download($templateURL, $xml_file, $error)) {
    write("language: $error\n");
    @unlink($xml_file);
    done(1);
  }
  // run hook scripts for pre processing
  pre_hooks();
  $version = language('Version', $xml_file, $error);
  if ($version === false) {
    write("language: $error\n");
    // run hook scripts for post processing
    post_hooks($error);
    done(1);
  }
  write("$version\n");
  // run hook scripts for post processing
  post_hooks();
  done(0);
}

// language update [language]
//
if ($method == 'update') {
  $name = $argv[2];
  write("language: updating language pack\n");
  $link_file = "$plugins/lang-$name.xml";
  $lang_file = @readlink($link_file);
  if ($lang_file === false) {
    write("language: $name language pack not installed\n");
    done(1);
  }
  // verify previous check has been done
  $xml_file = "$tmp/lang-$name.xml";
  if (!file_exists($xml_file)) {
    write("language: update does not exist, perform a check first\n");
    exit (1);
  }
  $lang = language('Language', $xml_file, $error) ?: $name;
  // check for a reinstall of same version
  $old_version = language('Version', $lang_file, $error);
  $new_version = language('Version', $xml_file, $error);
  if ($new_version == $old_version) {
    write("language: $lang language pack not reinstalling same version\n");
    done(1);
  }
  // install the updated plugin
  @unlink("$boot/dynamix/lang-$name.zip");
  @unlink($link_file);
  // run hook scripts for pre processing
  pre_hooks();
  if (language('install', $xml_file, $error) === false) {
    write("language: $error\n");
    // run hook scripts for post processing
    post_hooks($error);
    done(1);
  }
  copy($xml_file, $lang_file);
  symlink($lang_file, $link_file);
  write("language: $lang language pack updated\n");
  logger("language: $lang language pack updated");
  // run hook scripts for post processing
  post_hooks();
  done(0);
}

// language remove [language]
//
if ($method == 'remove') {
  $name = $argv[2];
  write("language: removing language pack: $name\n");
  $link_file = "$plugins/lang-$name.xml";
  $lang_file = @readlink($link_file);
  if ($lang_file === false) {
    write("language: $name language pack not installed\n");
    done(1);
  }
  $lang = language('Language', $lang_file, $error) ?: $name;
  // run hook scripts for pre processing
  pre_hooks();
  if (language('remove', $lang_file, $error) === false) {
    write("language: $error\n");
    // run hook scripts for post processing
    post_hooks($error);
    done(1);
  }
  write("language: $lang language pack removed\n");
  logger("language: $lang language pack removed");
  // run hook scripts for post processing
  post_hooks();
  done(0);
}

// return attribute
//
$xml_file =  $argv[2];
$value = language($method, $xml_file, $error);
if ($value === false) {
  write("language: $error\n");
  done(1);
}
write("$value\n");
done(0);
?>
