<?php

/*
 *   This file is part of NOALYSS.
 *
 *   NOALYSS 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; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   NOALYSS 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 NOALYSS; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

// Copyright Author Dany De Bontridder danydb@aevalys.eu

/*!\file
 * \brief the extension class manages the plugins for the security, the access
 * the inclusion...
 */
/*!
 *
 * \class Extension 
 *
 * \brief manage the extension, it involves the table extension
 *
 * Data member
 * - $cn database connection
 */
require_once NOALYSS_INCLUDE.'/database/menu_ref_sql.class.php';
require_once NOALYSS_INCLUDE.'/database/profile_sql.class.php';

class Extension extends Menu_Ref_sql
{
    // code of the standard plugin (from noalyss-plugins)
    const aStandard_plugin=
            array('AMORTIS','BACKNOADM','COPRO','IMPCARD',
                'IMPORTBANK','INVOICING','LISTING',
                'MODOP','RAPAV','SAV',
                'TOOLPCMN','TOOLS','TRANSFORM',
                'TVA');

    /**************************************************************************
     * Variables from plugin.xml
     *************************************************************************/
    var $version; //<! version of the plugin in the XML
    var $noalyss_version; //!< minimum version of NOALYSS for this plugin
    var $order ; //!< $order in the menu (can be override by config menu)
    var $depend; //!< default depending menu (can be override by config menu)
    var $schema; //!< default database schema to store data for the plugin
    public function verify()
    {
        // Verify that the elt we want to add is correct
        if (trim($this->me_code)=="")
            throw new Exception('Le code ne peut pas être vide');
        if (trim($this->me_menu)=="")
            throw new Exception('Le nom ne peut pas être vide');
        if (trim($this->me_file)=="")
            throw new Exception('Chemin incorrect');
        if (file_exists(NOALYSS_PLUGIN.'/'.$this->me_file)==false)
            throw new Exception("$this->me_code $this->me_file".'Extension non trouvée, le chemin est-il correct?');
    }

    /*!@brief search a extension, the what is the column (extends_code */

    function search($p_what)
    {
        $this->me_code=strtoupper($p_what);
        if ($this->load()==false)
            return null;
        return 1;
    }

    /*!\brief return 1 if the user given in parameter can access this extension
     * otherwise returns 0
     * \param $p_login the user login
     * \return 1 has access, 0 has no access
     */

    function can_request($p_login)
    {
        $cnt=$this->cn->get_value("select count(*) from menu_ref
                                                                join profile_menu using (me_code)
                                                                join profile_user using (p_id)
                                                                where
                                                                me_code=$1
                                                                and user_name=$2", array($this->me_code, $p_login));
        if ($cnt>0)
            return 1;
        return 0;
    }

    /*!@brief make an array of the available plugin for the current user
     * @return  an array
     * @see ISelect
     */

    static function make_array($cn)
    {
        $sql="select DISTINCT me_code as value, me_menu as label from ".
                " menu_ref join profile_menu using (me_code)
				 join profile_user using (p_id) where ".
                " user_name=$1 and me_type='PL' ORDER BY ME_MENU";
        $a=$cn->get_array($sql, array($_SESSION[SESSION_KEY.'g_user']));
        return $a;
    }
    /**
     * @brief check the version of the plugin , null stands for one of the standard plugins, it means
     * self::aStandard_plugin
     * @global type $version_noalyss
     * @param type $i
     * @param type $p_plugin_code
     * @return type
     */
    static function check_version($i,$p_plugin_code=null)
    {
        global $version_noalyss;
        if (!isset($version_noalyss)||$version_noalyss<$i)
        {
            alert(_('Cette extension ne fonctionne pas sur cette version de NOALYSS'.
                            ' Veuillez mettre votre programme a jour. Version minimum ').$i);
            return;
        }
        Extension::check_plugin_version($p_plugin_code);
    }

    /**
     * @brief insert into the table profile_menu for the given profile id and depending
     * of the module $p_module
     * @remark type $cn
     * @param type $p_id profile.p_id
     * @throws Exception 10 : profile absent , 20 module absent , 30 No parent menu
     */
    function insert_profile_menu($p_id=1)
    {
        global $cn;
        // Module for the plugin
        $p_module=$this->depend;
        //profile exists ?
        $profile=new Profile_sql($cn, $p_id);
        if ($profile->p_id!=$p_id)
        {
            throw new Exception(_('Profil inexistant'), 10);
        }
        // Menu exists

        $module=new Menu_Ref($cn, $p_module);
        if ($module->me_code==null)
        {
            throw new Exception(_('Module inexistant'), 20);
        }
        // Dependency
        $dep_id=$cn->get_array('select pm_id from profile_menu 
                where
                p_id=$1
                and me_code = $2 ', array($p_id, $p_module));
        // throw an exception if there is no dependency
        if (empty($dep_id))
        {
            $msg = sprintf(_("Le menu %s dont dépend %s doit être crée ou %s doit être ajouté depuis le menu C0PROFL"),
            $p_module,$this->me_code,$this->me_code);
            throw new Exception($msg, 30);
        }
        $nb_dep=count($dep_id);

        // insert at the right location
        for ($i=0; $i<$nb_dep; $i++)
        {
            $profil_menu=new Profile_Menu($cn);
            $profil_menu->me_code=$this->me_code;
            $profil_menu->me_code_dep=$p_module;
            $profil_menu->p_type_display='S';
            $profil_menu->p_id=$p_id;
            $profil_menu->pm_id_dep=$dep_id[$i]['pm_id'];
            $profil_menu->pm_default=0;
            $profil_menu->p_order=$this->order;

            $cnt=$profil_menu->count(' where pm_id_dep=$3 and p_id=$1 and me_code = $2',
                    array($p_id, $this->me_code, $dep_id[$i]['pm_id']));
            if ($cnt==0)
            {
                $profil_menu->insert();
            }
        }
    }

    function remove_from_profile_menu($p_id)
    {
        global $cn;

        $cn->exec_sql('delete from profile_menu  where (me_code = $1 or me_code in (select me_code from menu_ref where me_file=$2)) and p_id=$3',
                array($this->me_code, $this->me_file, $p_id));
    }

    /**
     * @brief save a plugin into MENU_REF , calls insert_plugin or update_plugin if it already exists
     * @return void
     */
    function save_plugin()
    {
        if ( $this->cn->get_value("select count(*) from menu_ref where me_code=$1",[$this->me_code]) > 0) {
            $this->update_plugin();
        } else {
            $this->insert_plugin();

        }
    }
    /**
     * @brief Insert a plugin into the given profile, by default always insert into EXT
     * 
     * @param type $p_id profile.p_id
     * @throws Exception if duplicate or error db
     */
    function insert_plugin()
    {
        try
        {
            $this->cn->start();
            $this->verify();
            // check if duplicate
            $this->me_code=strtoupper($this->me_code);
            $count=$this->cn->get_value("select count(*) from menu_ref where me_code=$1", array($this->me_code));
            if ($count!=0)
                throw new Exception("Doublon");
            $this->me_type='PL';
            $this->insert();
            $this->cn->commit();
        }
        catch (Exception $exc)
        {
            echo alert($exc->getMessage());
        }
    }

    function update_plugin()
    {
        try
        {
            $this->cn->start();
            $this->verify();
            $this->me_type='PL';
            $this->update();
            $this->cn->commit();
        }
        catch (Exception $exc)
        {
            echo alert($exc->getMessage());
        }
    }

    function remove_plugin()
    {
        try
        {
            $this->cn->start();
            $this->delete();
            $this->cn->commit();
        }
        catch (Exception $exc)
        {
            echo alert($exc->getMessage());
        }
    }

    /**
     * @brief remove all the standard  plugins schema
     * @param Database $p_cn
     */
    static function clean(Database $p_cn)
    {
        $a_ext=array("tva_belge", "amortissement", "impdol", "coprop", "importbank");
        for ($i=0; $i<count($a_ext); $i++)
        {
            if ($p_cn->exist_schema($a_ext[$i]))
            {
                $p_cn->exec_sql("drop schema ".$a_ext[$i]." cascade");
            }
        }
    }

    /**
     * @brief compare the version of the plugin and the last version , propose to update it if a new version exists
     * @todo add a mechanism to check once a day
     * @global User $g_user
     * @global number $version_plugin
     */
    static function check_plugin_version($p_plugin_code)
    {
        global $g_user, $version_plugin;
        if ($g_user->Admin()==1)
        {
            if ( in_array($p_plugin_code, self::aStandard_plugin) && SITE_UPDATE_PLUGIN!="")
            {
               $update=@file_get_contents(SITE_UPDATE_PLUGIN);
                if ($update>$version_plugin)
                {
                    echo '<div id="version_plugin_div_id" class="inner_box" style="position:absolute;zindex:2;top:5px;left:37.5%;width:25%">';
                    echo '<p class="notice">';
                    echo "Mise à jour disponible des plugins pour NOALYSS, version actuelle : $update votre version $version_plugin";
                    echo '</p>';
                    echo '<p style="text-align:center">'.
                    '<a id="version_plugin_button" class="button" onclick="$(\'version_plugin_div_id\').remove()">'.
                    _('Fermer').
                    "</a></p>";
                    echo '</div>';
                }
            }
        }
    }

    /**
     * Check that the xml contains all the needed information to change them into
     * a extension, the exception code is 0 if the element is optional
     * @brief Check XML.
     * @param SimpleXMLElement $xml
     * @throws Exception
     */
    function check_xml(SimpleXMLElement $xml)
    {
        try
        {
            if (!isset($xml->plugin))
                throw new Exception(_('Manque plugin'), 1);
            $nb_plugin=count($xml->plugin);

            for ($i=0; $i<$nb_plugin; $i++)
            {
                if (!isset($xml->plugin[$i]->name))
                    throw new Exception(_('Manque nom'), 1);
                if (!isset($xml->plugin[$i]->description))
                    throw new Exception(_('Manque description'), 0);
                if (!isset($xml->plugin[$i]->code))
                    throw new Exception(_('Manque code'), 1);
                if (!isset($xml->plugin[$i]->author))
                    throw new Exception(_('Manque auteur'), 0);
                if (!isset($xml->plugin[$i]->root))
                    throw new Exception(_('Manque répertoire racine'), 1);
                if (!isset($xml->plugin[$i]->file))
                    throw new Exception(_('Manque fichier à inclure'), 1);
                if (!isset($xml->plugin[$i]->version))
                    throw new Exception(_("Manque version de l'extension"), 1);

                if (!isset($xml->plugin[$i]->depend))
                    $xml->plugin[$i]->depend="EXT";
                if (!isset($xml->plugin[$i]->order))
                    $xml->plugin[$i]->order=9000;
            }
        }
        catch (Exception $ex)
        {
            throw $ex;
        }
    }

    /**
     * @brief Parse a XML file to complete an array of extension objects, in the plugin.xml file , you can find
     * several plugins sharing some parts.
     *
     * @param string $p_file filename
     * @return array  array of Extension
     */
    static function read_definition($p_file)
    {
        global $cn;
        $dom=new DomDocument('1.0');
        $dom->load($p_file);
        $xml=simplexml_import_dom($dom);
        $nb_plugin=count($xml->plugin);
        $a_extension=array();
        for ($i=0; $i<$nb_plugin; $i++)
        {

            $extension=new Extension($cn);
            try
            {
                $extension->check_xml($xml);
            }
            catch (Exception $ex)
            {
                echo_warning($ex->getMessage());
                if ($ex->getCode()==1)
                {
                    continue;
                }
            }
            $extension->me_file=trim($xml->plugin[$i]->root).'/'.trim($xml->plugin[$i]->file);
            $extension->me_code=trim($xml->plugin[$i]->code);
            $extension->me_description=(isset($xml->plugin[$i]->description))?trim($xml->plugin[$i]->description):"";
            $extension->me_description_etendue=(trim($xml->plugin[$i]->author))?trim($xml->plugin[$i]->author):"";
            $extension->me_type='PL';
            $extension->me_menu=trim($xml->plugin[$i]->name);
            $extension->me_parameter='plugin_code='.trim($xml->plugin[$i]->code);
            $extension->depend=(isset($xml->plugin[$i]->depend))?trim($xml->plugin[$i]->depend):"EXT";
            $extension->order=(isset($xml->plugin[$i]->order))?trim($xml->plugin[$i]->order):9000;
            $extension->version=trim($xml->plugin[$i]->version);
            $extension->noalyss_version=(isset($xml->plugin[$i]->noalyss_version))?trim($xml->plugin[$i]->noalyss_version):8000;
            $extension->schema=(isset($xml->plugin[$i]->schema))?trim($xml->plugin[$i]->schema):"public";
            
            $a_extension[]=clone $extension;
        }
        return $a_extension;
    }

    /**
     * @brief find the extension with the me_code = last part of access_code
     * @param $a_extension
     * @param $access_code find the ME_CODE (normally last part )
     * @return the extension or null
     */
    public static function find_extension_code($a_extension,$access_code):Extension|null
    {
        $a_me_code=explode("/", $access_code);
        if (empty($a_me_code ) ) return null;
        $nb_me_code=count($a_me_code);
        $me_code=$a_me_code[$nb_me_code-1];
        foreach ($a_extension as $extension) {
            if ($extension->me_code==$me_code) return $extension;
        }
        return null;
    }

    public function __toString(): string
    {
        $r = "";
        $r .= "  me_code " . $this->me_code.PHP_EOL;
        $r .= "  me_menu.".$this->me_menu.PHP_EOL;
        $r .= "  version".$this->version.PHP_EOL;
        $r .= "  noalyss_version".$this->noalyss_version.PHP_EOL;
        $r .= "  me_file" . $this->me_file.PHP_EOL;
        $r .= "  me_url" . $this->me_url.PHP_EOL;
        $r .= "  me_description" . $this->me_description.PHP_EOL;
        $r .= "  me_parameter" . $this->me_parameter.PHP_EOL;
        $r .= "  me_javascript" . $this->me_javascript.PHP_EOL;
        $r .= "  me_type" . $this->me_type.PHP_EOL;
        $r .= "  me_descrition_etendue" . $this->me_description_etendue.PHP_EOL;
        $r .= "  noalyss_version " . $this->noalyss_version.PHP_EOL;
        $r .= "  version " . $this->version.PHP_EOL;
        $r .= "  order " . $this->order.PHP_EOL;
        $r .= "  depend " . $this->depend.PHP_EOL;
        return "Extension $r";
    }

    /**
     * @brief retrieve the version of the current plugin
     * @param $xml_file always __DIR__."/plugin.xml"
     * @param $plugin_code the plugin or $_REQUEST['ac']
     * @return int version or -1 if not found
     */
    public static function get_version($xml_file,$plugin_code):int
    {
        $aExtension=\Extension::read_definition($xml_file);
        $extension=self::find_extension_code($aExtension, $plugin_code);
        if ( empty($plugin_code)) return -1;
        return $extension->version??0;
    }


}
