| 86 | |
| 87 | /** |
| 88 | * Builds a properly formatted and escaped SQL statement using an SQL template and a list of arguments. |
| 89 | * |
| 90 | * The function scans the template for query marks ? which are placeholders for the arguments |
| 91 | * Query marks are to be followed by format descriptors. |
| 92 | * |
| 93 | * The first argument, the template, is mandatory. If the template contains no query marks |
| 94 | * and no argument is given, the function does nothing. |
| 95 | * |
| 96 | * Placeholders have the following format and are not case sensitive: |
| 97 | * |
| 98 | * <b>?[nn][m]t</b> |
| 99 | * |
| 100 | * Where: |
| 101 | * |
| 102 | * <b>?</b> Begining of placeholder for argument |
| 103 | * |
| 104 | * <b>nn - <i>position</i></b> number of the argument to be replaced. |
| 105 | * Argument 0 is the template itself and is not valid. |
| 106 | * The first argument after the SQL template is number 1 |
| 107 | * If no number is given, arguments are taken sequentially. |
| 108 | * Numbered replacements do not move the sequential argument pointer. |
| 109 | * Arguments beyond the actual number present are considered null |
| 110 | * |
| 111 | * <b>m - <i>modifier</i></b> [optional] indicates what to do if the argument is null |
| 112 | * - m: mandatory, if the argument contains null, it will give a fatal error. |
| 113 | * - z: null, if the argument is 0 or an empty string, it will be replaced by null |
| 114 | * |
| 115 | * <b>t - <i>data type</i></b> the placeholder will be replaced by the argument as follows |
| 116 | * - s: string, if not null, it will be escaped and enclosed in quotes |
| 117 | * - i: integer, the integer value (intval() function) of the argument |
| 118 | * - f: float, the floating point value (floatval() function) of the argument |
| 119 | * - d: date, the argument will be assumed to represent a timestamp and it will be converted to yyyy-mm-dd and quoted |
| 120 | * - b: boolean, anything evaluated to false will be 0, otherwise 1 |
| 121 | * - t: table prefix, the value of the global variable <i>$table_prefix</i>, escaped and unquoted |
| 122 | * It takes no argument from the argument list |
| 123 | * |
| 124 | * Example: |
| 125 | * <code> |
| 126 | * echo BuildSql('Insert into ?ttable (?s,?ns,?mi,?d,?ni,?i)','Something','',5,time(),0,null); |
| 127 | * </code> |
| 128 | * will return: |
| 129 | * <pre> |
| 130 | * Insert into wp_table ('Something',null,5,'2006-05-15',null,0) |
| 131 | * </pre> |
| 132 | * |
| 133 | * Note that placeholders do not need to be quoted, if quotes are required (strings or dates) they will be provided |
| 134 | * |
| 135 | * @param string $query Template of SQL statement |
| 136 | * |
| 137 | * |
| 138 | * @param mixed $value,... Values to be replaced into placeholders, sequentially unless stated otherwise |
| 139 | * |
| 140 | * @return string properly formated and escaped SQL statement |
| 141 | * |
| 142 | * The function will call bail if an unknown formatting character is found. |
| 143 | * Unused arguments will produce warnings. |
| 144 | * Missing arguments will be assumed null and will trigger a fatal error |
| 145 | * if the placeholder has the mandatory modifier m. |
| 146 | * There is no provision to put a literal ? into the SQL statement since the ? is not a valid SQL operator, |
| 147 | * the only valid place for query marks are in literal string constants, which can be passed to this |
| 148 | * function in an argument |
| 149 | */ |
| 151 | function BuildSql($query) { |
| 152 | global $table_prefix; |
| 153 | |
| 154 | $num_args = func_num_args(); // number of arguments available |
| 155 | $args_used = (1 << $num_args) -2; // bit mask to check if arguments are used |
| 156 | /* +---------Anything up to first query mark |
| 157 | | +------ query mark, start of placeholder |
| 158 | | | +---- position of argument |
| 159 | | | | +----- modifier |
| 160 | | | | | +--- data type |
| 161 | | | | | | */ |
| 162 | if (preg_match_all('|([^\?]*)(\?(\d?\d)?([mn]?)([sifdbt])?)*|i',$query,$matches,PREG_SET_ORDER)) { |
| 163 | $arg_pointer = 1; // sequential pointer to arguments |
| 164 | $s = ''; // output SQL statement |
| 165 | foreach($matches as $match) { |
| 166 | $NullIfEmpty = false; |
| 167 | $s .= $match[1]; //concatenate everything up to question mark |
| 168 | $type = strtolower($match[5]); // read datatype |
| 169 | |
| 170 | // read the value of the argument |
| 171 | if ($type =='t') { |
| 172 | $value = $table_prefix; // t is a special case, it takes no argument from the list |
| 173 | $n = 0; |
| 174 | } elseif (intval($match[3])) { // if there is a nn position modifier, read it |
| 175 | // (null evaluates to 0, which is an invalid position anyhow) |
| 176 | $n = intval($match[3]); |
| 177 | } else { // else, read the next sequential argument an increment the argument pointer |
| 178 | $n = $arg_pointer++; |
| 179 | } |
| 180 | if ($n) { // if there was an argument to be read (notice t does not ask for an argument) |
| 181 | $args_used &= ~ (1 << $n); // some bitwise magic to unset the bit position representing the argument |
| 182 | if ($n >= $num_args) $value = null; // if argument requeste beyond those available, assume it null |
| 183 | else $value = func_get_arg($n); // otherwise, read it |
| 184 | } |
| 185 | |
| 186 | switch ($match[4]) { // read modifier |
| 187 | case 'm': // mandatory? |
| 188 | case 'M': |
| 189 | if (is_null($value)) $this->bail("<p>Missing value for argument $n near: ... ${match[0]} ...</p>"); |
| 190 | break; |
| 191 | case 'n': // null if empty? |
| 192 | case 'N': |
| 193 | $NullIfEmpty = true; // set flag |
| 194 | break; |
| 195 | } |
| 196 | switch($type) { // now we process $value according to datatype and $NullIfEmpty flag |
| 197 | case 's': |
| 198 | case 'S': |
| 199 | if ($NullIfEmpty and strlen($value) == 0) { |
| 200 | $s .='null'; |
| 201 | } else { |
| 202 | $s .= "'" . $this->escape($value) . "'"; |
| 203 | } |
| 204 | break; |
| 205 | case 'i': |
| 206 | case 'I': |
| 207 | if ($NullIfEmpty and $value == 0) { |
| 208 | $s .='null'; |
| 209 | } else { |
| 210 | $s .= intval($value); |
| 211 | } |
| 212 | break; |
| 213 | case 'f': |
| 214 | case 'F': |
| 215 | if ($NullIfEmpty and $value == 0) { |
| 216 | $s .='null'; |
| 217 | } else { |
| 218 | $s .= floatval($value); |
| 219 | } |
| 220 | break; |
| 221 | case 'd': |
| 222 | case 'D': |
| 223 | if ($NullIfEmpty and empty($value)) { |
| 224 | $s .='null'; |
| 225 | } else { |
| 226 | $s .= "'" . date('Y-m-d', $value) . "'"; |
| 227 | } |
| 228 | break; |
| 229 | case 'b': |
| 230 | case 'B': // booleans cannot be null |
| 231 | $s .= intval($value!=false); |
| 232 | break; |
| 233 | case 't': |
| 234 | case 'T': |
| 235 | $s .= $this->escape($value); |
| 236 | break; |
| 237 | case null: |
| 238 | break; |
| 239 | default: |
| 240 | $this->bail('<p>Unknown formatting character in BuildSql: ' . $match[0] . " en SQL <br />[$query]</p>"); |
| 241 | } |
| 242 | |
| 243 | } |
| 244 | } |
| 245 | // if all arguments used, $args_used will be zero |
| 246 | if ($args_used) { |
| 247 | for($i=1;$i<$num_args;$i++) { |
| 248 | $args_used >>=1; // bitmaks is shift right |
| 249 | // if right most bit still 1, it means it has not been used. |
| 250 | if ($args_used & 1) $this->bail("Argument $i not used"); |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | return $s; |
| 255 | } |
| 256 | |