Odyssey
commonPhpFunctions.i
1 <?php
2 /* File: functions.php
3  * Purpose: There are multiple functions here that help out data.php.
4  * SPB-- created to reduce redundant code looping through resultsets and validating data.
5  */
6 
7 /* For running any single non-select queries. It rolls back if there are any errors.
8  * Parameters: String $sql-- SQL to run, Database $db_con-- database connection to run SQL with
9  * Returns: Array returnArray.sql-- there is only one sql to run but it wraps it in an array for consistency,
10  * Array returnArray.error-- there also (probably) be at most one error but wrapped in an array.
11  */
12 function runExecStatement($sql, $dbh)
13 {
14  $errors= array();
15  $sth = db_query($sql,$dbh);
16  $newError= trim(db_last_error());
17  if ($newError != "")
18  $errors[]= $newError;
19 
20  db_free_result($sth);
21 
22  return array("sql" => array($sql), "error" => $errors);
23 }
24 
25 /* For running a group of update, insert, or delete statements that must be all executed or none executed.
26  * Parameters: Array $sqls-- list of SQL to run, Database $db_con-- database connection to run SQL with
27  * Returns: Array returnArray.sql-- there is only one sql to run but it wraps it in an array for consistency,
28  * Array returnArray.error-- there also (probably) be at most one error but wrapped in an array.
29  */
30 function runTransaction($sqls, $dbh)
31 {
32  $errors= array();
33 
34  db_query("begin", $dbh);
35  foreach($sqls as $sql)
36  {
37  db_query($sql, $dbh);
38  $error= trim(db_last_error());
39  if ($error != "")
40  {
41  $errors[]= $error;
42  db_query("rollback", $dbh);
43  break;
44  }
45  }
46  db_query("commit", $dbh);
47 
48  return array("sql" => $sqls, "error" => $errors);
49 }
50 
51 /* For running a select statement where the result is needed.
52  * Parameters: String $sql-- select statement to run, Database $dbh-- database handle,
53  * Array $translationArray-- Associative array from the actual column name to the alias name in the corresponding grid to populate (anything returned by the query but not in this array is lost),
54  * Array (optional) $changeToBooleanArray-- Associative array from the actual column name to a value that is true (typically "Y"). If column is this value, then return true, else false
55  * Returns: Array returnArray.sql-- the SQL wrapped in an array, Array returnArray.error-- any errors from execution,
56  * Array returnArray.record-- dataset with column names from the $translationArray and values either 1) trimmed strings from the table column or 2) booleans if column in the booleanArray.
57  */
58 function runSelectStatement($sql, $dbh, $translationArray, $changeToBooleanArray= array())
59 {
60  $errors= array();
61  $sth = db_query($sql,$dbh);
62  $newError= trim(db_last_error());
63  $gridData= array();
64  if ($newError != "")
65  {
66  $errors[]= $newError;
67  db_free_result($sth);
68  }
69  else
70  {
71  $gridRow= array();
72  $i= 0;
73  while($dRecord = db_fetch_assoc($sth, $i++))
74  {
75  foreach($translationArray as $column => $value)
76  {
77  $columnValue= trim($dRecord[$column]);
78  if (isset($changeToBooleanArray[$column]))
79  $columnValue= $columnValue == $changeToBooleanArray[$column];
80  $gridRow[$value]= $columnValue;
81  }
82  $gridData[]= $gridRow;
83  }
84  db_free_result($sth);
85  }
86 
87  return array("sql" => array($sql), "error" => $errors, "record" => $gridData);
88 }
89 
90 // This function will return a map with key => value where key is the first column in the select statement and value is the second column in the select statement.
91 function runSelectStatementMap($sql, $dbh)
92 {
93  $errors= array();
94  $sth = db_query($sql,$dbh);
95  $newError= trim(db_last_error());
96  $gridData= array();
97  if ($newError != "")
98  {
99  $errors[]= $newError;
100  db_free_result($sth);
101  }
102  else
103  {
104  $gridRow= array();
105  $i= 0;
106  while($dRecord = db_fetch_row($sth, $i++))
107  {
108  $gridData[$dRecord[0]]= $dRecord[1];
109  }
110  db_free_result($sth);
111  }
112 
113  return array("sql" => array($sql), "error" => $errors, "record" => $gridData);
114 }
115 
116 
117 /* Function to truncate string to the maxlength and escape characters that will fail Postgres. Copied from hcuScripts/hcuinc -> db_func.php
118  * Parameters: String $text-- string to prepare, Number (optional) $msize-- if specified, maxlength to truncate string at; else no limit ("text" postgres type)
119  * Returns: String $text-- string truncated at the max character amount and escaped for Postgres.
120  */
121 function prepSave($text, $msize=0)
122 {
123  if ($msize > 0)
124  $text = substr($text, 0, $msize);
125  $text = pg_escape_string(trim($text));
126  return $text;
127 }
128 
129 /* Parameters: String $text-- string to prepare
130  * Returns: String $text-- if "", text isn't a valid phone number so it is cleared; else, text is a valid phone number and changed to the format 123-456-7890
131  */
132 function cleanPhone($text)
133 {
134  $text= trim($text);
135  $normalPat= '/^[1-9]\d{2}-\d{3}-\d{4}$/'; // 123-456-7890
136  $pats= array();
137  $pats[]= '/^[1-9]\d{9}$/'; // 1234567890
138  $pats[]= '/^\(\s*[1-9]\d{2}\s*\)\s*\d{3}\s*-\s*\d{4}$/'; // (123 ) 456- 7890
139  $pats[]= '/^[1-9]\d{2}\s*-\s*\d{3}\s*-\s*\d{4}$/'; // 123- 456 - 7890
140  $pats[]= '/^[1-9]\d{2}\s*\.\s*\d{3}\s*\.\s*\d{4}$/'; // 123. 456 . 7890
141  $patReplace= '/[\s\.\-\(\)]*/';
142  if (preg_match($normalPat, $text))
143  return $text;
144  if (preg_match($pats[0], $text))
145  return substr($text, 0,3) . "-" . substr($text, 3,3) . "-" . substr($text, 6,4);
146  if (preg_match($pats[1], $text) || preg_match($pats[2], $text) || preg_match($pats[3], $text))
147  {
148  $text= preg_replace($patReplace, "", $text);
149  $text= substr($text, 0,3) . "-" . substr($text, 3,3) . "-" . substr($text, 6,4);
150  return $text;
151  }
152 
153  return "";
154 }
155 
156 /* Parameters: String $text-- string to prepare, Number (optional) $maxlength-- if set, truncate email to maxlength first and test regex
157  * Returns: String $text-- if "", text isn't a valid email so it is cleared; else, truncated text is a valid email and returned
158  */
159 function cleanEmail($text, $maxlength=0)
160 {
161  $text= trim(prepSave($text, $maxlength));
162 
163  /* This cannot contain any whitespace. Only one @ symbol is valid in the email.
164  * Periods cannot be directly behind or in front of the @. They must separate non-whitespace characters and cannot appear twice in a row.
165  * There must be at least one period after the @ symbol. The domain extension (after the last period) has to be more than two characters.
166  */
167  $pattern= '/^[^\s@\.]+(\.[^\s@\.]+)*@[^\s@\.]+(\.[^\s@\.]+)*\.\w{2,}$/'; // domain extension: {2,} is 2 or more chars
168  if (preg_match($pattern, $text))
169  return $text;
170  return "";
171 }
172 
173 /* Parameters: String $text-- string to prepare
174  * Returns: String $text-- if "", text isn't a valid date so it is cleared; else, date is valid and returned in the format Y-m-d e.g. 2015-01-01.
175  */
176 function cleanDate($text)
177 {
178  $text= trim($text);
179  if (preg_match('/^\d{8}$/', $text)) // in the case of yyyyMMdd (see login's BakeCookie).
180  $text= substr($text, 0, 4) . "-" . substr($text, 4,2) . "-" . substr($text, 6,2);
181  if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $text))
182  return "";
183  $timestamp= date_create_from_format('Y-m-d', $text);
184  if ($timestamp == false)
185  return "";
186  return $text;
187 }
188 
189 /* Parameters: String $text-- string to prepare
190  * Returns: String $text-- if "", text isn't a valid time so it is cleared; else, time is valid and returned in the format H:m.
191  */
192 function cleanTime($text)
193 {
194  $text= trim($text);
195  if (preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $text)) // Changed so that the seconds are optional
196  {
197  $hours= intval(substr($text, 0,2));
198  $minutes= intval(substr($text, 3,2));
199  if ($hours >= 0 && $hours <= 23 && $minutes >= 0 && $minutes <= 59)
200  return $text;
201  }
202  return "";
203 }
204 
205 /* Parameters: String $text-- string to prepare, String (optional) $subtype-- if "month", between 0 and 12 is valid, if "year", either 4 digits or 2 digits is valid,
206  * Boolean (optional) $allowNegatives-- if true, integers < 0 are legal
207  * Returns: String $text-- if "ERROR", text isn't a valid number is it is cleared; else, number is returned.
208  */
209 function cleanInteger($text, $allowNegatives= false, $subtype= "")
210 {
211  $text= trim($text);
212  $pattern= '/^[+-]?\d+$/';
213  if (preg_match($pattern, $text))
214  {
215  $number= intval($text);
216  if (!$allowNegatives && $number < 0)
217  return "ERROR";
218  if (trim($subtype) != "")
219  switch($subtype)
220  {
221  case "month":
222  if ($number >= 0 && $number <= 12)
223  return $number;
224  else
225  return "ERROR";
226  case "year":
227  if ($number >= 1000 && $number <= 9999)
228  return $number;
229  else if ($number >= 1 && $number <= 99)
230  return 2000+$number;
231  else
232  return "ERROR";
233  default:
234  return "ERROR";
235  }
236  return strval($number);
237  }
238  return "ERROR";
239 }
240 
241 /* Parameters: String $text-- string to prepare
242  * Returns: String $text-- if "ERROR", text isn't a valid number is it is cleared; else, number is returned.
243  */
244 function cleanNumber($text)
245 {
246  $text= trim($text);
247  $pattern= '/^[+-]?\d+\.?\d*$/';
248  if (preg_match($pattern, $text))
249  return strval($text);
250  return "ERROR";
251 }
252 
253 /* Parameters: String $text-- the text to clean, String (optional) $firstValue-- what to store a TRUE value into the database,
254  * String (optional) $secondValue is what to store a FALSE value into the database.
255  * Returns: String $text-- if either $firstValue or $secondValue, returns itself; else if $required is true, returns ""; else returns $secondValue
256  */
257 function cleanBoolean($text, $required, $firstValue = "Y", $secondValue = "N")
258 {
259  $text= trim(strtoupper($text));
260  $firstValue= trim(strtoupper($firstValue));
261  $secondValue= trim(strtoupper($secondValue));
262  if ($text == $firstValue || $text == $secondValue)
263  return $text;
264 
265  if ($required)
266  return "";
267  else
268  return $secondValue;
269 }
270 
271 /* Goes through parameter list ($parameters) and validates it against array $validation. Formats to what the database expects (if able).
272  * Outputs a list of errors. $validation looks like $validation[name]= array("required" => (true/false), "type" => (string));
273  * Type can be one of the following: string, phone, email, date, int, and boolean. An "maxlength" attribute is required for string and email.
274  * Boolean takes in two more (optional) parameters: "firstValue" and "secondValue".
275  */
276 function cleanValuesForDatabase(&$parameters, $validation)
277 {
278  $errors= array();
279 
280  if (!is_array($parameters))
281  $errors[]= "Expected an array but parameters is not an array.";
282  if (!is_array($validation))
283  $errors[]= "Expected an array but validation is not an array.";
284 
285  if (count($errors) == 0)
286  foreach($parameters as $key => $value)
287  {
288  if (!isset($validation[$key]))
289  continue;
290 
291  $record= $validation[$key];
292  if (!isset($record["required"]) || !isset($record["type"]))
293  {
294  $errors[]= 'validation record for ' . $key . ' is malformed. "required" and "type" attributes are required.';
295  continue;
296  }
297 
298  if (trim($value) == "")
299  {
300  $parameters[$key]= "";
301  if ($record["required"] == true)
302  $errors[]= "$key is required.";
303  else if ($record["type"] == "number")
304  $parameters[$key]= 0;
305  continue;
306  }
307 
308  switch ($record["type"])
309  {
310  case "string":
311  if (isset($record["maxlength"]))
312  $parameters[$key]= prepSave($value, intval($record["maxlength"]));
313  else
314  $parameters[$key]= prepSave($value);
315  break;
316  case "phone":
317  $phone= cleanPhone($value);
318  if ($phone == "")
319  $errors[]= "$key has an invalid phone format.";
320  $parameters[$key]= $phone;
321  break;
322  case "email":
323  if (isset($record["maxlength"]))
324  $email= cleanEmail($value, intval($record["maxlength"]));
325  else
326  $email= cleanEmail($value);
327  if ($email == "")
328  $errors[]= "$key has an invalid email format.";
329  $parameters[$key]= $email;
330  break;
331  case "date":
332  $date= cleanDate($value);
333  if ($date == "")
334  $errors[]= "$key has an invalid date format.";
335  $parameters[$key]= $date;
336  break;
337  case "int":
338  case "integer":
339  $allowNegatives= isset($record["allowNegatives"]) && $record["allowNegatives"];
340  if (isset($record["subtype"]))
341  $number= cleanInteger($value, $allowNegatives, $record["subtype"]);
342  else
343  $number= cleanInteger($value, $allowNegatives);
344  if ($number == "ERROR")
345  $errors[]= "$key has an invalid integer format.";
346  $parameters[$key]= $number;
347  break;
348  case "number":
349  $number= cleanNumber($value);
350  if ($number == "ERROR")
351  $errors[]= "$key has an invalid number format.";
352  $parameters[$key]= $number;
353  break;
354  case "boolean":
355  if (isset($record["firstValue"]) && isset($record["secondValue"]))
356  $boolean= cleanBoolean($value, $record["required"], $record["firstValue"], $record["secondValue"]);
357  else
358  $boolean= cleanBoolean($value, $record["required"]);
359  if ($boolean == "")
360  $errors[]= "$key has an invalid boolean format.";
361  $parameters[$key]= $boolean;
362  break;
363  case "time":
364  $time= cleanTime($value);
365  if ($time == "")
366  $errors[]= "$key has an invalid time format.";
367  $parameters[$key]= $time;
368  break;
369  default:
370  $parameters[$key]= "";
371  $errors[]= "$key doesn't have an appropriate type.";
372  }
373  }
374  return $errors;
375 }
376 
377 function dbErrorNoFormatting() {
378  global $dbplat;
379  switch ($dbplat)
380  {
381  case "Sybase":
382  return @sybase_get_last_message();
383  case "MySql":
384  return @mysql_error();
385  case "Postgres":
386  return @pg_errormessage();
387  }
388 }