GS_DataObject.php 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * GS DB object abstraction
  18. *
  19. * @package GNUsocial
  20. * @author Diogo Cordeiro <diogo@fc.up.pt>
  21. * @copyright 2010-2019 Free Software Foundation, Inc http://www.fsf.org
  22. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  23. */
  24. use Doctrine\DBAL\Connection;
  25. use Doctrine\DBAL\DriverManager;
  26. defined('GNUSOCIAL') || die();
  27. /**
  28. * GNU social abstraction of the Database library
  29. *
  30. * @copyright 2010-2019 Free Software Foundation, Inc http://www.fsf.org
  31. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  32. */
  33. class GS_DataObject extends Connection
  34. {
  35. /**
  36. * Gets the DB object related to an object
  37. *
  38. * @access public
  39. * @return object The DB connection
  40. * @throws \Doctrine\DBAL\DBALException
  41. */
  42. public function getDatabaseConnection()
  43. {
  44. return DriverManager::getConnection($params, $config);
  45. }
  46. /**
  47. * Find the number of results from a simple query
  48. *
  49. * for example
  50. *
  51. * $object = new mytable();
  52. * $object->name = "fred";
  53. * echo $object->count();
  54. * echo $object->count(true); // dont use object vars.
  55. * echo $object->count('distinct mycol'); count distinct mycol.
  56. * echo $object->count('distinct mycol',true); // dont use object vars.
  57. * echo $object->count('distinct'); // count distinct id (eg. the primary key)
  58. *
  59. *
  60. * @param bool|string (optional)
  61. * (true|false => see below not on whereAddonly)
  62. * (string)
  63. * "DISTINCT" => does a distinct count on the tables 'key' column
  64. * otherwise => normally it counts primary keys - you can use
  65. * this to do things like $do->count('distinct mycol');
  66. *
  67. * @param bool $whereAddOnly (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
  68. * we will build the condition only using the whereAdd's. Default is to
  69. * build the condition using the object parameters as well.
  70. *
  71. * @access public
  72. * @return int
  73. */
  74. public function count(): int
  75. {
  76. global $_DB_DATAOBJECT;
  77. if (is_bool($countWhat)) {
  78. $whereAddOnly = $countWhat;
  79. }
  80. $t = clone($this);
  81. $items = $t->table();
  82. $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  83. if (!isset($t->_query)) {
  84. $this->raiseError(
  85. "You cannot do run count after you have run fetch()",
  86. DB_DATAOBJECT_ERROR_INVALIDARGS);
  87. return false;
  88. }
  89. $this->_connect();
  90. $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  91. if (!$whereAddOnly && $items) {
  92. $t->_build_condition($items);
  93. }
  94. $keys = $this->keys();
  95. if (empty($keys[0]) && (!is_string($countWhat) || (strtoupper($countWhat) == 'DISTINCT'))) {
  96. $this->raiseError(
  97. "You cannot do run count without keys - use \$do->count('id'), or use \$do->count('distinct id')';",
  98. DB_DATAOBJECT_ERROR_INVALIDARGS,PEAR_ERROR_DIE);
  99. return false;
  100. }
  101. $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName());
  102. $key_col = empty($keys[0]) ? '' : (($quoteIdentifiers ? $DB->quoteIdentifier($keys[0]) : $keys[0]));
  103. $as = ($quoteIdentifiers ? $DB->quoteIdentifier('DATAOBJECT_NUM') : 'DATAOBJECT_NUM');
  104. // support distinct on default keys.
  105. $countWhat = (strtoupper($countWhat) == 'DISTINCT') ?
  106. "DISTINCT {$table}.{$key_col}" : $countWhat;
  107. $countWhat = is_string($countWhat) ? $countWhat : "{$table}.{$key_col}";
  108. $r = $t->_query(
  109. "SELECT count({$countWhat}) as $as
  110. FROM $table {$t->_join} {$t->_query['condition']}");
  111. if (PEAR::isError($r)) {
  112. return false;
  113. }
  114. $result = $_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid];
  115. $l = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ORDERED);
  116. // free the results - essential on oracle.
  117. $t->free();
  118. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  119. $this->debug('Count returned '. $l[0] ,1);
  120. }
  121. return (int) $l[0];
  122. }
  123. /**
  124. * Deletes items from table which match current objects variables
  125. *
  126. * Returns the true on success
  127. *
  128. * for example
  129. *
  130. * Designed to be extended
  131. *
  132. * $object = new mytable();
  133. * $object->ID=123;
  134. * echo $object->delete(); // builds a conditon
  135. *
  136. * $object = new mytable();
  137. * $object->whereAdd('age > 12');
  138. * $object->limit(1);
  139. * $object->orderBy('age DESC');
  140. * $object->delete(true); // dont use object vars, use the conditions, limit and order.
  141. *
  142. * @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
  143. * we will build the condition only using the whereAdd's. Default is to
  144. * build the condition only using the object parameters.
  145. *
  146. * @access public
  147. * @return mixed Int (No. of rows affected) on success, false on failure, 0 on no data affected
  148. */
  149. public function delete($useWhere = false)
  150. {
  151. global $_DB_DATAOBJECT;
  152. // connect will load the config!
  153. $this->_connect();
  154. $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  155. $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  156. $extra_cond = ' ' . (isset($this->_query['order_by']) ? $this->_query['order_by'] : '');
  157. if (!$useWhere) {
  158. $keys = $this->keys();
  159. $this->_query = array(); // as it's probably unset!
  160. $this->_query['condition'] = ''; // default behaviour not to use where condition
  161. $this->_build_condition($this->table(),$keys);
  162. // if primary keys are not set then use data from rest of object.
  163. if (!$this->_query['condition']) {
  164. $this->_build_condition($this->table(),array(),$keys);
  165. }
  166. $extra_cond = '';
  167. }
  168. // don't delete without a condition
  169. if (($this->_query !== false) && $this->_query['condition']) {
  170. $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName());
  171. $sql = "DELETE ";
  172. // using a joined delete. - with useWhere..
  173. $sql .= (!empty($this->_join) && $useWhere) ?
  174. "{$table} FROM {$table} {$this->_join} " :
  175. "FROM {$table} ";
  176. $sql .= $this->_query['condition']. $extra_cond;
  177. // add limit..
  178. if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
  179. if (!isset($_DB_DATAOBJECT['CONFIG']['db_driver']) ||
  180. ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
  181. // pear DB
  182. $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);
  183. } else {
  184. // MDB2
  185. $DB->setLimit( $this->_query['limit_count'],$this->_query['limit_start']);
  186. }
  187. }
  188. $r = $this->_query($sql);
  189. if (PEAR::isError($r)) {
  190. $this->raiseError($r);
  191. return false;
  192. }
  193. if ($r < 1) {
  194. return 0;
  195. }
  196. $this->_clear_cache();
  197. return $r;
  198. } else {
  199. $this->raiseError("delete: No condition specifed for query", DB_DATAOBJECT_ERROR_NODATA);
  200. return false;
  201. }
  202. }
  203. /**
  204. * Get a result using key, value.
  205. *
  206. * for example
  207. * $object->get("ID",1234);
  208. * Returns Number of rows located (usually 1) for success,
  209. * and puts all the table columns into this classes variables
  210. *
  211. * see the fetch example on how to extend this.
  212. *
  213. * if no value is entered, it is assumed that $key is a value
  214. * and get will then use the first key in keys()
  215. * to obtain the key.
  216. *
  217. * @param string $k column
  218. * @param string $v value
  219. * @access public
  220. * @return int No. of rows
  221. */
  222. public function get($k = null, $v = null): int
  223. {
  224. global $_DB_DATAOBJECT;
  225. if (empty($_DB_DATAOBJECT['CONFIG'])) {
  226. DB_DataObject::_loadConfig();
  227. }
  228. $keys = array();
  229. if ($v === null) {
  230. $v = $k;
  231. $keys = $this->keys();
  232. if (!$keys) {
  233. $this->raiseError("No Keys available for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  234. return false;
  235. }
  236. $k = $keys[0];
  237. }
  238. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  239. $this->debug("$k $v " .print_r($keys,true), "GET");
  240. }
  241. if ($v === null) {
  242. $this->raiseError("No Value specified for get", DB_DATAOBJECT_ERROR_INVALIDARGS);
  243. return false;
  244. }
  245. $this->$k = $v;
  246. return $this->find(1);
  247. }
  248. public static function staticGet(string $class, string $k, $v = null)
  249. {
  250. $lclass = strtolower($class);
  251. global $_DB_DATAOBJECT;
  252. if (empty($_DB_DATAOBJECT['CONFIG'])) {
  253. DB_DataObject::_loadConfig();
  254. }
  255. $key = "$k:$v";
  256. if ($v === null) {
  257. $key = $k;
  258. }
  259. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  260. DB_DataObject::debug("$class $key","STATIC GET - TRY CACHE");
  261. }
  262. if (!empty($_DB_DATAOBJECT['CACHE'][$lclass][$key])) {
  263. return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
  264. }
  265. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  266. DB_DataObject::debug("$class $key","STATIC GET - NOT IN CACHE");
  267. }
  268. $obj = DB_DataObject::factory(substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix'])));
  269. if (PEAR::isError($obj)) {
  270. $dor = new DB_DataObject();
  271. $dor->raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS);
  272. $r = false;
  273. return $r;
  274. }
  275. if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) {
  276. $_DB_DATAOBJECT['CACHE'][$lclass] = array();
  277. }
  278. if (!$obj->get($k,$v)) {
  279. $dor = new DB_DataObject();
  280. $dor->raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA);
  281. $r = false;
  282. return $r;
  283. }
  284. $_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj;
  285. return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
  286. }
  287. /**
  288. * fetches next row into this objects var's
  289. *
  290. * returns 1 on success 0 on failure
  291. *
  292. *
  293. *
  294. * Example
  295. * $object = new mytable();
  296. * $object->name = "fred";
  297. * $object->find();
  298. * $store = array();
  299. * while ($object->fetch()) {
  300. * echo $this->ID;
  301. * $store[] = $object; // builds an array of object lines.
  302. * }
  303. *
  304. * to add features to a fetch
  305. * function fetch () {
  306. * $ret = parent::fetch();
  307. * $this->date_formated = date('dmY',$this->date);
  308. * return $ret;
  309. * }
  310. *
  311. * @access public
  312. * @return bool on success
  313. */
  314. public function fetch(): bool
  315. {
  316. global $_DB_DATAOBJECT;
  317. if (empty($_DB_DATAOBJECT['CONFIG'])) {
  318. DB_DataObject::_loadConfig();
  319. }
  320. if (empty($this->N)) {
  321. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  322. $this->debug("No data returned from FIND (eg. N is 0)","FETCH", 3);
  323. }
  324. return false;
  325. }
  326. if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]) ||
  327. !is_object($result = $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]))
  328. {
  329. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  330. $this->debug('fetched on object after fetch completed (no results found)');
  331. }
  332. return false;
  333. }
  334. $array = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ASSOC);
  335. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  336. $this->debug(serialize($array),"FETCH");
  337. }
  338. // fetched after last row..
  339. if ($array === null) {
  340. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  341. $t= explode(' ',microtime());
  342. $this->debug("Last Data Fetch'ed after " .
  343. ($t[0]+$t[1]- $_DB_DATAOBJECT['QUERYENDTIME'] ) .
  344. " seconds",
  345. "FETCH", 1);
  346. }
  347. // reduce the memory usage a bit... (but leave the id in, so count() works ok on it)
  348. unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  349. // we need to keep a copy of resultfields locally so toArray() still works
  350. // however we dont want to keep it in the global cache..
  351. if (!empty($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  352. $this->_resultFields = $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid];
  353. unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]);
  354. }
  355. // this is probably end of data!!
  356. //DB_DataObject::raiseError("fetch: no data returned", DB_DATAOBJECT_ERROR_NODATA);
  357. return false;
  358. }
  359. // make sure resultFields is always empty..
  360. $this->_resultFields = false;
  361. if (!isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  362. // note: we dont declare this to keep the print_r size down.
  363. $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]= array_flip(array_keys($array));
  364. }
  365. $replace = array('.', ' ');
  366. foreach($array as $k=>$v) {
  367. // use strpos as str_replace is slow.
  368. $kk = (strpos($k, '.') === false && strpos($k, ' ') === false) ?
  369. $k : str_replace($replace, '_', $k);
  370. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  371. $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
  372. }
  373. $this->$kk = $array[$k];
  374. }
  375. // set link flag
  376. $this->_link_loaded=false;
  377. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  378. $this->debug("{$this->tableName()} DONE", "fetchrow",2);
  379. }
  380. if (($this->_query !== false) && empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) {
  381. $this->_query = false;
  382. }
  383. return true;
  384. }
  385. /**
  386. * find results, either normal or crosstable
  387. *
  388. * for example
  389. *
  390. * $object = new mytable();
  391. * $object->ID = 1;
  392. * $object->find();
  393. *
  394. *
  395. * will set $object->N to number of rows, and expects next command to fetch rows
  396. * will return $object->N
  397. *
  398. * if an error occurs $object->N will be set to false and return value will also be false;
  399. * if numRows is not supported it will
  400. *
  401. *
  402. * @param bool $n Fetch first result
  403. * @access public
  404. * @return mixed (number of rows returned, or true if numRows fetching is not supported)
  405. */
  406. public function find($n = false)
  407. {
  408. global $_DB_DATAOBJECT;
  409. if ($this->_query === false) {
  410. $this->raiseError(
  411. "You cannot do two queries on the same object (copy it before finding)",
  412. DB_DATAOBJECT_ERROR_INVALIDARGS);
  413. return false;
  414. }
  415. if (empty($_DB_DATAOBJECT['CONFIG'])) {
  416. DB_DataObject::_loadConfig();
  417. }
  418. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  419. $this->debug($n, "find",1);
  420. }
  421. if (!strlen($this->tableName())) {
  422. // xdebug can backtrace this!
  423. trigger_error("NO \$__table SPECIFIED in class definition",E_USER_ERROR);
  424. }
  425. $this->N = 0;
  426. $query_before = $this->_query;
  427. $this->_build_condition($this->table()) ;
  428. $this->_connect();
  429. $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  430. $sql = $this->_build_select();
  431. foreach ($this->_query['unions'] as $union_ar) {
  432. $sql .= $union_ar[1] . $union_ar[0]->_build_select() . " \n";
  433. }
  434. $sql .= $this->_query['order_by'] . " \n";
  435. /* We are checking for method modifyLimitQuery as it is PEAR DB specific */
  436. if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) ||
  437. ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
  438. /* PEAR DB specific */
  439. if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
  440. $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);
  441. }
  442. } else {
  443. /* theoretically MDB2! */
  444. if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
  445. $DB->setLimit($this->_query['limit_count'],$this->_query['limit_start']);
  446. }
  447. }
  448. $err = $this->_query($sql);
  449. if (is_a($err,'PEAR_Error')) {
  450. return false;
  451. }
  452. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  453. $this->debug("CHECK autofetchd $n", "find", 1);
  454. }
  455. // find(true)
  456. $ret = $this->N;
  457. if (!$ret && !empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {
  458. // clear up memory if nothing found!?
  459. unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  460. }
  461. if ($n && $this->N > 0 ) {
  462. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  463. $this->debug("ABOUT TO AUTOFETCH", "find", 1);
  464. }
  465. $fs = $this->fetch();
  466. // if fetch returns false (eg. failed), then the backend doesnt support numRows (eg. ret=true)
  467. // - hence find() also returns false..
  468. $ret = ($ret === true) ? $fs : $ret;
  469. }
  470. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  471. $this->debug("DONE", "find", 1);
  472. }
  473. $this->_query = $query_before;
  474. return $ret;
  475. }
  476. /**
  477. * fetches all results as an array,
  478. *
  479. * return format is dependant on args.
  480. * if selectAdd() has not been called on the object, then it will add the correct columns to the query.
  481. *
  482. * A) Array of values (eg. a list of 'id')
  483. *
  484. * $x = DB_DataObject::factory('mytable');
  485. * $x->whereAdd('something = 1')
  486. * $ar = $x->fetchAll('id');
  487. * -- returns array(1,2,3,4,5)
  488. *
  489. * B) Array of values (not from table)
  490. *
  491. * $x = DB_DataObject::factory('mytable');
  492. * $x->whereAdd('something = 1');
  493. * $x->selectAdd();
  494. * $x->selectAdd('distinct(group_id) as group_id');
  495. * $ar = $x->fetchAll('group_id');
  496. * -- returns array(1,2,3,4,5)
  497. * *
  498. * C) A key=>value associative array
  499. *
  500. * $x = DB_DataObject::factory('mytable');
  501. * $x->whereAdd('something = 1')
  502. * $ar = $x->fetchAll('id','name');
  503. * -- returns array(1=>'fred',2=>'blogs',3=> .......
  504. *
  505. * D) array of objects
  506. * $x = DB_DataObject::factory('mytable');
  507. * $x->whereAdd('something = 1');
  508. * $ar = $x->fetchAll();
  509. *
  510. * E) array of arrays (for example)
  511. * $x = DB_DataObject::factory('mytable');
  512. * $x->whereAdd('something = 1');
  513. * $ar = $x->fetchAll(false,false,'toArray');
  514. *
  515. *
  516. * @param string|false $k key
  517. * @param string|false $v value
  518. * @param string|false $method method to call on each result to get array value (eg. 'toArray')
  519. * @access public
  520. * @return array format dependant on arguments, may be empty
  521. */
  522. function fetchAll($k= false, $v = false, $method = false)
  523. {
  524. // should it even do this!!!?!?
  525. if ($k !== false &&
  526. ( // only do this is we have not been explicit..
  527. empty($this->_query['data_select']) ||
  528. ($this->_query['data_select'] == '*')
  529. )
  530. ) {
  531. $this->selectAdd();
  532. $this->selectAdd($k);
  533. if ($v !== false) {
  534. $this->selectAdd($v);
  535. }
  536. }
  537. $this->find();
  538. $ret = array();
  539. while ($this->fetch()) {
  540. if ($v !== false) {
  541. $ret[$this->$k] = $this->$v;
  542. continue;
  543. }
  544. $ret[] = $k === false ?
  545. ($method == false ? clone($this) : $this->$method())
  546. : $this->$k;
  547. }
  548. return $ret;
  549. }
  550. /**
  551. * fetches a specific row into this object variables
  552. *
  553. * Not recommended - better to use fetch()
  554. *
  555. * Returns true on success
  556. *
  557. * @param int $row row
  558. * @access public
  559. * @return bool true on success
  560. */
  561. public function fetchRow($row = null): bool
  562. {
  563. global $_DB_DATAOBJECT;
  564. if (empty($_DB_DATAOBJECT['CONFIG'])) {
  565. $this->_loadConfig();
  566. }
  567. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  568. $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow",3);
  569. }
  570. if (!$this->tableName()) {
  571. $this->raiseError("fetchrow: No table", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  572. return false;
  573. }
  574. if ($row === null) {
  575. $this->raiseError("fetchrow: No row specified", DB_DATAOBJECT_ERROR_INVALIDARGS);
  576. return false;
  577. }
  578. if (!$this->N) {
  579. $this->raiseError("fetchrow: No results avaiable", DB_DATAOBJECT_ERROR_NODATA);
  580. return false;
  581. }
  582. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  583. $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow",3);
  584. }
  585. $result = $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
  586. $array = $result->fetchrow(DB_DATAOBJECT_FETCHMODE_ASSOC,$row);
  587. if (!is_array($array)) {
  588. $this->raiseError("fetchrow: No results available", DB_DATAOBJECT_ERROR_NODATA);
  589. return false;
  590. }
  591. $replace = array('.', ' ');
  592. foreach($array as $k => $v) {
  593. // use strpos as str_replace is slow.
  594. $kk = (strpos($k, '.') === false && strpos($k, ' ') === false) ?
  595. $k : str_replace($replace, '_', $k);
  596. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  597. $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
  598. }
  599. $this->$kk = $array[$k];
  600. }
  601. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  602. $this->debug("{$this->tableName()} DONE", "fetchrow", 3);
  603. }
  604. return true;
  605. }
  606. /**
  607. * Insert the current objects variables into the database
  608. *
  609. * Returns the ID of the inserted element (if auto increment or sequences are used.)
  610. *
  611. * for example
  612. *
  613. * Designed to be extended
  614. *
  615. * $object = new mytable();
  616. * $object->name = "fred";
  617. * echo $object->insert();
  618. *
  619. * @access public
  620. * @return mixed false on failure, int when auto increment or sequence used, otherwise true on success
  621. */
  622. public function insert()
  623. {
  624. global $_DB_DATAOBJECT;
  625. // we need to write to the connection (For nextid) - so us the real
  626. // one not, a copyied on (as ret-by-ref fails with overload!)
  627. if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  628. $this->_connect();
  629. }
  630. $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  631. $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  632. $items = $this->table();
  633. if (!$items) {
  634. $this->raiseError("insert:No table definition for {$this->tableName()}",
  635. DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  636. return false;
  637. }
  638. $options = $_DB_DATAOBJECT['CONFIG'];
  639. $datasaved = 1;
  640. $leftq = '';
  641. $rightq = '';
  642. $seqKeys = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()]) ?
  643. $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] :
  644. $this->sequenceKey();
  645. $key = isset($seqKeys[0]) ? $seqKeys[0] : false;
  646. $useNative = isset($seqKeys[1]) ? $seqKeys[1] : false;
  647. $seq = isset($seqKeys[2]) ? $seqKeys[2] : false;
  648. $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["phptype"];
  649. // nativeSequences or Sequences..
  650. // big check for using sequences
  651. if (($key !== false) && !$useNative) {
  652. if (!$seq) {
  653. $keyvalue = $DB->nextId($this->tableName());
  654. } else {
  655. $f = $DB->getOption('seqname_format');
  656. $DB->setOption('seqname_format','%s');
  657. $keyvalue = $DB->nextId($seq);
  658. $DB->setOption('seqname_format',$f);
  659. }
  660. if (PEAR::isError($keyvalue)) {
  661. $this->raiseError($keyvalue->toString(), DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  662. return false;
  663. }
  664. $this->$key = $keyvalue;
  665. }
  666. // if we haven't set disable_null_strings to "full"
  667. $ignore_null = !isset($options['disable_null_strings'])
  668. || !is_string($options['disable_null_strings'])
  669. || strtolower($options['disable_null_strings']) !== 'full' ;
  670. foreach($items as $k => $v) {
  671. // if we are using autoincrement - skip the column...
  672. if ($key && ($k == $key) && $useNative) {
  673. continue;
  674. }
  675. // Ignore INTEGERS which aren't set to a value - or empty string..
  676. if ( (!isset($this->$k) || ($v == 1 && $this->$k === ''))
  677. && $ignore_null
  678. ) {
  679. continue;
  680. }
  681. // dont insert data into mysql timestamps
  682. // use query() if you really want to do this!!!!
  683. if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
  684. continue;
  685. }
  686. if ($leftq) {
  687. $leftq .= ', ';
  688. $rightq .= ', ';
  689. }
  690. $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ') : "$k ");
  691. if (is_object($this->$k) && is_a($this->$k,'DB_DataObject_Cast')) {
  692. $value = $this->$k->toString($v,$DB);
  693. if (PEAR::isError($value)) {
  694. $this->raiseError($value->toString() ,DB_DATAOBJECT_ERROR_INVALIDARGS);
  695. return false;
  696. }
  697. $rightq .= $value;
  698. continue;
  699. }
  700. if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) {
  701. $rightq .= " NULL ";
  702. continue;
  703. }
  704. // DATE is empty... on a col. that can be null..
  705. // note: this may be usefull for time as well..
  706. if (!$this->$k &&
  707. (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) &&
  708. !($v & DB_DATAOBJECT_NOTNULL)) {
  709. $rightq .= " NULL ";
  710. continue;
  711. }
  712. if ($v & DB_DATAOBJECT_STR) {
  713. $rightq .= $this->_quote((string) (
  714. ($v & DB_DATAOBJECT_BOOL) ?
  715. // this is thanks to the braindead idea of postgres to
  716. // use t/f for boolean.
  717. (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) :
  718. $this->$k
  719. )) . " ";
  720. continue;
  721. }
  722. if (is_numeric($this->$k)) {
  723. $rightq .=" {$this->$k} ";
  724. continue;
  725. }
  726. /* flag up string values - only at debug level... !!!??? */
  727. if (is_object($this->$k) || is_array($this->$k)) {
  728. $this->debug('ODD DATA: ' .$k . ' ' . print_r($this->$k,true),'ERROR');
  729. }
  730. // at present we only cast to integers
  731. // - V2 may store additional data about float/int
  732. $rightq .= ' ' . intval($this->$k) . ' ';
  733. }
  734. // not sure why we let empty insert here.. - I guess to generate a blank row..
  735. if ($leftq || $useNative) {
  736. $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName());
  737. if (($dbtype == 'pgsql') && empty($leftq)) {
  738. $r = $this->_query("INSERT INTO {$table} DEFAULT VALUES");
  739. } else {
  740. $r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) ");
  741. }
  742. if (PEAR::isError($r)) {
  743. $this->raiseError($r);
  744. return false;
  745. }
  746. if ($r < 1) {
  747. return 0;
  748. }
  749. // now do we have an integer key!
  750. if ($key && $useNative) {
  751. switch ($dbtype) {
  752. case 'mysql':
  753. case 'mysqli':
  754. $method = "{$dbtype}_insert_id";
  755. $this->$key = $method(
  756. $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection
  757. );
  758. break;
  759. case 'mssql':
  760. // note this is not really thread safe - you should wrapp it with
  761. // transactions = eg.
  762. // $db->query('BEGIN');
  763. // $db->insert();
  764. // $db->query('COMMIT');
  765. $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
  766. $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne';
  767. $mssql_key = $DB->$method("SELECT @@IDENTITY");
  768. if (PEAR::isError($mssql_key)) {
  769. $this->raiseError($mssql_key);
  770. return false;
  771. }
  772. $this->$key = $mssql_key;
  773. break;
  774. case 'pgsql':
  775. if (!$seq) {
  776. $seq = $DB->getSequenceName(strtolower($this->tableName()));
  777. }
  778. $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
  779. $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne';
  780. $pgsql_key = $DB->$method("SELECT currval('".$seq . "')");
  781. if (PEAR::isError($pgsql_key)) {
  782. $this->raiseError($pgsql_key);
  783. return false;
  784. }
  785. $this->$key = $pgsql_key;
  786. break;
  787. case 'ifx':
  788. $this->$key = array_shift (
  789. ifx_fetch_row (
  790. ifx_query(
  791. "select DBINFO('sqlca.sqlerrd1') FROM systables where tabid=1",
  792. $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection,
  793. IFX_SCROLL
  794. ),
  795. "FIRST"
  796. )
  797. );
  798. break;
  799. }
  800. }
  801. if (isset($_DB_DATAOBJECT['CACHE'][strtolower(get_class($this))])) {
  802. $this->_clear_cache();
  803. }
  804. if ($key) {
  805. return $this->$key;
  806. }
  807. return true;
  808. }
  809. $this->raiseError("insert: No Data specifed for query", DB_DATAOBJECT_ERROR_NODATA);
  810. return false;
  811. }
  812. /**
  813. * joinAdd - adds another dataobject to this, building a joined query.
  814. *
  815. * example (requires links.ini to be set up correctly)
  816. * // get all the images for product 24
  817. * $i = new DataObject_Image();
  818. * $pi = new DataObjects_Product_image();
  819. * $pi->product_id = 24; // set the product id to 24
  820. * $i->joinAdd($pi); // add the product_image connectoin
  821. * $i->find();
  822. * while ($i->fetch()) {
  823. * // do stuff
  824. * }
  825. * // an example with 2 joins
  826. * // get all the images linked with products or productgroups
  827. * $i = new DataObject_Image();
  828. * $pi = new DataObject_Product_image();
  829. * $pgi = new DataObject_Productgroup_image();
  830. * $i->joinAdd($pi);
  831. * $i->joinAdd($pgi);
  832. * $i->find();
  833. * while ($i->fetch()) {
  834. * // do stuff
  835. * }
  836. *
  837. *
  838. * @param optional $obj object |array the joining object (no value resets the join)
  839. * If you use an array here it should be in the format:
  840. * array('local_column','remotetable:remote_column');
  841. * if remotetable does not have a definition, you should
  842. * use @ to hide the include error message..
  843. * array('local_column', $dataobject , 'remote_column');
  844. * if array has 3 args, then second is assumed to be the linked dataobject.
  845. *
  846. * @param optional $joinType string | array
  847. * 'LEFT'|'INNER'|'RIGHT'|'' Inner is default, '' indicates
  848. * just select ... from a,b,c with no join and
  849. * links are added as where items.
  850. *
  851. * If second Argument is array, it is assumed to be an associative
  852. * array with arguments matching below = eg.
  853. * 'joinType' => 'INNER',
  854. * 'joinAs' => '...'
  855. * 'joinCol' => ....
  856. * 'useWhereAsOn' => false,
  857. *
  858. * @param optional $joinAs string if you want to select the table as anther name
  859. * useful when you want to select multiple columsn
  860. * from a secondary table.
  861. * @param optional $joinCol string The column on This objects table to match (needed
  862. * if this table links to the child object in
  863. * multiple places eg.
  864. * user->friend (is a link to another user)
  865. * user->mother (is a link to another user..)
  866. *
  867. * optional 'useWhereAsOn' bool default false;
  868. * convert the where argments from the object being added
  869. * into ON arguments.
  870. *
  871. *
  872. * @return void
  873. * @access public
  874. * @author Stijn de Reede <sjr@gmx.co.uk>
  875. */
  876. public function joinAdd($obj = false, $joinType='INNER', $joinAs=false, $joinCol=false)//:void XXX PHP: Upgrade to PHP 7.1
  877. {
  878. }
  879. /**
  880. * Updates current objects variables into the database
  881. * uses the keys() to decide how to update
  882. * Returns the true on success
  883. *
  884. * for example
  885. *
  886. * $object = DB_DataObject::factory('mytable');
  887. * $object->get("ID",234);
  888. * $object->email="testing@test.com";
  889. * if(!$object->update())
  890. * echo "UPDATE FAILED";
  891. *
  892. * to only update changed items :
  893. * $dataobject->get(132);
  894. * $original = $dataobject; // clone/copy it..
  895. * $dataobject->setFrom($_POST);
  896. * if ($dataobject->validate()) {
  897. * $dataobject->update($original);
  898. * } // otherwise an error...
  899. *
  900. * performing global updates:
  901. * $object = DB_DataObject::factory('mytable');
  902. * $object->status = "dead";
  903. * $object->whereAdd('age > 150');
  904. * $object->update(DB_DATAOBJECT_WHEREADD_ONLY);
  905. *
  906. * @param object dataobject (optional) | DB_DATAOBJECT_WHEREADD_ONLY - used to only update changed items.
  907. * @access public
  908. * @return int rows affected or false on failure
  909. */
  910. public function update($dataObject = false): int
  911. {
  912. global $_DB_DATAOBJECT;
  913. // connect will load the config!
  914. $this->_connect();
  915. $original_query = $this->_query;
  916. $items = $this->table();
  917. // only apply update against sequence key if it is set?????
  918. $seq = $this->sequenceKey();
  919. if ($seq[0] !== false) {
  920. $keys = array($seq[0]);
  921. if (!isset($this->{$keys[0]}) && $dataObject !== true) {
  922. $this->raiseError("update: trying to perform an update without
  923. the key set, and argument to update is not
  924. DB_DATAOBJECT_WHEREADD_ONLY
  925. " . print_r(array('seq' => $seq, 'keys' => $keys), true), DB_DATAOBJECT_ERROR_INVALIDARGS);
  926. return false;
  927. }
  928. } else {
  929. $keys = $this->keys();
  930. }
  931. if (!$items) {
  932. $this->raiseError("update:No table definition for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  933. return false;
  934. }
  935. $datasaved = 1;
  936. $settings = '';
  937. $this->_connect();
  938. $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  939. $dbtype = $DB->dsn["phptype"];
  940. $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  941. $options = $_DB_DATAOBJECT['CONFIG'];
  942. $ignore_null = !isset($options['disable_null_strings'])
  943. || !is_string($options['disable_null_strings'])
  944. || strtolower($options['disable_null_strings']) !== 'full';
  945. foreach ($items as $k => $v) {
  946. // I think this is ignoring empty vlalues
  947. if ((!isset($this->$k) || ($v == 1 && $this->$k === ''))
  948. && $ignore_null
  949. ) {
  950. continue;
  951. }
  952. // ignore stuff thats
  953. // dont write things that havent changed..
  954. if (($dataObject !== false) && isset($dataObject->$k) && ($dataObject->$k === $this->$k)) {
  955. continue;
  956. }
  957. // - dont write keys to left.!!!
  958. if (in_array($k, $keys)) {
  959. continue;
  960. }
  961. // dont insert data into mysql timestamps
  962. // use query() if you really want to do this!!!!
  963. if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
  964. continue;
  965. }
  966. if ($settings) {
  967. $settings .= ', ';
  968. }
  969. $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
  970. if (is_object($this->$k) && is_a($this->$k, 'DB_DataObject_Cast')) {
  971. $value = $this->$k->toString($v, $DB);
  972. if (PEAR::isError($value)) {
  973. $this->raiseError($value->getMessage(), DB_DATAOBJECT_ERROR_INVALIDARG);
  974. return false;
  975. }
  976. $settings .= "$kSql = $value ";
  977. continue;
  978. }
  979. // special values ... at least null is handled...
  980. if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this, $k)) {
  981. $settings .= "$kSql = NULL ";
  982. continue;
  983. }
  984. // DATE is empty... on a col. that can be null..
  985. // note: this may be usefull for time as well..
  986. if (!$this->$k &&
  987. (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) &&
  988. !($v & DB_DATAOBJECT_NOTNULL)) {
  989. $settings .= "$kSql = NULL ";
  990. continue;
  991. }
  992. if ($v & DB_DATAOBJECT_STR) {
  993. $settings .= "$kSql = " . $this->_quote((string)(
  994. ($v & DB_DATAOBJECT_BOOL) ?
  995. // this is thanks to the braindead idea of postgres to
  996. // use t/f for boolean.
  997. (($this->$k === 'f') ? 0 : (int)(bool)$this->$k) :
  998. $this->$k
  999. )) . ' ';
  1000. continue;
  1001. }
  1002. if (is_numeric($this->$k)) {
  1003. $settings .= "$kSql = {$this->$k} ";
  1004. continue;
  1005. }
  1006. // at present we only cast to integers
  1007. // - V2 may store additional data about float/int
  1008. $settings .= "$kSql = " . intval($this->$k) . ' ';
  1009. }
  1010. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1011. $this->debug("got keys as " . serialize($keys), 3);
  1012. }
  1013. if ($dataObject !== true) {
  1014. $this->_build_condition($items, $keys);
  1015. } else {
  1016. // prevent wiping out of data!
  1017. if (empty($this->_query['condition'])) {
  1018. $this->raiseError("update: global table update not available
  1019. do \$do->whereAdd('1=1'); if you really want to do that.
  1020. ", DB_DATAOBJECT_ERROR_INVALIDARGS);
  1021. return false;
  1022. }
  1023. }
  1024. // echo " $settings, $this->condition ";
  1025. if ($settings && isset($this->_query) && $this->_query['condition']) {
  1026. $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName());
  1027. $r = $this->_query("UPDATE {$table} SET {$settings} {$this->_query['condition']} ");
  1028. // restore original query conditions.
  1029. $this->_query = $original_query;
  1030. if (PEAR::isError($r)) {
  1031. $this->raiseError($r);
  1032. return false;
  1033. }
  1034. if ($r < 1) {
  1035. return 0;
  1036. }
  1037. $this->_clear_cache();
  1038. return $r;
  1039. }
  1040. // restore original query conditions.
  1041. $this->_query = $original_query;
  1042. // if you manually specified a dataobject, and there where no changes - then it's ok..
  1043. if ($dataObject !== false) {
  1044. return true;
  1045. }
  1046. $this->raiseError(
  1047. "update: No Data specifed for query $settings , {$this->_query['condition']}",
  1048. DB_DATAOBJECT_ERROR_NODATA);
  1049. return false;
  1050. }
  1051. }