Generator.php 61 KB

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