Merge pull request #12639 from aniesshsethh/issue_314
[civicrm-core.git] / CRM / Utils / Type.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
6b83d5bd 6 | Copyright CiviCRM LLC (c) 2004-2019 |
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
6b83d5bd 31 * @copyright CiviCRM LLC (c) 2004-2019
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 69
7c7ab278 70 /**
71 * Maximum size of a MySQL BLOB or TEXT column in bytes.
72 */
73 const BLOB_SIZE = 65535;
74
d01ae020 75 /**
76 * Maximum value of a MySQL signed INT column.
77 */
78 const INT_MAX = 2147483647;
79
6a488035 80 /**
a3379cc1 81 * Gets the string representation for a data type.
6a488035 82 *
a3379cc1
AH
83 * @param int $type
84 * Integer number identifying the data type.
6a488035 85 *
a3379cc1
AH
86 * @return string
87 * String identifying the data type, e.g. 'Int' or 'String'.
6a488035 88 */
00be9182 89 public static function typeToString($type) {
d6528d9c
AH
90 // @todo Use constants in the case statements, e.g. "case T_INT:".
91 // @todo return directly, instead of assigning a value.
92 // @todo Use a lookup array, as a property or as a local variable.
6a488035
TO
93 switch ($type) {
94 case 1:
95 $string = 'Int';
96 break;
97
98 case 2:
99 $string = 'String';
100 break;
101
102 case 3:
103 $string = 'Enum';
104 break;
105
106 case 4:
107 $string = 'Date';
108 break;
109
110 case 8:
111 $string = 'Time';
112 break;
113
114 case 16:
115 $string = 'Boolean';
116 break;
117
118 case 32:
119 $string = 'Text';
120 break;
121
122 case 64:
123 $string = 'Blob';
124 break;
125
e7292422 126 // CRM-10404
6a488035
TO
127 case 12:
128 case 256:
129 $string = 'Timestamp';
130 break;
131
132 case 512:
133 $string = 'Float';
134 break;
135
136 case 1024:
137 $string = 'Money';
138 break;
139
140 case 2048:
141 $string = 'Date';
142 break;
143
144 case 4096:
145 $string = 'Email';
146 break;
147
148 case 16384:
149 $string = 'Mediumblob';
150 break;
151 }
152
153 return (isset($string)) ? $string : "";
154 }
155
067c2e09 156 /**
157 * @return array
158 * An array of type in the form 'type name' => 'int representing type'
159 */
160 public static function getValidTypes() {
161 return array(
162 'Int' => self::T_INT,
163 'String' => self::T_STRING,
164 'Enum' => self::T_ENUM,
165 'Date' => self::T_DATE,
166 'Time' => self::T_TIME,
167 'Boolean' => self::T_BOOLEAN,
168 'Text' => self::T_TEXT,
169 'Blob' => self::T_BLOB,
170 'Timestamp' => self::T_TIMESTAMP,
171 'Float' => self::T_FLOAT,
172 'Money' => self::T_MONEY,
173 'Email' => self::T_EMAIL,
174 'Mediumblob' => self::T_MEDIUMBLOB,
175 );
176 }
177
d86e674f 178 /**
179 * Get the data_type for the field.
180 *
181 * @param array $fieldMetadata
182 * Metadata about the field.
183 *
184 * @return string
185 */
186 public static function getDataTypeFromFieldMetadata($fieldMetadata) {
187 if (isset($fieldMetadata['data_type'])) {
188 return $fieldMetadata['data_type'];
189 }
190 if (empty($fieldMetadata['type'])) {
191 // I would prefer to throw an e-notice but there is some,
192 // probably unnecessary logic, that only retrieves activity fields
193 // if they are 'in the profile' and probably they are not 'in'
194 // until they are added - which might lead to ? who knows!
195 return '';
196 }
197 return self::typeToString($fieldMetadata['type']);
198 }
199
7c99af4f 200 /**
bf48aa29 201 * Helper function to call escape on arrays.
7c99af4f 202 *
203 * @see escape
204 */
205 public static function escapeAll($data, $type, $abort = TRUE) {
206 foreach ($data as $key => $value) {
207 $data[$key] = CRM_Utils_Type::escape($value, $type, $abort);
208 }
209 return $data;
210 }
211
0fa4baf0
MM
212 /**
213 * Helper function to call validate on arrays
214 *
215 * @see validate
216 */
217 public static function validateAll($data, $type, $abort = TRUE) {
218 foreach ($data as $key => $value) {
219 $data[$key] = CRM_Utils_Type::validate($value, $type, $abort);
220 }
221 return $data;
222 }
223
6a488035 224 /**
a3379cc1 225 * Verify that a variable is of a given type, and apply a bit of processing.
6a488035 226 *
a3379cc1
AH
227 * @param mixed $data
228 * The value to be verified/escaped.
229 * @param string $type
230 * The type to verify against.
77855840 231 * @param bool $abort
a3379cc1 232 * If TRUE, the operation will CRM_Core_Error::fatal() on invalid data.
6a488035 233 *
a3379cc1
AH
234 * @return mixed
235 * The data, escaped if necessary.
6a488035
TO
236 */
237 public static function escape($data, $type, $abort = TRUE) {
238 switch ($type) {
239 case 'Integer':
240 case 'Int':
241 if (CRM_Utils_Rule::integer($data)) {
2b0e7d03 242 return (int) $data;
6a488035
TO
243 }
244 break;
245
246 case 'Positive':
43b6f159
CW
247 if (CRM_Utils_Rule::positiveInteger($data)) {
248 return (int) $data;
249 }
250 break;
251
252 // CRM-8925 for custom fields of this type
6a488035
TO
253 case 'Country':
254 case 'StateProvince':
88ccd161
CW
255 // Handle multivalued data in delimited or array format
256 if (is_array($data) || (strpos($data, CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE)) {
43b6f159 257 $valid = TRUE;
88ccd161 258 foreach (CRM_Utils_Array::explodePadded($data) as $item) {
43b6f159
CW
259 if (!CRM_Utils_Rule::positiveInteger($item)) {
260 $valid = FALSE;
9ff5f6c0
N
261 }
262 }
43b6f159 263 if ($valid) {
9ff5f6c0
N
264 return $data;
265 }
266 }
9ff5f6c0 267 elseif (CRM_Utils_Rule::positiveInteger($data)) {
43b6f159 268 return (int) $data;
6a488035
TO
269 }
270 break;
271
e7dcccf0 272 case 'File':
6a488035 273 if (CRM_Utils_Rule::positiveInteger($data)) {
43b6f159 274 return (int) $data;
6a488035
TO
275 }
276 break;
277
278 case 'Link':
279 if (CRM_Utils_Rule::url($data = trim($data))) {
280 return $data;
281 }
282 break;
283
284 case 'Boolean':
285 if (CRM_Utils_Rule::boolean($data)) {
286 return $data;
287 }
288 break;
289
290 case 'Float':
291 case 'Money':
292 if (CRM_Utils_Rule::numeric($data)) {
293 return $data;
294 }
295 break;
296
297 case 'String':
298 case 'Memo':
85bdc94e 299 case 'Text':
6a488035
TO
300 return CRM_Core_DAO::escapeString($data);
301
302 case 'Date':
303 case 'Timestamp':
304 // a null date or timestamp is valid
305 if (strlen(trim($data)) == 0) {
306 return trim($data);
307 }
308
309 if ((preg_match('/^\d{8}$/', $data) ||
310 preg_match('/^\d{14}$/', $data)
311 ) &&
312 CRM_Utils_Rule::mysqlDate($data)
313 ) {
314 return $data;
315 }
316 break;
317
318 case 'ContactReference':
319 if (strlen(trim($data)) == 0) {
320 return trim($data);
321 }
322
323 if (CRM_Utils_Rule::validContact($data)) {
258570f7 324 return (int) $data;
6a488035
TO
325 }
326 break;
327
f19a5565 328 case 'MysqlColumnNameOrAlias':
a33b83c5
MM
329 if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) {
330 $data = str_replace('`', '', $data);
0fa4baf0 331 $parts = explode('.', $data);
da93a1ab 332 $data = '`' . implode('`.`', $parts) . '`';
0fa4baf0 333
00f11506
MM
334 return $data;
335 }
336 break;
337
338 case 'MysqlOrderByDirection':
537e8cb5 339 if (CRM_Utils_Rule::mysqlOrderByDirection($data)) {
0fa4baf0
MM
340 return strtolower($data);
341 }
342 break;
343
344 case 'MysqlOrderBy':
345 if (CRM_Utils_Rule::mysqlOrderBy($data)) {
346 $parts = explode(',', $data);
9d5c7f14 347
348 // The field() syntax is tricky here because it uses commas & when
349 // we separate by them we break it up. But we want to keep the clauses in order.
350 // so we just clumsily re-assemble it. Test cover exists.
351 $fieldClauseStart = NULL;
352 foreach ($parts as $index => &$part) {
353 if (substr($part, 0, 6) === 'field(') {
354 // Looking to escape a string like 'field(contribution_status_id,3,4,5) asc'
355 // to 'field(`contribution_status_id`,3,4,5) asc'
356 $fieldClauseStart = $index;
357 continue;
358 }
359 if ($fieldClauseStart !== NULL) {
360 // this is part of the list of field options. Concatenate it back on.
361 $parts[$fieldClauseStart] .= ',' . $part;
362 unset($parts[$index]);
363 if (!strstr($parts[$fieldClauseStart], ')')) {
364 // we have not reached the end of the list.
365 continue;
366 }
367 // We have the last piece of the field() clause, time to escape it.
368 $parts[$fieldClauseStart] = self::mysqlOrderByFieldFunctionCallback($parts[$fieldClauseStart]);
369 $fieldClauseStart = NULL;
370 continue;
371
372 }
373 // Normal clause.
dd78a9ad 374 $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
375 }
376 return implode(', ', $parts);
00f11506
MM
377 }
378 break;
379
6a488035 380 default:
4f1da757
EM
381 CRM_Core_Error::fatal(
382 $type . " is not a recognised (camel cased) data type."
383 );
6a488035
TO
384 break;
385 }
386
d6528d9c 387 // @todo Use exceptions instead of CRM_Core_Error::fatal().
6a488035
TO
388 if ($abort) {
389 $data = htmlentities($data);
390 CRM_Core_Error::fatal("$data is not of the type $type");
391 }
392 return NULL;
393 }
394
395 /**
fe482240 396 * Verify that a variable is of a given type.
6a488035 397 *
a3379cc1
AH
398 * @param mixed $data
399 * The value to validate.
400 * @param string $type
401 * The type to validate against.
77855840 402 * @param bool $abort
a3379cc1 403 * If TRUE, the operation will CRM_Core_Error::fatal() on invalid data.
5e4ccea5 404 * @param string $name
a3379cc1 405 * The name of the attribute
5e4ccea5 406 * @param bool $isThrowException
407 * Should an exception be thrown rather than a using a deprecated fatal error.
6a488035 408 *
a3379cc1
AH
409 * @return mixed
410 * The data, escaped if necessary
5e4ccea5 411 *
412 * @throws \CRM_Core_Exception
6a488035 413 */
5e4ccea5 414 public static function validate($data, $type, $abort = TRUE, $name = 'One of parameters ', $isThrowException = FALSE) {
415
416 $possibleTypes = array(
417 'Integer',
418 'Int',
419 'Positive',
420 'CommaSeparatedIntegers',
421 'Boolean',
422 'Float',
423 'Money',
424 'Text',
425 'String',
426 'Link',
427 'Memo',
428 'Date',
429 'Timestamp',
430 'ContactReference',
431 'MysqlColumnNameOrAlias',
432 'MysqlOrderByDirection',
433 'MysqlOrderBy',
434 'ExtensionKey',
88251439 435 'Json',
d22982f3 436 'Alphanumeric',
5e4ccea5 437 );
438 if (!in_array($type, $possibleTypes)) {
439 if ($isThrowException) {
440 throw new CRM_Core_Exception(ts('Invalid type, must be one of : ' . implode($possibleTypes)));
441 }
442 CRM_Core_Error::fatal(ts('Invalid type, must be one of : ' . implode($possibleTypes)));
443 }
6a488035
TO
444 switch ($type) {
445 case 'Integer':
446 case 'Int':
447 if (CRM_Utils_Rule::integer($data)) {
2b0e7d03 448 return (int) $data;
6a488035
TO
449 }
450 break;
451
452 case 'Positive':
453 if (CRM_Utils_Rule::positiveInteger($data)) {
258570f7 454 return (int) $data;
6a488035
TO
455 }
456 break;
457
fe61faf3
CW
458 case 'CommaSeparatedIntegers':
459 if (CRM_Utils_Rule::commaSeparatedIntegers($data)) {
460 return $data;
461 }
462 break;
463
6a488035
TO
464 case 'Boolean':
465 if (CRM_Utils_Rule::boolean($data)) {
466 return $data;
467 }
468 break;
469
470 case 'Float':
471 case 'Money':
472 if (CRM_Utils_Rule::numeric($data)) {
473 return $data;
474 }
475 break;
476
477 case 'Text':
478 case 'String':
479 case 'Link':
480 case 'Memo':
481 return $data;
482
483 case 'Date':
484 // a null date is valid
485 if (strlen(trim($data)) == 0) {
486 return trim($data);
487 }
488
489 if (preg_match('/^\d{8}$/', $data) &&
490 CRM_Utils_Rule::mysqlDate($data)
491 ) {
492 return $data;
493 }
494 break;
495
496 case 'Timestamp':
497 // a null timestamp is valid
498 if (strlen(trim($data)) == 0) {
499 return trim($data);
500 }
501
502 if ((preg_match('/^\d{14}$/', $data) ||
503 preg_match('/^\d{8}$/', $data)
504 ) &&
505 CRM_Utils_Rule::mysqlDate($data)
506 ) {
507 return $data;
508 }
509 break;
510
511 case 'ContactReference':
512 // null is valid
513 if (strlen(trim($data)) == 0) {
514 return trim($data);
515 }
516
517 if (CRM_Utils_Rule::validContact($data)) {
518 return $data;
519 }
520 break;
521
f19a5565 522 case 'MysqlColumnNameOrAlias':
a33b83c5 523 if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) {
5d817a13
MM
524 return $data;
525 }
526 break;
527
528 case 'MysqlOrderByDirection':
529 if (CRM_Utils_Rule::mysqlOrderByDirection($data)) {
0fa4baf0 530 return strtolower($data);
5d817a13
MM
531 }
532 break;
533
534 case 'MysqlOrderBy':
535 if (CRM_Utils_Rule::mysqlOrderBy($data)) {
536 return $data;
537 }
538 break;
539
5df85a46 540 case 'ExtensionKey':
9e1d9d01 541 if (CRM_Utils_Rule::checkExtensionKeyIsValid($data)) {
5df85a46
SL
542 return $data;
543 }
544 break;
88251439 545
546 case 'Json':
547 if (CRM_Utils_Rule::json($data)) {
548 return $data;
549 }
550 break;
d22982f3
SM
551
552 case 'Alphanumeric':
553 if (CRM_Utils_Rule::alphanumeric($data)) {
554 return $data;
555 }
556 break;
6a488035
TO
557 }
558
559 if ($abort) {
560 $data = htmlentities($data);
5e4ccea5 561 if ($isThrowException) {
562 throw new CRM_Core_Exception("$name (value: $data) is not of the type $type");
563 }
6a488035
TO
564 CRM_Core_Error::fatal("$name (value: $data) is not of the type $type");
565 }
566
567 return NULL;
568 }
96025800 569
9d5c7f14 570 /**
571 * Preg_replace_callback for mysqlOrderByFieldFunction escape.
572 *
573 * Add backticks around the field name.
574 *
575 * @param string $clause
576 *
577 * @return string
578 */
579 public static function mysqlOrderByFieldFunctionCallback($clause) {
580 return preg_replace('/field\((\w*)/', 'field(`${1}`', $clause);
581 }
582
0fa4baf0
MM
583 /**
584 * preg_replace_callback for MysqlOrderBy escape.
585 */
586 public static function mysqlOrderByCallback($matches) {
587 $output = '';
a33b83c5 588 $matches = str_replace('`', '', $matches);
0fa4baf0 589
a33b83c5 590 // Table name.
f19a5565 591 if (isset($matches[1]) && $matches[1]) {
a33b83c5 592 $output .= '`' . $matches[1] . '`.';
0fa4baf0
MM
593 }
594
a33b83c5 595 // Column name.
0fa4baf0 596 if (isset($matches[2]) && $matches[2]) {
a33b83c5 597 $output .= '`' . $matches[2] . '`';
0fa4baf0
MM
598 }
599
600 // Sort order.
601 if (isset($matches[3]) && $matches[3]) {
602 $output .= ' ' . $matches[3];
603 }
604
605 return $output;
606 }
607
eaecfa20 608 /**
067c2e09 609 * Get list of avaliable Data Types for Option Groups
eaecfa20
SL
610 *
611 * @return array
612 */
613 public static function dataTypes() {
614 $types = array(
d67d221a 615 'Integer',
eaecfa20 616 'String',
d67d221a
SL
617 'Date',
618 'Time',
eaecfa20 619 'Timestamp',
d67d221a 620 'Money',
eaecfa20
SL
621 'Email',
622 );
623 return array_combine($types, $types);
624 }
625
6a488035 626}