Use error message from database class instead of odbc_error
[misc/kostenrechnung] / lib / rico / ricoXmlResponse.php
1 <?php
2
3 class ricoXmlResponse {
4
5   // public properties
6   var $orderByRef;
7   var $sendDebugMsgs;
8   var $readAllRows;    // always return the total number of rows? (if true, the user will always see the total number of rows, but there is a small performance hit)
9   var $convertCharSet; // set to true if database is ISO-8859-1 encoded, false if UTF-8
10   var $AllRowsMax;     // max # of rows to send if numrows=-1
11  
12   // private properties
13   var $objDB;
14   var $eof;
15   var $oParse;
16   var $sqltext;
17   var $arParams;
18   var $allParams;
19   var $condType;
20
21   function ricoXmlResponse() {
22     if (is_object($GLOBALS['oDB'])) {
23       $this->objDB=$GLOBALS['oDB'];   // use oDB global as database connection, if it exists
24     }
25     $this->orderByRef=false;
26     $this->sendDebugMsgs=false;
27     $this->readAllRows=true;    // has no effect on SQL Server 2005, Oracle, and MySQL because they use Query2xmlRaw_Limit()
28     $this->convertCharSet=false;
29     $this->AllRowsMax=1999;
30   }
31
32   // All Oracle and SQL Server 2005 queries *must* have an ORDER BY clause
33   // "as" clauses are now ok
34   // If numrows < 0, then retrieve all rows
35
36   function Query2xml($sqlselect, $offset, $numrows, $gettotal, $filters=array()) {
37     if ($numrows >= 0) {
38       $Dialect=$this->objDB->Dialect;
39     } else {
40       $numrows=$this->AllRowsMax;
41       $Dialect="";  // don't use limit query
42     }
43     if ($this->sendDebugMsgs) {
44       echo "\n<debug>Query2xml: SQL Dialect=".htmlspecialchars($Dialect)."</debug>";
45       echo "\n<debug>Query2xml: numrows=".$numrows."</debug>";
46       echo "\n<debug>Query2xml: resource type=".get_resource_type($this->objDB->dbMain)."</debug>";
47     }
48     switch ($this->objDB->Dialect) {
49       case "MySQL": $this->orderByRef=true; break;
50     }
51     $this->arParams=array('H'=>array(), 'W'=>array());
52     $this->oParse= new sqlParse();
53     $this->oParse->ParseSelect($sqlselect);
54     $this->ApplyQStringParms($filters);
55     $this->allParams=array_merge($this->arParams['W'],$this->arParams['H']);
56     echo "\n<rows update_ui='true' offset='".$offset."'>";
57     switch ($Dialect) {
58
59       case "TSQL":
60         $this->objDB->SingleRecordQuery("select @@VERSION", $version);
61         if (strtoupper(substr($sqlselect,0,7))!="SELECT ") {
62           $this->allParams=array();
63           $totcnt=$this->Query2xmlRaw($sqlselect, $offset, $numrows);
64         }
65         else if (preg_match("/SQL Server 200(5|8)/i",$version[0])) {
66           $this->sqltext=$this->UnparseWithRowNumber($offset, $numrows + 1, true);
67           $totcnt=$this->Query2xmlRaw_Limit($this->sqltext, $offset, $numrows, 1);
68         }
69         else {
70           $this->sqltext=$this->oParse->UnparseSelect();
71           $totcnt=$this->Query2xmlRaw($this->sqltext, $offset, $numrows);
72         }
73         break;
74
75       case "Oracle":
76         $this->sqltext=$this->UnparseWithRowNumber($offset, $numrows + 1, false);
77         $totcnt=$this->Query2xmlRaw_Limit($this->sqltext, $offset, $numrows, 1);
78         break;
79
80       case "MySQL":
81         $this->sqltext=$this->oParse->UnparseSelect()." LIMIT ".$offset.",".($numrows + 1);
82         $totcnt=$this->Query2xmlRaw_Limit($this->sqltext, $offset, $numrows, 0);
83         break;
84
85       default:
86         $this->sqltext=$this->oParse->UnparseSelect();
87         $totcnt=$this->Query2xmlRaw($this->sqltext, $offset, $numrows);
88         break;
89     }
90     echo "\n</rows>";
91     if ($this->sendDebugMsgs) {
92       echo "\n<debug>origQuery=".htmlspecialchars($sqlselect)."</debug>";
93       echo "\n<debug>execQuery=".htmlspecialchars($this->objDB->db->lastQuery)."</debug>";
94     }
95     if (!$this->eof && $gettotal) {
96       $totcnt=$this->getTotalRowCount();
97       if ($this->sendDebugMsgs) {
98         echo "\n<debug>cntQuery=".htmlspecialchars($this->objDB->db->lastQuery)."</debug>";
99       }
100     }
101     if ($this->eof) {
102       echo "\n<rowcount>".$totcnt."</rowcount>";
103     }
104     $this->oParse=NULL;
105     return $totcnt;
106   }
107
108
109   function Query2xmlDistinct($sqlselect, $colnum, $numrows, $filters=array()) {
110     if ($numrows < 0) $numrows=$this->AllRowsMax;
111     $this->arParams=array('H'=>array(), 'W'=>array());
112     $this->oParse= new sqlParse();
113     $this->oParse->ParseSelect($sqlselect);
114     $this->ApplyQStringParms($filters);
115     $this->allParams=array_merge($this->arParams['W'],$this->arParams['H']);
116     echo "\n<rows update_ui='true' offset='0' distinct='" . $colnum . "'>";
117     $this->sqltext=$this->oParse->UnparseDistinctColumn($colnum);
118     $totcnt=$this->Query2xmlRaw($this->sqltext, 0, $numrows);
119     echo "\n</rows>";
120     if ($this->sendDebugMsgs) {
121       echo "\n<debug>origQuery=".htmlspecialchars($sqlselect)."</debug>";
122       echo "\n<debug>execQuery=".htmlspecialchars($this->objDB->db->lastQuery)."</debug>";
123     }
124     $this->oParse=NULL;
125   }
126
127
128   // Tested ok with SQL Server 2005, MySQL, and Oracle
129   function getTotalRowCount() {
130     $countSql="SELECT ".$this->oParse->UnparseColumnList()." FROM ".$this->oParse->FromClause;
131     if (!empty($this->oParse->WhereClause)) {
132       $countSql.=" WHERE ".$this->oParse->WhereClause;
133     }
134     if (is_array($this->oParse->arGroupBy)) {
135       if (count($this->oParse->arGroupBy) >  0) {
136         $countSql.=" GROUP BY ".implode(",",$this->oParse->arGroupBy);
137       }
138     }
139     if (!empty($this->oParse->HavingClause)) {
140       $countSql.=" HAVING ".$this->oParse->HavingClause;
141     }
142     $countSql="SELECT COUNT(*) FROM (".$countSql.")";
143     if ($this->objDB->Dialect != "Oracle") {
144       $countSql.=" AS rico_Main";
145     }
146     if (count($this->allParams)>0) {
147       $rsMain=$this->objDB->RunParamQuery($countSql,$this->allParams);
148     } else {
149       $rsMain=$this->objDB->RunQuery($countSql);
150     }
151     if (!$rsMain) {
152       echo "\n<debug>getTotalRowCount: rsMain is null</debug>";
153       return;
154     }
155     if (!$this->objDB->db->FetchArray($rsMain,$a)) return;
156     $this->objDB->rsClose($rsMain);
157     $this->eof=true;
158     return $a[0];
159   }
160
161
162   function UnparseWithRowNumber($offset, $numrows, $includeAS) {
163     if (is_array($this->oParse->arOrderBy)) {
164       if (count($this->oParse->arOrderBy) >  0) {
165         $strOrderBy=implode(",",$this->oParse->arOrderBy);
166       }
167     }
168     if (empty($strOrderBy) && !preg_match("/\bjoin\b/",$this->oParse->FromClause)) {
169       // order by clause should be included in main sql select statement
170       // However, if it isn't, then use primary key as sort - assuming FromClause is a simple table name
171       $strOrderBy=$this->objDB->PrimaryKey($this->oParse->FromClause);
172     }
173     $unparseText="SELECT ROW_NUMBER() OVER (ORDER BY ".$strOrderBy.") AS rico_rownum,";
174     $unparseText.=$this->oParse->UnparseColumnList()." FROM ".$this->oParse->FromClause;
175     if (!empty($this->oParse->WhereClause)) {
176       $unparseText.=" WHERE ".$this->oParse->WhereClause;
177     }
178     if (is_array($this->oParse->arGroupBy)) {
179       if (count($this->oParse->arGroupBy) >  0) {
180         $unparseText.=" GROUP BY ".implode(",",$this->oParse->arGroupBy);
181       }
182     }
183     if (!empty($this->oParse->HavingClause)) {
184       $unparseText.=" HAVING ".$this->oParse->HavingClause;
185     }
186     $unparseText="SELECT * FROM (".$unparseText.")";
187     if ($includeAS) {
188       $unparseText.=" AS rico_Main";
189     }
190     $unparseText.=" WHERE rico_rownum > ".$offset." AND rico_rownum <= ".($offset + $numrows);
191     return $unparseText;
192   }
193
194   function Query2xmlRaw($rawsqltext, $offset, $numrows) {
195     if (count($this->allParams)>0) {
196       $rsMain=$this->objDB->RunParamQuery($rawsqltext,$this->allParams);
197     } else {
198       $rsMain=$this->objDB->RunQuery($rawsqltext);
199     }
200     if (!$rsMain) {
201       echo "\n<debug>Query2xmlRaw: rsMain is null - ".htmlspecialchars($this->objDB->ErrorMsg())."</debug>";
202       return;
203     }
204   
205     $colcnt = $this->objDB->db->NumFields($rsMain);
206     $totcnt = $this->objDB->db->NumRows($rsMain);
207     //echo "<debug>Query2xmlRaw: NumRows=$totcnt</debug>";
208     if ($offset < $totcnt || $totcnt==-1)
209     {
210       $rowcnt=0;
211       $this->objDB->db->Seek($rsMain,$offset);
212       while(($this->objDB->db->FetchRow($rsMain,$row)) && $rowcnt < $numrows)
213       {
214         $rowcnt++;
215         print "\n<tr>";
216         for ($i=0; $i < $colcnt; $i++)
217           print $this->XmlStringCell($row[$i]);
218         print "</tr>";
219       }
220       if ($totcnt < 0) {
221         $totcnt=$offset+$rowcnt;
222         while($this->objDB->db->FetchRow($rsMain,$row))
223           $totcnt++;
224       }
225     }
226     else
227     {
228       $totcnt=$offset;
229     }
230     $this->objDB->rsClose($rsMain);
231     $this->eof=true;
232     return $totcnt;
233   }
234
235   function Query2xmlRaw_Limit($rawsqltext, $offset, $numrows, $firstcol) {
236     if (count($this->allParams)>0) {
237       $rsMain=$this->objDB->RunParamQuery($rawsqltext,$this->allParams);
238     } else {
239       $rsMain=$this->objDB->RunQuery($rawsqltext);
240     }
241     if ($this->objDB->db->HasError()) echo "<error>" . $this->objDB->db->ErrorMsg() . "</error>";
242     $totcnt=$offset;
243     $this->eof=true;
244     if (!$rsMain) {
245       echo "<debug>Query2xmlRaw_Limit: rsMain is null</debug>";
246       return;
247     }
248     $colcnt = $this->objDB->db->NumFields($rsMain);
249     $rowcnt=0;
250     while(($this->objDB->db->FetchRow($rsMain,$row)) && $rowcnt < $numrows)
251     {
252       $rowcnt++;
253       print "\n<tr>";
254       for ($i=$firstcol; $i < $colcnt; $i++)
255         print $this->XmlStringCell($row[$i]);
256       print "</tr>";
257     }
258     $totcnt+=$rowcnt;
259     $this->eof=($rowcnt < $numrows);
260     $this->objDB->rsClose($rsMain);
261     return $totcnt;
262   }
263
264   function SetDbConn(&$dbcls) {
265     $this->objDB=&$dbcls;
266   }
267
268   function PushParam($newvalue) {
269     $parm=$this->convertCharSet ? utf8_decode($newvalue) : $newvalue;
270     if (get_magic_quotes_gpc()) $parm=stripslashes($parm);
271     array_push($this->arParams[$this->condType], $parm);
272     if ($this->sendDebugMsgs) {
273       echo "\n<debug>".$this->condType." param=".htmlspecialchars($parm)."</debug>";
274     }
275   }
276   
277   function setCondType($selectItem) {
278     $this->condType=(preg_match("/\bmin\(|\bmax\(|\bsum\(|\bcount\(/i",$selectItem) && !preg_match("/\bselect\b/i",$selectItem)) ? 'H' : 'W';
279   }
280   
281   function addCondition($newfilter) {
282     switch ($this->condType) {
283       case 'H': $this->oParse->AddHavingCondition($newfilter); break;
284       case 'W': $this->oParse->AddWhereCondition($newfilter); break;
285     }
286   }
287
288   function ApplyQStringParms($filters) {
289     foreach($_GET as $qs => $value) {
290       $prefix=substr($qs,0,1);
291       switch ($prefix) {
292
293         // user-invoked condition
294         case "w":
295         case "h":
296           $i=substr($qs,1);
297           if (!is_numeric($i)) break;
298           $i=intval($i);
299           if ($i<0 || $i>=count($filters)) break;
300           $newfilter=$filters[$i];
301           $this->condType=strtoupper($prefix);
302
303           $j=strpos($newfilter," in (?)");
304           if ($j !== false) {
305             $a=explode(",", $value);
306             for ($i=0; $i < count($a); $i++) {
307               $this->PushParam($a[$i]);
308               $a[$i]="?";
309             }
310             $newfilter=substr($newfilter,0,$j+4) . implode(",",$a) . substr($newfilter,$j+5);
311           } elseif (strpos($newfilter,"?") !== false) {
312             $this->PushParam($value);
313           }
314
315           $this->addCondition($newfilter);
316           break;
317
318         // sort
319         case "s":
320           $i=substr($qs,1);
321           if (!is_numeric($i)) break;
322           $i=intval($i);
323           if ($i<0 || $i>=count($this->oParse->arSelList)) break;
324           $value=strtoupper(substr($value,0,4));
325           if (!in_array($value,array('ASC','DESC'))) $value="ASC";
326           if ($this->orderByRef)
327             $this->oParse->AddSort(($i + 1)." ".$value);
328           else
329             $this->oParse->AddSort($this->oParse->arSelList[$i]." ".$value);
330           break;
331
332         // user-supplied filter
333         case "f":
334           //print_r($value);
335           foreach($value as $i => $filter) {
336             if ($i<0 || $i>=count($this->oParse->arSelList)) break;
337             $newfilter=$this->oParse->arSelList[$i];
338             $this->setCondType($newfilter);
339             switch ($filter['op']) {
340               case "EQ":
341                 if ($filter[0]=="") {
342                   if ($this->objDB->Dialect=="Access") {
343                     $newfilter="iif(IsNull(" . $newfilter . "),''," . $newfilter . ")";
344                   } else {
345                     $newfilter="coalesce(" . $newfilter . ",'')";
346                   }
347                 }
348                 $newfilter.="=?";
349                 $this->PushParam($filter[0]);
350                 break;
351               case "LE":
352                 $newfilter.="<=?";
353                 $this->PushParam($filter[0]);
354                 break;
355               case "GE":
356                 $newfilter.=">=?";
357                 $this->PushParam($filter[0]);
358                 break;
359               case "NULL": $newfilter.=" is null"; break;
360               case "NOTNULL": $newfilter.=" is not null"; break;
361               case "LIKE":
362                 $newfilter.=" LIKE ?";
363                 $this->PushParam(str_replace("*",$this->objDB->Wildcard,$filter[0]));
364                 break;
365               case "NE":
366                 $flen=$filter['len'];
367                 if (!is_numeric($flen)) break;
368                 $flen=intval($flen);
369                 $newfilter.=" NOT IN (";
370                 for ($j=0; $j<$flen; $j++) {
371                   if ($j > 0) $newfilter.=",";
372                   $newfilter.='?';
373                   $this->PushParam($filter[$j]);
374                 }
375                 $newfilter.=")";
376                 break;
377             }
378             $this->addCondition($newfilter);
379           }
380           break;
381       }
382     }
383   }
384
385   function XmlStringCell($value) {
386     if (!isset($value)) {
387       $result="";
388     }
389     else {
390       if ($this->convertCharSet) $value=utf8_encode($value);
391       $result=htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
392     }
393     return "<td>".$result."</td>";
394   }
395
396   // for the root node, parentID should "" (empty string)
397   // containerORleaf: L/zero (leaf), C/non-zero (container)
398   // selectable:      0->not selectable, 1->selectable
399   function WriteTreeRow($parentID, $ID, $description, $containerORleaf, $selectable) {
400     echo "\n<tr>";
401     echo $this->XmlStringCell($parentID);
402     echo $this->XmlStringCell($ID);
403     echo $this->XmlStringCell($description);
404     echo $this->XmlStringCell($containerORleaf);
405     echo $this->XmlStringCell($selectable);
406     echo "</tr>";
407   }
408
409 }
410
411 ?>
412