Generated docs for classEditor.
[awl.git] / inc / DataUpdate.php
blob29f96a42ee1110655b003ee4e8dd900aae622472
1 <?php
2 /**
3 * Some functions and a base class to help with updating records.
5 * This subpackage provides some functions that are useful around single
6 * record database activities such as insert and update.
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
15 require_once('AWLUtilities.php');
16 require_once('PgQuery.php');
19 /**
20 * Build SQL INSERT/UPDATE statement from an associative array of fieldnames => values.
21 * @param array $obj The object of fieldnames => values.
22 * @param string $type The word "update" or something else (which implies "insert").
23 * @param string $tablename The name of the table being updated.
24 * @param string $where What the "WHERE ..." clause needs to be for an UPDATE statement.
25 * @param string $fprefix An optional string which all fieldnames in $assoc are prefixed with.
26 * @return string An SQL Update or Insert statement with all fields/values from the array.
28 function sql_from_object( $obj, $type, $tablename, $where, $fprefix = "" ) {
29 $fields = get_fields($tablename);
30 $update = strtolower($type) == "update";
31 if ( $update )
32 $sql = "UPDATE $tablename SET ";
33 else
34 $sql = "INSERT INTO $tablename (";
36 $flst = "";
37 $vlst = "";
38 foreach( $fields as $fn => $typ ) {
39 // $prefixed_fn = $fprefix . $fn;
40 dbg_error_log( "DataUpdate", ":sql_from_object: %s => %s (%s)", $fn, $typ, (isset($obj->{$fn})?$obj->{$fn}:"[undefined value]"));
41 if ( !isset($obj->{$fn}) && isset($obj->{"xxxx$fn"}) ) {
42 // Sometimes we will have prepended 'xxxx' to the field name so that the field
43 // name differs from the column name in the database.
44 $obj->{$fn} = $obj->{"xxxx$fn"};
46 if ( !isset($obj->{$fn}) ) continue;
47 $value = str_replace( "'", "''", str_replace("\\", "\\\\", $obj->{$fn}));
48 if ( $fn == "password" ) {
49 if ( $value == "******" || $value == "" ) continue;
50 if ( !preg_match('/\*[0-9a-z]+\*[0-9a-z{}]+/i', $value ) )
51 $value = (function_exists("session_salted_sha1")
52 ? session_salted_sha1($value)
53 : (function_exists('session_salted_md5')
54 ? session_salted_md5($value)
55 : md5($value)
59 if ( eregi("(time|date|interval)", $typ ) && $value == "" ) {
60 $value = "NULL";
62 else if ( eregi("bool", $typ) ) {
63 $value = ( $value == false || $value == "f" || $value == "off" || $value == "no" ? "FALSE"
64 : ( $value == true || $value == "t" || $value == "on" || $value == "yes" ? "TRUE"
65 : "NULL" ));
67 else if ( eregi("interval", $typ) ) {
68 $value = "'$value'::$typ";
70 else if ( eregi("^int", $typ) ) {
71 $value = ($value == '' || $value === null ? 'NULL' : intval( $value ));
73 else if ( eregi("^bit", $typ) ) {
74 $value = ($value == '' || $value === null ? 'NULL' : "'$value'");
76 else if ( eregi("(text|varchar)", $typ) ) {
77 $value = "'$value'";
79 else
80 $value = "'$value'::$typ";
82 if ( $update )
83 $flst .= ", $fn = $value";
84 else {
85 $flst .= ", $fn";
86 $vlst .= ", $value";
89 $flst = substr($flst,2);
90 $vlst = substr($vlst,2);
91 $sql .= $flst;
92 if ( $update ) {
93 $sql .= " $where; ";
95 else {
96 $sql .= ") VALUES( $vlst ); ";
98 return $sql;
103 * Build SQL INSERT/UPDATE statement from the $_POST associative array
104 * @param string $type The word "update" or something else (which implies "insert").
105 * @param string $tablename The name of the table being updated.
106 * @param string $where What the "WHERE ..." clause needs to be for an UPDATE statement.
107 * @param string $fprefix An optional string which all fieldnames in $assoc are prefixed with.
108 * @return string An SQL Update or Insert statement with all fields/values from the array.
110 function sql_from_post( $type, $tablename, $where, $fprefix = "" ) {
111 $fields = get_fields($tablename);
112 $update = strtolower($type) == "update";
113 if ( $update )
114 $sql = "UPDATE $tablename SET ";
115 else
116 $sql = "INSERT INTO $tablename (";
118 $flst = "";
119 $vlst = "";
120 foreach( $fields as $fn => $typ ) {
121 $fn = $fprefix . $fn;
122 dbg_error_log( "DataUpdate", ":sql_from_post: $fn => $typ (".(isset($_POST[$fn])?$_POST[$fn]:'##null##').")");
123 if ( !isset($_POST[$fn]) && isset($_POST["xxxx$fn"]) ) {
124 // Sometimes we will have prepended 'xxxx' to the field name so that the field
125 // name differs from the column name in the database.
126 $_POST[$fn] = $_POST["xxxx$fn"];
127 dbg_error_log( "DataUpdate", ":sql_from_post: xxxx$fn => $typ (".$_POST[$fn].")");
129 if ( !isset($_POST[$fn]) ) continue;
130 $value = str_replace( "'", "''", str_replace("\\", "\\\\", $_POST[$fn]));
131 if ( $fn == "password" ) {
132 if ( $value == "******" || $value == "" ) continue;
133 if ( !preg_match('/\*[0-9a-z]+\*[0-9a-z{}]+/i', $value ) )
134 $value = (function_exists("session_salted_sha1")
135 ? session_salted_sha1($value)
136 : (function_exists('session_salted_md5')
137 ? session_salted_md5($value)
138 : md5($value)
142 if ( eregi("(time|date|interval)", $typ ) && $value == "" ) {
143 $value = "NULL";
145 else if ( eregi("bool", $typ) ) {
146 $value = ( $value == "f" || $value == "off" ? "FALSE" : "TRUE" );
148 else if ( eregi("interval", $typ) ) {
149 $value = "'$value'::$typ";
151 else if ( eregi("^int", $typ) ) {
152 $value = ($value == '' || $value === null ? 'NULL' : intval( $value ));
154 else if ( eregi("^bit", $typ) ) {
155 $value = ($value == '' || $value === null ? 'NULL' : "'$value'");
157 else if ( eregi("(text|varchar)", $typ) ) {
158 $value = "'$value'";
160 else
161 $value = "'$value'::$typ";
163 if ( $update )
164 $flst .= ", $fn = $value";
165 else {
166 $flst .= ", $fn";
167 $vlst .= ", $value";
170 $flst = substr($flst,2);
171 $vlst = substr($vlst,2);
172 $sql .= $flst;
173 if ( $update ) {
174 $sql .= " $where; ";
176 else {
177 $sql .= ") VALUES( $vlst ); ";
179 return $sql;
184 * A Base class to use for records which will be read/written from the database.
185 * @package awl
187 class DBRecord
189 /**#@+
190 * @access private
193 * The database table that this record goes in
194 * @var string
196 var $Table;
199 * The field names for the record. The array index is the field name
200 * and the array value is the field type.
201 * @var array
203 var $Fields;
206 * The keys for the record as an array of key => value pairs
207 * @var array
209 var $Keys;
212 * The field values for the record
213 * @var object
215 var $Values;
218 * The type of database write we will want: either "update" or "insert"
219 * @var object
221 var $WriteType;
224 * A list of associated other tables.
225 * @var array of string
227 var $OtherTable;
230 * The field names for each of the other tables associated. The array index
231 * is the table name, the string is a list of field names (and perhaps aliases)
232 * to stuff into the target list for the SELECT.
233 * @var array of string
235 var $OtherTargets;
238 * An array of JOIN ... clauses. The first array index is the table name and the array value
239 * is the JOIN clause like "LEFT JOIN tn t1 USING (myforeignkey)".
240 * @var array of string
242 var $OtherJoin;
245 * An array of partial WHERE clauses. These will be combined (if present) with the key
246 * where clause on the main table.
247 * @var array of string
249 var $OtherWhere;
251 /**#@-*/
253 /**#@+
254 * @access public
257 * The mode we are in for any form
258 * @var object
260 var $EditMode;
262 /**#@-*/
265 * Really numbingly simple construction.
267 function DBRecord( ) {
268 dbg_error_log( "DBRecord", ":Constructor: called" );
269 $this->WriteType = "insert";
270 $this->EditMode = false;
271 $this->prefix = "";
272 $values = (object) array();
273 $this->Values = &$values;
277 * This will read the record from the database if it's available, and
278 * the $keys parameter is a non-empty array.
279 * @param string $table The name of the database table
280 * @param array $keys An associative array containing fieldname => value pairs for the record key.
282 function Initialise( $table, $keys = array() ) {
283 dbg_error_log( "DBRecord", ":Initialise: called" );
284 $this->Table = $table;
285 $this->Fields = get_fields($this->Table);
286 $this->Keys = $keys;
287 $this->WriteType = "insert";
291 * This will join an additional table to the maintained set
292 * @param string $table The name of the database table
293 * @param array $keys An associative array containing fieldname => value pairs for the record key.
294 * @param string $join A PostgreSQL join clause.
295 * @param string $prefix A field prefix to use for these fields to distinguish them from fields
296 * in other joined tables with the same name.
298 function AddTable( $table, $target_list, $join_clause, $and_where ) {
299 dbg_error_log( "DBRecord", ":AddTable: $table called" );
300 $this->OtherTable[] = $table;
301 $this->OtherTargets[$table] = $target_list;
302 $this->OtherJoin[$table] = $join_clause;
303 $this->OtherWhere[$table] = $and_where;
307 * This will assign $_POST values to the internal Values object for each
308 * field that exists in the Fields array.
310 function PostToValues( $prefix = "" ) {
311 foreach ( $this->Fields AS $fname => $ftype ) {
312 @dbg_error_log( "DBRecord", ":PostToValues: %s => %s", $fname, $_POST["$prefix$fname"] );
313 if ( isset($_POST["$prefix$fname"]) ) {
314 $this->Set($fname, $_POST["$prefix$fname"]);
315 @dbg_error_log( "DBRecord", ":PostToValues: %s => %s", $fname, $_POST["$prefix$fname"] );
321 * Builds a table join clause
322 * @return string A simple SQL target join clause excluding the primary table.
324 function _BuildJoinClause() {
325 $clause = "";
326 foreach( $this->OtherJoins AS $t => $join ) {
327 if ( ! preg_match( '/^\s*$/', $join ) ) {
328 $clause .= ( $clause == "" ? "" : " " ) . $join;
332 return $clause;
336 * Builds a field target list
337 * @return string A simple SQL target field list for each field, possibly including prefixes.
339 function _BuildFieldList() {
340 $list = "";
341 foreach( $this->Fields AS $fname => $ftype ) {
342 $list .= ( $list == "" ? "" : ", " );
343 $list .= "$fname" . ( $this->prefix == "" ? "" : " AS \"$this->prefix$fname\"" );
346 foreach( $this->OtherTargets AS $t => $targets ) {
347 if ( ! preg_match( '/^\s*$/', $targets ) ) {
348 $list .= ( $list == "" ? "" : ", " ) . $targets;
352 return $list;
356 * Builds a where clause to match the supplied keys
357 * @param boolean $overwrite_values Controls whether the data values for the key fields will be forced to match the key values
358 * @return string A simple SQL where clause, including the initial "WHERE", for each key / value.
360 function _BuildWhereClause($overwrite_values=false) {
361 $where = "";
362 foreach( $this->Keys AS $k => $v ) {
363 // At least assign the key fields...
364 if ( $overwrite_values ) $this->Values->{$k} = $v;
365 // And build the WHERE clause
366 $where .= ( $where == "" ? "WHERE " : " AND " );
367 $where .= "$k = " . qpg($v);
370 if ( isset($this->OtherWhere) && is_array($this->OtherWhere) ) {
371 foreach( $this->OtherWhere AS $t => $and_where ) {
372 if ( ! preg_match( '/^\s*$/', $and_where ) ) {
373 $where .= ( $where == "" ? "WHERE " : " AND " ) . $and_where;
378 return $where;
382 * Sets a single field in the record
383 * @param string $fname The name of the field to set the value for
384 * @param string $fval The value to set the field to
385 * @return mixed The new value of the field (i.e. $fval).
387 function Set($fname, $fval) {
388 dbg_error_log( "DBRecord", ":Set: %s => %s", $fname, $fval );
389 $this->Values->{$fname} = $fval;
390 return $fval;
394 * Returns a single field from the record
395 * @param string $fname The name of the field to set the value for
396 * @return mixed The current value of the field.
398 function Get($fname) {
399 @dbg_error_log( "DBRecord", ":Get: %s => %s", $fname, $this->Values->{$fname} );
400 return (isset($this->Values->{$fname}) ? $this->Values->{$fname} : null);
404 * Unsets a single field from the record
405 * @param string $fname The name of the field to unset the value for
406 * @return mixed The current value of the field.
408 function Undefine($fname) {
409 if ( !isset($this->Values->{$fname}) ) return null;
410 $current = $this->Values->{$fname};
411 dbg_error_log( 'DBRecord', ': Unset: %s =was> %s', $fname, $current );
412 unset($this->Values->{$fname});
413 return $current;
417 * To write the record to the database
418 * @return boolean Success.
420 function Write() {
421 dbg_error_log( "DBRecord", ":Write: %s record as %s.", $this->Table, $this->WriteType );
422 $sql = sql_from_object( $this->Values, $this->WriteType, $this->Table, $this->_BuildWhereClause(), $this->prefix );
423 $qry = new PgQuery($sql);
424 return $qry->Exec( "DBRecord", __LINE__, __FILE__ );
428 * To read the record from the database.
429 * If we don't have any keys then the record will be blank.
430 * @return boolean Whether we actually read a record.
432 function Read() {
433 $i_read_the_record = false;
434 $values = (object) array();
435 $this->EditMode = true;
436 $where = $this->_BuildWhereClause(true);
437 if ( "" != $where ) {
438 // $fieldlist = $this->_BuildFieldList();
439 $fieldlist = "*";
440 // $join = $this->_BuildJoinClause(true);
441 $sql = "SELECT $fieldlist FROM $this->Table $where";
442 $qry = new PgQuery($sql);
443 if ( $qry->Exec( "DBRecord", __LINE__, __FILE__ ) && $qry->rows > 0 ) {
444 $i_read_the_record = true;
445 $values = $qry->Fetch();
446 $this->EditMode = false; // Default to not editing if we read the record.
447 dbg_error_log( "DBRecord", ":Read: Read %s record from table.", $this->Table, $this->WriteType );
450 $this->Values = &$values;
451 $this->WriteType = ( $i_read_the_record ? "update" : "insert" );
452 dbg_error_log( "DBRecord", ":Read: Record %s write type is %s.", $this->Table, $this->WriteType );
453 return $i_read_the_record;