PageRenderTime 23ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/DataUpdate.php

https://gitlab.com/karora/awl
PHP | 391 lines | 293 code | 20 blank | 78 comment | 31 complexity | b2ae84399b3f9e72520db202fef6c23e MD5 | raw file
  1. <?php
  2. /**
  3. * Some functions and a base class to help with updating records.
  4. *
  5. * This subpackage provides some functions that are useful around single
  6. * record database activities such as insert and update.
  7. *
  8. * @package awl
  9. * @subpackage DataUpdate
  10. * @author Andrew McMillan <andrew@mcmillan.net.nz>
  11. * @copyright Catalyst IT Ltd, Morphoss Ltd <http://www.morphoss.com/>
  12. * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
  13. */
  14. require_once('AWLUtilities.php');
  15. require_once('AwlQuery.php');
  16. /**
  17. * Build SQL INSERT/UPDATE statement from an associative array of fieldnames => values.
  18. * @param array $obj The object of fieldnames => values.
  19. * @param string $type The word "update" or something else (which implies "insert").
  20. * @param string $tablename The name of the table being updated.
  21. * @param string $where What the "WHERE ..." clause needs to be for an UPDATE statement.
  22. * @param string $fprefix An optional string which all fieldnames in $assoc are prefixed with.
  23. * @return string An SQL Update or Insert statement with all fields/values from the array.
  24. */
  25. function sql_from_object( $obj, $type, $tablename, $where, $fprefix = "" ) {
  26. $fields = get_fields($tablename);
  27. $update = strtolower($type) == "update";
  28. if ( $update )
  29. $sql = "UPDATE $tablename SET ";
  30. else
  31. $sql = "INSERT INTO $tablename (";
  32. $flst = "";
  33. $vlst = "";
  34. foreach( $fields as $fn => $typ ) {
  35. // $prefixed_fn = $fprefix . $fn;
  36. dbg_error_log( "DataUpdate", ":sql_from_object: %s => %s (%s)", $fn, $typ, (isset($obj->{$fn})?$obj->{$fn}:"[undefined value]"));
  37. if ( !isset($obj->{$fn}) && isset($obj->{"xxxx$fn"}) ) {
  38. // Sometimes we will have prepended 'xxxx' to the field name so that the field
  39. // name differs from the column name in the database.
  40. $obj->{$fn} = $obj->{"xxxx$fn"};
  41. }
  42. if ( !isset($obj->{$fn}) ) continue;
  43. $value = $obj->{$fn};
  44. if ( $fn == "password" ) {
  45. if ( $value == "******" || $value == "" ) continue;
  46. if ( !preg_match('/^\*[0-9a-z+\/=]+\*({SSHA})?[0-9a-z+\/=]+$/i', $value ) ) {
  47. $value = (function_exists("session_salted_sha1")
  48. ? session_salted_sha1($value)
  49. : (function_exists('session_salted_md5')
  50. ? session_salted_md5($value)
  51. : md5($value)
  52. )
  53. );
  54. }
  55. }
  56. $value = str_replace( "'", "''", str_replace("\\", "\\\\", $value));
  57. if ( preg_match('{^(time|date|interval)}i', $typ ) && $value == "" ) {
  58. $value = "NULL";
  59. }
  60. else if ( preg_match('{^bool}i', $typ) ) {
  61. $value = ( $value == false || $value == "f" || $value == "off" || $value == "no" ? "FALSE"
  62. : ( $value == true || $value == "t" || $value == "on" || $value == "yes" ? "TRUE"
  63. : "NULL" ));
  64. }
  65. else if ( preg_match('{^interval}i', $typ) ) {
  66. $value = "'$value'::$typ";
  67. }
  68. else if ( preg_match('{^int}i', $typ) ) {
  69. $value = ($value == '' || $value === null ? 'NULL' : intval( $value ));
  70. }
  71. else if ( preg_match('{^bit}i', $typ) ) {
  72. $value = ($value == '' || $value === null ? 'NULL' : "'$value'");
  73. }
  74. else if ( preg_match('{^(text|varchar)}i', $typ) ) {
  75. $value = "'$value'";
  76. }
  77. else
  78. $value = "'$value'::$typ";
  79. if ( $update )
  80. $flst .= ", $fn = $value";
  81. else {
  82. $flst .= ", $fn";
  83. $vlst .= ", $value";
  84. }
  85. }
  86. $flst = substr($flst,2);
  87. $vlst = substr($vlst,2);
  88. $sql .= $flst;
  89. if ( $update ) {
  90. $sql .= " $where; ";
  91. }
  92. else {
  93. $sql .= ") VALUES( $vlst ); ";
  94. }
  95. return $sql;
  96. }
  97. /**
  98. * Build SQL INSERT/UPDATE statement from the $_POST associative array
  99. * @param string $type The word "update" or something else (which implies "insert").
  100. * @param string $tablename The name of the table being updated.
  101. * @param string $where What the "WHERE ..." clause needs to be for an UPDATE statement.
  102. * @param string $fprefix An optional string which all fieldnames in $assoc are prefixed with.
  103. * @return string An SQL Update or Insert statement with all fields/values from the array.
  104. */
  105. function sql_from_post( $type, $tablename, $where, $fprefix = "" ) {
  106. $fakeobject = (object) $_POST;
  107. return sql_from_object( $fakeobject, $type, $tablename, $where, $fprefix );
  108. }
  109. /**
  110. * A Base class to use for records which will be read/written from the database.
  111. * @package awl
  112. */
  113. class DBRecord
  114. {
  115. /**#@+
  116. * @access private
  117. */
  118. /**
  119. * The database table that this record goes in
  120. * @var string
  121. */
  122. var $Table;
  123. /**
  124. * The field names for the record. The array index is the field name
  125. * and the array value is the field type.
  126. * @var array
  127. */
  128. var $Fields;
  129. /**
  130. * The keys for the record as an array of key => value pairs
  131. * @var array
  132. */
  133. var $Keys;
  134. /**
  135. * The field values for the record
  136. * @var object
  137. */
  138. var $Values;
  139. /**
  140. * The type of database write we will want: either "update" or "insert"
  141. * @var object
  142. */
  143. var $WriteType;
  144. /**
  145. * A list of associated other tables.
  146. * @var array of string
  147. */
  148. var $OtherTable;
  149. /**
  150. * The field names for each of the other tables associated. The array index
  151. * is the table name, the string is a list of field names (and perhaps aliases)
  152. * to stuff into the target list for the SELECT.
  153. * @var array of string
  154. */
  155. var $OtherTargets;
  156. /**
  157. * An array of JOIN ... clauses. The first array index is the table name and the array value
  158. * is the JOIN clause like "LEFT JOIN tn t1 USING (myforeignkey)".
  159. * @var array of string
  160. */
  161. var $OtherJoin;
  162. /**
  163. * An array of partial WHERE clauses. These will be combined (if present) with the key
  164. * where clause on the main table.
  165. * @var array of string
  166. */
  167. var $OtherWhere;
  168. /**#@-*/
  169. /**#@+
  170. * @access public
  171. */
  172. /**
  173. * The mode we are in for any form
  174. * @var object
  175. */
  176. var $EditMode;
  177. /**#@-*/
  178. /**
  179. * Really numbingly simple construction.
  180. */
  181. function DBRecord( ) {
  182. dbg_error_log( "DBRecord", ":Constructor: called" );
  183. $this->WriteType = "insert";
  184. $this->EditMode = false;
  185. $this->prefix = "";
  186. $values = (object) array();
  187. $this->Values = &$values;
  188. }
  189. /**
  190. * This will read the record from the database if it's available, and
  191. * the $keys parameter is a non-empty array.
  192. * @param string $table The name of the database table
  193. * @param array $keys An associative array containing fieldname => value pairs for the record key.
  194. */
  195. function Initialise( $table, $keys = array() ) {
  196. dbg_error_log( "DBRecord", ":Initialise: called" );
  197. $this->Table = $table;
  198. $this->Fields = get_fields($this->Table);
  199. $this->Keys = $keys;
  200. $this->WriteType = "insert";
  201. }
  202. /**
  203. * This will join an additional table to the maintained set
  204. * @param string $table The name of the database table
  205. * @param array $keys An associative array containing fieldname => value pairs for the record key.
  206. * @param string $join A PostgreSQL join clause.
  207. * @param string $prefix A field prefix to use for these fields to distinguish them from fields
  208. * in other joined tables with the same name.
  209. */
  210. function AddTable( $table, $target_list, $join_clause, $and_where ) {
  211. dbg_error_log( "DBRecord", ":AddTable: $table called" );
  212. $this->OtherTable[] = $table;
  213. $this->OtherTargets[$table] = $target_list;
  214. $this->OtherJoin[$table] = $join_clause;
  215. $this->OtherWhere[$table] = $and_where;
  216. }
  217. /**
  218. * This will assign $_POST values to the internal Values object for each
  219. * field that exists in the Fields array.
  220. */
  221. function PostToValues( $prefix = "" ) {
  222. foreach ( $this->Fields AS $fname => $ftype ) {
  223. @dbg_error_log( "DBRecord", ":PostToValues: %s => %s", $fname, $_POST["$prefix$fname"] );
  224. if ( isset($_POST["$prefix$fname"]) ) {
  225. $this->Set($fname, $_POST["$prefix$fname"]);
  226. @dbg_error_log( "DBRecord", ":PostToValues: %s => %s", $fname, $_POST["$prefix$fname"] );
  227. }
  228. }
  229. }
  230. /**
  231. * Builds a table join clause
  232. * @return string A simple SQL target join clause excluding the primary table.
  233. */
  234. function _BuildJoinClause() {
  235. $clause = "";
  236. foreach( $this->OtherJoins AS $t => $join ) {
  237. if ( ! preg_match( '/^\s*$/', $join ) ) {
  238. $clause .= ( $clause == "" ? "" : " " ) . $join;
  239. }
  240. }
  241. return $clause;
  242. }
  243. /**
  244. * Builds a field target list
  245. * @return string A simple SQL target field list for each field, possibly including prefixes.
  246. */
  247. function _BuildFieldList() {
  248. $list = "";
  249. foreach( $this->Fields AS $fname => $ftype ) {
  250. $list .= ( $list == "" ? "" : ", " );
  251. $list .= "$fname" . ( $this->prefix == "" ? "" : " AS \"$this->prefix$fname\"" );
  252. }
  253. foreach( $this->OtherTargets AS $t => $targets ) {
  254. if ( ! preg_match( '/^\s*$/', $targets ) ) {
  255. $list .= ( $list == "" ? "" : ", " ) . $targets;
  256. }
  257. }
  258. return $list;
  259. }
  260. /**
  261. * Builds a where clause to match the supplied keys
  262. * @param boolean $overwrite_values Controls whether the data values for the key fields will be forced to match the key values
  263. * @return string A simple SQL where clause, including the initial "WHERE", for each key / value.
  264. */
  265. function _BuildWhereClause($overwrite_values=false) {
  266. $where = "";
  267. foreach( $this->Keys AS $k => $v ) {
  268. // At least assign the key fields...
  269. if ( $overwrite_values ) $this->Values->{$k} = $v;
  270. // And build the WHERE clause
  271. $where .= ( $where == '' ? 'WHERE ' : ' AND ' );
  272. $where .= $k . '=' . AwlQuery::quote($v);
  273. }
  274. if ( isset($this->OtherWhere) && is_array($this->OtherWhere) ) {
  275. foreach( $this->OtherWhere AS $t => $and_where ) {
  276. if ( ! preg_match( '/^\s*$/', $and_where ) ) {
  277. $where .= ($where == '' ? 'WHERE ' : ' AND (' ) . $and_where . ')';
  278. }
  279. }
  280. }
  281. return $where;
  282. }
  283. /**
  284. * Sets a single field in the record
  285. * @param string $fname The name of the field to set the value for
  286. * @param string $fval The value to set the field to
  287. * @return mixed The new value of the field (i.e. $fval).
  288. */
  289. function Set($fname, $fval) {
  290. dbg_error_log( "DBRecord", ":Set: %s => %s", $fname, $fval );
  291. $this->Values->{$fname} = $fval;
  292. return $fval;
  293. }
  294. /**
  295. * Returns a single field from the record
  296. * @param string $fname The name of the field to set the value for
  297. * @return mixed The current value of the field.
  298. */
  299. function Get($fname) {
  300. @dbg_error_log( "DBRecord", ":Get: %s => %s", $fname, $this->Values->{$fname} );
  301. return (isset($this->Values->{$fname}) ? $this->Values->{$fname} : null);
  302. }
  303. /**
  304. * Unsets a single field from the record
  305. * @param string $fname The name of the field to unset the value for
  306. * @return mixed The current value of the field.
  307. */
  308. function Undefine($fname) {
  309. if ( !isset($this->Values->{$fname}) ) return null;
  310. $current = $this->Values->{$fname};
  311. dbg_error_log( 'DBRecord', ': Unset: %s =was> %s', $fname, $current );
  312. unset($this->Values->{$fname});
  313. return $current;
  314. }
  315. /**
  316. * To write the record to the database
  317. * @return boolean Success.
  318. */
  319. function Write() {
  320. dbg_error_log( "DBRecord", ":Write: %s record as %s.", $this->Table, $this->WriteType );
  321. $sql = sql_from_object( $this->Values, $this->WriteType, $this->Table, $this->_BuildWhereClause(), $this->prefix );
  322. $qry = new AwlQuery($sql);
  323. return $qry->Exec( "DBRecord", __LINE__, __FILE__ );
  324. }
  325. /**
  326. * To read the record from the database.
  327. * If we don't have any keys then the record will be blank.
  328. * @return boolean Whether we actually read a record.
  329. */
  330. function Read() {
  331. $i_read_the_record = false;
  332. $values = (object) array();
  333. $this->EditMode = true;
  334. $where = $this->_BuildWhereClause(true);
  335. if ( "" != $where ) {
  336. // $fieldlist = $this->_BuildFieldList();
  337. $fieldlist = "*";
  338. // $join = $this->_BuildJoinClause(true);
  339. $sql = "SELECT $fieldlist FROM $this->Table $where";
  340. $qry = new AwlQuery($sql);
  341. if ( $qry->Exec( "DBRecord", __LINE__, __FILE__ ) && $qry->rows() > 0 ) {
  342. $i_read_the_record = true;
  343. $values = $qry->Fetch();
  344. $this->EditMode = false; // Default to not editing if we read the record.
  345. dbg_error_log( "DBRecord", ":Read: Read %s record from table.", $this->Table, $this->WriteType );
  346. }
  347. }
  348. $this->Values = &$values;
  349. $this->WriteType = ( $i_read_the_record ? "update" : "insert" );
  350. dbg_error_log( "DBRecord", ":Read: Record %s write type is %s.", $this->Table, $this->WriteType );
  351. return $i_read_the_record;
  352. }
  353. }