Merge pull request #10162 from yashodha/CRM-20429
[civicrm-core.git] / CRM / Utils / Type.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
7e9e8871 4 | CiviCRM version 4.7 |
6a488035 5 +--------------------------------------------------------------------+
0f03f337 6 | Copyright CiviCRM LLC (c) 2004-2017 |
6a488035
TO
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
0f03f337 31 * @copyright CiviCRM LLC (c) 2004-2017
6a488035
TO
32 */
33class CRM_Utils_Type {
7da04cde 34 const
353ffa53
TO
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,
6a488035
TO
50 T_MEDIUMBLOB = 16384;
51
2c05985c
CB
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.
7da04cde 56 const
353ffa53
TO
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,
6a488035 67 FORTYFIVE = 45,
353ffa53 68 HUGE = 45;
6a488035
TO
69
70 /**
a3379cc1 71 * Gets the string representation for a data type.
6a488035 72 *
a3379cc1
AH
73 * @param int $type
74 * Integer number identifying the data type.
6a488035 75 *
a3379cc1
AH
76 * @return string
77 * String identifying the data type, e.g. 'Int' or 'String'.
6a488035 78 */
00be9182 79 public static function typeToString($type) {
d6528d9c
AH
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.
6a488035
TO
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
e7292422 116 // CRM-10404
6a488035
TO
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
d86e674f 146 /**
147 * Get the data_type for the field.
148 *
149 * @param array $fieldMetadata
150 * Metadata about the field.
151 *
152 * @return string
153 */
154 public static function getDataTypeFromFieldMetadata($fieldMetadata) {
155 if (isset($fieldMetadata['data_type'])) {
156 return $fieldMetadata['data_type'];
157 }
158 if (empty($fieldMetadata['type'])) {
159 // I would prefer to throw an e-notice but there is some,
160 // probably unnecessary logic, that only retrieves activity fields
161 // if they are 'in the profile' and probably they are not 'in'
162 // until they are added - which might lead to ? who knows!
163 return '';
164 }
165 return self::typeToString($fieldMetadata['type']);
166 }
167
7c99af4f 168 /**
bf48aa29 169 * Helper function to call escape on arrays.
7c99af4f 170 *
171 * @see escape
172 */
173 public static function escapeAll($data, $type, $abort = TRUE) {
174 foreach ($data as $key => $value) {
175 $data[$key] = CRM_Utils_Type::escape($value, $type, $abort);
176 }
177 return $data;
178 }
179
0fa4baf0
MM
180 /**
181 * Helper function to call validate on arrays
182 *
183 * @see validate
184 */
185 public static function validateAll($data, $type, $abort = TRUE) {
186 foreach ($data as $key => $value) {
187 $data[$key] = CRM_Utils_Type::validate($value, $type, $abort);
188 }
189 return $data;
190 }
191
6a488035 192 /**
a3379cc1 193 * Verify that a variable is of a given type, and apply a bit of processing.
6a488035 194 *
a3379cc1
AH
195 * @param mixed $data
196 * The value to be verified/escaped.
197 * @param string $type
198 * The type to verify against.
77855840 199 * @param bool $abort
a3379cc1 200 * If TRUE, the operation will CRM_Core_Error::fatal() on invalid data.
6a488035 201 *
a3379cc1
AH
202 * @return mixed
203 * The data, escaped if necessary.
6a488035
TO
204 */
205 public static function escape($data, $type, $abort = TRUE) {
206 switch ($type) {
207 case 'Integer':
208 case 'Int':
209 if (CRM_Utils_Rule::integer($data)) {
2b0e7d03 210 return (int) $data;
6a488035
TO
211 }
212 break;
213
214 case 'Positive':
43b6f159
CW
215 if (CRM_Utils_Rule::positiveInteger($data)) {
216 return (int) $data;
217 }
218 break;
219
220 // CRM-8925 for custom fields of this type
6a488035
TO
221 case 'Country':
222 case 'StateProvince':
88ccd161
CW
223 // Handle multivalued data in delimited or array format
224 if (is_array($data) || (strpos($data, CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE)) {
43b6f159 225 $valid = TRUE;
88ccd161 226 foreach (CRM_Utils_Array::explodePadded($data) as $item) {
43b6f159
CW
227 if (!CRM_Utils_Rule::positiveInteger($item)) {
228 $valid = FALSE;
9ff5f6c0
N
229 }
230 }
43b6f159 231 if ($valid) {
9ff5f6c0
N
232 return $data;
233 }
234 }
9ff5f6c0 235 elseif (CRM_Utils_Rule::positiveInteger($data)) {
43b6f159 236 return (int) $data;
6a488035
TO
237 }
238 break;
239
e7dcccf0 240 case 'File':
6a488035 241 if (CRM_Utils_Rule::positiveInteger($data)) {
43b6f159 242 return (int) $data;
6a488035
TO
243 }
244 break;
245
246 case 'Link':
247 if (CRM_Utils_Rule::url($data = trim($data))) {
248 return $data;
249 }
250 break;
251
252 case 'Boolean':
253 if (CRM_Utils_Rule::boolean($data)) {
254 return $data;
255 }
256 break;
257
258 case 'Float':
259 case 'Money':
260 if (CRM_Utils_Rule::numeric($data)) {
261 return $data;
262 }
263 break;
264
265 case 'String':
266 case 'Memo':
85bdc94e 267 case 'Text':
6a488035
TO
268 return CRM_Core_DAO::escapeString($data);
269
270 case 'Date':
271 case 'Timestamp':
272 // a null date or timestamp is valid
273 if (strlen(trim($data)) == 0) {
274 return trim($data);
275 }
276
277 if ((preg_match('/^\d{8}$/', $data) ||
278 preg_match('/^\d{14}$/', $data)
279 ) &&
280 CRM_Utils_Rule::mysqlDate($data)
281 ) {
282 return $data;
283 }
284 break;
285
286 case 'ContactReference':
287 if (strlen(trim($data)) == 0) {
288 return trim($data);
289 }
290
291 if (CRM_Utils_Rule::validContact($data)) {
258570f7 292 return (int) $data;
6a488035
TO
293 }
294 break;
295
f19a5565 296 case 'MysqlColumnNameOrAlias':
a33b83c5
MM
297 if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) {
298 $data = str_replace('`', '', $data);
0fa4baf0 299 $parts = explode('.', $data);
da93a1ab 300 $data = '`' . implode('`.`', $parts) . '`';
0fa4baf0 301
00f11506
MM
302 return $data;
303 }
304 break;
305
306 case 'MysqlOrderByDirection':
537e8cb5 307 if (CRM_Utils_Rule::mysqlOrderByDirection($data)) {
0fa4baf0
MM
308 return strtolower($data);
309 }
310 break;
311
312 case 'MysqlOrderBy':
313 if (CRM_Utils_Rule::mysqlOrderBy($data)) {
314 $parts = explode(',', $data);
9d5c7f14 315
316 // The field() syntax is tricky here because it uses commas & when
317 // we separate by them we break it up. But we want to keep the clauses in order.
318 // so we just clumsily re-assemble it. Test cover exists.
319 $fieldClauseStart = NULL;
320 foreach ($parts as $index => &$part) {
321 if (substr($part, 0, 6) === 'field(') {
322 // Looking to escape a string like 'field(contribution_status_id,3,4,5) asc'
323 // to 'field(`contribution_status_id`,3,4,5) asc'
324 $fieldClauseStart = $index;
325 continue;
326 }
327 if ($fieldClauseStart !== NULL) {
328 // this is part of the list of field options. Concatenate it back on.
329 $parts[$fieldClauseStart] .= ',' . $part;
330 unset($parts[$index]);
331 if (!strstr($parts[$fieldClauseStart], ')')) {
332 // we have not reached the end of the list.
333 continue;
334 }
335 // We have the last piece of the field() clause, time to escape it.
336 $parts[$fieldClauseStart] = self::mysqlOrderByFieldFunctionCallback($parts[$fieldClauseStart]);
337 $fieldClauseStart = NULL;
338 continue;
339
340 }
341 // Normal clause.
dd78a9ad 342 $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));
0fa4baf0
MM
343 }
344 return implode(', ', $parts);
00f11506
MM
345 }
346 break;
347
6a488035 348 default:
4f1da757
EM
349 CRM_Core_Error::fatal(
350 $type . " is not a recognised (camel cased) data type."
351 );
6a488035
TO
352 break;
353 }
354
d6528d9c 355 // @todo Use exceptions instead of CRM_Core_Error::fatal().
6a488035
TO
356 if ($abort) {
357 $data = htmlentities($data);
358 CRM_Core_Error::fatal("$data is not of the type $type");
359 }
360 return NULL;
361 }
362
363 /**
fe482240 364 * Verify that a variable is of a given type.
6a488035 365 *
a3379cc1
AH
366 * @param mixed $data
367 * The value to validate.
368 * @param string $type
369 * The type to validate against.
77855840 370 * @param bool $abort
a3379cc1
AH
371 * If TRUE, the operation will CRM_Core_Error::fatal() on invalid data.
372 * @name string $name
373 * The name of the attribute
6a488035 374 *
a3379cc1
AH
375 * @return mixed
376 * The data, escaped if necessary
6a488035
TO
377 */
378 public static function validate($data, $type, $abort = TRUE, $name = 'One of parameters ') {
379 switch ($type) {
380 case 'Integer':
381 case 'Int':
382 if (CRM_Utils_Rule::integer($data)) {
2b0e7d03 383 return (int) $data;
6a488035
TO
384 }
385 break;
386
387 case 'Positive':
388 if (CRM_Utils_Rule::positiveInteger($data)) {
258570f7 389 return (int) $data;
6a488035
TO
390 }
391 break;
392
393 case 'Boolean':
394 if (CRM_Utils_Rule::boolean($data)) {
395 return $data;
396 }
397 break;
398
399 case 'Float':
400 case 'Money':
401 if (CRM_Utils_Rule::numeric($data)) {
402 return $data;
403 }
404 break;
405
406 case 'Text':
407 case 'String':
408 case 'Link':
409 case 'Memo':
410 return $data;
411
412 case 'Date':
413 // a null date is valid
414 if (strlen(trim($data)) == 0) {
415 return trim($data);
416 }
417
418 if (preg_match('/^\d{8}$/', $data) &&
419 CRM_Utils_Rule::mysqlDate($data)
420 ) {
421 return $data;
422 }
423 break;
424
425 case 'Timestamp':
426 // a null timestamp is valid
427 if (strlen(trim($data)) == 0) {
428 return trim($data);
429 }
430
431 if ((preg_match('/^\d{14}$/', $data) ||
432 preg_match('/^\d{8}$/', $data)
433 ) &&
434 CRM_Utils_Rule::mysqlDate($data)
435 ) {
436 return $data;
437 }
438 break;
439
440 case 'ContactReference':
441 // null is valid
442 if (strlen(trim($data)) == 0) {
443 return trim($data);
444 }
445
446 if (CRM_Utils_Rule::validContact($data)) {
447 return $data;
448 }
449 break;
450
f19a5565 451 case 'MysqlColumnNameOrAlias':
a33b83c5 452 if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) {
5d817a13
MM
453 return $data;
454 }
455 break;
456
457 case 'MysqlOrderByDirection':
458 if (CRM_Utils_Rule::mysqlOrderByDirection($data)) {
0fa4baf0 459 return strtolower($data);
5d817a13
MM
460 }
461 break;
462
463 case 'MysqlOrderBy':
464 if (CRM_Utils_Rule::mysqlOrderBy($data)) {
465 return $data;
466 }
467 break;
468
6a488035
TO
469 default:
470 CRM_Core_Error::fatal("Cannot recognize $type for $data");
471 break;
472 }
473
474 if ($abort) {
475 $data = htmlentities($data);
476 CRM_Core_Error::fatal("$name (value: $data) is not of the type $type");
477 }
478
479 return NULL;
480 }
96025800 481
9d5c7f14 482 /**
483 * Preg_replace_callback for mysqlOrderByFieldFunction escape.
484 *
485 * Add backticks around the field name.
486 *
487 * @param string $clause
488 *
489 * @return string
490 */
491 public static function mysqlOrderByFieldFunctionCallback($clause) {
492 return preg_replace('/field\((\w*)/', 'field(`${1}`', $clause);
493 }
494
0fa4baf0
MM
495 /**
496 * preg_replace_callback for MysqlOrderBy escape.
497 */
498 public static function mysqlOrderByCallback($matches) {
499 $output = '';
a33b83c5 500 $matches = str_replace('`', '', $matches);
0fa4baf0 501
a33b83c5 502 // Table name.
f19a5565 503 if (isset($matches[1]) && $matches[1]) {
a33b83c5 504 $output .= '`' . $matches[1] . '`.';
0fa4baf0
MM
505 }
506
a33b83c5 507 // Column name.
0fa4baf0 508 if (isset($matches[2]) && $matches[2]) {
a33b83c5 509 $output .= '`' . $matches[2] . '`';
0fa4baf0
MM
510 }
511
512 // Sort order.
513 if (isset($matches[3]) && $matches[3]) {
514 $output .= ' ' . $matches[3];
515 }
516
517 return $output;
518 }
519
eaecfa20
SL
520 /**
521 * Get list of avaliable Data Tupes for Option Groups
522 *
523 * @return array
524 */
525 public static function dataTypes() {
526 $types = array(
d67d221a 527 'Integer',
eaecfa20 528 'String',
d67d221a
SL
529 'Date',
530 'Time',
eaecfa20 531 'Timestamp',
d67d221a 532 'Money',
eaecfa20
SL
533 'Email',
534 );
535 return array_combine($types, $types);
536 }
537
6a488035 538}