Merge pull request #9596 from eileenmcnaughton/performance
[civicrm-core.git] / CRM / Utils / Type.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2017 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2017
32 */
33 class CRM_Utils_Type {
34 const
35 T_INT = 1,
36 T_STRING = 2,
37 T_ENUM = 2,
38 T_DATE = 4,
39 T_TIME = 8,
40 T_BOOLEAN = 16,
41 T_TEXT = 32,
42 T_LONGTEXT = 32,
43 T_BLOB = 64,
44 T_TIMESTAMP = 256,
45 T_FLOAT = 512,
46 T_MONEY = 1024,
47 T_EMAIL = 2048,
48 T_URL = 4096,
49 T_CCNUM = 8192,
50 T_MEDIUMBLOB = 16384;
51
52 // @TODO What's the point of these constants? Backwards compatibility?
53 //
54 // These are used for field size (<input type=text size=2>), but redundant TWO=2
55 // usages are rare and should be eliminated. See CRM-18810.
56 const
57 TWO = 2,
58 FOUR = 4,
59 SIX = 6,
60 EIGHT = 8,
61 TWELVE = 12,
62 SIXTEEN = 16,
63 TWENTY = 20,
64 MEDIUM = 20,
65 THIRTY = 30,
66 BIG = 30,
67 FORTYFIVE = 45,
68 HUGE = 45;
69
70 /**
71 * Gets the string representation for a data type.
72 *
73 * @param int $type
74 * Integer number identifying the data type.
75 *
76 * @return string
77 * String identifying the data type, e.g. 'Int' or 'String'.
78 */
79 public static function typeToString($type) {
80 // @todo Use constants in the case statements, e.g. "case T_INT:".
81 // @todo return directly, instead of assigning a value.
82 // @todo Use a lookup array, as a property or as a local variable.
83 switch ($type) {
84 case 1:
85 $string = 'Int';
86 break;
87
88 case 2:
89 $string = 'String';
90 break;
91
92 case 3:
93 $string = 'Enum';
94 break;
95
96 case 4:
97 $string = 'Date';
98 break;
99
100 case 8:
101 $string = 'Time';
102 break;
103
104 case 16:
105 $string = 'Boolean';
106 break;
107
108 case 32:
109 $string = 'Text';
110 break;
111
112 case 64:
113 $string = 'Blob';
114 break;
115
116 // CRM-10404
117 case 12:
118 case 256:
119 $string = 'Timestamp';
120 break;
121
122 case 512:
123 $string = 'Float';
124 break;
125
126 case 1024:
127 $string = 'Money';
128 break;
129
130 case 2048:
131 $string = 'Date';
132 break;
133
134 case 4096:
135 $string = 'Email';
136 break;
137
138 case 16384:
139 $string = 'Mediumblob';
140 break;
141 }
142
143 return (isset($string)) ? $string : "";
144 }
145
146 /**
147 * Helper function to call escape on arrays
148 *
149 * @see escape
150 */
151 public static function escapeAll($data, $type, $abort = TRUE) {
152 foreach ($data as $key => $value) {
153 $data[$key] = CRM_Utils_Type::escape($value, $type, $abort);
154 }
155 return $data;
156 }
157
158 /**
159 * Helper function to call validate on arrays
160 *
161 * @see validate
162 */
163 public static function validateAll($data, $type, $abort = TRUE) {
164 foreach ($data as $key => $value) {
165 $data[$key] = CRM_Utils_Type::validate($value, $type, $abort);
166 }
167 return $data;
168 }
169
170 /**
171 * Verify that a variable is of a given type, and apply a bit of processing.
172 *
173 * @param mixed $data
174 * The value to be verified/escaped.
175 * @param string $type
176 * The type to verify against.
177 * @param bool $abort
178 * If TRUE, the operation will CRM_Core_Error::fatal() on invalid data.
179 *
180 * @return mixed
181 * The data, escaped if necessary.
182 */
183 public static function escape($data, $type, $abort = TRUE) {
184 switch ($type) {
185 case 'Integer':
186 case 'Int':
187 if (CRM_Utils_Rule::integer($data)) {
188 return (int) $data;
189 }
190 break;
191
192 case 'Positive':
193 if (CRM_Utils_Rule::positiveInteger($data)) {
194 return (int) $data;
195 }
196 break;
197
198 // CRM-8925 for custom fields of this type
199 case 'Country':
200 case 'StateProvince':
201 // Handle multivalued data in delimited or array format
202 if (is_array($data) || (strpos($data, CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE)) {
203 $valid = TRUE;
204 foreach (CRM_Utils_Array::explodePadded($data) as $item) {
205 if (!CRM_Utils_Rule::positiveInteger($item)) {
206 $valid = FALSE;
207 }
208 }
209 if ($valid) {
210 return $data;
211 }
212 }
213 elseif (CRM_Utils_Rule::positiveInteger($data)) {
214 return (int) $data;
215 }
216 break;
217
218 case 'File':
219 if (CRM_Utils_Rule::positiveInteger($data)) {
220 return (int) $data;
221 }
222 break;
223
224 case 'Link':
225 if (CRM_Utils_Rule::url($data = trim($data))) {
226 return $data;
227 }
228 break;
229
230 case 'Boolean':
231 if (CRM_Utils_Rule::boolean($data)) {
232 return $data;
233 }
234 break;
235
236 case 'Float':
237 case 'Money':
238 if (CRM_Utils_Rule::numeric($data)) {
239 return $data;
240 }
241 break;
242
243 case 'String':
244 case 'Memo':
245 case 'Text':
246 return CRM_Core_DAO::escapeString($data);
247
248 case 'Date':
249 case 'Timestamp':
250 // a null date or timestamp is valid
251 if (strlen(trim($data)) == 0) {
252 return trim($data);
253 }
254
255 if ((preg_match('/^\d{8}$/', $data) ||
256 preg_match('/^\d{14}$/', $data)
257 ) &&
258 CRM_Utils_Rule::mysqlDate($data)
259 ) {
260 return $data;
261 }
262 break;
263
264 case 'ContactReference':
265 if (strlen(trim($data)) == 0) {
266 return trim($data);
267 }
268
269 if (CRM_Utils_Rule::validContact($data)) {
270 return (int) $data;
271 }
272 break;
273
274 case 'MysqlColumnNameOrAlias':
275 if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) {
276 $data = str_replace('`', '', $data);
277 $parts = explode('.', $data);
278 $data = '`' . implode('`.`', $parts) . '`';
279
280 return $data;
281 }
282 break;
283
284 case 'MysqlOrderByDirection':
285 if (CRM_Utils_Rule::mysqlOrderByDirection($data)) {
286 return strtolower($data);
287 }
288 break;
289
290 case 'MysqlOrderBy':
291 if (CRM_Utils_Rule::mysqlOrderBy($data)) {
292 $parts = explode(',', $data);
293 foreach ($parts as &$part) {
294 $part = preg_replace_callback('/^(?:(?:((?:`[\w-]{1,64}`|[\w-]{1,64}))(?:\.))?(`[\w-]{1,64}`|[\w-]{1,64})(?: (asc|desc))?)$/i', array('CRM_Utils_Type', 'mysqlOrderByCallback'), trim($part));
295 }
296 return implode(', ', $parts);
297 }
298 break;
299
300 default:
301 CRM_Core_Error::fatal(
302 $type . " is not a recognised (camel cased) data type."
303 );
304 break;
305 }
306
307 // @todo Use exceptions instead of CRM_Core_Error::fatal().
308 if ($abort) {
309 $data = htmlentities($data);
310 CRM_Core_Error::fatal("$data is not of the type $type");
311 }
312 return NULL;
313 }
314
315 /**
316 * Verify that a variable is of a given type.
317 *
318 * @param mixed $data
319 * The value to validate.
320 * @param string $type
321 * The type to validate against.
322 * @param bool $abort
323 * If TRUE, the operation will CRM_Core_Error::fatal() on invalid data.
324 * @name string $name
325 * The name of the attribute
326 *
327 * @return mixed
328 * The data, escaped if necessary
329 */
330 public static function validate($data, $type, $abort = TRUE, $name = 'One of parameters ') {
331 switch ($type) {
332 case 'Integer':
333 case 'Int':
334 if (CRM_Utils_Rule::integer($data)) {
335 return (int) $data;
336 }
337 break;
338
339 case 'Positive':
340 if (CRM_Utils_Rule::positiveInteger($data)) {
341 return (int) $data;
342 }
343 break;
344
345 case 'Boolean':
346 if (CRM_Utils_Rule::boolean($data)) {
347 return $data;
348 }
349 break;
350
351 case 'Float':
352 case 'Money':
353 if (CRM_Utils_Rule::numeric($data)) {
354 return $data;
355 }
356 break;
357
358 case 'Text':
359 case 'String':
360 case 'Link':
361 case 'Memo':
362 return $data;
363
364 case 'Date':
365 // a null date is valid
366 if (strlen(trim($data)) == 0) {
367 return trim($data);
368 }
369
370 if (preg_match('/^\d{8}$/', $data) &&
371 CRM_Utils_Rule::mysqlDate($data)
372 ) {
373 return $data;
374 }
375 break;
376
377 case 'Timestamp':
378 // a null timestamp is valid
379 if (strlen(trim($data)) == 0) {
380 return trim($data);
381 }
382
383 if ((preg_match('/^\d{14}$/', $data) ||
384 preg_match('/^\d{8}$/', $data)
385 ) &&
386 CRM_Utils_Rule::mysqlDate($data)
387 ) {
388 return $data;
389 }
390 break;
391
392 case 'ContactReference':
393 // null is valid
394 if (strlen(trim($data)) == 0) {
395 return trim($data);
396 }
397
398 if (CRM_Utils_Rule::validContact($data)) {
399 return $data;
400 }
401 break;
402
403 case 'MysqlColumnNameOrAlias':
404 if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) {
405 return $data;
406 }
407 break;
408
409 case 'MysqlOrderByDirection':
410 if (CRM_Utils_Rule::mysqlOrderByDirection($data)) {
411 return strtolower($data);
412 }
413 break;
414
415 case 'MysqlOrderBy':
416 if (CRM_Utils_Rule::mysqlOrderBy($data)) {
417 return $data;
418 }
419 break;
420
421 default:
422 CRM_Core_Error::fatal("Cannot recognize $type for $data");
423 break;
424 }
425
426 if ($abort) {
427 $data = htmlentities($data);
428 CRM_Core_Error::fatal("$name (value: $data) is not of the type $type");
429 }
430
431 return NULL;
432 }
433
434 /**
435 * preg_replace_callback for MysqlOrderBy escape.
436 */
437 public static function mysqlOrderByCallback($matches) {
438 $output = '';
439 $matches = str_replace('`', '', $matches);
440
441 // Table name.
442 if (isset($matches[1]) && $matches[1]) {
443 $output .= '`' . $matches[1] . '`.';
444 }
445
446 // Column name.
447 if (isset($matches[2]) && $matches[2]) {
448 $output .= '`' . $matches[2] . '`';
449 }
450
451 // Sort order.
452 if (isset($matches[3]) && $matches[3]) {
453 $output .= ' ' . $matches[3];
454 }
455
456 return $output;
457 }
458
459 /**
460 * Get list of avaliable Data Tupes for Option Groups
461 *
462 * @return array
463 */
464 public static function dataTypes() {
465 $types = array(
466 'Integer',
467 'String',
468 'Date',
469 'Time',
470 'Timestamp',
471 'Money',
472 'Email',
473 );
474 return array_combine($types, $types);
475 }
476
477 }