PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/db/class.atkdb2db.inc

https://github.com/ibuildingsnl/ATK
PHP | 699 lines | 385 code | 79 blank | 235 comment | 61 complexity | 56932cc1f4fc89ddc12662acf87df4e1 MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, LGPL-3.0
  1. <?php
  2. /**
  3. * This file is part of the Achievo ATK distribution.
  4. * Detailed copyright and licensing information can be found
  5. * in the doc/COPYRIGHT and doc/LICENSE files which should be
  6. * included in the distribution.
  7. *
  8. * @package atk
  9. * @subpackage db
  10. *
  11. * @copyright (c)2000-2007 Ibuildings.nl BV
  12. * @license http://www.achievo.org/atk/licensing ATK Open Source License
  13. *
  14. * @version $Revision: 5.38 $
  15. * $Id: class.atkdb2db.inc,v 5.38 2007/04/05 13:47:58 harrie Exp $
  16. */
  17. /**
  18. * Driver for IBM DB2 databases.
  19. *
  20. * @author Harrie Verveer <harrie@ibuildings.nl>
  21. * @package atk
  22. * @subpackage db
  23. */
  24. class atkdb2db extends atkDb
  25. {
  26. /* sequence table */
  27. var $m_seq_table = "db_sequence";
  28. // the field in the seq_table that contains the counter..
  29. var $m_seq_field = "nextid";
  30. // the field in the seq_table that countains the name of the sequence..
  31. var $m_seq_namefield = "seq_name";
  32. var $m_type = "db2";
  33. /**
  34. * Base constructor
  35. */
  36. function atkdb2db()
  37. {
  38. // set the user error's
  39. $this->m_type = "db2";
  40. $this->m_vendor = "db2";
  41. $this->m_user_error=array();
  42. $this->m_seq_table = atkconfig("db_sequence_table");
  43. $this->m_seq_field = atkconfig("db_sequence_field");
  44. $this->m_seq_namefield = atkconfig("db_sequence_namefield");
  45. }
  46. /**
  47. * Connect to the database
  48. *
  49. * @param string $host Hostname
  50. * @param string $user Username
  51. * @param string $password Password
  52. * @return mixed Connection status
  53. */
  54. function doConnect($host,$user,$password,$database,$port,$charset)
  55. {
  56. /* establish connection */
  57. if (empty($this->m_link_id))
  58. {
  59. if (empty($port))
  60. $port = 50000;
  61. $options = array("DB2_ATTR_CASE"=>DB2_CASE_LOWER);
  62. $dbconfig = atkconfig("db");
  63. $dbconfig = $dbconfig[$this->m_connection];
  64. if (array_key_exists('i5_lib',$dbconfig) && $dbconfig['i5_lib'] != "")
  65. $options['i5_lib'] = $dbconfig['i5_lib'];
  66. $this->m_link_id = db2_connect($database,$user,$password,$options);
  67. if (!$this->m_link_id)
  68. {
  69. $this->halt($this->getErrorMsg());
  70. }
  71. }
  72. return DB_SUCCESS;
  73. }
  74. /**
  75. * Translates known database errors to developer-friendly messages
  76. *
  77. * @todo Make DB2
  78. * @return int Flag of the error
  79. */
  80. function _translateError()
  81. {
  82. $this->_setErrorVariables();
  83. switch ($this->m_errno)
  84. {
  85. case 0: return DB_SUCCESS;
  86. case 1044: return DB_ACCESSDENIED_DB;
  87. case 1045: return DB_ACCESSDENIED_USER;
  88. case 1049: return DB_UNKNOWNDATABASE;
  89. case 2004:
  90. case 2005: return DB_UNKNOWNHOST;
  91. default:
  92. atkdebug("mysqldb::translateError -> MySQL Error: ".
  93. $this->m_errno." -> ".$this->m_error);
  94. return DB_UNKNOWNERROR;
  95. }
  96. }
  97. /**
  98. * Store MySQL errors in internal variables
  99. * @access private
  100. */
  101. function _setErrorVariables()
  102. {
  103. if (!empty($this->m_link_id))
  104. {
  105. $this->m_errno = db2_conn_errormsg($this->m_link_id);
  106. $this->m_error = db2_conn_error($this->m_link_id);
  107. }
  108. else
  109. {
  110. $this->m_errno = db2_conn_errormsg();
  111. $this->m_error = db2_conn_error();
  112. }
  113. }
  114. /**
  115. * Disconnect from database
  116. */
  117. function disconnect()
  118. {
  119. if ($this->m_link_id)
  120. {
  121. atkdebug("Disconnecting from database...");
  122. @db2_close($this->m_link_id);
  123. $this->m_link_id = 0;
  124. }
  125. }
  126. /**
  127. * Performs a query
  128. * @param $query the query
  129. * @param $offset offset in record list
  130. * @param $limit maximum number of records
  131. *
  132. * @todo Convert to DB2
  133. */
  134. function query($query, $offset=-1, $limit=-1)
  135. {
  136. atkimport("atk.utils.atkdebugger");
  137. atkdebug("In query dinges");
  138. atkdebug("Query:".$query);
  139. atkdebug("Query added to debugger...");
  140. atkdebug("atkdb2db::query -> connect with mode = $mode");
  141. /* connect to database */
  142. if ($this->connect($mode)==DB_SUCCESS)
  143. {
  144. /* free old results */
  145. if ($this->m_query_id)
  146. {
  147. if (is_resource($this->m_query_id))
  148. db2_free_result($this->m_query_id);
  149. $this->m_query_id = 0;
  150. }
  151. $this->m_affected_rows = 0;
  152. /* query database */
  153. $this->m_query_id = @db2_exec($this->m_link_id,$query,array("cursor"=>DB2_SCROLLABLE));
  154. /* invalid query */
  155. if (!$this->m_query_id)
  156. {
  157. $this->_setErrorVariables();
  158. $this->halt("Invalid SQL: $query.");
  159. return false;
  160. }
  161. $this->m_affected_rows = db2_num_rows($this->m_query_id);
  162. $this->m_row = 0;
  163. /* return query id */
  164. return true;
  165. }
  166. return false;
  167. }
  168. /**
  169. * Goto the next record in the result set
  170. * @return result of going to the next record
  171. */
  172. function next_record($row_number = null)
  173. {
  174. /* goto next record */
  175. $this->m_record = @db2_fetch_assoc($this->m_query_id,$row_number);
  176. if (isset($this->m_record) && is_array($this->m_record))
  177. {
  178. foreach($this->m_record as $key => $value)
  179. {
  180. if (is_string($value))
  181. $this->m_record[$key] = trim($value);
  182. }
  183. }
  184. $this->m_row++;
  185. $this->m_errno = db2_conn_error($this->m_link_id);
  186. $this->m_error = db2_conn_errormsg($this->m_link_id);
  187. /* are we there? */
  188. $result = is_array($this->m_record);
  189. if (!$result && $this->m_auto_free)
  190. {
  191. @db2_free_result($this->m_query_id);
  192. $this->m_query_id = 0;
  193. }
  194. /* return result */
  195. return $result;
  196. }
  197. /**
  198. * Goto a certain position in result set.
  199. * Not specifying a position will set the pointer
  200. * at the beginning of the result set.
  201. * @param $position the position
  202. */
  203. function seek($position=0)
  204. {
  205. atkerror("Seek not (yet) implemented for db2 driver");
  206. }
  207. /**
  208. * Lock a certain table in the database
  209. * @param $table the table name
  210. * @param $mode the type of locking
  211. * @return result of locking
  212. */
  213. function lock($table, $mode="write")
  214. {
  215. // we don't lock/unlock
  216. return true;
  217. /* connect first */
  218. if ($this->connect("w")==DB_SUCCESS)
  219. {
  220. $query = "LOCK TABLE $table IN " . ($mode == "write" ? "EXCLUSIVE" : "SHARE") . " MODE";
  221. atkimport("atk.utils.atkdebugger");
  222. atkDebugger::addQuery($query);
  223. /* lock */
  224. $result = @db2_exec($this->m_link_id,$query);
  225. if (!$result) $this->halt("$mode lock on $table failed.");
  226. /* return result */
  227. return $result;
  228. }
  229. return 0;
  230. }
  231. /**
  232. * Unlock table(s) in the database
  233. * @return result of unlocking
  234. */
  235. function unlock()
  236. {
  237. // we don't lock/unlock
  238. return true;
  239. /* connect first */
  240. if ($this->connect("w")==DB_SUCCESS)
  241. {
  242. // In DB2, the table will remain locked until the next commit or rollback. As
  243. // we never use rollback in ATK, lets just commit() to release all locks
  244. $result = db2_exec($this->m_link_id,"COMMIT");
  245. if (!$result) $this->halt("unlock tables failed.");
  246. /* return result */
  247. return $result;
  248. }
  249. return 0;
  250. }
  251. /**
  252. * Evaluate the result; which rows were
  253. * affected by the query.
  254. * @return affected rows
  255. */
  256. function affected_rows()
  257. {
  258. return @db2_num_rows($this->m_query_id);
  259. }
  260. /**
  261. * Evaluate the result; how many rows
  262. * were affected by the query.
  263. * @return number of affected rows
  264. */
  265. function num_rows()
  266. {
  267. return @db2_num_rows($this->m_query_id);
  268. }
  269. /**
  270. * Evaluatie the result; how many fields
  271. * where affected by the query.
  272. * @return number of affected fields
  273. */
  274. function num_fields()
  275. {
  276. return @db2_num_fields($this->m_query_id);
  277. }
  278. /**
  279. * Get the next sequence number
  280. * of a certain sequence.
  281. * @param $sequence the sequence name
  282. * @return the next sequence id
  283. */
  284. function nextid($sequence)
  285. {
  286. /* first connect */
  287. if ($this->connect("w")==DB_SUCCESS)
  288. {
  289. /* lock sequence table */
  290. if ($this->lock($this->m_seq_table))
  291. {
  292. /* get sequence number (locked) and increment */
  293. $query = "SELECT ".$this->m_seq_field." FROM ".$this->m_seq_table." WHERE ".$this->m_seq_namefield." = '$sequence'";
  294. $id = @db2_exec($this->m_link_id,$query);
  295. $result = @db2_fetch_assoc($id);
  296. /* no current value, make one */
  297. if (!is_array($result))
  298. {
  299. $query = "INSERT INTO ".$this->m_seq_table." (".$this->m_seq_namefield.",".$this->m_seq_field.") VALUES('$sequence', 1)";
  300. $id = @db2_exec($this->m_link_id,$query);
  301. $this->unlock();
  302. return 1;
  303. }
  304. /* enter next value */
  305. else
  306. {
  307. $nextid = $result[$this->m_seq_field] + 1;
  308. $query = "UPDATE ".$this->m_seq_table." SET ".$this->m_seq_field." = '$nextid' WHERE ".$this->m_seq_namefield." = '$sequence'";
  309. $id = @db2_exec($this->m_link_id,$query);
  310. $this->unlock();
  311. return $nextid;
  312. }
  313. }
  314. return 0;
  315. }
  316. /* cannot lock */
  317. else
  318. {
  319. $this->halt("cannot lock ".$this->m_seq_table." - has it been created?");
  320. }
  321. }
  322. /**
  323. * Returns the table type.
  324. *
  325. * @param string $table table name
  326. * @return string table type
  327. */
  328. function _getTableType($table)
  329. {
  330. atkerror("_getTableType not implemented yet for db2 database");
  331. }
  332. /**
  333. * Return the meta data of a certain table
  334. * @param String $table the table name (optionally in 'database.tablename' format)
  335. * @param boolean $full all meta data or not
  336. * @return array with meta data
  337. */
  338. function metadata($table, $full=false)
  339. {
  340. $tbl = strtoupper($table);
  341. $result = array();
  342. if ($this->connect()==DB_SUCCESS)
  343. {
  344. $ddl = &atkDDL::create("db2");
  345. $query = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$tbl'";
  346. $dbconfig = atkconfig("db");
  347. $dbconfig = $dbconfig[$this->m_connection];
  348. if (array_key_exists('i5_lib',$dbconfig) && $dbconfig['i5_lib'] != "")
  349. $query .= " AND TABLE_SCHEMA = '{$dbconfig['i5_lib']}'";
  350. $result_metadata = $this->getRows($query);
  351. for ($i=0,$_i=count($result_metadata);$i<$_i;$i++)
  352. {
  353. $result[$i]["table"] = $result_metadata[$i]['table_schema'] . "." . $result_metadata[$i]['table_name'];
  354. $result[$i]["table_type"] = NULL;
  355. $result[$i]["name"] = strtolower($result_metadata[$i]['column_name']);
  356. $result[$i]["type"] = $result_metadata[$i]['data_type'];
  357. $result[$i]["gentype"] = $ddl->getGenericType($result_metadata[$i]['data_type']);
  358. $result[$i]["len"] = $result_metadata[$i]['character_maximum_length'];
  359. $result[$i]["flags"] = $this->metadataToFlags($result_metadata[$i]);
  360. $result[$i]["flags"] =
  361. (in_array('primary_key', $result[$i]["flags"]) ? MF_PRIMARY : 0) |
  362. (in_array('unique_key', $result[$i]["flags"]) ? MF_UNIQUE : 0) |
  363. (in_array('not_null', $result[$i]["flags"]) ? MF_NOT_NULL : 0) |
  364. (in_array('auto_increment', $result[$i]["flags"]) ? MF_AUTO_INCREMENT : 0);
  365. if ($full)
  366. $result["meta"][$result[$i]["name"]] = $i;
  367. }
  368. }
  369. return $result;
  370. }
  371. /**
  372. * We need to reconstruct the flags array ourselves based on the fields
  373. * we obtained from INFORMATION_SCHEMA.COLUMNS. Get properties from the
  374. * row provided and return an array based on these properties.
  375. *
  376. * @param array $metadata The row as obtained from INFORMATION_SCHEMA.COLUMNS
  377. * @return array Flags to use in the metadata we return
  378. */
  379. function metadataToFlags($metadata)
  380. {
  381. $ret = array();
  382. $ret[] = $metadata['is_nullable'] == "NO" ? "not_null" : "null";
  383. // todo: auto_increment
  384. // todo: primary_key
  385. // todo: unique_key
  386. return $ret;
  387. }
  388. /**
  389. * Return the available table names
  390. * @return array with table names etc.
  391. */
  392. function table_names()
  393. {
  394. atkerror("table_names for atkdb2db not implemented yet!");
  395. /* query */
  396. $this->query("SHOW TABLES"); // not sure if this is the right syntax for db2?
  397. /* get table names */
  398. $result = array();
  399. for ($i=0; $info = db2_fetch_row($this->m_query_id); $i++)
  400. {
  401. $result[$i]["table_name"] = $info[0];
  402. $result[$i]["tablespace_name"] = $this->m_database;
  403. $result[$i]["database"] = $this->m_database;
  404. }
  405. /* return result */
  406. return $result;
  407. }
  408. /**
  409. * This function checks the database for a table with
  410. * the provide name
  411. *
  412. * @param String $tableName the table to find
  413. * @return boolean true if found, false if not found
  414. */
  415. function tableExists($tablename)
  416. {
  417. $query = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '$tablename'";
  418. $dbconfig = atkconfig("db");
  419. $dbconfig = $dbconfig[$this->m_connection];
  420. if (array_key_exists('i5_lib',$dbconfig) && $dbconfig['i5_lib'] != "")
  421. $query .= " AND TABLE_SCHEMA = '{$dbconfig['i5_lib']}'";
  422. $res = $this->getrows($query);
  423. return (count($res)==0 ? false : true);
  424. }
  425. /**
  426. * This function indicates what searchmodes the database supports.
  427. * @return array with search modes
  428. */
  429. function getSearchModes()
  430. {
  431. return array("exact","substring","wildcard","regexp","soundex","greaterthan","greaterthanequal","lessthan","lessthanequal","between");
  432. }
  433. /**
  434. * Get TO_CHAR() equivalent for the current database.
  435. * Each database driver should override this method to perform vendor
  436. * specific conversion.
  437. *
  438. * @param String $fieldname The field to generate the to_char for.
  439. * @param String $format Format specifier. The format is compatible with
  440. * php's date() function (http://www.php.net/date)
  441. * The default is what's specified by
  442. * $config_date_to_char, or "Y-m-d" if not
  443. * set in the configuration.
  444. * @return String Piece of sql query that converts a date field to char
  445. * for the current database
  446. *
  447. * @todo make me db2
  448. */
  449. function func_datetochar($fieldname, $format="")
  450. {
  451. return "TO_CHAR($fieldname, 'YYYY-MM-DD')";
  452. }
  453. /**
  454. * Convert a php date() format specifier to a mysql specific format
  455. * specifier.
  456. *
  457. * Note that currently, only the common specifiers Y, m, d, H, h, i and
  458. * s are supported.
  459. * @param String $format Format specifier. The format is compatible with
  460. * php's date() function (http://www.php.net/date)
  461. * @return String Mysql specific format specifier.
  462. *
  463. * @todo make me db2
  464. */
  465. function vendorDateFormat($format)
  466. {
  467. $php_fmt = array("Y", "m", "d", "H", "h", "i", "s");
  468. $db_fmt = array("%Y", "%m", "%d", "%H", "%h", "%i", "%s");
  469. return str_replace($php_fmt, $db_fmt, $format);
  470. }
  471. /**
  472. * Get TO_CHAR() equivalent for the current database.
  473. *
  474. * TODO/FIXME: add format paramater. Current format is always yyyy-mm-dd hh:mi.
  475. *
  476. * @todo make me db2
  477. */
  478. function func_datetimetochar($fieldname)
  479. {
  480. return "TO_CHAR($fieldname, 'YYYY-MM-DD HH24:MI:SS')";
  481. }
  482. /**
  483. * Set database sequence value.
  484. *
  485. * @param string $seqname sequence name
  486. * @param int $value sequence value
  487. */
  488. function setSequenceValue($seqname, $value)
  489. {
  490. $query = "UPDATE ".$this->m_seq_table." SET ".$this->m_seq_field." = '$value' WHERE ".$this->m_seq_namefield." = '$seqname'";
  491. $this->query($query);
  492. }
  493. /**
  494. * Get all rows that are the result
  495. * of a certain specified query
  496. *
  497. * Note: This is not an efficient way to retrieve
  498. * records, as this will load all records into one
  499. * array into memory. If you retrieve a lot of records,
  500. * you might hit the memory_limit and your script will die.
  501. *
  502. * @param $query the query
  503. * @return array with rows
  504. */
  505. function getrows($query, $offset=-1, $limit=-1)
  506. {
  507. $result = array();
  508. // IBM DB2 doesn't support LIMIT syntax as MySQL and PostgreSQL do.
  509. // We have to seek through the results to go to a certain offset
  510. // and retrieve only the number of rows specified in the limit.
  511. // In class.atkdb2query.inc we add a fake LIMIT clause which we
  512. // remove here and transform to values for the offset and limit
  513. // parameters.
  514. if ($offset == -1 && $limit == -1)
  515. {
  516. $lines = explode("\n", $query);
  517. if (count($lines) > 0 && strpos($lines[count($lines) -1], 'LIMIT') === 0)
  518. {
  519. $last = array_pop($lines);
  520. $query = implode("\n", $lines);
  521. if (preg_match('/LIMIT ([0-9]+) OFFSET ([0-9]+)/', $last, $matches))
  522. {
  523. $limit = $matches[1];
  524. $offset = $matches[2];
  525. }
  526. }
  527. }
  528. $replaceEbcdic = $this->getEbcdicFields($query);
  529. $this->query($query);
  530. if ($limit > 0)
  531. {
  532. for ($i = 1; $i <= $limit; $i++)
  533. {
  534. if (!$this->next_record($offset+$i)) break;
  535. $result[] = $this->m_record;
  536. }
  537. }
  538. else
  539. {
  540. while ($this->next_record())
  541. $result[] = $this->m_record;
  542. }
  543. if (is_array($result) && count($result) > 0 && is_array($replaceEbcdic) && count($replaceEbcdic) > 0)
  544. {
  545. for($i=0,$_i=count($result);$i<$_i;$i++)
  546. {
  547. foreach($replaceEbcdic as $colname)
  548. $result[$i][$colname] = ebcdic2ascii($result[$i][$colname]);
  549. }
  550. }
  551. return $result;
  552. }
  553. /**
  554. * Sometimes, when certain database functions are used, DB2 returns EBCDIC
  555. * instead of ASCII. Figure out what fields need to get a special treatment.
  556. *
  557. * @param string $query The query we want to execute
  558. * @return Array An array with fieldnames that will be returned in EBCDIC
  559. */
  560. function getEbcdicFields($query)
  561. {
  562. // functions that cause DB2 to return EBCDIC (escaped for regex usage)
  563. $functions = array("to_char","concat");
  564. $matches = array();
  565. $regex = "/(".implode("|",$functions).")\(.*\) as (al_.+)/Uis";
  566. preg_match_all($regex,$query,$matches);
  567. if (!is_array($matches) || count($matches) < 3)
  568. return array();
  569. else
  570. return $matches[2];
  571. }
  572. }
  573. /**
  574. * Workaround until the official PHP-version of ebcdic2ascii is released. For
  575. * release noted see: http://www.php.net/manual/en/function.ebcdic2ascii.php
  576. */
  577. if (!function_exists("ebcdic2ascii"))
  578. {
  579. function ebcdic2ascii($input)
  580. {
  581. static $translate_mapping = array(
  582. 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
  583. 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
  584. 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
  585. 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
  586. 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
  587. 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
  588. 0x2E, 0x2E, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
  589. 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x2E, 0x3F,
  590. 0x20, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
  591. 0x2E, 0x2E, 0x2E, 0x3C, 0x28, 0x2B, 0x7C,
  592. 0x26, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
  593. 0x2E, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0x5E,
  594. 0x2D, 0x2F, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
  595. 0x2E, 0x7C, 0x2C, 0x25, 0x5F, 0x3E, 0x3F,
  596. 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
  597. 0x2E, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22,
  598. 0x2E, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
  599. 0x69, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
  600. 0x2E, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71,
  601. 0x72, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
  602. 0x2E, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
  603. 0x7A, 0x2E, 0x2E, 0x2E, 0x5B, 0x2E, 0x2E,
  604. 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
  605. 0x2E, 0x2E, 0x2E, 0x2E, 0x5D, 0x2E, 0x2E,
  606. 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
  607. 0x49, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
  608. 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51,
  609. 0x52, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
  610. 0x5C, 0x2E, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
  611. 0x5A, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
  612. 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
  613. 0x39, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E);
  614. $output = "";
  615. for($i=0,$_i=strlen($input);$i<$_i;$i++)
  616. $output .= chr($translate_mapping[ord($input[$i])]);
  617. return $output;
  618. }
  619. }
  620. ?>