Source for file SQLiteAdmin.class.php

Documentation is available at SQLiteAdmin.class.php

  1. <?php
  2.  
  3. /**
  4. * Implementation file of the SQLiteAdmin class.
  5. *
  6. * <pre>
  7. * PROJECT : SQLiteAdmin
  8. * A Crafty demonstration project.
  9. * AUTHOR : Crafty Team <crafty@zulan.net>
  10. * COPYRIGHT : (c) Thomas Ryssel, 2004
  11. *
  12. * FILE : [ROOT]\includes\SQLiteAdmin.class.php
  13. * DESCRIPTION: Implementation file of the SQLiteAdmin class.
  14. * </pre>
  15. *
  16. * This library is free software; you can redistribute it and/or
  17. * modify it under the terms of the GNU Lesser General Public
  18. * License as published by the Free Software Foundation; either
  19. * version 2.1 of the License, or (at your option) any later version.
  20. *
  21. * This library is distributed in the hope that it will be useful,
  22. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  24. * Lesser General Public License for more details.
  25. *
  26. * You should have received a copy of the GNU Lesser General Public
  27. * License along with this library; if not, write to the Free Software
  28. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  29. *
  30. * SQLiteAdmin is only a demonstration project for {@link Crafty}, so please be
  31. * aware of the fact that this has minor priority compared to the main project
  32. * itself.
  33. *
  34. * @package SQLiteAdmin
  35. * @author Crafty Team <crafty@zulan.net>
  36. * @copyright Copyright (c) Thomas Ryssel 2004-2005
  37. * @version 1.0.4.2005.02.18
  38. * @link sqliteadmin.zulan.net
  39. * @license http://www.gnu.org/licenses/lgpl.html
  40. * GNU Lesser General Public License Version 2.1
  41. ***/
  42.  
  43. /**
  44. * SQLiteAdmin - Class implementation
  45. *
  46. * This class is a wrapper for the new SQLite extension of PHP5. It's an OOP
  47. * environment designed for easier Usage of SQLite. This class is designed for
  48. * and tested with SQLite Version 2.8.14. There is no guarantee that it will run
  49. * with any other SQLite Version, although it will work most likely with versions
  50. * prior to 2.8.14. As SQLite itself, this is only a testing stage and there
  51. * is no guarantee for complete work.
  52. *
  53. * The SQLite Project itself could be called a 'front-end' to this class, since
  54. * it simply passes all tasks to it.
  55. *
  56. * You can use SQLiteAdmin by spawning this class:
  57. *
  58. * <pre>
  59. * $sqlite = new SQLiteAdmin('filename.db');
  60. * </pre>
  61. *
  62. * See the descriptions of the class members and methods for further information.
  63. *
  64. * @package SQLiteAdmin
  65. * @author Crafty Team <crafty@zulan.net>
  66. * @copyright Copyright (c) Thomas Ryssel 2004-2005
  67. * @version 1.0.4.2005.02.16
  68. * @link sqliteadmin.zulan.net
  69. * @license http://www.gnu.org/licenses/lgpl.html
  70. * GNU Lesser General Public License Version 2.1
  71. ***/
  72. class SQLiteAdmin
  73. {
  74. /**
  75. * Internal database resource
  76. *
  77. * The internally stored database handle. It is set by {@link openDB}
  78. * or {@link createDB} and used by most of the public functions. You do not
  79. * have to take care of this.
  80. *
  81. * @var resource
  82. ***/
  83. protected $_dblink = NULL;
  84.  
  85. /**
  86. * Internal result resource
  87. *
  88. * The internally stored result resource. It can be an SQLite query resource
  89. * or a boolean value depending on the kind of query (whether or not
  90. * 'SELECT'). It is set whenever the {@link query} public function is called
  91. * with the parameter '$remember' set to true.
  92. *
  93. * @var resource
  94. ***/
  95. protected $_result = NULL;
  96.  
  97. /**
  98. * SQL Datatype array
  99. *
  100. * List of accepted data types. Used by {@link getTableInfo}.
  101. *
  102. * @var array
  103. ***/
  104. public $datatypeArray = array();
  105.  
  106. /**
  107. * SQL Constraints array
  108. *
  109. * List of accepted constraints. Used by {@link getTableInfo}.
  110. *
  111. * @var array
  112. ***/
  113. public $constraintsArray = array();
  114.  
  115. /**#@+
  116. * @access public
  117. ***/
  118. /**
  119. * Database filename
  120. *
  121. * This stores the complete path and file name of the opened database file.
  122. * If this is empty, no database has (yet) been opened.
  123. *
  124. * @var string
  125. ***/
  126. public $filename = '';
  127.  
  128. /**
  129. * Database short filename
  130. *
  131. * This stores the file name without path of the opened database file.
  132. * If this is empty, no database has (yet) been opened.
  133. *
  134. * @var string
  135. ***/
  136. public $shortname = '';
  137.  
  138. /**
  139. * Database filename
  140. *
  141. * This stores the complete path and file name of the opened database file,
  142. * encoded by PHP's {@link http://php.net/base64_encode base64_encode}
  143. * public function. That is primarily for correct filename passing via HTTP.
  144. * If this is empty, no database has (yet) been opened.
  145. *
  146. * @var string
  147. ***/
  148. public $base64name = '';
  149.  
  150. /**
  151. * Message buffer
  152. *
  153. * This stores all messages produced during processes done by this class.
  154. * Mostly in cases of error, this string will contain a more accurate
  155. * description of what went wrong.
  156. *
  157. * @var string
  158. ***/
  159. public $message = '';
  160.  
  161.  
  162. /**
  163. * Initializes a SQLiteAdmin instance
  164. *
  165. * This initializes an SQLiteAdmin instance. If $filename is not empty, the
  166. * object try to open (see {@link openDB}) the database file and
  167. * establish a connection to it.<br/>
  168. * Additionally, it sets up two internal data arrays for keyword lists in the
  169. * later Table structure parser. These can be given by parameter, too.
  170. *
  171. * @param string $filename [optional] Database file, that should be opened.
  172. * @param array $datatypes [optional] List of accepted SQL data types.
  173. * @param array $constraints [optional] List of accepted SQL constraints.
  174. ***/
  175. public function __construct($filename = '', $datatypes = NULL,
  176. $constraints = NULL)
  177. {
  178. if (!empty($filename)) {
  179. $this->openDB($filename);
  180. }
  181.  
  182. if (!empty($datatypes)) {
  183. $this->datatypeArray = $datatypes;
  184. } else {
  185. $this->datatypeArray = array(
  186. 'VARCHAR', 'TINYINT', 'TEXT', 'DATE',
  187. 'SMALLINT', 'MEDIUMINT', 'INT', 'INTEGER',
  188. 'BIGINT', 'FLOAT', 'DOUBLE', 'DECIMAL',
  189. 'DATETIME', 'TIMESTAMP', 'TIME', 'YEAR',
  190. 'CHAR', 'TINYBLOB', 'TINYTEXT', 'BLOB',
  191. 'MEDIUMBLOB', 'MEDIUMTEXT', 'LONGBLOB',
  192. 'LONGTEXT', 'ENUM', 'SET', 'BOOLEAN'
  193. );
  194. }
  195.  
  196. if (!empty($constraints)) {
  197. $this->constraintsArray = $constraints;
  198. } else {
  199. $this->constraintsArray = array(
  200. 'NOT NULL', 'PRIMARY KEY', 'UNIQUE'
  201. );
  202. }
  203. }
  204.  
  205. /**
  206. * Open an existing database file
  207. *
  208. * This will try to open the given database file. In addition, if it succeeds
  209. * it will setup the internal {@link $_dblink} and create the specific
  210. * {@link $filename filename} strings.
  211. *
  212. * @param string $filename Database file to open
  213. * @return boolean State of success
  214. ***/
  215. public function openDB($filename)
  216. {
  217. if (!file_exists($filename) || !($db = @sqlite_open($filename))) {
  218. return false;
  219. }
  220. $this->filename = $filename;
  221. $this->base64name = base64_encode($filename);
  222. $this->shortname = (false !== strrpos($filename, DIRECTORY_SEPARATOR))
  223. ? substr($filename, strrpos($filename,
  224. DIRECTORY_SEPARATOR) + 1)
  225. : $filename;
  226.  
  227. $this->_dblink = $db;
  228. return true;
  229. }
  230.  
  231. /**
  232. * Create a new database file
  233. *
  234. * This will try to create a new database file. In addition, if it succeeds
  235. * it will setup the internal {@link $_dblink} and create the specific
  236. * {@link $filename filename} strings. If if a table name is passed, it will
  237. * automatically try to create a new table using {@link tableCreate}.
  238. *
  239. * @param string $filename Database file to create.
  240. * @param string $tablename [optional] Table to create in new database.
  241. * @return boolean State of success
  242. ***/
  243. public function createDB($filename, $tablename = '')
  244. {
  245. if (file_exists($filename) || !($db = @sqlite_open($filename))) {
  246. return false;
  247. }
  248. $this->filename = $filename;
  249. $this->base64name = base64_encode($filename);
  250. $this->shortname = (false !== strrpos($filename, DIRECTORY_SEPARATOR))
  251. ? substr($filename, strrpos($filename,
  252. DIRECTORY_SEPARATOR) + 1)
  253. : $filename;
  254.  
  255. $this->_dblink = $db;
  256. $this->tableCreate($tablename);
  257. return false;
  258. }
  259.  
  260. /**
  261. * Close currently opened database
  262. *
  263. * This will close the currently opened database connection. In any normal
  264. * case, this will be done at the end of your script automatically. You will
  265. * need this only if you want to open another database with the same
  266. * SQLiteAdmin instance.
  267. *
  268. * @return boolean State of success
  269. ***/
  270. public function closeDB()
  271. {
  272. if (@sqlite_close($this->_dblink)) {
  273. $this->filename = '';
  274. $this->shortname = '';
  275. $this->base64name = '';
  276. return true;
  277. }
  278. return false;
  279. }
  280.  
  281. /**
  282. * Execute an SQL Query
  283. *
  284. * This will execute an SQL query using the internal database link. $remember
  285. * decides whether or not an result resource of a SELECT query shall be stored
  286. * in the internal {@link $_result} resource. If that's the case, you can call
  287. * {@link fetchArray} without any other parameters.
  288. *
  289. * @param string $query SQL Query to execute
  290. * @param boolean $remember [optional, default=true] Save in
  291. * internal result resource?
  292. * @return boolean|resource Result resource in case of SELECT queries, state
  293. * of success in any other case.
  294. ***/
  295. public function query($query, $remember = true)
  296. {
  297. $preg = '~(?>\s*(?>/\*.*?\*/|//[^\r\n]*))*\s*([^\'";]*(?:(["\']?+)'
  298. . '(?>.*?\2)[^\'";]*)*);~s';
  299. if (1 < preg_match_all($preg, $query, $matches, PREG_SET_ORDER)) {
  300. $queries = array();
  301. foreach ($matches as $m) {
  302. $queries[] = $m[1];
  303. }
  304. } else {
  305. $queries = array($query);
  306. }
  307.  
  308. foreach ($queries as $query_i) {
  309. if (0 !== strpos(strtoupper($query_i), 'SELECT')
  310. && 0 !== strpos(strtoupper($query_i), 'ALTER TABLE')) {
  311. if (@sqlite_query($this->_dblink, $query_i)) {
  312. continue;
  313. } else {
  314. $this->message .= $this->lastError() . '<br />';
  315. return FALSE;
  316. }
  317. } elseif (0 === strpos(strtoupper($query_i), 'SELECT')) {
  318. if ($res = @sqlite_query($this->_dblink, $query_i)) {
  319. if ($remember) {
  320. $this->_result = $res;
  321. }
  322. return $res;
  323. } else {
  324. $this->message .= $this->lastError() . '<br />';
  325. return FALSE;
  326. }
  327. } else {
  328. return $this->queryAlterWrapper($query_i);
  329. }
  330. }
  331. if (count($queries) > 1) {
  332. $this->message .= 'Executed ' . count($queries) . ' queries.<br />';
  333. }
  334. return TRUE;
  335. }
  336. /**
  337. * Handle ALTER TABLE queries
  338. *
  339. * This is a wrapper function for ALTER TABLE queries, since SQLite does
  340. * not provide that functionality by itself. It merely parses the given
  341. * query string and provides the collected data to the internal functions
  342. * {@link tableAddField()}, {@link tableEditField()} and
  343. * {@link tableDeleteField}.
  344. *
  345. * @param string $query ALTER TABLE SQL Query to execute
  346. * @return boolean State of success.
  347. ***/
  348. public function queryAlterWrapper($query)
  349. {
  350. $table_data = Array();
  351.  
  352. /* replace arrays to prepare sql table creation string */
  353. $pats = array('/[\r\n]{1,2}/', '/\s+/', '/\( /', '/ \)/', '/, /');
  354. $reps = array('', ' ', '(', ')', ',');
  355.  
  356. /* prepare pregs for table data extraction */
  357. $alter_pattern = '/ALTER TABLE (.*?)[ ]*(ADD|MODIFY|DROP|RENAME COLUMN)[ ]*'
  358. . '(?:\((.*)\)|(.*?$))/i';
  359. $fname = '(\S+)';
  360. $ftype = '(?<=\W)(' . implode('|', $this->datatypeArray)
  361. . ')(?=\W|$)(?: ?\(([^\)]+)\))?';
  362. $fconst = ' ?([^,]*)';
  363. $fld_pattern = sprintf('/%1$s %2$s(?:%3$s)?(?:,|$)/i',
  364. $fname, $ftype, $fconst);
  365. $con_pattern = '/(?<=\W|^)(' . implode('|', $this->constraintsArray)
  366. . ')(?=\W|$)/i';
  367. $flddrop_pat = '~([^ ,$]+)(?:,[ ]*|$)~';
  368. $fldren_pat = '~([^ ,$]+)\s+TO\s+([^ ,$]+)(?:,[ ]*|$)~i';
  369. $raw = trim(preg_replace($pats, $reps, $query));
  370. if (preg_match($alter_pattern, $raw, $matches)) {
  371. $tbl_name = $matches[1];
  372. $alter_type = strtoupper($matches[2]);
  373. if ($alter_type == 'ADD' ||$alter_type == 'MODIFY') {
  374. preg_match_all($fld_pattern,
  375. (!empty($matches[3]) ? $matches[3] : $matches[4]),
  376. $fld_matches, PREG_SET_ORDER);
  377. $tbl_fields = array();
  378. foreach ($fld_matches as $id => $field) {
  379. $fld = array();
  380. $fld['field_name'] = trim($field[1], "'");
  381. $fld['field_type'] = strtoupper($field[2]);
  382. $fld['field_size'] = $field[3];
  383. $fld['field_keys'] = array();
  384. if (preg_match_all($con_pattern, $field[4], $con_matches)) {
  385. $fld['field_keys'] = $con_matches[1];
  386. foreach($fld['field_keys'] as $k => $v) {
  387. $fld['field_keys'][$k] = strtoupper($v);
  388. }
  389. }
  390. $tbl_fields[] = $fld;
  391. }
  392. } elseif ($alter_type == 'DROP') {
  393. preg_match_all($flddrop_pat,
  394. (!empty($matches[3]) ? $matches[3] : $matches[4]),
  395. $fld_matches, PREG_SET_ORDER);
  396. $tbl_fields = array();
  397. foreach ($fld_matches as $id => $field) {
  398. $tbl_fields[] = $field[1];
  399. }
  400. } elseif ($alter_type == 'RENAME COLUMN') {
  401. preg_match_all($fldren_pat,
  402. (!empty($matches[3]) ? $matches[3] : $matches[4]),
  403. $fld_matches, PREG_SET_ORDER);
  404. $tbl_fields = array();
  405. foreach ($fld_matches as $id => $field) {
  406. $tbl_fields[] = array('old' => $field[1], 'new' => $field[2]);
  407. }
  408. }
  409. // used for rename column mode -> don't put it into foreach loop
  410. $t = $this->getTableInfo($tbl_name);
  411. foreach ($tbl_fields as $field) {
  412. switch ($alter_type){
  413. case 'ADD':
  414. if (!$this->tableAddField($tbl_name,
  415. $field['field_name'],
  416. $field['field_type'],
  417. $field['field_keys'],
  418. $field['field_size'],
  419. '0')) {
  420. return false;
  421. }
  422. break;
  423. case 'MODIFY':
  424. if (!$this->tableEditField($tbl_name,
  425. $field['field_name'],
  426. $field['field_name'],
  427. $field['field_type'],
  428. $field['field_keys'],
  429. $field['field_size'])) {
  430. return false;
  431. }
  432. break;
  433. case 'DROP':
  434. if (!$this->tableDeleteField($tbl_name,
  435. $field)) {
  436. return false;
  437. }
  438. break;
  439. case 'RENAME COLUMN':
  440. $ff = array();
  441. foreach ($t['fields'] as $f) {
  442. var_dump($f);
  443. var_dump($field);
  444. if ($f['field_name'] == $field['old']) {
  445. echo 'BUH!';
  446. $ff = $f;
  447. break;
  448. }
  449. }
  450. if (empty($ff)) {
  451. return false;
  452. }
  453. if (!$this->tableEditField($tbl_name,
  454. $ff['field_name'],
  455. $field['new'],
  456. $ff['field_type'],
  457. $ff['field_keys'],
  458. $ff['field_size'])) {
  459. return false;
  460. }
  461. break;
  462. default:
  463. return false;
  464. }
  465. }
  466. }
  467. return true;
  468. }
  469.  
  470. /**
  471. * Fetch a result array
  472. *
  473. * This will perform a FetchArray operation using the given data. If $res is
  474. * NULL, the internal {@link $_result} resource will be used. In any other
  475. * case $res will be treated as result resource. $type is the kind of array
  476. * that shoud be returned. Will return false if for whatever reason no result
  477. * row can be fetched.
  478. *
  479. * @param resource|NULL $res [optional, default=NULL] Result resource
  480. * @param integer $type [optional, default=SQLITE_ASSOC] The type
  481. * of the result row that should be delivered.
  482. * See an SQLite manual for further information.
  483. * @return boolean|array An array containing the current query result row,
  484. * false if fails.
  485. ***/
  486. public function fetchArray($res = NULL, $type = SQLITE_ASSOC)
  487. {
  488. if ($res == NULL) {
  489. if ($this->_result != NULL) {
  490. $res = $this->_result;
  491. } else {
  492. return false;
  493. }
  494. }
  495. if ($row = @sqlite_fetch_array($res, $type)) {
  496. return $row;
  497. }
  498. return false;
  499. }
  500.  
  501. /**
  502. * Reset internal result resource
  503. *
  504. * This will reset the internal {@link $_result} resource, so that it can be
  505. * freely used again for further operations.
  506. **/
  507.  
  508. public function resetResult()
  509. {
  510. $this->_result = NULL;
  511. }
  512.  
  513. /**
  514. * Check if result exists
  515. *
  516. * This returns true if there is currently a valid {@link $_result} resource
  517. * that can be used for {@link fetchArray()} operations.
  518. *
  519. * @return boolean Whether or not there is a result.
  520. ***/
  521. public function hasResult()
  522. {
  523. return (bool) $this->_result;
  524. }
  525.  
  526. /**
  527. * Get number of rows in current result set
  528. *
  529. * Returns the number of rows in the given result set. If $result is NULL,
  530. * the internal result resource link will be used.
  531. *
  532. * @param resoure|NULL $result Result resource link
  533. * @return integer Number of rows in result set
  534. ***/
  535. public function numRows($result = NULL)
  536. {
  537. if ($result == NULL) {
  538. $result = $this->_result;
  539. }
  540. return @sqlite_num_rows($result);
  541. }
  542. /**
  543. * Last inserted row id
  544. *
  545. * Returns the row id created by the last insert instruction.
  546. *
  547. * @return integer Last inserted row id
  548. ***/
  549. public function lastInsertRowID()
  550. {
  551. return @sqlite_last_insert_rowid($this->_dblink);
  552. }
  553. /**
  554. * Performs a query using a format string and a list of arguments.
  555. *
  556. * This will perform a query using the given format string and the list of
  557. * arguments passed. It works similar to the php {@link http://php.net/printf}
  558. * printf} function. If the first parameter after the format string is boolean
  559. * it will be taken as the $remember parameter (see {@link query()}) and the
  560. * other parameters as list. In any other case, all parameters after the format
  561. * string will be taken for the parameter list.
  562. *
  563. * Example:
  564. * <pre>
  565. * // without 'remember':
  566. * $res = $cfg_sql->queryFormatted('SELECT * FROM %s WHERE 1', false, 'table');
  567. * $row = $cfg_sql->fetchArray($res);
  568. *
  569. * // with 'remember':
  570. * $cfg_sql->queryFormatted('SELECT * FROM %s WHERE 1', 'table');
  571. * $row = $cfg_sql->fetchArray();
  572. * </pre>
  573. *
  574. * @param string $format Format string for query
  575. * @param boolean $remember [optional, default=true] Whether or not store
  576. * result in internal result link
  577. * @param mixed $args Argument list for format string
  578. * @return boolean|resource Result resource in case of SELECT queries, state
  579. * of success in any other case.
  580. ***/
  581. public function queryFormatted($format)
  582. {
  583. $args = func_get_args();
  584.  
  585. array_shift($args); // 'kill' format string
  586. if (is_bool($args[0])) {
  587. $remember = $args[0];
  588. array_shift($args); // 'kill' $remember
  589. } else {
  590. $remember = true;
  591. }
  592. if (count($args) > 0) {
  593. $format = vsprintf($format, $args);
  594. }
  595. return $this->query($format, $remember);
  596. }
  597. /**
  598. * Get last error
  599. *
  600. * This will return an error message string of the last error that occured
  601. * during an SQLite operation. It will return an {@link http://php.net/sprintf}
  602. * sprintf}ed string (error number is first and message is second parameter).
  603. *
  604. * @param string $format Message format string
  605. * @return string Error message
  606. ***/
  607. public function lastError($format = 'Error #%02d: "%s"')
  608. {
  609. return htmlspecialchars(sprintf($format, @sqlite_last_error($this->_dblink),
  610. sqlite_error_string(@sqlite_last_error($this->_dblink))));
  611. }
  612.  
  613. /**
  614. * Create new table
  615. *
  616. * This will try to create a new table with the name $tablename in the
  617. * currently opened database. It will return true if it succeeds, false if
  618. * fails. It will automatically create an 'id INTEGER PRIMARY KEY' field.
  619. *
  620. * @param string $tablename Name of the table to create
  621. * @return boolean State of success
  622. ***/
  623. public function tableCreate($tablename)
  624. {
  625. if (empty($tablename)) {
  626. return false;
  627. }
  628. if ($this->query("CREATE TABLE $tablename (id INTEGER PRIMARY KEY)",
  629. false)) {
  630. return true;
  631. }
  632. return false;
  633. }
  634.  
  635. /**
  636. * Delete table
  637. *
  638. * This will try to delete the table with the name $tablename in the
  639. * currently opened database. It will return true if it succeeds, false if
  640. * fails.
  641. *
  642. * @param string $tablename Name of the table to delete
  643. * @return boolean State of success
  644. ***/
  645. public function tableDelete($tablename)
  646. {
  647. if (empty($tablename)) {
  648. return false;
  649. }
  650. if ($this->query("DROP TABLE $tablename", false)) {
  651. return true;
  652. }
  653. return false;
  654. }
  655. /**
  656. * Check if table exists
  657. *
  658. * Returns true, if the given table exists in currently opened database,
  659. * or false in any other case.
  660. *
  661. * @param string $tablename Name of table
  662. * @return boolean Whether (true) or not (false) $tablename exists.
  663. ***/
  664. public function hasTable($tablename)
  665. {
  666. $tables = $this->getTableInfo();
  667. foreach ($tables as $t) {
  668. if ($t['name'] == $tablename) {
  669. return true;
  670. }
  671. }
  672. return false;
  673. }
  674.  
  675. /**
  676. * Get table information
  677. *
  678. * This will return an array of information of all tables in the currently
  679. * opened database. If $tablename is given the information will be limited
  680. * to this specific table.
  681. *
  682. * The returned array contains the following information. (Excerpt of one
  683. * element):
  684. *
  685. * <pre>
  686. * array {
  687. * ["name"] => Table name
  688. * ["entries"] => Number of entries in that table
  689. * ["fields"] => Array containing field data
  690. * array {
  691. * [linear_index]=>
  692. * array {
  693. * ["field_name"] => Field name
  694. * ["field_type"] => Field type
  695. * ["field_keys"] => Field constraints
  696. * ["field_size"] => Field size
  697. * }
  698. * }
  699. * }
  700. * </pre>
  701. *
  702. * @param string $tablename [optional] Table name
  703. * @return boolean|array Table information or false if fails
  704. ***/
  705. public function getTableInfo($tablename = '')
  706. {
  707. if (empty($tablename)) {
  708. $query_str = "SELECT * FROM sqlite_master WHERE type='table'";
  709. } else {
  710. $query_str = "SELECT * FROM sqlite_master WHERE
  711. type='table' AND name='$tablename'";
  712. }
  713. $dbfile = $this->base64name;
  714. if ($query_res = $this->query($query_str, false)) {
  715. $table_data = Array();
  716.  
  717. /* replace arrays to prepare sql table creation string */
  718. $pats = array('/[\r\n]{1,2}/', '/\s+/', '/\( /', '/ \)/', '/, /');
  719. $reps = array('', ' ', '(', ')', ',');
  720.  
  721. /* prepare pregs for table data extraction */
  722. $tbl_pattern = '/CREATE TABLE (.*?)[ ]*\((.*)\)/i';
  723. $fname = '(\S+)';
  724. $ftype = '(?<=\W)(' . implode('|', $this->datatypeArray)
  725. . ')(?=\W|$)(?: ?\(([^\)]+)\))?';
  726. $fconst = ' ?([^,]*)';
  727. $fld_pattern = sprintf('/%1$s %2$s(?:%3$s)?(?:,|$)/i',
  728. $fname, $ftype, $fconst);
  729. $con_pattern = '/(?<=\W|^)(' . implode('|', $this->constraintsArray)
  730. . ')(?=\W|$)/i';
  731. while ($row = $this->fetchArray($query_res)) {
  732. $raw = trim(preg_replace($pats, $reps, $row['sql']));
  733. if (preg_match_all($tbl_pattern, $raw, $matches, PREG_SET_ORDER)) {
  734. $tbl_name = $matches[0][1];
  735. preg_match_all($fld_pattern, $matches[0][2], $fld_matches,
  736. PREG_SET_ORDER);
  737. $tbl_ofields = array();
  738. foreach ($fld_matches as $id => $field) {
  739. $fld = array();
  740. $fld['field_name'] = trim($field[1], "'");
  741. $fld['field_type'] = strtoupper($field[2]);
  742. $fld['field_size'] = $field[3];
  743. $fld['field_keys'] = array();
  744. if (preg_match_all($con_pattern, $field[4], $con_matches)) {
  745. $fld['field_keys'] = $con_matches[1];
  746. foreach($fld['field_keys'] as $k => $v) {
  747. $fld['field_keys'][$k] = strtoupper($v);
  748. }
  749. }
  750. $tbl_ofields[] = $fld;
  751. }
  752. $cnt_res = $this->query('SELECT COUNT() FROM '
  753. . $row['tbl_name'], false);
  754. $cnt_row = $this->fetchArray($cnt_res);
  755.  
  756. $cur_table = array('name' => $row['tbl_name'],
  757. 'fields' => $tbl_ofields);
  758.  
  759. $cur_table['entries'] = $cnt_row['COUNT()'];
  760. }
  761.  
  762. if (!empty($tablename)) {
  763. return $cur_table;
  764. }
  765. $table_data[] = $cur_table;
  766. }
  767. } else {
  768. return false;
  769. }
  770. return $table_data;
  771. }
  772.  
  773. /**
  774. * Rename a table.
  775. *
  776. * This will rename $tblname to $tblnewname.
  777. *
  778. * <b>WARNING:</b> This public function should be considered instable. Since
  779. * there are no builtin public functions for table altering in SQLite, we have
  780. * to do a quite bad workaround by backing all data up, deleting the table and
  781. * recreating it, then putting all the data back. There has not yet been
  782. * very intensive testing as this is only a side project of {@link Crafty}.
  783. *
  784. * @param string $tblname Name of target table
  785. * @param string $fldname New name of target table
  786. * @return boolean State of success
  787. ***/
  788. public function tableRename($tblname, $newtblname)
  789. {
  790. $table_info = $this->getTableInfo($tblname);
  791. $this->message = '';
  792.  
  793. $create_str = 'CREATE TABLE ' . $newtblname . "(";
  794. $comma = '';
  795. foreach($table_info['fields'] as $f) {
  796. $s = (!empty($f['field_size']))
  797. ? '(' . $f['field_size'] . ')' : '';
  798. $create_str .= $comma . $f['field_name'] . ' '
  799. . $f['field_type'] . $s . ' '
  800. . implode(' ',$f['field_keys']) . "\n";
  801. $comma = ', ';
  802. }
  803. $create_str .= ')';
  804.  
  805. $backup_queries = array();
  806. $query_str = 'SELECT * FROM ' . $table_info['name'];
  807. if ($query_res = $this->query($query_str, false)) {
  808. while ($row = $this->fetchArray($query_res)) {
  809. $comma = '';
  810. $fields = '';
  811. $values = '';
  812. foreach ($row as $k => $v) {
  813. $fields .= $comma . $k;
  814. $values .= $comma . $this->getValidContent($v);
  815. $comma = ', ';
  816. }
  817. $backup_queries[] = 'INSERT INTO ' . $newtblname
  818. . " ($fields) VALUES ($values)";
  819. }
  820. }
  821.  
  822. $query_del = 'DROP TABLE ' . $tblname;
  823. if ($this->query($query_del, false)) {
  824. if ($this->query($create_str, false)) {
  825. $this->message .= 'Recreated table.';
  826. foreach ($backup_queries as $bq) {
  827. $bqh = htmlentities($bq);
  828. if (!$this->query($bq, false)) {
  829. $this->message .= "QUERY \"$bqh\" failed.<br />";
  830. } else {
  831. $this->message .= "QUERY \"$bqh\" succeeded.<br />";
  832. }
  833. }
  834. } else {
  835. $this->message .= 'Could not recreate table.';
  836. return false;
  837. }
  838. } else {
  839. $this->message = 'ERROR: Could not delete table.';
  840. return false;
  841. }
  842. return true;
  843. }
  844.  
  845. /**
  846. * Delete a field from a table.
  847. *
  848. * This will delete the field $fldname from the table $tblname.
  849. *
  850. * <b>WARNING:</b> This public function should be considered instable. Since
  851. * there are no builtin public functions for table altering in SQLite, we have
  852. * to do a quite bad workaround by backing all data up, deleting the table and
  853. * recreating it, then putting all the data back. There has not yet been
  854. * very intensive testing as this is only a side project of {@link Crafty}.
  855. *
  856. * @param string $tblname Name of target table
  857. * @param string $fldname Name of target field
  858. * @return boolean State of success
  859. ***/
  860. public function tableDeleteField($tblname, $fldname)
  861. {
  862. $table_info = $this->getTableInfo($tblname);
  863. $this->message = '';
  864.  
  865. if (count($table_info['fields']) > 1) {
  866. $create_str = 'CREATE TABLE ' . $table_info['name'] . "(";
  867. $comma = '';
  868. foreach($table_info['fields'] as $f) {
  869. if ($f['field_name'] != $fldname) {
  870. $s = (!empty($f['field_size']))
  871. ? '(' . $f['field_size'] . ')' : '';
  872. $create_str .= $comma . $f['field_name'] . ' '
  873. . $f['field_type'] . $s . ' '
  874. . implode(' ',$f['field_keys']) . "\n";
  875. $comma = ', ';
  876. }
  877. }
  878. $create_str .= ')';
  879.  
  880. $backup_queries = array();
  881. $query_str = 'SELECT * FROM ' . $table_info['name'];
  882. if ($query_res = $this->query($query_str, false)) {
  883. while ($row = $this->fetchArray($query_res)) {
  884. $comma = '';
  885. $fields = '';
  886. $values = '';
  887. foreach ($row as $k => $v) {
  888. if ($k != $fldname) {
  889. $fields .= $comma . $k;
  890. $values .= $comma . $this->getValidContent($v);
  891. $comma = ', ';
  892. }
  893. }
  894. $backup_queries[] = 'INSERT INTO ' . $table_info['name']
  895. . " ($fields) VALUES ($values)";
  896. }
  897. }
  898.  
  899. $query_del = 'DROP TABLE ' . $table_info['name'];
  900. if ($this->query($query_del, false)) {
  901. if ($this->query($create_str, false)) {
  902. $this->message .= 'Recreated table.';
  903. foreach ($backup_queries as $bq) {
  904. $bqh = htmlentities($bq);
  905. if (!$this->query($bq, false)) {
  906. $this->message .= "QUERY \"$bqh\" failed.<br />";
  907. } else {
  908. $this->message .= "QUERY \"$bqh\" succeeded.<br />";
  909. }
  910. }
  911. } else {
  912. $this->message .= 'Could not recreate table.';
  913. return false;
  914. }
  915. } else {
  916. $this->message = 'ERROR: Could not delete table.';
  917. return false;
  918. }
  919. } else {
  920. $this->message = 'ERROR: Cannot delete last field. This is equal to
  921. deleting the whole table.';
  922. return false;
  923. }
  924. return true;
  925. }
  926. /**
  927. * Add a field to a table.
  928. *
  929. * This will add the field $fldname to the table $tblname. Specifications of
  930. * the new field are passed. (See parameter list)
  931. *
  932. * <b>WARNING:</b> This public function should be considered instable. Since
  933. * there are no builtin public functions for table altering in SQLite, we have
  934. * to do a quite bad workaround by backing all data up, deleting the table and
  935. * recreating it, then putting all the data back. There has not yet been
  936. * very intensive testing as this is only a side project of {@link Crafty}.
  937. *
  938. * @param string $tblname Name of target table
  939. * @param string $fldname Name of new field
  940. * @param string $fldtype Type of new field
  941. * @param string $fldconst Constraints of new field
  942. * @param string $fldsize Size of new field
  943. * @param string $flddefault Default value of new field (for already
  944. * existing database entries)
  945. * @return boolean State of success
  946. ***/
  947. public function tableAddField($tblname, $fldname, $fldtype, $fldconst,
  948. $fldsize, $flddefault)
  949. {
  950. if (empty($fldname)) {
  951. $this->message = 'Field name has to be set.';
  952. return false;
  953. }
  954. $table_info = $this->getTableInfo($tblname);
  955. $this->message = '';
  956. if (!empty($fldconst)) {
  957. $fc = (is_array($fldconst)) ? $fldconst : array($fldconst);
  958. $fc2 = array();
  959. foreach ($fc as $_fc) {
  960. if ($_fc[0] != '*') {
  961. $fc2[] = $_fc;
  962. }
  963. }
  964. $fc = $fc2;
  965. } else {
  966. $fc = array();
  967. }
  968. $create_str = 'CREATE TABLE ' . $table_info['name'] . "(";
  969. $comma = '';
  970. $target_cnt = 0;
  971. foreach($table_info['fields'] as $f) {
  972. if ($f['field_name'] == $fldname) {
  973. $target_cnt++;
  974. }
  975. $s = (!empty($f['field_size'])) ? '(' . $f['field_size'] . ')' : '';
  976. $create_str .= $comma . $f['field_name'] . ' ' . $f['field_type']
  977. . $s . ' ' . implode(' ',$f['field_keys']);
  978. $comma = ', ';
  979. }
  980. $s = (!empty($fldsize)) ? '(' . $fldsize . ')' : '';
  981. $create_str .= $comma . $fldname . ' ' . $fldtype . $s . ' '
  982. . implode(' ',$fc);
  983. $create_str .= ')';
  984.  
  985. if ($target_cnt > 0) {
  986. $this->message = 'ERROR: Field adding would create duplicate fields
  987. in db.';
  988. return false;
  989. } else {
  990. $backup_queries = array();
  991. $query_str = 'SELECT * FROM ' . $table_info['name'];
  992. if ($query_res = $this->query($query_str, false)) {
  993. $fill = $this->getValidContent($flddefault);
  994. while ($row = $this->fetchArray($query_res)) {
  995. $comma = '';
  996. $fields = '';
  997. $values = '';
  998. foreach ($row as $k => $v) {
  999. $fields .= $comma . $k;
  1000. $values .= $comma . $this->getValidContent($v);
  1001. $comma = ', ';
  1002. }
  1003. $fields .= $comma . $fldname;
  1004. $values .= $comma . $fill;
  1005. $backup_queries[] = 'INSERT INTO ' . $table_info['name']
  1006. . " ($fields) VALUES ($values)";
  1007. }
  1008. }
  1009.  
  1010. $query_del = 'DROP TABLE ' . $table_info['name'];
  1011. if ($this->query($query_del, false)) {
  1012. if ($this->query($create_str, false)) {
  1013. $this->message .= 'Recreated table.';
  1014. foreach ($backup_queries as $bq) {
  1015. $bqh = htmlentities($bq);
  1016. if (!$this->query($bq, false)) {
  1017. $this->message .= "QUERY \"$bqh\" failed.<br />";
  1018. } else {
  1019. $this->message .= "QUERY \"$bqh\" succeeded.<br />";
  1020. }
  1021. }
  1022. } else {
  1023. $this->message .= 'Could not recreate table.';
  1024. return false;
  1025. }
  1026. return true;
  1027. } else {
  1028. $this->message = 'ERROR: Could not delete table.';
  1029. return false;
  1030. }
  1031. }
  1032. }
  1033.  
  1034. /**
  1035. * Alter a field of a table.
  1036. *
  1037. * This will alter the field $fldname in the table $tblname. Specifications
  1038. * of the field are passed. (See parameter list)
  1039. *
  1040. * <b>WARNING:</b> This public function should be considered instable. Since
  1041. * there are no builtin public functions for table altering in SQLite, we have
  1042. * to do a quite bad workaround by backing all data up, deleting the table and
  1043. * recreating it, then putting all the data back. There has not yet been
  1044. * very intensive testing as this is only a side project of {@link Crafty}.
  1045. *
  1046. * @param string $tblname Name of target table
  1047. * @param string $fldoldname Name of target field
  1048. * @param string $fldname New name of target field
  1049. * @param string $fldtype New type of target field
  1050. * @param string $fldconst New constraints of target field
  1051. * @param string $fldsize New size of target field
  1052. * @return boolean State of success
  1053. ***/
  1054. public function tableEditField($tblname, $fldoldname, $fldname, $fldtype,
  1055. $fldconst, $fldsize)
  1056. {
  1057. if (empty($fldname)) {
  1058. $this->message = 'Field name has to be set.';
  1059. return false;
  1060. }
  1061. $table_info = $this->getTableInfo($tblname);
  1062. $this->message = '';
  1063.  
  1064. if (!empty($fldconst)) {
  1065. $fc = (is_array($fldconst)) ? $fldconst : array($fldconst);
  1066. $fc2 = array();
  1067. foreach ($fc as $_fc) {
  1068. if ($_fc[0] != '*') {
  1069. $fc2[] = $_fc;
  1070. }
  1071. }
  1072. $fc = $fc2;
  1073. } else {
  1074. $fc = array();
  1075. }
  1076. for($x = 0; $x < count($table_info['fields']); $x++) {
  1077. if ($table_info['fields'][$x]['field_name'] == $fldoldname) {
  1078. $new = array();
  1079. $new['field_name'] = $fldname;
  1080. $new['field_size'] = $fldsize;
  1081. $new['field_keys'] = $fc;
  1082. $new['field_type'] = $fldtype;
  1083. $table_info['fields'][$x] = $new;
  1084. break;
  1085. }
  1086. }
  1087. $create_str = 'CREATE TABLE ' . $table_info['name'] . "(";
  1088. $comma = '';
  1089. $target_cnt = 0;
  1090. foreach($table_info['fields'] as $f) {
  1091. if ($f['field_name'] == $fldname) {
  1092. $target_cnt++;
  1093. }
  1094. $s = (!empty($f['field_size'])) ? '(' . $f['field_size'] . ')' : '';
  1095. $create_str .= $comma . $f['field_name'] . ' ' . $f['field_type']
  1096. . $s . ' ' . implode(' ',$f['field_keys']) . "\n";
  1097. $comma = ', ';
  1098. }
  1099. $create_str .= ')';
  1100.  
  1101. if ($target_cnt > 1) {
  1102. $this->message = 'ERROR: Field editing would create duplicate fields
  1103. in db.';
  1104. return false;
  1105. } else {
  1106. $backup_queries = array();
  1107. $query_str = 'SELECT * FROM ' . $table_info['name'];
  1108. if ($query_res = $this->query($query_str, false)) {
  1109. while ($row = $this->fetchArray($query_res)) {
  1110. $comma = '';
  1111. $fields = '';
  1112. $values = '';
  1113. foreach ($row as $k => $v) {
  1114. if ($k == $fldoldname && $fldoldname != $fldname) {
  1115. $row[$fldname] = $v;
  1116. unset($row[$k]);
  1117. $k = $fldname;
  1118. }
  1119. $fields .= $comma . $k;
  1120. $values .= $comma . $this->getValidContent($v);
  1121. $comma = ', ';
  1122. }
  1123. $backup_queries[] = 'INSERT INTO ' . $table_info['name']
  1124. . " ($fields) VALUES ($values)";
  1125. }
  1126. }
  1127.  
  1128. $query_del = 'DROP TABLE ' . $table_info['name'];
  1129. if ($this->query($query_del, false)) {
  1130. if ($this->query($create_str, false)) {
  1131. $this->message .= 'Recreated table.';
  1132. foreach ($backup_queries as $bq) {
  1133. $bqh = htmlentities($bq);
  1134. if (!$this->query($bq, false)) {
  1135. $this->message .= "QUERY \"$bqh\" failed.<br />";
  1136. return false;
  1137. } else {
  1138. $this->message .= "QUERY \"$bqh\" succeeded.<br />";
  1139. }
  1140. }
  1141. } else {
  1142. $this->message .= 'Could not recreate table.';
  1143. return false;
  1144. }
  1145. return true;
  1146. } else {
  1147. $this->message = 'ERROR: Could not delete table.';
  1148. return false;
  1149. }
  1150. }
  1151. }
  1152.  
  1153. /**
  1154. * Get VIEW information
  1155. *
  1156. * This will return an array of information of all VIEWs in the currently
  1157. * opened database.
  1158. *
  1159. * The returned array contains the following information. (Excerpt of one
  1160. * element):
  1161. *
  1162. * <pre>
  1163. * array {
  1164. * ["name"] => VIEW name
  1165. * ["rootpage"] => VIEW rootpage
  1166. * ["sql"] => Complete SQL string that has been used for creating
  1167. * the VIEW
  1168. * ["sql_short"] => The SELECT statement the VIEW represents
  1169. * ["table"] => Name of the table the VIEW uses
  1170. * }
  1171. * </pre>
  1172. *
  1173. * @return boolean|array VIEW information or false if fails
  1174. ***/
  1175. public function getViewInfo()
  1176. {
  1177. $query_str = "SELECT name, rootpage, sql FROM sqlite_master WHERE "
  1178. . "type='view'";
  1179.  
  1180. if ($query_res = $this->query($query_str, false)) {
  1181. $view_data = Array();
  1182. while ($data = $this->fetchArray($query_res)) {
  1183. if (preg_match('~from\s+(\'?)(.*?)(\\1)?\s+where~i',
  1184. $data['sql'], $matches)) {
  1185. $data['table'] = $matches[2];
  1186. } else {
  1187. $data['table'] = $data['name'];
  1188. }
  1189. if (preg_match('~(select.*?)$~i', $data['sql'], $matches)) {
  1190. $data['sql_short'] = $matches[1];
  1191. } else {
  1192. $data['sql_short'] = $data['sql'];
  1193. }
  1194. $view_data[] = $data;
  1195. }
  1196. return $view_data;
  1197. }
  1198. return false;
  1199. }
  1200. /**
  1201. * Create a new VIEW
  1202. *
  1203. * This will create a new VIEW named $name with the SELECT statement $sql
  1204. * in the currently opened database.
  1205. *
  1206. * @param string $name VIEW name
  1207. * @param string $sql SELECT statement
  1208. * @return boolean State of success
  1209. ***/
  1210. public function createView($name, $sql)
  1211. {
  1212. if ($this->query("CREATE VIEW $name AS $sql", false)) {
  1213. return true;
  1214. } else {
  1215. return false;
  1216. }
  1217. }
  1218.  
  1219. /**
  1220. * Delete a VIEW
  1221. *
  1222. * This will delete the VIEW named $name in the currently opened database.
  1223. *
  1224. * @param string $name VIEW name
  1225. * @return boolean State of success
  1226. ***/
  1227. public function dropView($name)
  1228. {
  1229. if ($this->query("DROP VIEW $name", false)) {
  1230. return true;
  1231. } else {
  1232. return false;
  1233. }
  1234. }
  1235.  
  1236.  
  1237. /**
  1238. * Get Index information
  1239. *
  1240. * This will return an array of information of all indices on the table
  1241. * $tablename in the currently opened database.
  1242. *
  1243. * The returned array contains the following information. (Excerpt of one
  1244. * element):
  1245. *
  1246. * <pre>
  1247. * array {
  1248. * ["name"] => Index name
  1249. * ["rootpage"] => Index rootpage
  1250. * ["sql"] => Complete SQL string that has been used for creating
  1251. * the Index
  1252. * ["type"] => Index type ('STANDARD' or 'UNIQUE')
  1253. * ["columns"] => Linear array of names of affected columns
  1254. * ["columns_string"] => Comma-seperated list of names of affected columns
  1255. * }
  1256. * </pre>
  1257. *
  1258. * @param string $tablename Table name
  1259. * @return boolean|array VIEW information or false if fails
  1260. ***/
  1261. public function getIndexInfo($tablename)
  1262. {
  1263. $query_str = "SELECT name, rootpage, sql FROM sqlite_master WHERE "
  1264. . "type='index' AND tbl_name='$tablename'";
  1265.  
  1266. if ($query_res = $this->query($query_str, false)) {
  1267. $index_data = Array();
  1268. while ($data = $this->fetchArray($query_res)) {
  1269. if (strpos(strtoupper($data['sql']), 'CREATE UNIQUE') === 0) {
  1270. $data['type'] = 'UNIQUE';
  1271. } else {
  1272. $data['type'] = 'STANDARD';
  1273. }
  1274. if (preg_match('~\\(((?:(\'?).*?(\\2),?)+\\))~i',
  1275. $data['sql'], $matches)) {
  1276. if (preg_match_all('~(\'?)(.*?)(\\1)[,\\)]\s*~', $matches[1],
  1277. $matches2)) {
  1278. $data['columns'] = $matches2[2];
  1279. $data['columns_string'] = implode(', ', $matches2[2]);
  1280. } else {
  1281. $data['columns'] = Array();
  1282. $data['columns_string'] = '';
  1283. }
  1284. } else {
  1285. $data['columns'] = Array();
  1286. $data['columns_string'] = '';
  1287. }
  1288. $index_data[] = $data;
  1289. }
  1290. return $index_data;
  1291. }
  1292. return false;
  1293. }
  1294.  
  1295. /**
  1296. * Create an Index
  1297. *
  1298. * This will create the Index named $name on the table $table in the currently
  1299. * opened database. It will be of the type $type and affect the fields $fields.
  1300. *
  1301. * @param string $name Index name
  1302. * @param string $table Table on which the index shall be created
  1303. * @param string $type Index type (STANDARD or UNIQUE)
  1304. * @param array $fields Affected fields
  1305. * @return boolean State of success
  1306. ***/
  1307. public function createIndex($name, $type, $table, $fields)
  1308. {
  1309. $fields = implode(', ', $fields);
  1310. $type = ($type == 'UNIQUE') ? ' UNIQUE' : '';
  1311. if ($this->query("CREATE$type INDEX $name ON $table ($fields)", false)) {
  1312. return true;
  1313. } else {
  1314. return false;
  1315. }
  1316. }
  1317.  
  1318. /**
  1319. * Delete an Index
  1320. *
  1321. * This will delete the Index named $name in the currently opened database.
  1322. *
  1323. * @param string $name Index name
  1324. * @return boolean State of success
  1325. ***/
  1326. public function dropIndex($name)
  1327. {
  1328. if ($this->query("DROP INDEX $name", false)) {
  1329. return true;
  1330. } else {
  1331. return false;
  1332. }
  1333. }
  1334.  
  1335. /**
  1336. * Get a valid content string
  1337. *
  1338. * Returns a valid content string created from given content string. Some
  1339. * strings are key contents (like NULL) in SQLite. They must not be set in
  1340. * single quotes. All others have to. That is what this public function takes
  1341. * care of. Mostly for internal use.
  1342. *
  1343. * @param string $cont Given content string
  1344. * @return string Valid content string
  1345. ***/
  1346. public function getValidContent($cont)
  1347. {
  1348. if (empty($cont) && $cont !== "0") {
  1349. return 'NULL';
  1350. }
  1351. if (preg_match('~^(NULL)$~i', $cont)) {
  1352. return $cont;
  1353. } else {
  1354. return "'$cont'";
  1355. }
  1356. }
  1357. /**#@-*/
  1358. }
  1359.  
  1360. ?>

Documentation generated on Sat, 19 Feb 2005 01:43:59 +0100 by phpDocumentor 1.3.0RC3