Merge pull request #15699 from mattwire/participant_cleanup_completeOrderPBRef
[civicrm-core.git] / CRM / Utils / Type.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
f299f7db 6 | Copyright CiviCRM LLC (c) 2004-2020 |
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
f299f7db 31 * @copyright CiviCRM LLC (c) 2004-2020
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() {
be2fb01f 161 return [
067c2e09 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,
be2fb01f 175 ];
067c2e09 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.
b832662c 236 * @throws CRM_Core_Exception
6a488035
TO
237 */
238 public static function escape($data, $type, $abort = TRUE) {
239 switch ($type) {
240 case 'Integer':
241 case 'Int':
6a488035 242 case 'Positive':
ab2fa56a
MWMC
243 case 'Float':
244 case 'Money':
245 case 'Date':
246 case 'Timestamp':
247 case 'ContactReference':
248 case 'MysqlOrderByDirection':
249 $validatedData = self::validate($data, $type, $abort);
250 if (isset($validatedData)) {
251 return $validatedData;
43b6f159
CW
252 }
253 break;
254
255 // CRM-8925 for custom fields of this type
6a488035
TO
256 case 'Country':
257 case 'StateProvince':
88ccd161
CW
258 // Handle multivalued data in delimited or array format
259 if (is_array($data) || (strpos($data, CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE)) {
43b6f159 260 $valid = TRUE;
88ccd161 261 foreach (CRM_Utils_Array::explodePadded($data) as $item) {
43b6f159
CW
262 if (!CRM_Utils_Rule::positiveInteger($item)) {
263 $valid = FALSE;
9ff5f6c0
N
264 }
265 }
43b6f159 266 if ($valid) {
9ff5f6c0
N
267 return $data;
268 }
269 }
9ff5f6c0 270 elseif (CRM_Utils_Rule::positiveInteger($data)) {
43b6f159 271 return (int) $data;
6a488035
TO
272 }
273 break;
274
e7dcccf0 275 case 'File':
6a488035 276 if (CRM_Utils_Rule::positiveInteger($data)) {
43b6f159 277 return (int) $data;
6a488035
TO
278 }
279 break;
280
281 case 'Link':
282 if (CRM_Utils_Rule::url($data = trim($data))) {
283 return $data;
284 }
285 break;
286
287 case 'Boolean':
288 if (CRM_Utils_Rule::boolean($data)) {
289 return $data;
290 }
291 break;
292
6a488035
TO
293 case 'String':
294 case 'Memo':
85bdc94e 295 case 'Text':
ab2fa56a 296 return CRM_Core_DAO::escapeString(self::validate($data, $type, $abort));
6a488035 297
f19a5565 298 case 'MysqlColumnNameOrAlias':
a33b83c5
MM
299 if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) {
300 $data = str_replace('`', '', $data);
0fa4baf0 301 $parts = explode('.', $data);
da93a1ab 302 $data = '`' . implode('`.`', $parts) . '`';
0fa4baf0 303
00f11506
MM
304 return $data;
305 }
306 break;
307
0fa4baf0
MM
308 case 'MysqlOrderBy':
309 if (CRM_Utils_Rule::mysqlOrderBy($data)) {
310 $parts = explode(',', $data);
9d5c7f14 311
312 // The field() syntax is tricky here because it uses commas & when
313 // we separate by them we break it up. But we want to keep the clauses in order.
314 // so we just clumsily re-assemble it. Test cover exists.
315 $fieldClauseStart = NULL;
316 foreach ($parts as $index => &$part) {
317 if (substr($part, 0, 6) === 'field(') {
318 // Looking to escape a string like 'field(contribution_status_id,3,4,5) asc'
319 // to 'field(`contribution_status_id`,3,4,5) asc'
320 $fieldClauseStart = $index;
321 continue;
322 }
323 if ($fieldClauseStart !== NULL) {
324 // this is part of the list of field options. Concatenate it back on.
325 $parts[$fieldClauseStart] .= ',' . $part;
326 unset($parts[$index]);
327 if (!strstr($parts[$fieldClauseStart], ')')) {
328 // we have not reached the end of the list.
329 continue;
330 }
331 // We have the last piece of the field() clause, time to escape it.
332 $parts[$fieldClauseStart] = self::mysqlOrderByFieldFunctionCallback($parts[$fieldClauseStart]);
333 $fieldClauseStart = NULL;
334 continue;
335
336 }
337 // Normal clause.
be2fb01f 338 $part = preg_replace_callback('/^(?:(?:((?:`[\w-]{1,64}`|[\w-]{1,64}))(?:\.))?(`[\w-]{1,64}`|[\w-]{1,64})(?: (asc|desc))?)$/i', ['CRM_Utils_Type', 'mysqlOrderByCallback'], trim($part));
0fa4baf0
MM
339 }
340 return implode(', ', $parts);
00f11506
MM
341 }
342 break;
343
6a488035 344 default:
4f1da757
EM
345 CRM_Core_Error::fatal(
346 $type . " is not a recognised (camel cased) data type."
347 );
6a488035
TO
348 break;
349 }
350
d6528d9c 351 // @todo Use exceptions instead of CRM_Core_Error::fatal().
6a488035
TO
352 if ($abort) {
353 $data = htmlentities($data);
ab2fa56a 354
6a488035
TO
355 CRM_Core_Error::fatal("$data is not of the type $type");
356 }
357 return NULL;
358 }
359
360 /**
fe482240 361 * Verify that a variable is of a given type.
6a488035 362 *
a3379cc1
AH
363 * @param mixed $data
364 * The value to validate.
365 * @param string $type
366 * The type to validate against.
77855840 367 * @param bool $abort
a3379cc1 368 * If TRUE, the operation will CRM_Core_Error::fatal() on invalid data.
5e4ccea5 369 * @param string $name
a3379cc1 370 * The name of the attribute
5e4ccea5 371 * @param bool $isThrowException
372 * Should an exception be thrown rather than a using a deprecated fatal error.
6a488035 373 *
a3379cc1
AH
374 * @return mixed
375 * The data, escaped if necessary
5e4ccea5 376 *
377 * @throws \CRM_Core_Exception
6a488035 378 */
d8bf477d 379 public static function validate($data, $type, $abort = TRUE, $name = 'One of parameters ', $isThrowException = TRUE) {
5e4ccea5 380
be2fb01f 381 $possibleTypes = [
5e4ccea5 382 'Integer',
383 'Int',
384 'Positive',
385 'CommaSeparatedIntegers',
386 'Boolean',
387 'Float',
388 'Money',
389 'Text',
390 'String',
391 'Link',
392 'Memo',
393 'Date',
394 'Timestamp',
395 'ContactReference',
396 'MysqlColumnNameOrAlias',
397 'MysqlOrderByDirection',
398 'MysqlOrderBy',
399 'ExtensionKey',
88251439 400 'Json',
d22982f3 401 'Alphanumeric',
8a52ae34 402 'Color',
be2fb01f 403 ];
5e4ccea5 404 if (!in_array($type, $possibleTypes)) {
405 if ($isThrowException) {
406 throw new CRM_Core_Exception(ts('Invalid type, must be one of : ' . implode($possibleTypes)));
407 }
408 CRM_Core_Error::fatal(ts('Invalid type, must be one of : ' . implode($possibleTypes)));
409 }
6a488035
TO
410 switch ($type) {
411 case 'Integer':
412 case 'Int':
413 if (CRM_Utils_Rule::integer($data)) {
2b0e7d03 414 return (int) $data;
6a488035
TO
415 }
416 break;
417
418 case 'Positive':
419 if (CRM_Utils_Rule::positiveInteger($data)) {
258570f7 420 return (int) $data;
6a488035
TO
421 }
422 break;
423
6a488035
TO
424 case 'Float':
425 case 'Money':
426 if (CRM_Utils_Rule::numeric($data)) {
427 return $data;
428 }
429 break;
430
431 case 'Text':
432 case 'String':
433 case 'Link':
434 case 'Memo':
435 return $data;
436
437 case 'Date':
6a488035
TO
438 case 'Timestamp':
439 // a null timestamp is valid
440 if (strlen(trim($data)) == 0) {
441 return trim($data);
442 }
443
444 if ((preg_match('/^\d{14}$/', $data) ||
445 preg_match('/^\d{8}$/', $data)
446 ) &&
447 CRM_Utils_Rule::mysqlDate($data)
448 ) {
449 return $data;
450 }
451 break;
452
453 case 'ContactReference':
454 // null is valid
455 if (strlen(trim($data)) == 0) {
456 return trim($data);
457 }
458
459 if (CRM_Utils_Rule::validContact($data)) {
ab2fa56a 460 return (int) $data;
6a488035
TO
461 }
462 break;
463
5d817a13
MM
464 case 'MysqlOrderByDirection':
465 if (CRM_Utils_Rule::mysqlOrderByDirection($data)) {
0fa4baf0 466 return strtolower($data);
5d817a13
MM
467 }
468 break;
469
5df85a46 470 case 'ExtensionKey':
9e1d9d01 471 if (CRM_Utils_Rule::checkExtensionKeyIsValid($data)) {
5df85a46
SL
472 return $data;
473 }
474 break;
88251439 475
03616e6d
CW
476 default:
477 $check = lcfirst($type);
478 if (CRM_Utils_Rule::$check($data)) {
8a52ae34
CW
479 return $data;
480 }
6a488035
TO
481 }
482
483 if ($abort) {
484 $data = htmlentities($data);
5e4ccea5 485 if ($isThrowException) {
486 throw new CRM_Core_Exception("$name (value: $data) is not of the type $type");
487 }
6a488035
TO
488 CRM_Core_Error::fatal("$name (value: $data) is not of the type $type");
489 }
490
491 return NULL;
492 }
96025800 493
9d5c7f14 494 /**
495 * Preg_replace_callback for mysqlOrderByFieldFunction escape.
496 *
497 * Add backticks around the field name.
498 *
499 * @param string $clause
500 *
501 * @return string
502 */
503 public static function mysqlOrderByFieldFunctionCallback($clause) {
504 return preg_replace('/field\((\w*)/', 'field(`${1}`', $clause);
505 }
506
0fa4baf0
MM
507 /**
508 * preg_replace_callback for MysqlOrderBy escape.
509 */
510 public static function mysqlOrderByCallback($matches) {
511 $output = '';
a33b83c5 512 $matches = str_replace('`', '', $matches);
0fa4baf0 513
a33b83c5 514 // Table name.
f19a5565 515 if (isset($matches[1]) && $matches[1]) {
a33b83c5 516 $output .= '`' . $matches[1] . '`.';
0fa4baf0
MM
517 }
518
a33b83c5 519 // Column name.
0fa4baf0 520 if (isset($matches[2]) && $matches[2]) {
a33b83c5 521 $output .= '`' . $matches[2] . '`';
0fa4baf0
MM
522 }
523
524 // Sort order.
525 if (isset($matches[3]) && $matches[3]) {
526 $output .= ' ' . $matches[3];
527 }
528
529 return $output;
530 }
531
eaecfa20 532 /**
067c2e09 533 * Get list of avaliable Data Types for Option Groups
eaecfa20
SL
534 *
535 * @return array
536 */
537 public static function dataTypes() {
be2fb01f 538 $types = [
d67d221a 539 'Integer',
eaecfa20 540 'String',
d67d221a
SL
541 'Date',
542 'Time',
eaecfa20 543 'Timestamp',
d67d221a 544 'Money',
eaecfa20 545 'Email',
be2fb01f 546 ];
eaecfa20
SL
547 return array_combine($types, $types);
548 }
549
6a488035 550}