<?php

use oat\generis\model\OntologyRdfs;
use oat\tao\helpers\ApplicationHelper;
use oat\tao\model\menu\MenuService;

/**
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; under version 2
 * of the License (non-upgradable).
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg (under the project TAO & TAO2);
 *               2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung (under the project TAO-TRANSFER);
 *               2009-2012 (update and modification) Public Research Centre Henri Tudor (under the project TAO-SUSTAIN & TAO-DEV);
 *
 */

/**
 * The TaoTranslate script aims at providing command line tools to manage
 * of tao. It enables you to manage the i18n of the messages found in the source
 * (gettext) but also i18n of RDF Models.
 *
 * @access public
 * @author Joel Bout, <joel.bout@tudor.lu>
 * @package tao

 */
class tao_scripts_TaoTranslate extends tao_scripts_Runner
{
    // --- ASSOCIATIONS ---


    // --- ATTRIBUTES ---

    /**
     * Short description of attribute DEF_INPUT_DIR
     *
     * @access public
     * @var string
     */
    const DEF_INPUT_DIR = '.';

    /**
     * Short description of attribute DEF_OUTPUT_DIR
     *
     * @access public
     * @var string
     */
    const DEF_OUTPUT_DIR = 'locales';

    /**
     * Short description of attribute DEF_PO_FILENAME
     *
     * @access public
     * @var string
     */
    const DEF_PO_FILENAME = 'messages.po';

    /**
     * Short description of attribute DEF_JS_FILENAME
     *
     * @access public
     * @var string
     */
    const DEF_JS_FILENAME = 'messages_po.js';

    /**
     * Short description of attribute options
     *
     * @access protected
     * @var array
     */
    protected $options = [];

    /**
     * Short description of attribute DEF_LANG_FILENAME
     *
     * @access public
     * @var string
     */
    const DEF_LANG_FILENAME = 'lang.rdf';

    private static $WHITE_LIST = [
        'actions',
        'helpers',
        'models',
        'views',
        'helper',
        'controller',
        'model',
        'scripts',
    ];

    protected $verbose = false;
    // --- OPERATIONS ---


    /**
     * keys - action names from user input
     * value - base name for method to call
     * @return array
     */
    protected function getAllowedActions()
    {
        return [
            'create' => 'Create',
            'update' => 'Update',
            'delete' => 'Delete',
            'updateall' => 'UpdateAll',
            'deleteall' => 'DeleteAll',
            'enable' => 'Enable',
            'disable' => 'Disable',
            'compile' => 'Compile',
            'compileall' => 'CompileAll',
            'changecode' => 'ChangeCode',
            'getallextensions' => 'GetExt',
        ];
    }

    /**
     * Things that must happen before script execution.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function preRun()
    {
        
        $this->options = ['verbose' => false,
                               'action' => null,
                               'extension' => null];
        
        $this->options = array_merge($this->options, $this->parameters);
        
        if ($this->options['verbose'] == true) {
            $this->verbose = true;
        }
        
        // The 'action' parameter is always required.
        if ($this->options['action'] == null) {
            $this->err("Please enter the 'action' parameter.", true);
        } else {
            $this->options['action'] = strtolower($this->options['action']);
            if (!in_array($this->options['action'], array_keys($this->getAllowedActions()))) {
                $this->err("'" . $this->options['action'] . "' is not a valid action.", true);
            } else {
                // The 'action' parameter is ok.
                // Let's check additional inputs depending on the value of the 'action' parameter.
                $this->checkInput();
            }
        }
    }

    /**
     * Main script implementation.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function run()
    {
        // Select the action to perform depending on the 'action' parameter.
        // Verification of the value of 'action' performed in self::preRun().
        $actions = $this->getAllowedActions();
        $pendingActionName = 'action' . $actions[$this->options['action']];
        if (method_exists($this, $pendingActionName)) {
            return $this->$pendingActionName();
        }
    }

    /**
     * Things that must happen after the run() method.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function postRun()
    {
    }

    /**
     * Checks the inputs for the current script call.
     *
     * @access private
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    private function checkInput()
    {
        $actions = $this->getAllowedActions();
        $pendingActionName = 'check' . $actions[$this->options['action']] . 'Input';
        if (method_exists($this, $pendingActionName)) {
            return $this->$pendingActionName();
        }
    }

    /**
     * Checks the inputs for the 'create' action.
     *
     * @access private
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    private function checkCreateInput()
    {
        
        $defaults = ['language' => null,
                          'languageLabel' => null,
                          'extension' => null,
                          'input' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_INPUT_DIR,
                          'output' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_OUTPUT_DIR,
                          'build' => true, // Build translation files by having a look in source code, models.
                          'force' => false]; // Do not force rebuild if locale already exist.
        
        $this->options = array_merge($defaults, $this->options);
        
        if (is_null($this->options['language'])) {
            $this->err("Please provide a 'language' identifier such as en-US, fr-CA, IT, ...", true);
        } else {
            if (is_null($this->options['extension'])) {
                $this->err("Please provide an 'extension' for which the 'language' will be created", true);
            } else {
                // Check if the extension(s) exists.
                $extensionsToCreate = explode(',', $this->options['extension']);
                $extensionsToCreate = array_unique($extensionsToCreate);
                foreach ($extensionsToCreate as $etc) {
                    $this->options['input'] = dirname(__FILE__) . '/../../' . $etc . '/' . self::DEF_INPUT_DIR;
                    $this->options['output'] = dirname(__FILE__) . '/../../' . $etc . '/' . self::DEF_OUTPUT_DIR;
                    $extensionDir = dirname(__FILE__) . '/../../' . $etc;
                    if (!is_dir($extensionDir)) {
                        $this->err("The extension '" . $etc . "' does not exist.", true);
                    } elseif (!is_readable($extensionDir)) {
                        $this->err("The '" . $etc . "' directory is not readable. Please check permissions on this directory.", true);
                    } elseif (!is_writable($extensionDir)) {
                        $this->err("The '" . $etc . "' directory is not writable. Please check permissions on this directory.", true);
                    }
                    
                    // The input 'parameter' is optional.
                    // (and only used if the 'build' parameter is set to true)
                    $this->checkInputOption();

                    // The 'output' parameter is optional.
                    $this->checkOutputOption();
                }
            }
        }
    }

    /**
     * Checks the inputs for the 'update' action.
     *
     * @access private
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    private function checkUpdateInput()
    {
        
        $defaults = ['language' => null,
                          'extension' => null,
                          'input' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_INPUT_DIR,
                          'output' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_OUTPUT_DIR];
        
        $this->options = array_merge($defaults, $this->options);
        
        if (is_null($this->options['language'])) {
            $this->err("Please provide a 'language' identifier such as en-US, fr-CA, IT, ...", true);
        } else {
            // Check if the language folder exists and is readable/writable.
            $languageDir = $this->buildLanguagePath($this->options['extension'], $this->options['language']);
            if (!is_dir($languageDir)) {
                $this->err("The 'language' directory ${languageDir} does not exist.", true);
            } elseif (!is_readable($languageDir)) {
                $this->err("The 'language' directory ${languageDir} is not readable. Please check permissions on this directory.");
            } elseif (!is_writable($languageDir)) {
                $this->err("The 'language' directory ${languageDir} is not writable. Please check permissions on this directory.");
            } else {
                if (is_null($this->options['extension'])) {
                    $this->err("Please provide an 'extension' for which the 'language' will be created", true);
                } else {
                    // Check if the extension exists.
                    $extensionDir = dirname(__FILE__) . '/../../' . $this->options['extension'];
                    if (!is_dir($extensionDir)) {
                        $this->err("The extension '" . $this->options['extension'] . "' does not exist.", true);
                    } elseif (!is_readable($extensionDir)) {
                        $this->err("The '" . $this->options['extension'] . "' directory is not readable. Please check permissions on this directory.", true);
                    } elseif (!is_writable($extensionDir)) {
                        $this->err("The '" . $this->options['extension'] . "' directory is not writable. Please check permissions on this directory.", true);
                    } else {
                        // And can we read the messages.po file ?
                        if (!file_exists($languageDir . '/' . self::DEF_PO_FILENAME)) {
                            $this->err("Cannot find " . self::DEF_PO_FILENAME . " for extension '" . $this->options['extension'] . "' and language '" . $this->options['language'] . "'.", true);
                        } elseif (!is_readable($languageDir . '/' . self::DEF_PO_FILENAME)) {
                            $this->err(self::DEF_PO_FILENAME . " is not readable for '" . $this->options['extension'] . "' and language '" . $this->options['language'] . "'. Please check permissions for this file.", true);
                        } else {
                            // The input 'parameter' is optional.
                            $this->checkInputOption();

                            // The 'output' parameter is optional.
                            $this->checkOutputOption();
                        }
                    }
                }
            }
        }
    }

    /**
     * checks the input for the 'updateAll' action.
     *
     * @access private
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    private function checkUpdateAllInput()
    {
        
        $defaults = ['input' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_INPUT_DIR,
                          'output' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_OUTPUT_DIR,
                          'extension' => null];
        
        $this->options = array_merge($defaults, $this->options);
        
        // The input 'parameter' is optional.
        $this->checkInputOption();

        // The 'output' parameter is optional.
        $this->checkOutputOption();
    }

    /**
     * Checks the inputs for the 'delete' action.
     *
     * @access private
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void/
     */
    private function checkDeleteInput()
    {
        
        $defaults = ['language' => null,
                          'input' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_INPUT_DIR,
                          'output' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_OUTPUT_DIR,
                          'extension' => null];
        
        $this->options = array_merge($defaults, $this->options);
        
        if (is_null($this->options['extension'])) {
            $this->err("Please provide an 'extension' identifier.", true);
        } else {
            if (is_null($this->options['language'])) {
                $this->err("Please provide a 'language' identifier such as en-US, fr-CA, IT, ...", true);
            } else {
                $this->checkInputOption();
            }
        }
    }

    /**
     * Checks inputs for the 'deleteAll' action.
     *
     * @access private
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    private function checkDeleteAllInput()
    {
        
        $defaults = ['input' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_INPUT_DIR,
                          'output' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_OUTPUT_DIR,
                          'extension' => null];
        
        $this->options = array_merge($defaults, $this->options);
        
        // The input 'parameter' is optional.
        if (!is_null($this->options['extension'])) {
            $this->checkInputOption();
        } else {
            $this->err("Please provide an 'extension' identifier.", true);
        }
    }
    
    private function checkChangeCodeInput()
    {
        $defaults = ['input' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_INPUT_DIR,
                          'output' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_OUTPUT_DIR,
                          'extension' => null,
                          'language' => null,
                          'targetLanguage' => null];
        
        $this->options = array_merge($defaults, $this->options);
        
        if (empty($this->options['language'])) {
            $this->err("Please provide a source 'language' identifier such as en-US, fr-CA, IT, ...", true);
        } elseif (empty($this->options['targetLanguage'])) {
            $this->err("Please provide a 'targetLanguage' identifier such as en-US, fr-CA, IT, ...", true);
        } elseif (empty($this->options['extension'])) {
            $this->err("Please provide an 'extension' identifier.", true);
        } elseif (!is_readable($this->options['output'] . DIRECTORY_SEPARATOR . $this->options['language'])) {
            $this->err("The '" . $this->options['language'] . "' locale directory is not readable.", true);
        } elseif (!is_writable($this->options['output'])) {
            $this->err("The locales directory of extension '" . $this->options['extension'] . "' is not writable.");
        }
    }

    /**
     * Implementation of the 'create' action.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function actionCreate()
    {
        
        $extensionsToCreate = explode(',', $this->options['extension']);
        $extensionsToCreate = array_unique($extensionsToCreate);
        
        foreach ($extensionsToCreate as $etc) {
            $this->options['extension'] = $etc;
            $this->options['input'] = dirname(__FILE__) . '/../../' . $etc . '/' . self::DEF_INPUT_DIR;
            $this->options['output'] = dirname(__FILE__) . '/../../' . $etc . '/' . self::DEF_OUTPUT_DIR;
            
            $this->outVerbose("Creating language '" . $this->options['language'] . "' for extension '" . $this->options['extension'] . "' ...");
             
            // We first create the directory where locale files will go.
            $dir = $this->buildLanguagePath($this->options['extension'], $this->options['language']);
            $dirExists = false;
            
            if (file_exists($dir) && is_dir($dir) && $this->options['force'] == true) {
                $dirExists = true;
                $this->outVerbose("Language '" . $this->options['language'] . "' exists for extension '" . $this->options['extension'] . "'. Creation will be forced.");
                 
                // Clean it up.
                foreach (scandir($dir) as $d) {
                    if ($d !== '.' && $d !== '..' && $d !== '.svn') {
                        if (!tao_helpers_File::remove($dir . '/' . $d, true)) {
                            $this->err("Unable to clean up 'language' directory '" . $dir . "'.", true);
                        }
                    }
                }
            } elseif (file_exists($dir) && is_dir($dir) && $this->options['force'] == false) {
                $this->err("The 'language' " . $this->options['language'] . " already exists in the file system. Use the 'force' parameter to overwrite it.", true);
            }
            
            // If we are still here... it means that we have to create the language directory.
            if (!$dirExists && !@mkdir($dir)) {
                $this->err("Unable to create 'language' directory '" . $this->options['language'] . "'.", true);
            } else {
                if ($this->options['build'] == true) {
                    $sortingMethod = tao_helpers_translation_TranslationFile::SORT_ASC_I;
                    $this->outVerbose("Building language '" . $this->options['language'] . "' for extension '" . $this->options['extension'] . "' ...");
                    // Let's populate the language with raw PO files containing sources but no targets.
                    // Source code extraction.
                    $fileExtensions = ['php', 'tpl', 'js', 'ejs'];
                    $filePaths = [];
                    foreach (self::$WHITE_LIST as $subFolder) {
                        $filePaths[] = $this->options['input'] . DIRECTORY_SEPARATOR . $subFolder;
                    }
                     
                    $sourceExtractor = new tao_helpers_translation_SourceCodeExtractor($filePaths, $fileExtensions);
                    $sourceExtractor->extract();
            
                    $translationFile = new tao_helpers_translation_POFile();
                    $translationFile->setSourceLanguage(tao_helpers_translation_Utils::getDefaultLanguage());
                    $translationFile->setTargetLanguage($this->options['language']);
                    $translationFile->addTranslationUnits($sourceExtractor->getTranslationUnits());
                    
                    $file = MenuService::getStructuresFilePath($this->options['extension']);
                    if (!is_null($file)) {
                        $structureExtractor = new tao_helpers_translation_StructureExtractor([$file]);
                        $structureExtractor->extract();
                        $translationFile->addTranslationUnits($structureExtractor->getTranslationUnits());
                    }

                    $sortedTus = $translationFile->sortBySource($sortingMethod);
            
                    $sortedTranslationFile = new tao_helpers_translation_POFile();
                    $sortedTranslationFile->setSourceLanguage(tao_helpers_translation_Utils::getDefaultLanguage());
                    $sortedTranslationFile->setTargetLanguage($this->options['language']);
                    $sortedTranslationFile->addTranslationUnits($sortedTus);
                    $this->preparePOFile($sortedTranslationFile, true);

                    $poPath = $dir . '/' . self::DEF_PO_FILENAME;
                    $writer = new tao_helpers_translation_POFileWriter(
                        $poPath,
                        $sortedTranslationFile
                    );
                    $writer->write();
                    $this->outVerbose("PO Translation file '" . basename($poPath) . "' in '" . $this->options['language'] . "' created for extension '" . $this->options['extension'] . "'.");
                    $writer->write();

                    // Writing JS files
                    $jsPath = $dir . '/' . self::DEF_JS_FILENAME;
                    $writer = new tao_helpers_translation_JSFileWriter(
                        $jsPath,
                        $sortedTranslationFile
                    );
                    $writer->write(false);
                    $this->outVerbose("JavaScript Translation file '" . basename($jsPath) . "' in '" . $this->options['language'] . "' created for extension '" . $this->options['extension'] . "'.");
                    $writer->write();

                    // Now that PO files & JS files are created, we can create the translation models
                    // if we find RDF models to load for this extension.
                    $translatableProperties = [OntologyRdfs::RDFS_LABEL, OntologyRdfs::RDFS_COMMENT];
            
                    foreach ($this->getOntologyFiles() as $f) {
                        common_Logger::d('reading rdf ' . $f);
                        $translationFile = $this->extractPoFileFromRDF($f, $translatableProperties);

                        $writer = new tao_helpers_translation_POFileWriter($dir . '/' . $this->getOntologyPOFileName($f), $translationFile);
                        $writer->write();

                        $this->outVerbose("PO Translation file '" .  $this->getOntologyPOFileName($f) . "' in '" . $this->options['language'] . "' created for extension '" . $this->options['extension'] . "'.");
                    }
            
                    $this->outVerbose("Language '" . $this->options['language'] . "' created for extension '" . $this->options['extension'] . "'.");
                } else {
                    // Only build virgin files.
                    // (Like a virgin... woot !)
                    $translationFile = new tao_helpers_translation_POFile();
                    $translationFile->setSourceLanguage(tao_helpers_translation_Utils::getDefaultLanguage());
                    $translationFile->setTargetLanguage($this->options['language']);
                    $this->preparePOFile($translationFile, true);

                    foreach ($this->getOntologyFiles() as $f) {
                            common_Logger::d('reading rdf ' . $f);
                            $translationFile = new tao_helpers_translation_POFile();
                            $translationFile->setSourceLanguage(tao_helpers_translation_Utils::getDefaultLanguage());
                            $translationFile->setTargetLanguage($this->options['language']);
                            $translationFile->setExtensionId($this->options['extension']);

                        $writer = new tao_helpers_translation_POFileWriter($dir . '/' . $this->getOntologyPOFileName($f), $translationFile);
                        $writer->write();
                    }
            
                    $this->outVerbose("Language '" . $this->options['language'] . "' created for extension '" . $this->options['extension'] . "'.");
                }
            
                // Create the language manifest in RDF.
                if ($this->options['extension'] == 'tao') {
                    $langDescription = tao_helpers_translation_RDFUtils::createLanguageDescription(
                        $this->options['language'],
                        $this->options['languageLabel']
                    );
                    $langDescription->save($dir . '/' . self::DEF_LANG_FILENAME);
                }
            }
        }
    }

    /**
     * Implementation of the 'update' action.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function actionUpdate()
    {
        
        $this->outVerbose("Updating language '" . $this->options['language'] . "' for extension '" . $this->options['extension'] . "'...");
        $sortingMethod = tao_helpers_translation_TranslationFile::SORT_ASC_I;
        
        // Get virgin translations from the source code and manifest.
        $filePaths = [];
        foreach (self::$WHITE_LIST as $subFolder) {
            $filePaths[] = $this->options['input'] . DIRECTORY_SEPARATOR . $subFolder;
        }
        
        $extensions = ['php', 'tpl', 'js', 'ejs'];
        $sourceCodeExtractor = new tao_helpers_translation_SourceCodeExtractor($filePaths, $extensions);
        $sourceCodeExtractor->extract();
        
        $translationFile = new tao_helpers_translation_POFile();
        $translationFile->setSourceLanguage(tao_helpers_translation_Utils::getDefaultLanguage());
        $translationFile->setTargetLanguage($this->options['language']);
        $translationFile->addTranslationUnits($sourceCodeExtractor->getTranslationUnits());
        
        $file = MenuService::getStructuresFilePath($this->options['extension']);
        if (!is_null($file)) {
            $structureExtractor = new tao_helpers_translation_StructureExtractor([$file]);
            $structureExtractor->extract();
            $structureUnits = $structureExtractor->getTranslationUnits();
            $this->outVerbose(count($structureUnits) . ' units extracted from structures.xml.');
            $translationFile->addTranslationUnits($structureUnits);
        }

        // For each TU that was recovered, have a look in an older version
        // of the translations.
        $oldFilePath = $this->buildLanguagePath($this->options['extension'], $this->options['language']) . '/' . self::DEF_PO_FILENAME;
        $translationFileReader = new tao_helpers_translation_POFileReader($oldFilePath);
        $translationFileReader->read();
        $oldTranslationFile = $translationFileReader->getTranslationFile();
        
        foreach ($oldTranslationFile->getTranslationUnits() as $oldTu) {
            if (($newTu = $translationFile->getBySource($oldTu)) !== null && $oldTu->getTarget() != '') {
                // No duplicates in TFs so I simply add it whatever happens.
                // If it already has the same one, it means we will update it.
                $newTu->setTarget($oldTu->getTarget());
            }
        }
        
        $sortedTranslationFile = new tao_helpers_translation_POFile();
        $sortedTranslationFile->setSourceLanguage($translationFile->getSourceLanguage());
        $sortedTranslationFile->setTargetLanguage($translationFile->getTargetLanguage());
        $sortedTranslationFile->addTranslationUnits($translationFile->sortBySource($sortingMethod));
        $this->preparePOFile($sortedTranslationFile, true);
        
        // Write the new ones.
        $poFileWriter = new tao_helpers_translation_POFileWriter($oldFilePath, $sortedTranslationFile);
        $poFileWriter->write();
        $this->outVerbose("PO translation file '" . basename($oldFilePath) . "' in '" . $this->options['language'] . "' updated for extension '" . $this->options['extension'] . "'.");

        $translatableProperties = [OntologyRdfs::RDFS_LABEL, OntologyRdfs::RDFS_COMMENT];

        // We now deal with RDF models.
        foreach ($this->getOntologyFiles() as $f) {
                // Loop on 'master' models.
                $translationFile = $this->extractPoFileFromRDF($f, $translatableProperties);

                // The slave RDF file is the translation of the ontology that we find in /extId/Locales/langCode.
                $slavePOFilePath = $this->buildLanguagePath($this->options['extension'], $this->options['language']) . '/' . $this->getOntologyPOFileName($f);
            if (file_exists($slavePOFilePath)) {
                // Read the existing RDF Translation file for this RDF model.
                $poReader = new tao_helpers_translation_POFileReader($slavePOFilePath);
                $poReader->read();
                $slavePOFile = $poReader->getTranslationFile();
                    
                // Try to update translation units found in the master PO file with
                // targets found in the old translation of the ontology.
                foreach ($slavePOFile->getTranslationUnits() as $oTu) {
                    $translationFile->addTranslationUnit($oTu);
                }
                    
                // Remove Slave PO file. It will be overwritten by the modified Master PO file.
                tao_helpers_File::remove($slavePOFilePath);
            }
                
                // Write Master PO file as the new Slave PO file.
                $rdfWriter = new tao_helpers_translation_POFileWriter($slavePOFilePath, $translationFile);
                $rdfWriter->write();
                $this->outVerbose("Translation model {$this->getOntologyPOFileName($f)}  in '" . $this->options['language'] . "' updated for extension '" . $this->options['extension'] . "'.");
        }
        
        $this->outVerbose("Language '" . $this->options['language'] . "' updated for extension '" . $this->options['extension'] . "'.");
    }

    /**
     * Implementation of the 'updateAll' action.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function actionUpdateAll()
    {
        
        // Scan the locales folder for languages in the wwextension and
        // launch actionUpdate for each of them.

        // Get the list of languages that will be updated.
        $locales = $this->getLanguageList();

        // We now identified locales to be updated.
        $this->outVerbose("Languages '" . implode(',', $locales) . "' will be updated for extension '" . $this->options['extension'] . "'.");
        foreach ($locales as $l) {
            $this->options['language'] = $l;
            $this->checkUpdateInput();
            $this->actionUpdate();
            
            $this->outVerbose("");
        }
    }

    /**
     * Implementation of the 'delete' action.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function actionDelete()
    {
        
        $this->outVerbose("Deleting language '" . $this->options['language'] . "' for extension '" . $this->options['extension'] . "' ...");
        
        $dir = $this->buildLanguagePath($this->options['extension'], $this->options['language']);
        if (!tao_helpers_File::remove($dir, true)) {
            $this->err("Could not delete language '" . $this->options['language'] . "' for extension '" . $this->options['extension'] . "'.", true);
        }
        
        $this->outVerbose("Language '" . $this->options['language'] . "' for extension '" . $this->options['extension'] . "' successfully deleted.");
    }

    /**
     * Implementation of the 'deleteAll' action.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function actionDeleteAll()
    {
        
        // Get the list of languages that will be deleted.
        $this->outVerbose("Deleting all languages for extension '" . $this->options['extension'] . "'...");

        $locales = $this->getLanguageList();
        
        foreach ($locales as $l) {
            $this->options['language'] = $l;
            $this->checkDeleteInput();
            $this->actionDelete();
            
            $this->outVerbose("");
        }
    }
    
    public function actionChangeCode()
    {
        $this->outVerbose("Changing code of locale '" . $this->options['language'] . "' to '" . $this->options['targetLanguage'] . "' for extension '" . $this->options['extension'] . "'...");
        
        // First we copy the old locale to a new directory named as 'targetLanguage'.
        $sourceLocaleDir = $this->options['output'] . DIRECTORY_SEPARATOR . $this->options['language'];
        $destLocaleDir = $this->options['output'] . DIRECTORY_SEPARATOR . $this->options['targetLanguage'];
        
        if (!tao_helpers_File::copy($sourceLocaleDir, $destLocaleDir, true, true)) {
            $this->err("Locale '" . $this->options['language'] . "' could not be copied to locale '" . $this->options['targetLanguage'] . "'.");
        }
        
        // We now apply transformations to the new locale.
        foreach (scandir($destLocaleDir) as $f) {
            $sourceLang = $this->options['language'];
            $destLang = $this->options['targetLanguage'];
            $qSourceLang = preg_quote($sourceLang);
            $qDestLang = preg_quote($destLang);
            
            if (!is_dir($f) && $f[0] != '.') {
                if ($f == 'messages.po') {
                    // Change the language tag in the PO file.
                    
                    $pattern = "/Language: ${qSourceLang}/u";
                    $count = 0;
                    $content = file_get_contents($destLocaleDir . DIRECTORY_SEPARATOR . 'messages.po');
                    $newFileContent = preg_replace($pattern, "Language: ${destLang}", $content, -1, $count);
                    
                    if ($count == 1) {
                        $this->outVerbose("Language tag '${destLang}' applied to messages.po.");
                        file_put_contents($destLocaleDir . DIRECTORY_SEPARATOR . 'messages.po', $newFileContent);
                    } else {
                        $this->err("Could not change language tag in messages.po.");
                    }
                } elseif ($f == 'messages_po.js') {
                    // Change the language tag in comments.
                    // Change the langCode JS variable.
                    $pattern = "/var langCode = '${qSourceLang}';/u";
                    $count1 = 0;
                    $content = file_get_contents($destLocaleDir . DIRECTORY_SEPARATOR . 'messages_po.js');
                    $newFileContent = preg_replace($pattern, "var langCode = '${destLang}';", $content, -1, $count1);
                    
                    $pattern = "|/\\* lang: ${qSourceLang} \\*/|u";
                    $count2 = 0;
                    $newFileContent = preg_replace($pattern, "/* lang: ${destLang} */", $newFileContent, -1, $count2);
                    
                    if ($count1 + $count2 == 2) {
                        $this->outVerbose("Language tag '${destLang}' applied to messages_po.js");
                        file_put_contents($destLocaleDir . DIRECTORY_SEPARATOR . 'messages_po.js', $newFileContent);
                    } else {
                        $this->err("Could not change language tag in messages_po.js");
                    }
                } elseif ($f == 'lang.rdf') {
                    // Change <![CDATA[XX]]>
                    // Change http://www.tao.lu/Ontologies/TAO.rdf#LangXX
                    $pattern = "/<!\\[CDATA\\[${qSourceLang}\\]\\]>/u";
                    $count1 = 0;
                    $content = file_get_contents($destLocaleDir . DIRECTORY_SEPARATOR . 'lang.rdf');
                    $newFileContent = preg_replace($pattern, "<![CDATA[${destLang}]]>", $content, -1, $count1);
                    
                    $pattern = "|http://www.tao.lu/Ontologies/TAO.rdf#Lang${qSourceLang}|u";
                    $count2 = 0;
                    $newFileContent = preg_replace($pattern, "http://www.tao.lu/Ontologies/TAO.rdf#Lang${destLang}", $newFileContent, -1, $count2);

                    $pattern = '/xml:lang="EN"/u';
                    $count3 = 0;
                    $newFileContent = preg_replace($pattern, 'xml:lang="en-US"', $newFileContent, -1, $count3);
                    
                    if ($count1 + $count2 + $count3 == 3) {
                        $this->outVerbose("Language tag '${destLang}' applied to lang.rdf");
                        file_put_contents($destLocaleDir . DIRECTORY_SEPARATOR . 'lang.rdf', $newFileContent);
                    } else {
                        $this->err("Could not change language tag in lang.rdf");
                    }
                } else {
                    // Check for a .rdf extension.
                    $infos = pathinfo($destLocaleDir . DIRECTORY_SEPARATOR . $f);
                    if (isset($infos['extension']) && $infos['extension'] == 'rdf') {
                        // Change annotations @sourceLanguage and @targetLanguage
                        // Change xml:lang
                        $pattern = "/@sourceLanguage EN/u";
                        $content = file_get_contents($destLocaleDir . DIRECTORY_SEPARATOR . $f);
                        $newFileContent = preg_replace($pattern, "@sourceLanguage en-US", $content);
                        
                        $pattern = "/@targetLanguage ${qSourceLang}/u";
                        $newFileContent = preg_replace($pattern, "@targetLanguage ${destLang}", $newFileContent);
                        
                        $pattern = '/xml:lang="' . $qSourceLang . '"/u';
                        $newFileContent = preg_replace($pattern, 'xml:lang="' . $destLang . '"', $newFileContent);
                        
                        $this->outVerbose("Language tag '${destLang}' applied to ${f}");
                        file_put_contents($destLocaleDir . DIRECTORY_SEPARATOR . $f, $newFileContent);
                    }
                }
            }
        }
    }

    /**
     * Builds the path to files dedicated to a given language (locale) for a
     * extension ID.
     *
     * @access private
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @param  string extension
     * @param  string language
     * @return string
     */
    private function buildLanguagePath($extension, $language)
    {
        $returnValue = (string) '';

        
        $returnValue = dirname(__FILE__) . '/../../' . $extension . '/' . self::DEF_OUTPUT_DIR . '/' . $language;
        

        return (string) $returnValue;
    }

    /**
     * Find a structure.xml manifest in a given directory.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @param  string directory
     * @return mixed
     */
    public function findStructureManifest($directory = null)
    {
        $returnValue = null;

        
        if ($directory == null) {
            $actionsDir = $this->options['input'] . '/actions';
        } else {
            $actionsDir = $directory . '/actions';
        }
        
        $dirEntries = scandir($actionsDir);
        
        if ($dirEntries === false) {
            $returnValue = false;
        } else {
            $structureFile = null;
            foreach ($dirEntries as $f) {
                if (preg_match("/(.*)structure\.xml$/", $f)) {
                    $structureFile = $f;
                    break;
                }
            }
            
            if ($structureFile === null) {
                $returnValue = false;
            } else {
                $returnValue = $structureFile;
            }
        }
        

        return $returnValue;
    }

    /**
     * Prepare a PO file before output by adding headers to it.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @param tao_helpers_translation_POFile $poFile
     * @param bool $poEditorReady
     * @return void
     */
    public function preparePOFile(tao_helpers_translation_POFile $poFile, $poEditorReady = false)
    {
        
        $poFile->addHeader('Project-Id-Version', PRODUCT_NAME . ' ' . ApplicationHelper::getVersionName());
        $poFile->addHeader('PO-Revision-Date', date('Y-m-d') . 'T' . date('H:i:s'));
        $poFile->addHeader('Last-Translator', 'TAO Translation Team <translation@tao.lu>');
        $poFile->addHeader('MIME-Version', '1.0');
        $poFile->addHeader('Language', $poFile->getTargetLanguage());
        $poFile->addHeader('sourceLanguage', $poFile->getSourceLanguage());
        $poFile->addHeader('targetLanguage', $poFile->getTargetLanguage());
        $poFile->addHeader('Content-Type', 'text/plain; charset=utf-8');
        $poFile->addHeader('Content-Transfer-Encoding', '8bit');

        if ($poEditorReady) {
            $poFile->addHeader('X-Poedit-Basepath', '../../');
            $poFile->addHeader('X-Poedit-KeywordsList', '__');
            $poFile->addHeader('X-Poedit-SearchPath-0', '.');
        }
    }

    /**
     * Determines if an given directory actually contains a TAO extension.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @param  string directory
     * @return boolean
     */
    public function isExtension($directory)
    {
        $returnValue = (bool) false;

        
        $hasStructure = $this->findStructureManifest($directory) !== false;
        $hasPHPManifest = false;
        
        $files = scandir($this->options['input']);
        if ($files !== false) {
            foreach ($files as $f) {
                if (is_file($this->options['input'] . '/' . $f) && is_readable($this->options['input'] . '/' . $f)) {
                    if ($f == 'manifest.php') {
                        $hasPHPManifest = true;
                    }
                }
            }
        }
        
        $returnValue = $hasStructure || $hasPHPManifest;
        

        return (bool) $returnValue;
    }

    /**
     * Add translations as translation units found in a structure.xml manifest
     * a given PO file.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @param tao_helpers_translation_POFile $poFile
     * @throws Exception
     * @return void
     */
    public function addManifestsTranslations(tao_helpers_translation_POFile $poFile)
    {
        
        $this->outVerbose("Adding all manifests messages to extension '" . $this->options['extension'] . "'");
        
        $rootDir = dirname(__FILE__) . '/../../';
        $directories = scandir($rootDir);
        $exceptions = ['generis', 'tao', '.*'];
        
        if (false === $directories) {
            $this->err("The TAO root directory is not readable. Please check permissions on this directory.", true);
        } else {
            foreach ($directories as $dir) {
                if (is_dir($rootDir . $dir) && !in_array($dir, $exceptions)) {
                    // Maybe it should be read.
                    if (in_array('.*', $exceptions) && $dir[0] == '.') {
                        continue;
                    } else {
                        // Is this a TAO extension ?
                        $file = MenuService::getStructuresFilePath($this->options['extension']);
                        if (!is_null($file)) {
                            $structureExtractor = new tao_helpers_translation_StructureExtractor([$file]);
                            $structureExtractor->extract();
                            $poFile->addTranslationUnits($structureExtractor->getTranslationUnits());
                            $this->outVerbose("Manifest of extension '" . $dir . "' added to extension '" . $this->options['extension'] . "'");
                        }
                    }
                }
            }
        }
    }

    /**
     * Add the requested language in the ontology. It will used the parameters
     * the command line for logic.
     *
     * @access protected
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    protected function addLanguageToOntology()
    {
        
        $this->outVerbose("Importing RDF language description '" . $this->options['language'] . "' to ontology...");
        
        // RDF Language Descriptions are stored in the tao meta-extension locales.
        $expectedDescriptionPath = $this->buildLanguagePath('tao', $this->options['language']) . '/lang.rdf';
        
        if (file_exists($expectedDescriptionPath)) {
            if (is_readable($expectedDescriptionPath)) {
                // Let's remove any instance of the language description before inserting the new one.
                $taoNS = 'http://www.tao.lu/Ontologies/TAO.rdf#';
                $expectedLangUri = $taoNS . 'Lang' . $this->options['language'];
                $lgDescription = new core_kernel_classes_Resource($expectedLangUri);
                
                if ($lgDescription->exists()) {
                    $lgDescription->delete();
                    $this->outVerbose("Existing RDF Description language '" . $this->options['language'] . "' deleted.");
                }
                
                $generisAdapterRdf = new tao_helpers_data_GenerisAdapterRdf();
                if (true === $generisAdapterRdf->import($expectedDescriptionPath, null, LOCAL_NAMESPACE)) {
                    $this->outVerbose("RDF language description '" . $this->options['language'] . "' successfully imported.");
                } else {
                    $this->err("An error occured while importing the RDF language description '" . $this->options['language'] . "'.", true);
                }
            } else {
                $this->err("RDF language description (lang.rdf) cannot be read in meta-extension 'tao' for language '" . $this->options['language'] . "'.", true);
            }
        } else {
            $this->err("RDF language description (lang.rdf) not found in meta-extension 'tao' for language '" . $this->options['language'] . "'.", true);
        }
        
        
        $this->outVerbose("RDF language description '" . $this->options['language'] . "' added to ontology.");
    }

    /**
     * Removes a language from the Ontology and all triples with the related
     * tag. Will use command line parameters for logic.
     *
     * @access protected
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    protected function removeLanguageFromOntology()
    {
        
        $this->outVerbose("Removing RDF language description '" . $this->options['language'] . "' from ontology...");
        $taoNS = 'http://www.tao.lu/Ontologies/TAO.rdf#';
        $expectedDescriptionUri = $taoNS . 'Lang' . $this->options['language'];
        $lgResource = new core_kernel_classes_Resource($expectedDescriptionUri);
        
        if (true === $lgResource->exists()) {
            $lgResource->delete();
            $this->outVerbose("RDF language description '" . $this->options['language'] . "' successfully removed.");
        } else {
            $this->outVerbose("RDF language description '" . $this->options['language'] . "' not found but considered removed.");
        }
    }

    /**
     * Checks authentication parameters for the TAO API.
     *
     * @access protected
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    protected function checkAuthInput()
    {
        
        $defaults = ['user' => null,
                          'password' => null];
                          
        $this->options = array_merge($defaults, $this->options);
        
        if ($this->options['user'] == null) {
            $this->err("Please provide a value for the 'user' parameter.", true);
        } elseif ($this->options['password'] == null) {
            $this->err("Please provide a value for the 'password' parameter.", true);
        }
    }

    /**
     * Get the ontology file paths for a given extension, sorted by target name
     *
     * @access private
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return array
     */
    private function getOntologyFiles()
    {
        $returnValue = [];

        
        $ext = common_ext_ExtensionsManager::singleton()->getExtensionById($this->options['extension']);
        $returnValue = $ext->getManifest()->getInstallModelFiles();
        

        return (array) $returnValue;
    }

    /**
     * Check inputs for the 'enable' action.
     *
     * @access private
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    private function checkEnableInput()
    {
        
        $this->checkAuthInput();
        $defaults = ['language' => null,
                          'input' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_INPUT_DIR,
                          'output' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_OUTPUT_DIR];
                          
        $this->options = array_merge($defaults, $this->options);
        
        if ($this->options['language'] == null) {
            $this->err("Please provide the 'language' parameter.", true);
        }
    }

    /**
     * Short description of method checkDisableInput
     *
     * @access private
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    private function checkDisableInput()
    {
        
        $this->checkAuthInput();
        $defaults = ['language' => null];
        $this->options = array_merge($defaults, $this->options);
        if ($this->options['language'] == null) {
            $this->err("Please provide the 'language' parameter.", true);
        }
    }

    /**
     * Short description of method actionEnable
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function actionEnable()
    {
        
        $userService = tao_models_classes_UserService::singleton();
        $this->outVerbose("Connecting to TAO as '" . $this->options['user'] . "' ...");
        if ($userService->loginUser($this->options['user'], $this->options['password'])) {
            $this->outVerbose("Connected to TAO as '" . $this->options['user'] . "'.");
            $this->addLanguageToOntology();
            $userService->logout();
            $this->outVerbose("Disconnected from TAO.");
        } else {
            $this->err("Unable to connect to TAO as '" . $this->options['user'] . "'. Please check user name and password.", true);
        }
    }

    /**
     * Implementation of the 'disable' action. When this action is called, a
     * Language Description ('language' param) is removed from the Knowledge
     * The language is at this time not available anymore to end-users. However,
     * Triples that had a corresponding language tag are not remove from the
     * If the language is enabled again via the 'enable' action of this script,
     * having corresponding languages will be reachable again.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function actionDisable()
    {
        
        $userService = tao_models_classes_UserService::singleton();
        $this->outVerbose("Connecting to TAO as '" . $this->options['user'] . "' ...");
        if ($userService->loginUser($this->options['user'], $this->options['password'])) {
            $this->outVerbose("Connected to TAO as '" . $this->options['user'] . "'.");
            $this->removeLanguageFromOntology();
            $userService->logout();
            $this->outVerbose("Disconnected from TAO.");
        } else {
            $this->err("Unable to connect to TAO as '" . $this->options['user'] . "'. Please check user name and password.", true);
        }
    }

    /**
     * Implementation of the 'compile' action.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function actionCompile()
    {
        
        $extensionsToCreate = explode(',', $this->options['extension']);
        $extensionsToCreate = array_unique($extensionsToCreate);
        
        foreach ($extensionsToCreate as $extension) {
            $language = $this->options['language'];
            $compiledTranslationFile = new tao_helpers_translation_TranslationFile();
            $compiledTranslationFile->setTargetLanguage($this->options['language']);
            
            $this->outVerbose("Compiling language '${language}' for extension '${extension}'...");
            
            // Get the dependencies of the target extension.
            // @todo Deal with dependencies at compilation time.
            $dependencies = [];
            if ($extension !== 'tao') {
                $dependencies[] = 'tao';
            }
            
            $this->outVerbose("Resolving Dependencies...");
            foreach ($dependencies as $depExtId) {
                $this->outVerbose("Adding messages from extension '${depExtId}' in '${language}'...");
                // Does the locale exist for $depExtId?
                $depPath = $this->buildLanguagePath($depExtId, $language) . '/' . self::DEF_PO_FILENAME;
                if (!file_exists($depPath) || !is_readable($depPath)) {
                    $this->outVerbose("Dependency on extension '${depExtId}' in '${language}' does not exist. Trying to resolve default language...");
                    $depPath = $this->buildLanguagePath($depExtId, tao_helpers_translation_Utils::getDefaultLanguage() . '/' . self::DEF_PO_FILENAME);

                    if (!file_exists($depPath) || !is_readable($depPath)) {
                        $this->outVerbose("Dependency on extension '${depExtId}' in '${language}' does not exist.");
                        continue;
                    }
                }
                
                // Recompile the dependent extension (for the moment 'tao' meta-extension only).
                $oldVerbose = $this->options['verbose'];
                $this->parameters['verbose'] = false;
                $this->options['extension'] = $depExtId;
                $this->actionCompile();
                $this->options['extension'] = $extension;
                $this->parameters['verbose'] = $oldVerbose;
                
                $poFileReader = new tao_helpers_translation_POFileReader($depPath);
                $poFileReader->read();
                $poFile = $poFileReader->getTranslationFile();
                $poCount = $poFile->count();
                $compiledTranslationFile->addTranslationUnits($poFile->getTranslationUnits());
                
                $this->outVerbose("${poCount} messages added.");
            }

            if (($extDirectories = scandir(ROOT_PATH)) !== false) {
                // Get all public messages accross extensions.
                foreach ($extDirectories as $extDir) {
                    $extPath = ROOT_PATH . '/' . $extDir;
                    
                    if (is_dir($extPath) && is_readable($extPath) && $extDir[0] != '.' && !in_array($extDir, $dependencies) && $extDir != $extension && $extDir != 'generis') {
                        $this->outVerbose("Adding public messages from extension '${extDir}' in '${language}'...");
                        
                        $poPath = $this->buildLanguagePath($extDir, $language) . '/' . self::DEF_PO_FILENAME;
                        if (!file_exists($poPath) || !is_readable($poPath)) {
                            $this->outVerbose("Extension '${extDir}' is not translated in language '${language}'. Trying to retrieve default language...");
                            $poPath = $this->buildLanguagePath($extDir, tao_helpers_translation_Utils::getDefaultLanguage()) . '/' . self::DEF_PO_FILENAME;
                            
                            if (!file_exists($poPath) || !is_readable($poPath)) {
                                $this->outVerbose("Extension '${extDir}' in '${language}' does not exist.");
                                continue;
                            }
                        }

                        $poFileReader = new tao_helpers_translation_POFileReader($poPath);
                        $poFileReader->read();
                        $poFile = $poFileReader->getTranslationFile();
                        $poUnits = $poFile->getByFlag('tao-public');
                        $poCount = count($poUnits);
                        $compiledTranslationFile->addTranslationUnits($poUnits);
                        $this->outVerbose("${poCount} public messages added.");
                    }
                }
                
                // Finally, add the translation units related to the target extension.
                $path = $this->buildLanguagePath($extension, $language) . '/' . self::DEF_PO_FILENAME;
                if (file_exists($path) && is_readable($path)) {
                    $poFileReader = new tao_helpers_translation_POFileReader($path);
                    $poFileReader->read();
                    $poFile = $poFileReader->getTranslationFile();
                    $compiledTranslationFile->addTranslationUnits($poFile->getTranslationUnits());
                    
                    // Sort the TranslationUnits.
                    $sortingMethod = tao_helpers_translation_TranslationFile::SORT_ASC_I;
                    $compiledTranslationFile->setTranslationUnits($compiledTranslationFile->sortBySource($sortingMethod));
                    
                    $jsPath = $this->buildLanguagePath($extension, $language) . '/' . self::DEF_JS_FILENAME;
                    $jsFileWriter = new tao_helpers_translation_JSFileWriter($jsPath, $compiledTranslationFile);
                    $jsFileWriter->write();
                    $this->outVerbose("JavaScript compiled translations for extension '${extension}' with '${language}' written.");
                } else {
                    $this->err("PO file '${path}' for extension '${extension}' with language '${language}' cannot be read.", true);
                }
            } else {
                $this->err("Cannot list TAO Extensions from root path. Check your system rights.", true);
            }

            $this->outVerbose("Translations for '${extension}' with language '${language}' gracefully compiled.");
        }
    }

    /**
     * Checks the input for the 'compile' action.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    private function checkCompileInput()
    {
        
        $defaults = ['extension' => null,
                          'language' => null,
                          'input' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_INPUT_DIR,
                          'output' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_OUTPUT_DIR];
                          
        $this->options = array_merge($defaults, $this->options);
        
        if ($this->options['extension'] == null) {
            $this->err("Please provide the 'extension' parameter.", true);
        } elseif ($this->options['language'] == null) {
            $this->err("Please provide the 'language' parameter.", true);
        }
    }

    /**
     * Implementation of the 'compileAll' action.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    public function actionCompileAll()
    {
        
        // Get the list of languages that will be compiled.
        $this->outVerbose("Compiling all languages for extension '" . $this->options['extension'] . "'...");
        
        $rootDir = ROOT_PATH;
        $extensionDir = $rootDir . '/' . $this->options['extension'];
        $localesDir = $extensionDir . '/locales';
        $locales = [];
        
        $directories = scandir($localesDir);
        if ($directories === false) {
            $this->err("The locales directory of extension '" . $this->options['extension'] . "' cannot be read.", true);
        } else {
            foreach ($directories as $dir) {
                if ($dir[0] !== '.') {
                    // It is a language directory.
                    $locales[] = $dir;
                }
            }
        }
        
        foreach ($locales as $l) {
            $this->options['language'] = $l;
            $this->checkCompileInput();
            $this->actionCompile();
            
            $this->outVerbose("");
        }
    }

    /**
     * Checks the input of the 'compileAll' action.
     *
     * @access public
     * @author Joel Bout, <joel.bout@tudor.lu>
     * @return void
     */
    private function checkCompileAllInput()
    {
        
        $defaults = ['extension' => null,
                          'input' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_INPUT_DIR,
                          'output' => dirname(__FILE__) . '/../../' . $this->options['extension'] . '/' . self::DEF_OUTPUT_DIR];
                          
        $this->options = array_merge($defaults, $this->options);
        
        if ($this->options['extension'] == null) {
            $this->err("Please provide the 'extension' parameter.", true);
        }
    }
    
    private function actionGetExt()
    {
        $rootDir = ROOT_PATH;
        $extensions = [];
        $extensionsList = '';
        $language = $this->options['language'];

        // create initial folders array
        if ($dir = opendir($rootDir)) {
            $j = 0;
            while (($file = readdir($dir)) !== false) {
                if ($file[0] !== '.' && $file != '.' && $file != '..' && is_dir($rootDir . $file)) {
                    $j++;
                    $directories[$j] = $file;
                }
            }
        }

        if ($directories === false) {
            $this->err("The locales directory of extension '" . $this->options['extension'] . "' cannot be read.", true);
        } else {
            foreach ($directories as $dir) {
                // folder where these .po files should be
                $extensionLocalesDir = $dir . '/' . self::DEF_OUTPUT_DIR . '/' . $language;
                $poFile = $rootDir . $extensionLocalesDir . '/' . self::DEF_PO_FILENAME;
                
                // check .po file existency
                if (file_exists($poFile) && is_readable($poFile)) {
                    $extensions[] = $dir;
                    $extensionsList .= (!$extensionsList ? "" : ",") . $dir;
                }
            }
            sort($extensions);
            print_r($extensions);
            echo ( count($extensions) . ' extensions with translations: ' . $extensionsList . "\n");
        }
    }

    /**
     * @param $f
     * @param $translatableProperties
     * @return tao_helpers_translation_POFile
     * @throws tao_helpers_translation_TranslationException
     */
    protected function extractPoFileFromRDF($f, $translatableProperties)
    {
        $modelExtractor = new tao_helpers_translation_POExtractor([$f]);
        $modelExtractor->setTranslatableProperties($translatableProperties);
        $modelExtractor->extract();

        $translationFile = new tao_helpers_translation_POFile();
        $translationFile->setSourceLanguage(tao_helpers_translation_Utils::getDefaultLanguage());
        $translationFile->setTargetLanguage($this->options['language']);
        $translationFile->addTranslationUnits($modelExtractor->getTranslationUnits());
        $translationFile->setExtensionId($this->options['extension']);

        $this->preparePOFile($translationFile);
        return $translationFile;
    }

    protected function checkInputOption()
    {
        if (!is_null($this->options['input'])) {
            if (!is_dir($this->options['input'])) {
                $this->err("The 'input' parameter you provided is not a directory.", true);
            } else {
                if (!is_readable($this->options['input'])) {
                    $this->err("The 'input' directory is not readable.", true);
                }
            }
        }
    }

    protected function checkOutputOption()
    {
        if (!is_null($this->options['output'])) {
            if (!is_dir($this->options['output'])) {
                $this->err("The 'output' parameter you provided is not a directory.", true);
            } else {
                if (!is_writable($this->options['output'])) {
                    $this->err("The 'output' directory is not writable.", true);
                }
            }
        }
    }

    /**
     * @return array
     * @throws Exception
     */
    protected function getLanguageList()
    {
        $rootDir = dirname(__FILE__) . '/../..';
        $extensionDir = $rootDir . '/' . $this->options['extension'];
        $localesDir = $extensionDir . '/locales';
        $locales = [];

        $directories = scandir($localesDir);
        if ($directories === false) {
            $this->err("The locales directory of extension '" . $this->options['extension'] . "' cannot be read.", true);
            return $locales;
        } else {
            foreach ($directories as $dir) {
                if ($dir[0] !== '.') {
                    // It is a language directory.
                    $locales[] = $dir;
                }
            }
            return $locales;
        }
    }

    /**
     *
     * @param $f filename
     * @return string
     */
    protected function getOntologyPOFileName($f)
    {
        return basename($f) . '.po';
    }
}