Generator.php 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786
  1. <?php
  2. /**
  3. * Generation tools for DB_DataObject
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * LICENSE: This source file is subject to version 3.01 of the PHP license
  8. * that is available through the world-wide-web at the following URI:
  9. * http://www.php.net/license/3_01.txt. If you did not receive a copy of
  10. * the PHP License and are unable to obtain it through the web, please
  11. * send a note to license@php.net so we can mail you a copy immediately.
  12. *
  13. * @category Database
  14. * @package DB_DataObject
  15. * @author Alan Knowles <alan@akbkhome.com>
  16. * @copyright 1997-2006 The PHP Group
  17. * @license http://www.php.net/license/3_01.txt PHP License 3.01
  18. * @version CVS: $Id: Generator.php 336719 2015-05-05 10:37:33Z alan_k $
  19. * @link http://pear.php.net/package/DB_DataObject
  20. */
  21. /*
  22. * Security Notes:
  23. * This class uses eval to create classes on the fly.
  24. * The table name and database name are used to check the database before writing the
  25. * class definitions, we now check for quotes and semi-colon's in both variables
  26. * so I cant see how it would be possible to generate code even if
  27. * for some crazy reason you took the classname and table name from User Input.
  28. *
  29. * If you consider that wrong, or can prove it.. let me know!
  30. */
  31. /**
  32. *
  33. * Config _$ptions
  34. * [DB_DataObject]
  35. * ; optional default = DB/DataObject.php
  36. * extends_location =
  37. * ; optional default = DB_DataObject
  38. * extends =
  39. * ; alter the extends field when updating a class (defaults to only replacing DB_DataObject)
  40. * generator_class_rewrite = ANY|specific_name // default is DB_DataObject
  41. *
  42. */
  43. /**
  44. * Needed classes
  45. * We lazy load here, due to problems with the tests not setting up include path correctly.
  46. * FIXME!
  47. */
  48. class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php';
  49. //require_once('Config.php');
  50. /**
  51. * Generator class
  52. *
  53. * @package DB_DataObject
  54. */
  55. class DB_DataObject_Generator extends DB_DataObject
  56. {
  57. /* =========================================================== */
  58. /* Utility functions - for building db config files */
  59. /* =========================================================== */
  60. /**
  61. * Array of table names
  62. *
  63. * @var array
  64. * @access private
  65. */
  66. var $tables;
  67. /**
  68. * associative array table -> array of table row objects
  69. *
  70. * @var array
  71. * @access private
  72. */
  73. var $_definitions;
  74. /**
  75. * active table being output
  76. *
  77. * @var string
  78. * @access private
  79. */
  80. var $table; // active tablename
  81. /**
  82. * links (generated)
  83. *
  84. * @var array
  85. * @access private
  86. */
  87. var $_fkeys; // active tablename
  88. /**
  89. * The 'starter' = call this to start the process
  90. *
  91. * @access public
  92. * @return none
  93. */
  94. function start()
  95. {
  96. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  97. $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
  98. $databases = array();
  99. foreach($options as $k=>$v) {
  100. if (substr($k,0,9) == 'database_') {
  101. $databases[substr($k,9)] = $v;
  102. }
  103. }
  104. if (isset($options['database'])) {
  105. if ($db_driver == 'DB') {
  106. require_once 'DB.php';
  107. $dsn = DB::parseDSN($options['database']);
  108. } else {
  109. require_once 'MDB2.php';
  110. $dsn = MDB2::parseDSN($options['database']);
  111. }
  112. if (!isset($database[$dsn['database']])) {
  113. $databases[$dsn['database']] = $options['database'];
  114. }
  115. }
  116. foreach($databases as $databasename => $database) {
  117. if (!$database) {
  118. continue;
  119. }
  120. $this->debug("CREATING FOR $databasename\n");
  121. $class = get_class($this);
  122. $t = new $class;
  123. $t->_database_dsn = $database;
  124. $t->_database = $databasename;
  125. if ($db_driver == 'DB') {
  126. require_once 'DB.php';
  127. $dsn = DB::parseDSN($database);
  128. } else {
  129. require_once 'MDB2.php';
  130. $dsn = MDB2::parseDSN($database);
  131. }
  132. if (($dsn['phptype'] == 'sqlite') && is_file($databasename)) {
  133. $t->_database = basename($t->_database);
  134. }
  135. $t->_createTableList();
  136. $t->_createForiegnKeys();
  137. foreach(get_class_methods($class) as $method) {
  138. if (substr($method,0,8 ) != 'generate') {
  139. continue;
  140. }
  141. $this->debug("calling $method");
  142. $t->$method();
  143. }
  144. }
  145. $this->debug("DONE\n\n");
  146. }
  147. /**
  148. * Output File was config object, now just string
  149. * Used to generate the Tables
  150. *
  151. * @var string outputbuffer for table definitions
  152. * @access private
  153. */
  154. var $_newConfig;
  155. /**
  156. * Build a list of tables;
  157. * and store it in $this->tables and $this->_definitions[tablename];
  158. *
  159. * @access private
  160. * @return none
  161. */
  162. function _createTableList()
  163. {
  164. $this->_connect();
  165. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  166. $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
  167. $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
  168. $is_MDB2 = ($db_driver != 'DB') ? true : false;
  169. if (is_object($__DB) && is_a($__DB , 'PEAR_Error')) {
  170. return PEAR::raiseError($__DB->toString(), null, PEAR_ERROR_DIE);
  171. }
  172. if (!$is_MDB2) {
  173. // try getting a list of schema tables first. (postgres)
  174. $__DB->expectError(DB_ERROR_UNSUPPORTED);
  175. $this->tables = $__DB->getListOf('schema.tables');
  176. $__DB->popExpect();
  177. } else {
  178. /**
  179. * set portability and some modules to fetch the informations
  180. */
  181. $db_options = PEAR::getStaticProperty('MDB2','options');
  182. if (empty($db_options)) {
  183. $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
  184. }
  185. $__DB->loadModule('Manager');
  186. $__DB->loadModule('Reverse');
  187. }
  188. if ((empty($this->tables) || (is_object($this->tables) && is_a($this->tables , 'PEAR_Error')))) {
  189. //if that fails fall back to clasic tables list.
  190. if (!$is_MDB2) {
  191. // try getting a list of schema tables first. (postgres)
  192. $__DB->expectError(DB_ERROR_UNSUPPORTED);
  193. $this->tables = $__DB->getListOf('tables');
  194. $__DB->popExpect();
  195. } else {
  196. $this->tables = $__DB->manager->listTables();
  197. $sequences = $__DB->manager->listSequences();
  198. foreach ($sequences as $k => $v) {
  199. $this->tables[] = $__DB->getSequenceName($v);
  200. }
  201. }
  202. }
  203. if (is_object($this->tables) && is_a($this->tables , 'PEAR_Error')) {
  204. return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE);
  205. }
  206. // build views as well if asked to.
  207. if (!empty($options['build_views'])) {
  208. if (!$is_MDB2) {
  209. $views = $__DB->getListOf(is_string($options['build_views']) ?
  210. $options['build_views'] : 'views');
  211. } else {
  212. $views = $__DB->manager->listViews();
  213. }
  214. if (is_object($views) && is_a($views,'PEAR_Error')) {
  215. return PEAR::raiseError(
  216. 'Error getting Views (check the PEAR bug database for the fix to DB), ' .
  217. $views->toString(),
  218. null,
  219. PEAR_ERROR_DIE
  220. );
  221. }
  222. $this->tables = array_merge ($this->tables, $views);
  223. }
  224. // declare a temporary table to be filled with matching tables names
  225. $tmp_table = array();
  226. foreach($this->tables as $table) {
  227. if (isset($options['generator_include_regex']) &&
  228. !preg_match($options['generator_include_regex'],$table)) {
  229. $this->debug("SKIPPING (generator_include_regex) : $table");
  230. continue;
  231. }
  232. if (isset($options['generator_exclude_regex']) &&
  233. preg_match($options['generator_exclude_regex'],$table)) {
  234. continue;
  235. }
  236. $strip = empty($options['generator_strip_schema']) ? false : $options['generator_strip_schema'];
  237. $strip = is_numeric($strip) ? (bool) $strip : $strip;
  238. $strip = (is_string($strip) && strtolower($strip) == 'true') ? true : $strip;
  239. // postgres strip the schema bit from the
  240. if (!empty($strip) ) {
  241. if (!is_string($strip) || preg_match($strip, $table)) {
  242. $bits = explode('.', $table,2);
  243. $table = $bits[0];
  244. if (count($bits) > 1) {
  245. $table = $bits[1];
  246. }
  247. }
  248. }
  249. $this->debug("EXTRACTING : $table");
  250. $quotedTable = !empty($options['quote_identifiers_tableinfo']) ?
  251. $__DB->quoteIdentifier($table) : $table;
  252. if (!$is_MDB2) {
  253. $defs = $__DB->tableInfo($quotedTable);
  254. } else {
  255. $defs = $__DB->reverse->tableInfo($quotedTable);
  256. // rename the length value, so it matches db's return.
  257. }
  258. if (is_object($defs) && is_a($defs,'PEAR_Error')) {
  259. // running in debug mode should pick this up as a big warning..
  260. $this->debug("Error reading tableInfo: $table");
  261. $this->raiseError('Error reading tableInfo, '. $defs->toString());
  262. continue;
  263. }
  264. // cast all definitions to objects - as we deal with that better.
  265. foreach($defs as $def) {
  266. if (!is_array($def)) {
  267. continue;
  268. }
  269. // rename the length value, so it matches db's return.
  270. if (isset($def['length']) && !isset($def['len'])) {
  271. $def['len'] = $def['length'];
  272. }
  273. $this->_definitions[$table][] = (object) $def;
  274. }
  275. // we find a matching table, just store it into a temporary array
  276. $tmp_table[] = $table;
  277. }
  278. // the temporary table array is now the right one (tables names matching
  279. // with regex expressions have been removed)
  280. $this->tables = $tmp_table;
  281. //print_r($this->_definitions);
  282. }
  283. /**
  284. * Auto generation of table data.
  285. *
  286. * it will output to db_oo_{database} the table definitions
  287. *
  288. * @access private
  289. * @return none
  290. */
  291. function generateDefinitions()
  292. {
  293. $this->debug("Generating Definitions file: ");
  294. if (!$this->tables) {
  295. $this->debug("-- NO TABLES -- \n");
  296. return;
  297. }
  298. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  299. //$this->_newConfig = new Config('IniFile');
  300. $this->_newConfig = '';
  301. foreach($this->tables as $this->table) {
  302. $this->_generateDefinitionsTable();
  303. }
  304. $this->_connect();
  305. // dont generate a schema if location is not set
  306. // it's created on the fly!
  307. if (empty($options['schema_location']) && empty($options["ini_{$this->_database}"]) ) {
  308. return;
  309. }
  310. if (!empty($options['generator_no_ini'])) { // built in ini files..
  311. return;
  312. }
  313. $base = @$options['schema_location'];
  314. if (isset($options["ini_{$this->_database}"])) {
  315. $file = $options["ini_{$this->_database}"];
  316. } else {
  317. $file = "{$base}/{$this->_database}.ini";
  318. }
  319. if (!file_exists(dirname($file))) {
  320. require_once 'System.php';
  321. System::mkdir(array('-p','-m',0755,dirname($file)));
  322. }
  323. $this->debug("Writing ini as {$file}\n");
  324. //touch($file);
  325. $tmpname = tempnam(session_save_path(),'DataObject_');
  326. //print_r($this->_newConfig);
  327. $fh = fopen($tmpname,'w');
  328. if (!$fh) {
  329. return PEAR::raiseError(
  330. "Failed to create temporary file: $tmpname\n".
  331. "make sure session.save_path is set and is writable\n"
  332. ,null, PEAR_ERROR_DIE);
  333. }
  334. fwrite($fh,$this->_newConfig);
  335. fclose($fh);
  336. $perms = file_exists($file) ? fileperms($file) : 0755;
  337. // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
  338. if (!@rename($tmpname, $file)) {
  339. unlink($file);
  340. rename($tmpname, $file);
  341. }
  342. chmod($file,$perms);
  343. //$ret = $this->_newConfig->writeInput($file,false);
  344. //if (PEAR::isError($ret) ) {
  345. // return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE);
  346. // }
  347. }
  348. /**
  349. * create the data for Foreign Keys (for links.ini)
  350. * Currenly only works with mysql / mysqli / posgtreas
  351. * to use, you must set option: generate_links=true
  352. *
  353. * @author Pascal Sch�ni
  354. */
  355. function _createForiegnKeys()
  356. {
  357. $options = PEAR::getStaticProperty('DB_DataObject','options');
  358. if (empty($options['generate_links'])) {
  359. return false;
  360. }
  361. $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
  362. if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) {
  363. echo "WARNING: cant handle non-mysql and pgsql introspection for defaults.";
  364. return; // cant handle non-mysql introspection for defaults.
  365. }
  366. $this->debug("generateForeignKeys: Start");
  367. $DB = $this->getDatabaseConnection();
  368. $fk = array();
  369. switch ($DB->phptype) {
  370. case 'pgsql':
  371. foreach($this->tables as $this->table) {
  372. $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table;
  373. $res =& $DB->query("SELECT
  374. pg_catalog.pg_get_constraintdef(r.oid, true) AS condef
  375. FROM pg_catalog.pg_constraint r,
  376. pg_catalog.pg_class c
  377. WHERE c.oid=r.conrelid
  378. AND r.contype = 'f'
  379. AND c.relname = '" . $quotedTable . "'");
  380. if (PEAR::isError($res)) {
  381. die($res->getMessage());
  382. }
  383. while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  384. $treffer = array();
  385. // this only picks up one of these.. see this for why: http://pear.php.net/bugs/bug.php?id=17049
  386. preg_match(
  387. "/FOREIGN KEY \((\w*)\) REFERENCES (\w*)\((\w*)\)/i",
  388. $row['condef'],
  389. $treffer);
  390. if (!count($treffer)) {
  391. continue;
  392. }
  393. $fk[$this->table][$treffer[1]] = $treffer[2] . ":" . $treffer[3];
  394. }
  395. }
  396. break;
  397. case 'mysql':
  398. case 'mysqli':
  399. default:
  400. foreach($this->tables as $this->table) {
  401. $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table;
  402. $res =& $DB->query('SHOW CREATE TABLE ' . $quotedTable );
  403. if (PEAR::isError($res)) {
  404. die($res->getMessage());
  405. }
  406. $text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0);
  407. $treffer = array();
  408. // Extract FOREIGN KEYS
  409. preg_match_all(
  410. "/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i",
  411. $text[1],
  412. $treffer,
  413. PREG_SET_ORDER);
  414. if (!count($treffer)) {
  415. continue;
  416. }
  417. foreach($treffer as $i=> $tref) {
  418. $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3];
  419. }
  420. }
  421. }
  422. $this->_fkeys = $fk;
  423. }
  424. /**
  425. * generate Foreign Keys (for links.ini)
  426. * Currenly only works with mysql / mysqli
  427. * to use, you must set option: generate_links=true
  428. *
  429. * @author Pascal Sch�ni
  430. */
  431. function generateForeignKeys()
  432. {
  433. $options = PEAR::getStaticProperty('DB_DataObject','options');
  434. if (empty($options['generate_links'])) {
  435. return false;
  436. }
  437. $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
  438. if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) {
  439. echo "WARNING: cant handle non-mysql and pgsql introspection for defaults.";
  440. return; // cant handle non-mysql introspection for defaults.
  441. }
  442. $this->debug("generateForeignKeys: Start");
  443. $fk = $this->_fkeys;
  444. $links_ini = "";
  445. foreach($fk as $table => $details) {
  446. $links_ini .= "[$table]\n";
  447. foreach ($details as $col => $ref) {
  448. $links_ini .= "$col = $ref\n";
  449. }
  450. $links_ini .= "\n";
  451. }
  452. // dont generate a schema if location is not set
  453. // it's created on the fly!
  454. $options = PEAR::getStaticProperty('DB_DataObject','options');
  455. if (!empty($options['schema_location'])) {
  456. $file = "{$options['schema_location']}/{$this->_database}.links.ini";
  457. } elseif (isset($options["ini_{$this->_database}"])) {
  458. $file = preg_replace('/\.ini/','.links.ini',$options["ini_{$this->_database}"]);
  459. } else {
  460. $this->debug("generateForeignKeys: SKIP - schema_location or ini_{database} was not set");
  461. return;
  462. }
  463. if (!file_exists(dirname($file))) {
  464. mkdir(dirname($file),0755, true);
  465. }
  466. $this->debug("Writing ini as {$file}\n");
  467. //touch($file); // not sure why this is needed?
  468. $tmpname = tempnam(session_save_path(),'DataObject_');
  469. $fh = fopen($tmpname,'w');
  470. if (!$fh) {
  471. return PEAR::raiseError(
  472. "Failed to create temporary file: $tmpname\n".
  473. "make sure session.save_path is set and is writable\n"
  474. ,null, PEAR_ERROR_DIE);
  475. }
  476. fwrite($fh,$links_ini);
  477. fclose($fh);
  478. $perms = file_exists($file) ? fileperms($file) : 0755;
  479. // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
  480. if (!@rename($tmpname, $file)) {
  481. unlink($file);
  482. rename($tmpname, $file);
  483. }
  484. chmod($file, $perms);
  485. }
  486. /**
  487. * The table geneation part
  488. *
  489. * @access private
  490. * @return tabledef and keys array.
  491. */
  492. function _generateDefinitionsTable()
  493. {
  494. global $_DB_DATAOBJECT;
  495. $options = PEAR::getStaticProperty('DB_DataObject','options');
  496. $defs = $this->_definitions[$this->table];
  497. $this->_newConfig .= "\n[{$this->table}]\n";
  498. $keys_out = "\n[{$this->table}__keys]\n";
  499. $keys_out_primary = '';
  500. $keys_out_secondary = '';
  501. if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
  502. echo "TABLE STRUCTURE FOR {$this->table}\n";
  503. print_r($defs);
  504. }
  505. $DB = $this->getDatabaseConnection();
  506. $dbtype = $DB->phptype;
  507. $ret = array(
  508. 'table' => array(),
  509. 'keys' => array(),
  510. );
  511. $ret_keys_primary = array();
  512. $ret_keys_secondary = array();
  513. foreach($defs as $t) {
  514. $n=0;
  515. $write_ini = true;
  516. switch (strtoupper($t->type)) {
  517. case 'INT':
  518. case 'INT2': // postgres
  519. case 'INT4': // postgres
  520. case 'INT8': // postgres
  521. case 'SERIAL4': // postgres
  522. case 'SERIAL8': // postgres
  523. case 'INTEGER':
  524. case 'TINYINT':
  525. case 'SMALLINT':
  526. case 'MEDIUMINT':
  527. case 'BIGINT':
  528. $type = DB_DATAOBJECT_INT;
  529. if ($t->len == 1) {
  530. $type += DB_DATAOBJECT_BOOL;
  531. }
  532. break;
  533. case 'REAL':
  534. case 'DOUBLE':
  535. case 'DOUBLE PRECISION': // double precision (firebird)
  536. case 'FLOAT':
  537. case 'FLOAT4': // real (postgres)
  538. case 'FLOAT8': // double precision (postgres)
  539. case 'DECIMAL':
  540. case 'MONEY': // mssql and maybe others
  541. case 'NUMERIC':
  542. case 'NUMBER': // oci8
  543. $type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY...
  544. break;
  545. case 'YEAR':
  546. $type = DB_DATAOBJECT_INT;
  547. break;
  548. case 'BIT':
  549. case 'BOOL':
  550. case 'BOOLEAN':
  551. $type = DB_DATAOBJECT_BOOL;
  552. // postgres needs to quote '0'
  553. if ($dbtype == 'pgsql') {
  554. $type += DB_DATAOBJECT_STR;
  555. }
  556. break;
  557. case 'STRING':
  558. case 'CHAR':
  559. case 'VARCHAR':
  560. case 'VARCHAR2':
  561. case 'TINYTEXT':
  562. case 'ENUM':
  563. case 'SET': // not really but oh well
  564. case 'POINT': // mysql geometry stuff - not really string - but will do..
  565. case 'TIMESTAMPTZ': // postgres
  566. case 'BPCHAR': // postgres
  567. case 'INTERVAL': // postgres (eg. '12 days')
  568. case 'CIDR': // postgres IP net spec
  569. case 'INET': // postgres IP
  570. case 'MACADDR': // postgress network Mac address.
  571. case 'INTEGER[]': // postgres type
  572. case 'BOOLEAN[]': // postgres type
  573. $type = DB_DATAOBJECT_STR;
  574. break;
  575. case 'TEXT':
  576. case 'MEDIUMTEXT':
  577. case 'LONGTEXT':
  578. case '_TEXT': //postgres (?? view ??)
  579. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT;
  580. break;
  581. case 'DATE':
  582. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE;
  583. break;
  584. case 'TIME':
  585. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME;
  586. break;
  587. case 'DATETIME':
  588. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
  589. break;
  590. case 'TIMESTAMP': // do other databases use this???
  591. $type = ($dbtype == 'mysql') ?
  592. DB_DATAOBJECT_MYSQLTIMESTAMP :
  593. DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
  594. break;
  595. case 'BLOB': /// these should really be ignored!!!???
  596. case 'TINYBLOB':
  597. case 'MEDIUMBLOB':
  598. case 'LONGBLOB':
  599. case 'CLOB': // oracle character lob support
  600. case 'BYTEA': // postgres blob support..
  601. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB;
  602. break;
  603. default:
  604. echo "*****************************************************************\n".
  605. "** WARNING UNKNOWN TYPE **\n".
  606. "** Found column '{$t->name}', of type '{$t->type}' **\n".
  607. "** Please submit a bug, describe what type you expect this **\n".
  608. "** column to be **\n".
  609. "** ---------POSSIBLE FIX / WORKAROUND -------------------------**\n".
  610. "** Try using MDB2 as the backend - eg set the config option **\n".
  611. "** db_driver = MDB2 **\n".
  612. "*****************************************************************\n";
  613. $write_ini = false;
  614. break;
  615. }
  616. if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
  617. echo "*****************************************************************\n".
  618. "** WARNING COLUMN NAME UNUSABLE **\n".
  619. "** Found column '{$t->name}', of type '{$t->type}' **\n".
  620. "** Since this column name can't be converted to a php variable **\n".
  621. "** name, and the whole idea of mapping would result in a mess **\n".
  622. "** This column has been ignored... **\n".
  623. "*****************************************************************\n";
  624. continue;
  625. }
  626. if (!strlen(trim($t->name))) {
  627. continue; // is this a bug?
  628. }
  629. if (preg_match('/not[ _]null/i',$t->flags)) {
  630. $type += DB_DATAOBJECT_NOTNULL;
  631. }
  632. if (in_array($t->name,array('null','yes','no','true','false'))) {
  633. echo "*****************************************************************\n".
  634. "** WARNING **\n".
  635. "** Found column '{$t->name}', which is invalid in an .ini file **\n".
  636. "** This line will not be writen to the file - you will have **\n".
  637. "** define the keys()/method manually. **\n".
  638. "*****************************************************************\n";
  639. $write_ini = false;
  640. } else {
  641. $this->_newConfig .= "{$t->name} = $type\n";
  642. }
  643. $ret['table'][$t->name] = $type;
  644. // i've no idea if this will work well on other databases?
  645. // only use primary key or nextval(), cause the setFrom blocks you setting all key items...
  646. // if no keys exist fall back to using unique
  647. //echo "\n{$t->name} => {$t->flags}\n";
  648. $secondary_key_match = isset($options['generator_secondary_key_match']) ? $options['generator_secondary_key_match'] : 'primary|unique';
  649. $m = array();
  650. if (preg_match('/(auto_increment|nextval\(([^)]*))/i',rawurldecode($t->flags),$m)
  651. || (isset($t->autoincrement) && ($t->autoincrement === true))) {
  652. $sn = 'N';
  653. if ($DB->phptype == 'pgsql' && !empty($m[2])) {
  654. $sn = preg_replace('/[("]+/','', $m[2]);
  655. //echo urldecode($t->flags) . "\n" ;
  656. }
  657. // native sequences = 2
  658. if ($write_ini) {
  659. $keys_out_primary .= "{$t->name} = $sn\n";
  660. }
  661. $ret_keys_primary[$t->name] = $sn;
  662. } else if ($secondary_key_match && preg_match('/('.$secondary_key_match.')/i',$t->flags)) {
  663. // keys.. = 1
  664. $key_type = 'K';
  665. if (!preg_match("/(primary)/i",$t->flags)) {
  666. $key_type = 'U';
  667. }
  668. if ($write_ini) {
  669. $keys_out_secondary .= "{$t->name} = {$key_type}\n";
  670. }
  671. $ret_keys_secondary[$t->name] = $key_type;
  672. }
  673. }
  674. $this->_newConfig .= $keys_out . (empty($keys_out_primary) ? $keys_out_secondary : $keys_out_primary);
  675. $ret['keys'] = empty($keys_out_primary) ? $ret_keys_secondary : $ret_keys_primary;
  676. if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
  677. print_r(array("dump for {$this->table}", $ret));
  678. }
  679. return $ret;
  680. }
  681. /**
  682. * Convert a table name into a class name -> override this if you want a different mapping
  683. *
  684. * @access public
  685. * @return string class name;
  686. */
  687. function getClassNameFromTableName($table)
  688. {
  689. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  690. $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
  691. return $class_prefix.preg_replace('/[^A-Z0-9]/i','_',ucfirst(trim($this->table)));
  692. }
  693. /**
  694. * Convert a table name into a file name -> override this if you want a different mapping
  695. *
  696. * @access public
  697. * @return string file name;
  698. */
  699. function getFileNameFromTableName($table)
  700. {
  701. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  702. $base = $options['class_location'];
  703. if (strpos($base,'%s') !== false) {
  704. $base = dirname($base);
  705. }
  706. if (!file_exists($base)) {
  707. require_once 'System.php';
  708. System::mkdir(array('-p',$base));
  709. }
  710. if (strpos($options['class_location'],'%s') !== false) {
  711. $outfilename = sprintf($options['class_location'],
  712. preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)));
  713. } else {
  714. $outfilename = "{$base}/".preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)).".php";
  715. }
  716. return $outfilename;
  717. }
  718. /**
  719. * Convert a column name into a method name (usually prefixed by get/set/validateXXXXX)
  720. *
  721. * @access public
  722. * @return string method name;
  723. */
  724. function getMethodNameFromColumnName($col)
  725. {
  726. return ucfirst($col);
  727. }
  728. /*
  729. * building the class files
  730. * for each of the tables output a file!
  731. */
  732. function generateClasses()
  733. {
  734. //echo "Generating Class files: \n";
  735. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  736. $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
  737. $this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
  738. foreach($this->tables as $this->table) {
  739. $this->table = trim($this->table);
  740. $this->classname = $this->getClassNameFromTableName($this->table);
  741. $i = '';
  742. $outfilename = $this->getFileNameFromTableName($this->table);
  743. $oldcontents = '';
  744. if (file_exists($outfilename)) {
  745. // file_get_contents???
  746. $oldcontents = implode('',file($outfilename));
  747. }
  748. $out = $this->_generateClassTable($oldcontents);
  749. $this->debug( "writing $this->classname\n");
  750. $tmpname = tempnam(session_save_path(),'DataObject_');
  751. $fh = fopen($tmpname, "w");
  752. if (!$fh) {
  753. return PEAR::raiseError(
  754. "Failed to create temporary file: $tmpname\n".
  755. "make sure session.save_path is set and is writable\n"
  756. ,null, PEAR_ERROR_DIE);
  757. }
  758. fputs($fh,$out);
  759. fclose($fh);
  760. $perms = file_exists($outfilename) ? fileperms($outfilename) : 0755;
  761. // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
  762. if (!@rename($tmpname, $outfilename)) {
  763. unlink($outfilename);
  764. rename($tmpname, $outfilename);
  765. }
  766. chmod($outfilename, $perms);
  767. }
  768. //echo $out;
  769. }
  770. /**
  771. * class being extended (can be overridden by [DB_DataObject] extends=xxxx
  772. *
  773. * @var string
  774. * @access private
  775. */
  776. var $_extends = 'DB_DataObject';
  777. /**
  778. * line to use for require('DB/DataObject.php');
  779. *
  780. * @var string
  781. * @access private
  782. */
  783. var $_extendsFile = "DB/DataObject.php";
  784. /**
  785. * class being generated
  786. *
  787. * @var string
  788. * @access private
  789. */
  790. var $_className;
  791. /**
  792. * The table class geneation part - single file.
  793. *
  794. * @access private
  795. * @return none
  796. */
  797. function _generateClassTable($input = '')
  798. {
  799. // title = expand me!
  800. $foot = "";
  801. $head = "<?php\n/**\n * Table Definition for {$this->table}\n";
  802. $head .= $this->derivedHookPageLevelDocBlock();
  803. $head .= " */\n";
  804. $head .= $this->derivedHookExtendsDocBlock();
  805. // requires - if you set extends_location = (blank) then no require line will be set
  806. // this can be used if you have an autoloader
  807. if (!empty($this->_extendsFile)) {
  808. $head .= "require_once '{$this->_extendsFile}';\n\n";
  809. }
  810. // add dummy class header in...
  811. // class
  812. $head .= $this->derivedHookClassDocBlock();
  813. $head .= "class {$this->classname} extends {$this->_extends} \n{";
  814. $body = "\n ###START_AUTOCODE\n";
  815. $body .= " /* the code below is auto generated do not remove the above tag */\n\n";
  816. // table
  817. $p = str_repeat(' ',max(2, (18 - strlen($this->table)))) ;
  818. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  819. $var = (substr(phpversion(),0,1) > 4) ? 'public' : 'var';
  820. $var = !empty($options['generator_var_keyword']) ? $options['generator_var_keyword'] : $var;
  821. $body .= " {$var} \$__table = '{$this->table}'; {$p}// table name\n";
  822. // if we are using the option database_{databasename} = dsn
  823. // then we should add var $_database = here
  824. // as database names may not always match..
  825. if (empty($GLOBALS['_DB_DATAOBJECT']['CONFIG'])) {
  826. DB_DataObject::_loadConfig();
  827. }
  828. // Only include the $_database property if the omit_database_var is unset or false
  829. if (isset($options["database_{$this->_database}"]) && empty($GLOBALS['_DB_DATAOBJECT']['CONFIG']['generator_omit_database_var'])) {
  830. $p = str_repeat(' ', max(2, (16 - strlen($this->_database))));
  831. $body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n";
  832. }
  833. if (!empty($options['generator_novars'])) {
  834. $var = '//'.$var;
  835. }
  836. $defs = $this->_definitions[$this->table];
  837. // show nice information!
  838. $connections = array();
  839. $sets = array();
  840. foreach($defs as $t) {
  841. if (!strlen(trim($t->name))) {
  842. continue;
  843. }
  844. if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
  845. echo "*****************************************************************\n".
  846. "** WARNING COLUMN NAME UNUSABLE **\n".
  847. "** Found column '{$t->name}', of type '{$t->type}' **\n".
  848. "** Since this column name can't be converted to a php variable **\n".
  849. "** name, and the whole idea of mapping would result in a mess **\n".
  850. "** This column has been ignored... **\n".
  851. "*****************************************************************\n";
  852. continue;
  853. }
  854. $pad = str_repeat(' ',max(2, (30 - strlen($t->name))));
  855. $length = empty($t->len) ? '' : '('.$t->len.')';
  856. $flags = strlen($t->flags) ? (' '. trim($t->flags)) : '';
  857. $body .=" {$var} \${$t->name}; {$pad}// {$t->type}{$length}{$flags}\n";
  858. // can not do set as PEAR::DB table info doesnt support it.
  859. //if (substr($t->Type,0,3) == "set")
  860. // $sets[$t->Field] = "array".substr($t->Type,3);
  861. $body .= $this->derivedHookVar($t,strlen($p));
  862. }
  863. $body .= $this->derivedHookPostVar($defs);
  864. // THIS IS TOTALLY BORKED old FC creation
  865. // IT WILL BE REMOVED!!!!! in DataObjects 1.6
  866. // grep -r __clone * to find all it's uses
  867. // and replace them with $x = clone($y);
  868. // due to the change in the PHP5 clone design.
  869. $static = 'static';
  870. if ( substr(phpversion(),0,1) < 5) {
  871. $body .= "\n";
  872. $body .= " /* ZE2 compatibility trick*/\n";
  873. $body .= " function __clone() { return \$this;}\n";
  874. }
  875. // depricated - in here for BC...
  876. if (!empty($options['static_get'])) {
  877. // simple creation tools ! (static stuff!)
  878. $body .= "\n";
  879. $body .= " /* Static get */\n";
  880. $body .= " $static function staticGet(\$k,\$v=NULL) { " .
  881. "return DB_DataObject::staticGet('{$this->classname}',\$k,\$v = null); }\n";
  882. }
  883. // generate getter and setter methods
  884. $body .= $this->_generateGetters($input);
  885. $body .= $this->_generateSetters($input);
  886. $body .= $this->_generateLinkMethods($input);
  887. /*
  888. theoretically there is scope here to introduce 'list' methods
  889. based up 'xxxx_up' column!!! for heiracitcal trees..
  890. */
  891. // set methods
  892. //foreach ($sets as $k=>$v) {
  893. // $kk = strtoupper($k);
  894. // $body .=" function getSets{$k}() { return {$v}; }\n";
  895. //}
  896. if (!empty($options['generator_no_ini'])) {
  897. $def = $this->_generateDefinitionsTable(); // simplify this!?
  898. $body .= $this->_generateTableFunction($def['table']);
  899. $body .= $this->_generateKeysFunction($def['keys']);
  900. $body .= $this->_generateSequenceKeyFunction($def);
  901. $body .= $this->_generateDefaultsFunction($this->table, $def['table']);
  902. } else if (!empty($options['generator_add_defaults'])) {
  903. // I dont really like doing it this way (adding another option)
  904. // but it helps on older projects.
  905. $def = $this->_generateDefinitionsTable(); // simplify this!?
  906. $body .= $this->_generateDefaultsFunction($this->table,$def['table']);
  907. }
  908. $body .= $this->derivedHookFunctions($input);
  909. $body .= "\n /* the code above is auto generated do not remove the tag below */";
  910. $body .= "\n ###END_AUTOCODE\n";
  911. // stubs..
  912. if (!empty($options['generator_add_validate_stubs'])) {
  913. foreach($defs as $t) {
  914. if (!strlen(trim($t->name))) {
  915. continue;
  916. }
  917. $validate_fname = 'validate' . $this->getMethodNameFromColumnName($t->name);
  918. // dont re-add it..
  919. if (preg_match('/\s+function\s+' . $validate_fname . '\s*\(/i', $input)) {
  920. continue;
  921. }
  922. $body .= "\n function {$validate_fname}()\n {\n return false;\n }\n";
  923. }
  924. }
  925. $foot .= "}\n";
  926. $full = $head . $body . $foot;
  927. if (!$input) {
  928. return $full;
  929. }
  930. if (!preg_match('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n)/s',$input)) {
  931. return $full;
  932. }
  933. if (!preg_match('/(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',$input)) {
  934. return $full;
  935. }
  936. /* this will only replace extends DB_DataObject by default,
  937. unless use set generator_class_rewrite to ANY or a name*/
  938. $class_rewrite = 'DB_DataObject';
  939. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  940. if (empty($options['generator_class_rewrite']) || !($class_rewrite = $options['generator_class_rewrite'])) {
  941. $class_rewrite = 'DB_DataObject';
  942. }
  943. if ($class_rewrite == 'ANY') {
  944. $class_rewrite = '[a-z_]+';
  945. }
  946. $input = preg_replace(
  947. '/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' .$class_rewrite . '\s*(\n|\r\n)\{(\n|\r\n)/si',
  948. "\nclass {$this->classname} extends {$this->_extends} \n{\n",
  949. $input);
  950. $ret = preg_replace(
  951. '/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',
  952. $body,$input);
  953. if (!strlen($ret)) {
  954. return PEAR::raiseError(
  955. "PREG_REPLACE failed to replace body, - you probably need to set these in your php.ini\n".
  956. "pcre.backtrack_limit=1000000\n".
  957. "pcre.recursion_limit=1000000\n"
  958. ,null, PEAR_ERROR_DIE);
  959. }
  960. return $ret;
  961. }
  962. /**
  963. * hook to add extra methods to all classes
  964. *
  965. * called once for each class, use with $this->table and
  966. * $this->_definitions[$this->table], to get data out of the current table,
  967. * use it to add extra methods to the default classes.
  968. *
  969. * @access public
  970. * @return string added to class eg. functions.
  971. */
  972. function derivedHookFunctions($input = "")
  973. {
  974. // This is so derived generator classes can generate functions
  975. // It MUST NOT be changed here!!!
  976. return "";
  977. }
  978. /**
  979. * hook for var lines
  980. * called each time a var line is generated, override to add extra var
  981. * lines
  982. *
  983. * @param object t containing type,len,flags etc. from tableInfo call
  984. * @param int padding number of spaces
  985. * @access public
  986. * @return string added to class eg. functions.
  987. */
  988. function derivedHookVar(&$t,$padding)
  989. {
  990. // This is so derived generator classes can generate variabels
  991. // It MUST NOT be changed here!!!
  992. return "";
  993. }
  994. /**
  995. * hook for after var lines (
  996. * called at the end of the output of var line have generated, override to add extra var
  997. * lines
  998. *
  999. * @param array cols containing array of objects with type,len,flags etc. from tableInfo call
  1000. * @access public
  1001. * @return string added to class eg. functions.
  1002. */
  1003. function derivedHookPostVar($t)
  1004. {
  1005. // This is so derived generator classes can generate variabels
  1006. // It MUST NOT be changed here!!!
  1007. return "";
  1008. }
  1009. /**
  1010. * hook to add extra page-level (in terms of phpDocumentor) DocBlock
  1011. *
  1012. * called once for each class, use it add extra page-level docs
  1013. * @access public
  1014. * @return string added to class eg. functions.
  1015. */
  1016. function derivedHookPageLevelDocBlock() {
  1017. return '';
  1018. }
  1019. /**
  1020. * hook to add extra doc block (in terms of phpDocumentor) to extend string
  1021. *
  1022. * called once for each class, use it add extra comments to extends
  1023. * string (require_once...)
  1024. * @access public
  1025. * @return string added to class eg. functions.
  1026. */
  1027. function derivedHookExtendsDocBlock() {
  1028. return '';
  1029. }
  1030. /**
  1031. * hook to add extra class level DocBlock (in terms of phpDocumentor)
  1032. *
  1033. * called once for each class, use it add extra comments to class
  1034. * string (require_once...)
  1035. * @access public
  1036. * @return string added to class eg. functions.
  1037. */
  1038. function derivedHookClassDocBlock() {
  1039. return '';
  1040. }
  1041. /**
  1042. /**
  1043. * getProxyFull - create a class definition on the fly and instantate it..
  1044. *
  1045. * similar to generated files - but also evals the class definitoin code.
  1046. *
  1047. *
  1048. * @param string database name
  1049. * @param string table name of table to create proxy for.
  1050. *
  1051. *
  1052. * @return object Instance of class. or PEAR Error
  1053. * @access public
  1054. */
  1055. function getProxyFull($database,$table)
  1056. {
  1057. if ($err = $this->fillTableSchema($database,$table)) {
  1058. return $err;
  1059. }
  1060. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  1061. $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
  1062. $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
  1063. $this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
  1064. $classname = $this->classname = $this->getClassNameFromTableName($this->table);
  1065. $out = $this->_generateClassTable();
  1066. //echo $out;
  1067. eval('?>'.$out);
  1068. return new $classname;
  1069. }
  1070. /**
  1071. * fillTableSchema - set the database schema on the fly
  1072. *
  1073. *
  1074. *
  1075. * @param string database name
  1076. * @param string table name of table to create schema info for
  1077. *
  1078. * @return none | PEAR::error()
  1079. * @access public
  1080. */
  1081. function fillTableSchema($database,$table)
  1082. {
  1083. global $_DB_DATAOBJECT;
  1084. // a little bit of sanity testing.
  1085. if ((false !== strpos($database,"'")) || (false !== strpos($database,";"))) {
  1086. return PEAR::raiseError("Error: Database name contains a quote or semi-colon", null, PEAR_ERROR_DIE);
  1087. }
  1088. $this->_database = $database;
  1089. $this->_connect();
  1090. $table = trim($table);
  1091. // a little bit of sanity testing.
  1092. if ((false !== strpos($table,"'")) || (false !== strpos($table,";"))) {
  1093. return PEAR::raiseError("Error: Table contains a quote or semi-colon", null, PEAR_ERROR_DIE);
  1094. }
  1095. $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
  1096. $options = PEAR::getStaticProperty('DB_DataObject','options');
  1097. $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
  1098. $is_MDB2 = ($db_driver != 'DB') ? true : false;
  1099. if (!$is_MDB2) {
  1100. // try getting a list of schema tables first. (postgres)
  1101. $__DB->expectError(DB_ERROR_UNSUPPORTED);
  1102. $this->tables = $__DB->getListOf('schema.tables');
  1103. $__DB->popExpect();
  1104. } else {
  1105. /**
  1106. * set portability and some modules to fetch the informations
  1107. */
  1108. $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
  1109. $__DB->loadModule('Manager');
  1110. $__DB->loadModule('Reverse');
  1111. }
  1112. $quotedTable = !empty($options['quote_identifiers_tableinfo']) ?
  1113. $__DB->quoteIdentifier($table) : $table;
  1114. if (!$is_MDB2) {
  1115. $defs = $__DB->tableInfo($quotedTable);
  1116. } else {
  1117. $defs = $__DB->reverse->tableInfo($quotedTable);
  1118. if (PEAR::isError($defs)) {
  1119. return $defs;
  1120. }
  1121. foreach ($defs as $k => $v) {
  1122. if (!isset($defs[$k]['length'])) {
  1123. continue;
  1124. }
  1125. $defs[$k]['len'] = $defs[$k]['length'];
  1126. }
  1127. }
  1128. if (PEAR::isError($defs)) {
  1129. return $defs;
  1130. }
  1131. if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
  1132. $this->debug("getting def for $database/$table",'fillTable');
  1133. $this->debug(print_r($defs,true),'defs');
  1134. }
  1135. // cast all definitions to objects - as we deal with that better.
  1136. foreach($defs as $def) {
  1137. if (is_array($def)) {
  1138. $this->_definitions[$table][] = (object) $def;
  1139. }
  1140. }
  1141. $this->table = trim($table);
  1142. $ret = $this->_generateDefinitionsTable();
  1143. $_DB_DATAOBJECT['INI'][$database][$table] = $ret['table'];
  1144. $_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys'];
  1145. return false;
  1146. }
  1147. /**
  1148. * Generate getter methods for class definition
  1149. *
  1150. * @param string $input Existing class contents
  1151. * @return string
  1152. * @access public
  1153. */
  1154. function _generateGetters($input)
  1155. {
  1156. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  1157. $getters = '';
  1158. // only generate if option is set to true
  1159. if (empty($options['generate_getters'])) {
  1160. return '';
  1161. }
  1162. // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
  1163. $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
  1164. $getters .= "\n\n";
  1165. $defs = $this->_definitions[$this->table];
  1166. // loop through properties and create getter methods
  1167. foreach ($defs = $defs as $t) {
  1168. // build mehtod name
  1169. $methodName = 'get' . $this->getMethodNameFromColumnName($t->name);
  1170. if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
  1171. continue;
  1172. }
  1173. $getters .= " /**\n";
  1174. $getters .= " * Getter for \${$t->name}\n";
  1175. $getters .= " *\n";
  1176. $getters .= (stristr($t->flags, 'multiple_key')) ? " * @return object\n"
  1177. : " * @return {$t->type}\n";
  1178. $getters .= " * @access public\n";
  1179. $getters .= " */\n";
  1180. $getters .= (substr(phpversion(),0,1) > 4) ? ' public '
  1181. : ' ';
  1182. $getters .= "function $methodName() {\n";
  1183. $getters .= " return \$this->{$t->name};\n";
  1184. $getters .= " }\n\n";
  1185. }
  1186. return $getters;
  1187. }
  1188. /**
  1189. * Generate link setter/getter methods for class definition
  1190. *
  1191. * @param string Existing class contents
  1192. * @return string
  1193. * @access public
  1194. */
  1195. function _generateLinkMethods($input)
  1196. {
  1197. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  1198. $setters = '';
  1199. // only generate if option is set to true
  1200. // generate_link_methods true::
  1201. if (empty($options['generate_link_methods'])) {
  1202. //echo "skip lm? - not set";
  1203. return '';
  1204. }
  1205. if (empty($this->_fkeys)) {
  1206. // echo "skip lm? - fkyes empty";
  1207. return '';
  1208. }
  1209. if (empty($this->_fkeys[$this->table])) {
  1210. //echo "skip lm? - no fkeys for {$this->table}";
  1211. return '';
  1212. }
  1213. // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
  1214. $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
  1215. $setters .= "\n";
  1216. $defs = $this->_fkeys[$this->table];
  1217. // $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3];
  1218. // loop through properties and create setter methods
  1219. foreach ($defs as $k => $info) {
  1220. // build mehtod name
  1221. $methodName = is_callable($options['generate_link_methods']) ?
  1222. $options['generate_link_methods']($k) : $k;
  1223. if (!strlen(trim($k)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
  1224. continue;
  1225. }
  1226. $setters .= " /**\n";
  1227. $setters .= " * Getter / Setter for \${$k}\n";
  1228. $setters .= " *\n";
  1229. $setters .= " * @param mixed (optional) value to assign\n";
  1230. $setters .= " * @access public\n";
  1231. $setters .= " */\n";
  1232. $setters .= (substr(phpversion(),0,1) > 4) ? ' public '
  1233. : ' ';
  1234. $setters .= "function $methodName() {\n";
  1235. $setters .= " return \$this->link('$k', func_get_args());\n";
  1236. $setters .= " }\n\n";
  1237. }
  1238. return $setters;
  1239. }
  1240. /**
  1241. * Generate setter methods for class definition
  1242. *
  1243. * @param string Existing class contents
  1244. * @return string
  1245. * @access public
  1246. */
  1247. function _generateSetters($input)
  1248. {
  1249. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  1250. $setters = '';
  1251. // only generate if option is set to true
  1252. if (empty($options['generate_setters'])) {
  1253. return '';
  1254. }
  1255. // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
  1256. $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
  1257. $setters .= "\n";
  1258. $defs = $this->_definitions[$this->table];
  1259. // loop through properties and create setter methods
  1260. foreach ($defs = $defs as $t) {
  1261. // build mehtod name
  1262. $methodName = 'set' . $this->getMethodNameFromColumnName($t->name);
  1263. if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
  1264. continue;
  1265. }
  1266. $setters .= " /**\n";
  1267. $setters .= " * Setter for \${$t->name}\n";
  1268. $setters .= " *\n";
  1269. $setters .= " * @param mixed input value\n";
  1270. $setters .= " * @access public\n";
  1271. $setters .= " */\n";
  1272. $setters .= (substr(phpversion(),0,1) > 4) ? ' public '
  1273. : ' ';
  1274. $setters .= "function $methodName(\$value) {\n";
  1275. $setters .= " \$this->{$t->name} = \$value;\n";
  1276. $setters .= " }\n\n";
  1277. }
  1278. return $setters;
  1279. }
  1280. /**
  1281. * Generate table Function - used when generator_no_ini is set.
  1282. *
  1283. * @param array table array.
  1284. * @return string
  1285. * @access public
  1286. */
  1287. function _generateTableFunction($def)
  1288. {
  1289. $defines = explode(',','INT,STR,DATE,TIME,BOOL,TXT,BLOB,NOTNULL,MYSQLTIMESTAMP');
  1290. $ret = "\n" .
  1291. " function table()\n" .
  1292. " {\n" .
  1293. " return array(\n";
  1294. foreach($def as $k=>$v) {
  1295. $str = '0';
  1296. foreach($defines as $dn) {
  1297. if ($v & constant('DB_DATAOBJECT_' . $dn)) {
  1298. $str .= ' + DB_DATAOBJECT_' . $dn;
  1299. }
  1300. }
  1301. if (strlen($str) > 1) {
  1302. $str = substr($str,3); // strip the 0 +
  1303. }
  1304. // hopefully addslashes is good enough here!!!
  1305. $ret .= ' \''.addslashes($k).'\' => ' . $str . ",\n";
  1306. }
  1307. return $ret . " );\n" .
  1308. " }\n";
  1309. }
  1310. /**
  1311. * Generate keys Function - used generator_no_ini is set.
  1312. *
  1313. * @param array keys array.
  1314. * @return string
  1315. * @access public
  1316. */
  1317. function _generateKeysFunction($def)
  1318. {
  1319. $ret = "\n" .
  1320. " function keys()\n" .
  1321. " {\n" .
  1322. " return array(";
  1323. foreach($def as $k=>$type) {
  1324. // hopefully addslashes is good enough here!!!
  1325. $ret .= '\''.addslashes($k).'\', ';
  1326. }
  1327. $ret = preg_replace('#, $#', '', $ret);
  1328. return $ret . ");\n" .
  1329. " }\n";
  1330. }
  1331. /**
  1332. * Generate sequenceKey Function - used generator_no_ini is set.
  1333. *
  1334. * @param array table and key definition.
  1335. * @return string
  1336. * @access public
  1337. */
  1338. function _generateSequenceKeyFunction($def)
  1339. {
  1340. //print_r($def);
  1341. // DB_DataObject::debugLevel(5);
  1342. global $_DB_DATAOBJECT;
  1343. // print_r($def);
  1344. $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
  1345. $realkeys = $def['keys'];
  1346. $keys = array_keys($realkeys);
  1347. $usekey = isset($keys[0]) ? $keys[0] : false;
  1348. $table = $def['table'];
  1349. $seqname = false;
  1350. $ar = array(false,false,false);
  1351. if ($usekey !== false) {
  1352. if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
  1353. $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
  1354. if (strpos($usekey,':') !== false) {
  1355. list($usekey,$seqname) = explode(':',$usekey);
  1356. }
  1357. }
  1358. if (in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) &&
  1359. ($table[$usekey] & DB_DATAOBJECT_INT) &&
  1360. isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
  1361. ) {
  1362. // use native sequence keys.
  1363. $ar = array($usekey,true,$seqname);
  1364. } else {
  1365. // use generated sequence keys..
  1366. if ($table[$usekey] & DB_DATAOBJECT_INT) {
  1367. $ar = array($usekey,false,$seqname);
  1368. }
  1369. }
  1370. }
  1371. $ret = "\n" .
  1372. " function sequenceKey() // keyname, use native, native name\n" .
  1373. " {\n" .
  1374. " return array(";
  1375. foreach($ar as $v) {
  1376. switch (gettype($v)) {
  1377. case 'boolean':
  1378. $ret .= ($v ? 'true' : 'false') . ', ';
  1379. break;
  1380. case 'string':
  1381. $ret .= "'" . $v . "', ";
  1382. break;
  1383. default: // eak
  1384. $ret .= "null, ";
  1385. }
  1386. }
  1387. $ret = preg_replace('#, $#', '', $ret);
  1388. return $ret . ");\n" .
  1389. " }\n";
  1390. }
  1391. /**
  1392. * Generate defaults Function - used generator_add_defaults or generator_no_ini is set.
  1393. * Only supports mysql and mysqli ... welcome ideas for more..
  1394. *
  1395. *
  1396. * @param array table and key definition.
  1397. * @return string
  1398. * @access public
  1399. */
  1400. function _generateDefaultsFunction($table,$defs)
  1401. {
  1402. $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
  1403. if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
  1404. return; // cant handle non-mysql introspection for defaults.
  1405. }
  1406. $options = PEAR::getStaticProperty('DB_DataObject','options');
  1407. $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
  1408. $method = $db_driver == 'DB' ? 'getAll' : 'queryAll';
  1409. $res = $__DB->$method('DESCRIBE ' . $table,DB_FETCHMODE_ASSOC);
  1410. $defaults = array();
  1411. foreach($res as $ar) {
  1412. // this is initially very dumb... -> and it may mess up..
  1413. $type = $defs[$ar['Field']];
  1414. switch (true) {
  1415. case (is_null( $ar['Default'])):
  1416. $defaults[$ar['Field']] = 'null';
  1417. break;
  1418. case ($type & DB_DATAOBJECT_DATE):
  1419. case ($type & DB_DATAOBJECT_TIME):
  1420. case ($type & DB_DATAOBJECT_MYSQLTIMESTAMP): // not supported yet..
  1421. break;
  1422. case ($type & DB_DATAOBJECT_BOOL):
  1423. $defaults[$ar['Field']] = (int)(boolean) $ar['Default'];
  1424. break;
  1425. case ($type & DB_DATAOBJECT_STR):
  1426. $defaults[$ar['Field']] = "'" . addslashes($ar['Default']) . "'";
  1427. break;
  1428. default: // hopefully eveything else... - numbers etc.
  1429. if (!strlen($ar['Default'])) {
  1430. continue;
  1431. }
  1432. if (is_numeric($ar['Default'])) {
  1433. $defaults[$ar['Field']] = $ar['Default'];
  1434. }
  1435. break;
  1436. }
  1437. //var_dump(array($ar['Field'], $ar['Default'], $defaults[$ar['Field']]));
  1438. }
  1439. if (empty($defaults)) {
  1440. return;
  1441. }
  1442. $ret = "\n" .
  1443. " function defaults() // column default values \n" .
  1444. " {\n" .
  1445. " return array(\n";
  1446. foreach($defaults as $k=>$v) {
  1447. $ret .= ' \''.addslashes($k).'\' => ' . $v . ",\n";
  1448. }
  1449. return $ret . " );\n" .
  1450. " }\n";
  1451. }
  1452. }