<?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 contains the class for connecting to a postgresql database */ require_once NOALYSS_INCLUDE.'/constant.php'; require_once NOALYSS_INCLUDE.'/ac_common.php'; /**\brief * This class allow you to connect to the postgresql database, execute sql, retrieve data * */ class Database { private $db; /**< database connection */ private $ret; /**< return value */ private $is_open; /*!< true is connected */ /**\brief constructor * \param $p_database_id is the id of the dossier, or the modele following the * p_type if = 0 then connect to the repository * \param $p_type is 'DOS' (defaut) for dossier or 'MOD' */ function __construct($p_database_id=0, $p_type='dos') { if (IsNumber($p_database_id)==false||strlen($p_database_id)>5) die("-->Dossier invalide [$p_database_id]"); $noalyss_user=(defined("noalyss_user"))?noalyss_user:phpcompta_user; $password=(defined("noalyss_password"))?noalyss_password:phpcompta_password; $port=(defined("noalyss_psql_port"))?noalyss_psql_port:phpcompta_psql_port; $host=(!defined("noalyss_psql_host") )?'127.0.0.1':noalyss_psql_host; if (defined("MULTI")&&MULTI=="0") { $l_dossier=dbname; } else { if ($p_database_id==0) { /* connect to the repository */ $l_dossier=sprintf("%saccount_repository", strtolower(domaine)); } else if ($p_type=='dos') { /* connect to a folder (dossier) */ $l_dossier=sprintf("%sdossier%d", strtolower(domaine), $p_database_id); } else if ($p_type=='mod') { /* connect to a template (modele) */ $l_dossier=sprintf("%smod%d", strtolower(domaine), $p_database_id); } else if ($p_type=='template') { $l_dossier='template1'; } else { throw new Exception('Connection invalide'); } } ob_start(); $a=pg_connect("dbname=$l_dossier host='$host' user='$noalyss_user' password='$password' port=$port"); if ($a==false) { if (DEBUG) { ob_end_clean(); echo '<h2 class="error">Impossible de se connecter à postgreSql !</h2>'; echo '<p>'; echo "Vos paramètres sont incorrectes : <br>"; echo "<br>"; echo "base de donnée : $l_dossier<br>"; echo "Domaine : ".domaine."<br>"; echo "Port $port <br>"; echo "Utilisateur : $noalyss_user <br>"; echo '</p>'; die("Connection impossible : vérifiez vos paramètres de base de données"); } else { echo '<h2 class="error">Erreur de connexion !</h2>'; } } $this->db=$a; $this->is_open=TRUE; if ($this->exist_schema('comptaproc')) pg_exec($this->db, 'set search_path to public,comptaproc;'); pg_exec($this->db, 'set DateStyle to ISO, MDY;'); ob_end_clean(); } public function verify() { // Verify that the elt we want to add is correct } function set_encoding($p_charset) { pg_set_client_encoding($this->db, $p_charset); } /** * \brief send a sql string to the database * \param $p_string sql string * \param $p_array array for the SQL string (see pg_query_params) * \return the result of the query, a resource or false if an * error occured */ function exec_sql($p_string, $p_array=null) { try { if ( ! $this->is_open ) throw new Exception(' Database is closed'); $this->sql=$p_string; $this->array=$p_array; if ($p_array==null) { if (!DEBUG) $this->ret=pg_query($this->db, $p_string); else $this->ret=@pg_query($this->db, $p_string); } else { $a=is_array($p_array); if (!is_array($p_array)) { throw new Exception("Erreur : exec_sql attend un array"); } if (!DEBUG) $this->ret=pg_query_params($this->db, $p_string, $p_array); else $this->ret=@pg_query_params($this->db, $p_string, $p_array); } if (!$this->ret) { $str_error=pg_last_error($this->db).pg_result_error($this->ret); throw new Exception(" SQL ERROR $p_string ".$str_error, 1); } } catch (Exception $a) { if (DEBUG) { print_r($p_string); print_r($p_array); echo $a->getMessage(); echo $a->getTrace(); echo $a->getTraceAsString(); echo pg_last_error($this->db); } $this->rollback(); throw ($a); } return $this->ret; } /** \brief Count the number of row returned by a sql statement * * \param $p_sql sql string * \param $p_array if not null we use the safer pg_query_params */ function count_sql($p_sql, $p_array=null) { $r_sql=$this->exec_sql($p_sql, $p_array); return pg_NumRows($r_sql); } /**\brief get the current sequence value */ function get_current_seq($p_seq) { $Res=$this->get_value("select currval('$p_seq') as seq"); return $Res; } /**\brief get the next sequence value */ function get_next_seq($p_seq) { $Res=$this->exec_sql("select nextval('$p_seq') as seq"); $seq=pg_fetch_array($Res, 0); return $seq['seq']; } /** * @ brief : start a transaction * */ function start() { $Res=$this->exec_sql("start transaction"); } /** * Commit the transaction * */ function commit() { if ( ! $this->is_open) return; $Res=$this->exec_sql("commit"); } /** * rollback the current transaction */ function rollback() { if ( ! $this->is_open) return; $Res=$this->exec_sql("rollback"); } /** * @brief alter the sequence value * @param $p_name name of the sequence * @param $min the start value of the sequence */ function alter_seq($p_name, $min) { if ($min<1) $min=1; $Res=$this->exec_sql("alter sequence $p_name restart $min"); } /** * \brief Execute a sql script * \param $script script name */ function execute_script($script) { if (!DEBUG) ob_start(); $hf=fopen($script, 'r'); if ($hf==false) { throw new Exception ( 'Ne peut ouvrir '.$script); } $sql=""; $flag_function=false; while (!feof($hf)) { $buffer=fgets($hf); $buffer=str_replace("$", "\$", $buffer); print $buffer."<br>"; // comment are not execute if (substr($buffer, 0, 2)=="--") { //echo "comment $buffer"; continue; } // Blank Lines Are Skipped If (Strlen($buffer)==0) { //echo "Blank $buffer"; Continue; } if (strpos(strtolower($buffer), "create function")===0) { echo "found a function"; $flag_function=true; $sql=$buffer; continue; } if (strpos(strtolower($buffer), "create or replace function")===0) { echo "found a function"; $flag_function=true; $sql=$buffer; continue; } // No semi colon -> multiline command if ($flag_function==false&&strpos($buffer, ';')==false) { $sql.=$buffer; continue; } if ($flag_function) { if (strpos(strtolower($buffer), "language plpgsql")===false&& strpos(strtolower($buffer), "language 'plpgsql'")===false) { $sql.=$buffer; continue; } } else { // cut the semi colon $buffer=str_replace(';', '', $buffer); } $sql.=$buffer; if ($this->exec_sql($sql)==false) { $this->rollback(); if (!DEBUG) ob_end_clean(); print "ERROR : $sql"; throw new Exception("ERROR : $sql"); } $sql=""; $flag_function=false; print "<hr>"; } // while (feof) fclose($hf); if (!DEBUG) ob_end_clean(); } /** * \brief Get version of a database, the content of the * table version * * \return version number * */ function get_version() { $Res=$this->get_value("select val from version"); return $Res; } /** * @brief fetch the $p_indice array from the last query * @param $p_indice index * */ function fetch($p_indice) { if ($this->ret==false) throw new Exception('this->ret is empty'); return pg_fetch_array($this->ret, $p_indice); } /** * * @brief return the number of rows found by the last query, or the number * of rows from $p_ret * @param $p_ret is the result of a query, the default value is null, in that case * it is related to the last query * @note synomym for count() */ function size($p_ret=null) { if ($p_ret==null) return pg_NumRows($this->ret); else return pg_NumRows($p_ret); } /** * @brief synomym for size() */ function count($p_ret=null) { return $this->size($p_ret); } /** * \brief loop to apply all the path to a folder or * a template * \param $p_name database name * \param $from_setup == 1 if called from setup.php * */ function apply_patch($p_name, $from_setup=1) { if ( ! $this->exist_table('version')) { echo _('Base de donnée vide'); return; } $MaxVersion=DBVERSION-1; $succeed="<span style=\"font-size:18px;color:green\">✓</span>"; echo '<ul style="list-type-style:square">'; $add=($from_setup==0)?'admin/':''; for ($i=4; $i<=$MaxVersion; $i++) { $to=$i+1; if ($this->get_version()<=$i) { if ($this->get_version()==97) { if ($this->exist_schema("amortissement")) { $this->exec_sql('ALTER TABLE amortissement.amortissement_histo ADD CONSTRAINT internal_fk FOREIGN KEY (jr_internal) REFERENCES jrn (jr_internal) ON UPDATE CASCADE ON DELETE SET NULL'); } } echo "<li>Patching ".$p_name. " from the version ".$this->get_version()." to $to "; $this->execute_script($add.'sql/patch/upgrade'.$i.'.sql'); echo $succeed; if (!DEBUG) ob_start(); // specific for version 4 if ($i==4) { $sql="select jrn_def_id from jrn_def "; $Res=$this->exec_sql($sql); $Max=$this->size(); for ($seq=0; $seq<$Max; $seq++) { $row=pg_fetch_array($Res, $seq); $sql=sprintf("create sequence s_jrn_%d", $row['jrn_def_id']); $this->exec_sql($sql); } } // specific to version 7 if ($i==7) { // now we use sequence instead of computing a max // $Res2=$this->exec_sql('select coalesce(max(jr_grpt_id),1) as l from jrn'); $Max2=pg_NumRows($Res2); if ($Max2==1) { $Row=pg_fetch_array($Res2, 0); var_dump($Row); $M=$Row['l']; $this->exec_sql("select setval('s_grpt',$M,true)"); } } // specific to version 17 if ($i==17) { $this->execute_script($add.'sql/patch/upgrade17.sql'); $max=$this->get_value('select last_value from s_jnt_fic_att_value'); $this->alter_seq($p_cn, 's_jnt_fic_att_value', $max+1); } // version // reset sequence in the modele //-- if ($i==30&&$p_name=="mod") { $a_seq=array('s_jrn', 's_jrn_op', 's_centralized', 's_stock_goods', 'c_order', 's_central'); foreach ($a_seq as $seq) { $sql=sprintf("select setval('%s',1,false)", $seq); $Res=$this->exec_sql($sql); } $sql="select jrn_def_id from jrn_def "; $Res=$this->exec_sql($sql); $Max=pg_NumRows($Res); for ($seq=0; $seq<$Max; $seq++) { $row=pg_fetch_array($Res, $seq); $sql=sprintf("select setval('s_jrn_%d',1,false)", $row['jrn_def_id']); $this->exec_sql($sql); } } if ($i==36) { /* check the country and apply the path */ $res=$this->exec_sql("select pr_value from parameter where pr_id='MY_COUNTRY'"); $country=pg_fetch_result($res, 0, 0); $this->execute_script($add."sql/patch/upgrade36.".$country.".sql"); $this->exec_sql('update tmp_pcmn set pcm_type=find_pcm_type(pcm_val)'); } if ($i==59) { $res=$this->exec_sql("select pr_value from parameter where pr_id='MY_COUNTRY'"); $country=pg_fetch_result($res, 0, 0); if ($country=='BE') $this->exec_sql("insert into parm_code values ('SUPPLIER',440,'Poste par défaut pour les fournisseurs')"); if ($country=='FR') $this->exec_sql("insert into parm_code values ('SUPPLIER',400,'Poste par défaut pour les fournisseurs')"); } if ($i==61) { $country=$this->get_value("select pr_value from parameter where pr_id='MY_COUNTRY'"); $this->execute_script($add."sql/patch/upgrade61.".$country.".sql"); } if (!DEBUG) ob_end_clean(); } } echo '</ul>'; } /** * * \brief return the value of the sql, the sql will return only one value * with the value * \param $p_sql the sql stmt example :select s_value from document_state where s_id=2 * \param $p_array if array is not null we use the ExecSqlParm (safer) * \see exec_sql * \note print a warning if several value are found, if only the first value is needed * consider using a LIMIT clause * \return only the first value or an empty string if nothing is found */ function get_value($p_sql, $p_array=null) { $this->ret=$this->exec_sql($p_sql, $p_array); $r=pg_NumRows($this->ret); if ($r==0) return ""; if ($r>1) { $array=pg_fetch_all($this->ret); throw new Exception("Attention $p_sql retourne ".pg_NumRows($this->ret)." valeurs ". var_export($p_array, true)." values=".var_export($array, true)); } $r=pg_fetch_row($this->ret, 0); return $r[0]; } /** * @brief return the number of rows affected by the previous query */ function get_affected() { return Database::num_row($this->ret); } /** * \brief purpose return the result of a sql statment * in a array * \param $p_sql sql query * \param $p_array if not null we use ExecSqlParam * \return an empty array if nothing is found */ function get_array($p_sql, $p_array=null) { $r=$this->exec_sql($p_sql, $p_array); if (($Max=pg_NumRows($r))==0) return array(); $array=pg_fetch_all($r); return $array; } function create_sequence($p_name, $min=1) { if ($min<1) $min=1; $sql="create sequence ".$p_name." minvalue $min"; $this->exec_sql($sql); } /** * \brief test if a sequence exist */ /* \return true if the seq. exist otherwise false */ function exist_sequence($p_name) { $r=$this->count_sql("select relname from pg_class where relname=lower($1)", array($p_name)); if ($r==0) return false; return true; } /**\brief test if a table exist * \param $p_name table name * \param $schema name of the schema default public * \return true if a table exist otherwise false */ function exist_table($p_name, $p_schema='public') { $r=$this->count_sql("select table_name from information_schema.tables where table_schema=$1 and table_name=lower($2)", array($p_schema, $p_name)); if ($r==0) return false; return true; } /** * Check if a column exists in a table * @param $col : column name * @param $table :table name * @param $schema :schema name, default public * @return true or false */ function exist_column($col, $table, $schema) { $r=$this->get_value('select count(*) from information_schema.columns where table_name=lower($1) and column_name=lower($2) and table_schema=lower($3)', array($col, $table, $schema)); if ($r>0) return true; return false; } /** * return the name of the database with the domain name * @param $p_id of the folder WITHOUT the domain name * @param $p_type dos for folder mod for template * @return formatted name */ function format_name($p_id, $p_type) { switch ($p_type) { case 'dos': $sys_name=sprintf("%sdossier%d", strtolower(domaine), $p_id); break; case 'mod': $sys_name=sprintf("%smod%d", strtolower(domaine), $p_id); break; default: echo_error(__FILE__." format_name invalid type ".$p_type, __LINE__); throw new Exception(__FILE__." format_name invalid type ".$p_type. __LINE__); } return $sys_name; } /** * Count the database name in a system view * @param $p_name string database name * @return number of database found (normally 0 or 1) */ function exist_database($p_name) { $database_exist=$this->get_value('select count(*) from pg_catalog.pg_database where datname = lower($1)', array($p_name)); return $database_exist; } /** * @brief check if the large object exists * @param $p_oid of the large object * @return return true if the large obj exist or false if not */ function exist_blob($p_oid) { $r=$this->get_value('select count(loid) from pg_largeobject where loid=$1' , array($p_oid)); if ($r>0) return true; else return false; } /* * !\brief test if a view exist * \return true if the view. exist otherwise false */ function exist_view($p_name) { $r=$this->count_sql("select viewname from pg_views where viewname=lower($1)", array($p_name)); if ($r==0) return false; return true; } /* * !\brief test if a schema exists * \return true if the schemas exists otherwise false */ function exist_schema($p_name) { $r=$this->count_sql("select nspname from pg_namespace where nspname=lower($1)", array($p_name)); if ($r==0) return false; return true; } /** * \brief create a string containing the value separated by comma * for use in a SQL in statement * \return the string or empty if nothing is found * \see fid_card.php */ function make_list($sql, $p_array=null) { if ($p_array==null) { $aArray=$this->get_array($sql); } else { $aArray=$this->get_array($sql, $p_array); } if (empty($aArray)) return ""; $aIdx=array_keys($aArray[0]); $idx=$aIdx[0]; $ret=""; $f=""; for ($i=0; $i<count($aArray); $i++) { $row=$aArray[$i]; $ret.=$f.$aArray[$i][$idx]; $f=','; } $ret=trim($ret, ','); return $ret; } /** * \brief make a array with the sql. * * \param $p_sql sql statement, only the first two column will be returned in * an array. The first col. is the label and the second the value * \param $p_null if the array start with a null value * \param $p_array is the array with the bind value * \note this function is used with ISelect when it is needed to have a list of * options. * \return: a double array like \verbatim Array ( [0] => Array ( [value] => 1 [label] => Marchandise A ) [1] => Array ( [value] => 2 [label] => Marchandise B ) [2] => Array ( [value] => 3 [label] => Marchandise C ) ) \endverbatim * \see ISelect */ function make_array($p_sql, $p_null=0,$p_array=null) { $a=$this->exec_sql($p_sql,$p_array); $max=pg_NumRows($a); if ($max==0&&$p_null==0) return null; for ($i=0; $i<$max; $i++) { $row=pg_fetch_row($a); $r[$i]['value']=$row[0]; $r[$i]['label']=h($row[1]); } // add a blank item ? if ($p_null==1) { for ($i=$max; $i!=0; $i--) { $r[$i]['value']=$r[$i-1]['value']; $r[$i]['label']=h($r[$i-1]['label']); } $r[0]['value']=-1; $r[0]['label']=" "; } // if ( $p_null == 1 ) return $r; } /** * \brief Save a "piece justificative" * * \param $seq jr_grpt_id * \return $oid of the lob file if success * null if a error occurs * */ function save_upload_document($seq) { /* there is no file to upload */ if ($_FILES["pj"]["error"]==UPLOAD_ERR_NO_FILE) { return; } $new_name=tempnam($_ENV['TMP'], 'pj'); if ($_FILES["pj"]["error"]>0) { print_r($_FILES); echo_error(__FILE__.":".__LINE__."Error: ".$_FILES["pj"]["error"]); } if (strlen($_FILES['pj']['tmp_name'])!=0) { if (move_uploaded_file($_FILES['pj']['tmp_name'], $new_name)) { // echo "Image saved"; $oid=pg_lo_import($this->db, $new_name); if ($oid==false) { echo_error('postgres.php', __LINE__, "cannot upload document"); $this->rollback(); return; } // Remove old document $ret=$this->exec_sql("select jr_pj from jrn where jr_grpt_id=$seq"); if (pg_num_rows($ret)!=0) { $r=pg_fetch_array($ret, 0); $old_oid=$r['jr_pj']; if (strlen($old_oid)!=0) pg_lo_unlink($cn, $old_oid); } // Load new document $this->exec_sql("update jrn set jr_pj=$1 , jr_pj_name=$2, jr_pj_type=$3 where jr_grpt_id=$4", array($oid,$_FILES['pj']['name'] ,$_FILES['pj']['type'],$seq)); return $oid; } else { echo "<H1>Error</H1>"; $this->rollback(); } } return 0; } /**\brief wrapper for the function pg_NumRows * \param $ret is the result of a exec_sql * \return number of line affected */ static function num_row($ret) { return pg_NumRows($ret); } /**\brief wrapper for the function pg_fetch_array * \param $ret is the result of a pg_exec * \param $p_indice is the index * \return $array of column */ static function fetch_array($ret, $p_indice=0) { return pg_fetch_array($ret, $p_indice); } /**\brief wrapper for the function pg_fetch_all * \param $ret is the result of pg_exec (exec_sql) * \return double array (row x col ) */ static function fetch_all($ret) { return pg_fetch_all($ret); } /**\brief wrapper for the function pg_fetch_all * \param $ret is the result of pg_exec (exec_sql) * \param $p_row is the indice of the row * \param $p_col is the indice of the col * \return a string or an integer */ static function fetch_result($ret, $p_row=0, $p_col=0) { return pg_fetch_result($ret, $p_row, $p_col); } /**\brief wrapper for the function pg_fetch_row * \param $ret is the result of pg_exec (exec_sql) * \param $p_row is the indice of the row * \return an array indexed from 0 */ static function fetch_row($ret, $p_row) { return pg_fetch_row($ret, $p_row); } /**\brief wrapper for the function pg_lo_unlink * \param $p_oid is the of oid * \return return the result of the operation */ function lo_unlink($p_oid) { return pg_lo_unlink($this->db, $p_oid); } /**\brief wrapper for the function pg_prepare * \param $p_string string name for pg_prepare function * \param $p_sql is the sql to prepare * \return return the result of the operation */ function prepare($p_string, $p_sql) { return pg_prepare($this->db, $p_string, $p_sql); } /**\brief wrapper for the function pg_execute * \param $p_string string name of the stmt given in pg_prepare function * \param $p_array contains the variables * \note set this->ret to the return of pg_execute * \return return the result of the operation, */ function execute($p_string, $p_array) { $this->ret=pg_execute($this->db, $p_string, $p_array); return $this->ret; } /**\brief wrapper for the function pg_lo_export * \param $p_oid is the oid of the log * \param $tmp is the file * \return result of the operation */ function lo_export($p_oid, $tmp) { return pg_lo_export($this->db, $p_oid, $tmp); } /**\brief wrapper for the function pg_lo_export * \param $p_oid is the oid of the log * \param $tmp is the file * \return result of the operation */ function lo_import($p_oid) { return pg_lo_import($this->db, $p_oid); } /**\brief wrapper for the function pg_escape_string * \param $p_string is the string to escape * \return escaped string */ static function escape_string($p_string) { return pg_escape_string($p_string); } /**\brief wrapper for the function pg_close */ function close() { if ( $this->is_open ) pg_close($this->db); $this->is_open=FALSE; } /**\brief * \param * \return * \note * \see */ function __toString() { return "database "; } static function test_me() { } function status() { return pg_transaction_status($this->db); } /** * with the handle of a successull query, echo each row into CSV and * send it directly * @param type $ret handle to a query * @param type $aheader double array, each item of the array contains * a key type (num) and a key title */ function query_to_csv($ret, $aheader) { $seq=""; for ($i=0; $i<count($aheader); $i++) { echo $seq.'"'.$aheader[$i]['title'].'"'; $seq=";"; } printf("\n\r"); // fetch all the rows for ($i=0; $i<Database::num_row($ret); $i++) { $row=Database::fetch_array($ret, $i); $sep2=""; // for each rows, for each value for ($e=0; $e<count($row)/2; $e++) { switch ($aheader[$e]['type']) { case 'num': echo $sep2.nb($row[$e]); break; default: echo $sep2.'"'.$row[$e].'"'; } $sep2=";"; } printf("\n\r"); } } } /* test::test_me(); */