]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/adodb/drivers/adodb-ado.inc.php
new ADODB library 4.22 with multiple drivers (not only mysql as before), major change...
[SourceForge/phpwiki.git] / lib / WikiDB / adodb / drivers / adodb-ado.inc.php
1 <?php\r
2 /* \r
3 V4.22 15 Apr 2004  (c) 2000-2004 John Lim (jlim@natsoft.com.my). All rights reserved.\r
4   Released under both BSD license and Lesser GPL library license. \r
5   Whenever there is any discrepancy between the two licenses, \r
6   the BSD license will take precedence. \r
7 Set tabs to 4 for best viewing.\r
8   \r
9   Latest version is available at http://php.weblogs.com/\r
10   \r
11         Microsoft ADO data driver. Requires ADO. Works only on MS Windows.\r
12 */\r
13   define("_ADODB_ADO_LAYER", 1 );\r
14 /*--------------------------------------------------------------------------------------\r
15 --------------------------------------------------------------------------------------*/\r
16   \r
17 class ADODB_ado extends ADOConnection {\r
18         var $databaseType = "ado";      \r
19         var $_bindInputArray = false;\r
20         var $fmtDate = "'Y-m-d'";\r
21         var $fmtTimeStamp = "'Y-m-d, h:i:sA'";\r
22         var $replaceQuote = "''"; // string to use to replace quotes\r
23         var $dataProvider = "ado";      \r
24         var $hasAffectedRows = true;\r
25         var $adoParameterType = 201; // 201 = long varchar, 203=long wide varchar, 205 = long varbinary\r
26         var $_affectedRows = false;\r
27         var $_thisTransactions;\r
28         var $_cursor_type = 3; // 3=adOpenStatic,0=adOpenForwardOnly,1=adOpenKeyset,2=adOpenDynamic\r
29         var $_cursor_location = 3; // 2=adUseServer, 3 = adUseClient;\r
30         var $_lock_type = -1;\r
31         var $_execute_option = -1;\r
32         var $poorAffectedRows = true; \r
33         var $charPage;\r
34                 \r
35         function ADODB_ado() \r
36         {       \r
37                 $this->_affectedRows = new VARIANT;\r
38         }\r
39 \r
40         function ServerInfo()\r
41         {\r
42                 if (!empty($this->_connectionID)) $desc = $this->_connectionID->provider;\r
43                 return array('description' => $desc, 'version' => '');\r
44         }\r
45         \r
46         function _affectedrows()\r
47         {\r
48                 if (PHP_VERSION >= 5) return $this->_affectedRows;\r
49                 \r
50                 return $this->_affectedRows->value;\r
51         }\r
52         \r
53         // you can also pass a connection string like this:\r
54         //\r
55         // $DB->Connect('USER ID=sa;PASSWORD=pwd;SERVER=mangrove;DATABASE=ai',false,false,'SQLOLEDB');\r
56         function _connect($argHostname, $argUsername, $argPassword, $argProvider= 'MSDASQL')\r
57         {\r
58                 $u = 'UID';\r
59                 $p = 'PWD';\r
60         \r
61                 if (!empty($this->charPage))\r
62                         $dbc = new COM('ADODB.Connection',null,$this->charPage);\r
63                 else\r
64                         $dbc = new COM('ADODB.Connection');\r
65                         \r
66                 if (! $dbc) return false;\r
67 \r
68                 /* special support if provider is mssql or access */\r
69                 if ($argProvider=='mssql') {\r
70                         $u = 'User Id';  //User parameter name for OLEDB\r
71                         $p = 'Password'; \r
72                         $argProvider = "SQLOLEDB"; // SQL Server Provider\r
73                         \r
74                         // not yet\r
75                         //if ($argDatabasename) $argHostname .= ";Initial Catalog=$argDatabasename";\r
76                         \r
77                         //use trusted conection for SQL if username not specified\r
78                         if (!$argUsername) $argHostname .= ";Trusted_Connection=Yes";\r
79                 } else if ($argProvider=='access')\r
80                         $argProvider = "Microsoft.Jet.OLEDB.4.0"; // Microsoft Jet Provider\r
81                 \r
82                 if ($argProvider) $dbc->Provider = $argProvider;        \r
83                 \r
84                 if ($argUsername) $argHostname .= ";$u=$argUsername";\r
85                 if ($argPassword)$argHostname .= ";$p=$argPassword";\r
86                 \r
87                 if ($this->debug) ADOConnection::outp( "Host=".$argHostname."<BR>\n version=$dbc->version");\r
88                 // @ added below for php 4.0.1 and earlier\r
89                 @$dbc->Open((string) $argHostname);\r
90                 \r
91                 $this->_connectionID = $dbc;\r
92                 \r
93                 $dbc->CursorLocation = $this->_cursor_location;\r
94                 return  $dbc->State > 0;\r
95         }\r
96         \r
97         // returns true or false\r
98         function _pconnect($argHostname, $argUsername, $argPassword, $argProvider='MSDASQL')\r
99         {\r
100                 return $this->_connect($argHostname,$argUsername,$argPassword,$argProvider);\r
101         }       \r
102         \r
103 /*\r
104         adSchemaCatalogs        = 1,\r
105         adSchemaCharacterSets   = 2,\r
106         adSchemaCollations      = 3,\r
107         adSchemaColumns = 4,\r
108         adSchemaCheckConstraints        = 5,\r
109         adSchemaConstraintColumnUsage   = 6,\r
110         adSchemaConstraintTableUsage    = 7,\r
111         adSchemaKeyColumnUsage  = 8,\r
112         adSchemaReferentialContraints   = 9,\r
113         adSchemaTableConstraints        = 10,\r
114         adSchemaColumnsDomainUsage      = 11,\r
115         adSchemaIndexes = 12,\r
116         adSchemaColumnPrivileges        = 13,\r
117         adSchemaTablePrivileges = 14,\r
118         adSchemaUsagePrivileges = 15,\r
119         adSchemaProcedures      = 16,\r
120         adSchemaSchemata        = 17,\r
121         adSchemaSQLLanguages    = 18,\r
122         adSchemaStatistics      = 19,\r
123         adSchemaTables  = 20,\r
124         adSchemaTranslations    = 21,\r
125         adSchemaProviderTypes   = 22,\r
126         adSchemaViews   = 23,\r
127         adSchemaViewColumnUsage = 24,\r
128         adSchemaViewTableUsage  = 25,\r
129         adSchemaProcedureParameters     = 26,\r
130         adSchemaForeignKeys     = 27,\r
131         adSchemaPrimaryKeys     = 28,\r
132         adSchemaProcedureColumns        = 29,\r
133         adSchemaDBInfoKeywords  = 30,\r
134         adSchemaDBInfoLiterals  = 31,\r
135         adSchemaCubes   = 32,\r
136         adSchemaDimensions      = 33,\r
137         adSchemaHierarchies     = 34,\r
138         adSchemaLevels  = 35,\r
139         adSchemaMeasures        = 36,\r
140         adSchemaProperties      = 37,\r
141         adSchemaMembers = 38\r
142 \r
143 */\r
144         \r
145         function &MetaTables()\r
146         {\r
147                 $arr= array();\r
148                 $dbc = $this->_connectionID;\r
149                 \r
150                 $adors=@$dbc->OpenSchema(20);//tables\r
151                 if ($adors){\r
152                         $f = $adors->Fields(2);//table/view name\r
153                         $t = $adors->Fields(3);//table type\r
154                         while (!$adors->EOF){\r
155                                 $tt=substr($t->value,0,6);\r
156                                 if ($tt!='SYSTEM' && $tt !='ACCESS')\r
157                                         $arr[]=$f->value;\r
158                                 //print $f->value . ' ' . $t->value.'<br>';\r
159                                 $adors->MoveNext();\r
160                         }\r
161                         $adors->Close();\r
162                 }\r
163                 \r
164                 return $arr;\r
165         }\r
166         \r
167         function &MetaColumns($table)\r
168         {\r
169                 $table = strtoupper($table);\r
170                 $arr= array();\r
171                 $dbc = $this->_connectionID;\r
172                 \r
173                 $adors=@$dbc->OpenSchema(4);//tables\r
174         \r
175                 if ($adors){\r
176                         $t = $adors->Fields(2);//table/view name\r
177                         while (!$adors->EOF){\r
178                                 \r
179                                 \r
180                                 if (strtoupper($t->Value) == $table) {\r
181                                 \r
182                                         $fld = new ADOFieldObject();\r
183                                         $c = $adors->Fields(3);\r
184                                         $fld->name = $c->Value;\r
185                                         $fld->type = 'CHAR'; // cannot discover type in ADO!\r
186                                         $fld->max_length = -1;\r
187                                         $arr[strtoupper($fld->name)]=$fld;\r
188                                 }\r
189                 \r
190                                 $adors->MoveNext();\r
191                         }\r
192                         $adors->Close();\r
193                 }\r
194                 \r
195                 return $arr;\r
196         }\r
197         \r
198 \r
199 \r
200         \r
201         /* returns queryID or false */\r
202         function &_query($sql,$inputarr=false) \r
203         {\r
204                 \r
205                 $dbc = $this->_connectionID;\r
206                 \r
207         //      return rs       \r
208                 if ($inputarr) {\r
209                         \r
210                         if (!empty($this->charPage))\r
211                                 $oCmd = new COM('ADODB.Command',null,$this->charPage);\r
212                         else\r
213                                 $oCmd = new COM('ADODB.Command');\r
214                         $oCmd->ActiveConnection = $dbc;\r
215                         $oCmd->CommandText = $sql;\r
216                         $oCmd->CommandType = 1;\r
217 \r
218                         foreach($inputarr as $val) {\r
219                                 // name, type, direction 1 = input, len,\r
220                                 $this->adoParameterType = 130;\r
221                                 $p = $oCmd->CreateParameter('name',$this->adoParameterType,1,strlen($val),$val);\r
222                                 //print $p->Type.' '.$p->value;\r
223                                 $oCmd->Parameters->Append($p);\r
224                         }\r
225                         $p = false;\r
226                         $rs = $oCmd->Execute();\r
227                         $e = $dbc->Errors;\r
228                         if ($dbc->Errors->Count > 0) return false;\r
229                         return $rs;\r
230                 }\r
231                 \r
232                 $rs = @$dbc->Execute($sql,$this->_affectedRows, $this->_execute_option);\r
233                 /*\r
234                         $rs =  new COM('ADODB.Recordset');\r
235                         if ($rs) {\r
236                                 $rs->Open ($sql, $dbc, $this->_cursor_type,$this->_lock_type, $this->_execute_option);                                                  \r
237                         }\r
238                 */\r
239                 if ($dbc->Errors->Count > 0) return false;\r
240                 if (! $rs) return false;\r
241                 \r
242                 if ($rs->State == 0) return true; // 0 = adStateClosed means no records returned\r
243                 return $rs;\r
244         }\r
245 \r
246         \r
247         function BeginTrans() \r
248         { \r
249                 if ($this->transOff) return true;\r
250                 \r
251                 if (isset($this->_thisTransactions))\r
252                         if (!$this->_thisTransactions) return false;\r
253                 else {\r
254                         $o = $this->_connectionID->Properties("Transaction DDL");\r
255                         $this->_thisTransactions = $o ? true : false;\r
256                         if (!$o) return false;\r
257                 }\r
258                 @$this->_connectionID->BeginTrans();\r
259                 $this->transCnt += 1;\r
260                 return true;\r
261         }\r
262         function CommitTrans($ok=true) \r
263         { \r
264                 if (!$ok) return $this->RollbackTrans();\r
265                 if ($this->transOff) return true;\r
266                 \r
267                 @$this->_connectionID->CommitTrans();\r
268                 if ($this->transCnt) @$this->transCnt -= 1;\r
269                 return true;\r
270         }\r
271         function RollbackTrans() {\r
272                 if ($this->transOff) return true;\r
273                 @$this->_connectionID->RollbackTrans();\r
274                 if ($this->transCnt) @$this->transCnt -= 1;\r
275                 return true;\r
276         }\r
277         \r
278         /*      Returns: the last error message from previous database operation        */      \r
279 \r
280         function ErrorMsg() \r
281         {\r
282                 $errc = $this->_connectionID->Errors;\r
283                 if ($errc->Count == 0) return '';\r
284                 $err = $errc->Item($errc->Count-1);\r
285                 return $err->Description;\r
286         }\r
287         \r
288         function ErrorNo() \r
289         {\r
290                 $errc = $this->_connectionID->Errors;\r
291                 if ($errc->Count == 0) return 0;\r
292                 $err = $errc->Item($errc->Count-1);\r
293                 return $err->NativeError;\r
294         }\r
295 \r
296         // returns true or false\r
297         function _close()\r
298         {\r
299                 if ($this->_connectionID) $this->_connectionID->Close();\r
300                 $this->_connectionID = false;\r
301                 return true;\r
302         }\r
303         \r
304         \r
305 }\r
306         \r
307 /*--------------------------------------------------------------------------------------\r
308          Class Name: Recordset\r
309 --------------------------------------------------------------------------------------*/\r
310 \r
311 class ADORecordSet_ado extends ADORecordSet {   \r
312         \r
313         var $bind = false;\r
314         var $databaseType = "ado";      \r
315         var $dataProvider = "ado";      \r
316         var $_tarr = false; // caches the types\r
317         var $_flds; // and field objects\r
318         var $canSeek = true;\r
319         var $hideErrors = true;\r
320                   \r
321         function ADORecordSet_ado($id,$mode=false)\r
322         {\r
323                 if ($mode === false) { \r
324                         global $ADODB_FETCH_MODE;\r
325                         $mode = $ADODB_FETCH_MODE;\r
326                 }\r
327                 $this->fetchMode = $mode;\r
328                 return $this->ADORecordSet($id,$mode);\r
329         }\r
330 \r
331 \r
332         // returns the field object\r
333         function FetchField($fieldOffset = -1) {\r
334                 $off=$fieldOffset+1; // offsets begin at 1\r
335                 \r
336                 $o= new ADOFieldObject();\r
337                 $rs = $this->_queryID;\r
338                 $f = $rs->Fields($fieldOffset);\r
339                 $o->name = $f->Name;\r
340                 $t = $f->Type;\r
341                 $o->type = $this->MetaType($t);\r
342                 $o->max_length = $f->DefinedSize;\r
343                 $o->ado_type = $t;\r
344                 \r
345 \r
346                 //print "off=$off name=$o->name type=$o->type len=$o->max_length<br>";\r
347                 return $o;\r
348         }\r
349         \r
350         /* Use associative array to get fields array */\r
351         function Fields($colname)\r
352         {\r
353                 if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname];\r
354                 if (!$this->bind) {\r
355                         $this->bind = array();\r
356                         for ($i=0; $i < $this->_numOfFields; $i++) {\r
357                                 $o = $this->FetchField($i);\r
358                                 $this->bind[strtoupper($o->name)] = $i;\r
359                         }\r
360                 }\r
361                 \r
362                  return $this->fields[$this->bind[strtoupper($colname)]];\r
363         }\r
364 \r
365                 \r
366         function _initrs()\r
367         {\r
368                 $rs = $this->_queryID;\r
369                 $this->_numOfRows = $rs->RecordCount;\r
370                 \r
371                 $f = $rs->Fields;\r
372                 $this->_numOfFields = $f->Count;\r
373         }\r
374         \r
375         \r
376          // should only be used to move forward as we normally use forward-only cursors\r
377         function _seek($row)\r
378         {\r
379            $rs = $this->_queryID; \r
380                 // absoluteposition doesn't work -- my maths is wrong ?\r
381                 //      $rs->AbsolutePosition->$row-2;\r
382                 //      return true;\r
383                 if ($this->_currentRow > $row) return false;\r
384                 @$rs->Move((integer)$row - $this->_currentRow-1); //adBookmarkFirst\r
385                 return true;\r
386         }\r
387         \r
388 /*\r
389         OLEDB types\r
390         \r
391          enum DBTYPEENUM\r
392         {       DBTYPE_EMPTY    = 0,\r
393         DBTYPE_NULL     = 1,\r
394         DBTYPE_I2       = 2,\r
395         DBTYPE_I4       = 3,\r
396         DBTYPE_R4       = 4,\r
397         DBTYPE_R8       = 5,\r
398         DBTYPE_CY       = 6,\r
399         DBTYPE_DATE     = 7,\r
400         DBTYPE_BSTR     = 8,\r
401         DBTYPE_IDISPATCH        = 9,\r
402         DBTYPE_ERROR    = 10,\r
403         DBTYPE_BOOL     = 11,\r
404         DBTYPE_VARIANT  = 12,\r
405         DBTYPE_IUNKNOWN = 13,\r
406         DBTYPE_DECIMAL  = 14,\r
407         DBTYPE_UI1      = 17,\r
408         DBTYPE_ARRAY    = 0x2000,\r
409         DBTYPE_BYREF    = 0x4000,\r
410         DBTYPE_I1       = 16,\r
411         DBTYPE_UI2      = 18,\r
412         DBTYPE_UI4      = 19,\r
413         DBTYPE_I8       = 20,\r
414         DBTYPE_UI8      = 21,\r
415         DBTYPE_GUID     = 72,\r
416         DBTYPE_VECTOR   = 0x1000,\r
417         DBTYPE_RESERVED = 0x8000,\r
418         DBTYPE_BYTES    = 128,\r
419         DBTYPE_STR      = 129,\r
420         DBTYPE_WSTR     = 130,\r
421         DBTYPE_NUMERIC  = 131,\r
422         DBTYPE_UDT      = 132,\r
423         DBTYPE_DBDATE   = 133,\r
424         DBTYPE_DBTIME   = 134,\r
425         DBTYPE_DBTIMESTAMP      = 135\r
426         \r
427         ADO Types\r
428         \r
429         adEmpty = 0,\r
430         adTinyInt       = 16,\r
431         adSmallInt      = 2,\r
432         adInteger       = 3,\r
433         adBigInt        = 20,\r
434         adUnsignedTinyInt       = 17,\r
435         adUnsignedSmallInt      = 18,\r
436         adUnsignedInt   = 19,\r
437         adUnsignedBigInt        = 21,\r
438         adSingle        = 4,\r
439         adDouble        = 5,\r
440         adCurrency      = 6,\r
441         adDecimal       = 14,\r
442         adNumeric       = 131,\r
443         adBoolean       = 11,\r
444         adError = 10,\r
445         adUserDefined   = 132,\r
446         adVariant       = 12,\r
447         adIDispatch     = 9,\r
448         adIUnknown      = 13,   \r
449         adGUID  = 72,\r
450         adDate  = 7,\r
451         adDBDate        = 133,\r
452         adDBTime        = 134,\r
453         adDBTimeStamp   = 135,\r
454         adBSTR  = 8,\r
455         adChar  = 129,\r
456         adVarChar       = 200,\r
457         adLongVarChar   = 201,\r
458         adWChar = 130,\r
459         adVarWChar      = 202,\r
460         adLongVarWChar  = 203,\r
461         adBinary        = 128,\r
462         adVarBinary     = 204,\r
463         adLongVarBinary = 205,\r
464         adChapter       = 136,\r
465         adFileTime      = 64,\r
466         adDBFileTime    = 137,\r
467         adPropVariant   = 138,\r
468         adVarNumeric    = 139\r
469 */\r
470         function MetaType($t,$len=-1,$fieldobj=false)\r
471         {\r
472                 if (is_object($t)) {\r
473                         $fieldobj = $t;\r
474                         $t = $fieldobj->type;\r
475                         $len = $fieldobj->max_length;\r
476                 }\r
477                 \r
478                 if (!is_numeric($t)) return $t;\r
479                 \r
480                 switch ($t) {\r
481                 case 0:\r
482                 case 12: // variant\r
483                 case 8: // bstr\r
484                 case 129: //char\r
485                 case 130: //wc\r
486                 case 200: // varc\r
487                 case 202:// varWC\r
488                 case 128: // bin\r
489                 case 204: // varBin\r
490                 case 72: // guid\r
491                         if ($len <= $this->blobSize) return 'C';\r
492                 \r
493                 case 201:\r
494                 case 203:\r
495                         return 'X';\r
496                 case 128:\r
497                 case 204:\r
498                 case 205:\r
499                          return 'B';\r
500                 case 7:\r
501                 case 133: return 'D';\r
502                 \r
503                 case 134:\r
504                 case 135: return 'T';\r
505                 \r
506                 case 11: return 'L';\r
507                 \r
508                 case 16://      adTinyInt       = 16,\r
509                 case 2://adSmallInt     = 2,\r
510                 case 3://adInteger      = 3,\r
511                 case 4://adBigInt       = 20,\r
512                 case 17://adUnsignedTinyInt     = 17,\r
513                 case 18://adUnsignedSmallInt    = 18,\r
514                 case 19://adUnsignedInt = 19,\r
515                 case 20://adUnsignedBigInt      = 21,\r
516                         return 'I';\r
517                 default: return 'N';\r
518                 }\r
519         }\r
520         \r
521         // time stamp not supported yet\r
522         function _fetch()\r
523         {       \r
524                 $rs = $this->_queryID;\r
525                 if (!$rs or $rs->EOF) {\r
526                         $this->fields = false;\r
527                         return false;\r
528                 }\r
529                 $this->fields = array();\r
530         \r
531                 if (!$this->_tarr) {\r
532                         $tarr = array();\r
533                         $flds = array();\r
534                         for ($i=0,$max = $this->_numOfFields; $i < $max; $i++) {\r
535                                 $f = $rs->Fields($i);\r
536                                 $flds[] = $f;\r
537                                 $tarr[] = $f->Type;\r
538                         }\r
539                         // bind types and flds only once\r
540                         $this->_tarr = $tarr; \r
541                         $this->_flds = $flds;\r
542                 }\r
543                 $t = reset($this->_tarr);\r
544                 $f = reset($this->_flds);\r
545                 \r
546                 if ($this->hideErrors)  $olde = error_reporting(E_ERROR|E_CORE_ERROR);// sometimes $f->value be null\r
547                 for ($i=0,$max = $this->_numOfFields; $i < $max; $i++) {\r
548 \r
549                         switch($t) {\r
550                         case 135: // timestamp\r
551                                 if (!strlen((string)$f->value)) $this->fields[] = false;\r
552                                 else $this->fields[] = adodb_date('Y-m-d H:i:s',(float)$f->value);\r
553                                 break;                  \r
554                         case 133:// A date value (yyyymmdd) \r
555                                 if ($val = $f->value) {\r
556                                         $this->fields[] = substr($val,0,4).'-'.substr($val,4,2).'-'.substr($val,6,2);\r
557                                 } else\r
558                                         $this->fields[] = false;\r
559                                 break;\r
560                         case 7: // adDate\r
561                                 if (!strlen((string)$f->value)) $this->fields[] = false;\r
562                                 else $this->fields[] = adodb_date('Y-m-d',(float)$f->value);\r
563                                 break;\r
564                         case 1: // null\r
565                                 $this->fields[] = false;\r
566                                 break;\r
567                         case 6: // currency is not supported properly;\r
568                                 ADOConnection::outp( '<b>'.$f->Name.': currency type not supported by PHP</b>');\r
569                                 $this->fields[] = (float) $f->value;\r
570                                 break;\r
571                         default:\r
572                                 $this->fields[] = $f->value; \r
573                                 break;\r
574                         }\r
575                         //print " $f->value $t, ";\r
576                         $f = next($this->_flds);\r
577                         $t = next($this->_tarr);\r
578                 } // for\r
579                 if ($this->hideErrors) error_reporting($olde);\r
580                 @$rs->MoveNext(); // @ needed for some versions of PHP!\r
581                 \r
582                 if ($this->fetchMode & ADODB_FETCH_ASSOC) {\r
583                         $this->fields = &$this->GetRowAssoc(ADODB_ASSOC_CASE);\r
584                 }\r
585                 return true;\r
586         }\r
587         \r
588                 function NextRecordSet()\r
589                 {\r
590                         $rs = $this->_queryID;\r
591                         $this->_queryID = $rs->NextRecordSet();\r
592                         //$this->_queryID = $this->_QueryId->NextRecordSet();\r
593                         if ($this->_queryID == null) return false;\r
594                         \r
595                         $this->_currentRow = -1;\r
596                         $this->_currentPage = -1;\r
597                         $this->bind = false;\r
598                         $this->fields = false;\r
599                         $this->_flds = false;\r
600                         $this->_tarr = false;\r
601                         \r
602                         $this->_inited = false;\r
603                         $this->Init();\r
604                         return true;\r
605                 }\r
606 \r
607         function _close() {\r
608                 $this->_flds = false;\r
609                 @$this->_queryID->Close();// by Pete Dishman (peterd@telephonetics.co.uk)\r
610                 $this->_queryID = false;        \r
611         }\r
612 \r
613 }\r
614 \r
615 ?>