[NFC] towards CRM-19840 & others, load date metadata for custom & core profile fields.
[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 * 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
168 /**
169 * Helper function to call escape on arrays.
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
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
192 /**
193 * Verify that a variable is of a given type, and apply a bit of processing.
194 *
195 * @param mixed $data
196 * The value to be verified/escaped.
197 * @param string $type
198 * The type to verify against.
199 * @param bool $abort
200 * If TRUE, the operation will CRM_Core_Error::fatal() on invalid data.
201 *
202 * @return mixed
203 * The data, escaped if necessary.
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)) {
210 return (int) $data;
211 }
212 break;
213
214 case 'Positive':
215 if (CRM_Utils_Rule::positiveInteger($data)) {
216 return (int) $data;
217 }
218 break;
219
220 // CRM-8925 for custom fields of this type
221 case 'Country':
222 case 'StateProvince':
223 // Handle multivalued data in delimited or array format
224 if (is_array($data) || (strpos($data, CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE)) {
225 $valid = TRUE;
226 foreach (CRM_Utils_Array::explodePadded($data) as $item) {
227 if (!CRM_Utils_Rule::positiveInteger($item)) {
228 $valid = FALSE;
229 }
230 }
231 if ($valid) {
232 return $data;
233 }
234 }
235 elseif (CRM_Utils_Rule::positiveInteger($data)) {
236 return (int) $data;
237 }
238 break;
239
240 case 'File':
241 if (CRM_Utils_Rule::positiveInteger($data)) {
242 return (int) $data;
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':
267 case 'Text':
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)) {
292 return (int) $data;
293 }
294 break;
295
296 case 'MysqlColumnNameOrAlias':
297 if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) {
298 $data = str_replace('`', '', $data);
299 $parts = explode('.', $data);
300 $data = '`' . implode('`.`', $parts) . '`';
301
302 return $data;
303 }
304 break;
305
306 case 'MysqlOrderByDirection':
307 if (CRM_Utils_Rule::mysqlOrderByDirection($data)) {
308 return strtolower($data);
309 }
310 break;
311
312 case 'MysqlOrderBy':
313 if (CRM_Utils_Rule::mysqlOrderBy($data)) {
314 $parts = explode(',', $data);
315 foreach ($parts as &$part) {
316 $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));
317 }
318 return implode(', ', $parts);
319 }
320 break;
321
322 default:
323 CRM_Core_Error::fatal(
324 $type . " is not a recognised (camel cased) data type."
325 );
326 break;
327 }
328
329 // @todo Use exceptions instead of CRM_Core_Error::fatal().
330 if ($abort) {
331 $data = htmlentities($data);
332 CRM_Core_Error::fatal("$data is not of the type $type");
333 }
334 return NULL;
335 }
336
337 /**
338 * Verify that a variable is of a given type.
339 *
340 * @param mixed $data
341 * The value to validate.
342 * @param string $type
343 * The type to validate against.
344 * @param bool $abort
345 * If TRUE, the operation will CRM_Core_Error::fatal() on invalid data.
346 * @name string $name
347 * The name of the attribute
348 *
349 * @return mixed
350 * The data, escaped if necessary
351 */
352 public static function validate($data, $type, $abort = TRUE, $name = 'One of parameters ') {
353 switch ($type) {
354 case 'Integer':
355 case 'Int':
356 if (CRM_Utils_Rule::integer($data)) {
357 return (int) $data;
358 }
359 break;
360
361 case 'Positive':
362 if (CRM_Utils_Rule::positiveInteger($data)) {
363 return (int) $data;
364 }
365 break;
366
367 case 'Boolean':
368 if (CRM_Utils_Rule::boolean($data)) {
369 return $data;
370 }
371 break;
372
373 case 'Float':
374 case 'Money':
375 if (CRM_Utils_Rule::numeric($data)) {
376 return $data;
377 }
378 break;
379
380 case 'Text':
381 case 'String':
382 case 'Link':
383 case 'Memo':
384 return $data;
385
386 case 'Date':
387 // a null date is valid
388 if (strlen(trim($data)) == 0) {
389 return trim($data);
390 }
391
392 if (preg_match('/^\d{8}$/', $data) &&
393 CRM_Utils_Rule::mysqlDate($data)
394 ) {
395 return $data;
396 }
397 break;
398
399 case 'Timestamp':
400 // a null timestamp is valid
401 if (strlen(trim($data)) == 0) {
402 return trim($data);
403 }
404
405 if ((preg_match('/^\d{14}$/', $data) ||
406 preg_match('/^\d{8}$/', $data)
407 ) &&
408 CRM_Utils_Rule::mysqlDate($data)
409 ) {
410 return $data;
411 }
412 break;
413
414 case 'ContactReference':
415 // null is valid
416 if (strlen(trim($data)) == 0) {
417 return trim($data);
418 }
419
420 if (CRM_Utils_Rule::validContact($data)) {
421 return $data;
422 }
423 break;
424
425 case 'MysqlColumnNameOrAlias':
426 if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) {
427 return $data;
428 }
429 break;
430
431 case 'MysqlOrderByDirection':
432 if (CRM_Utils_Rule::mysqlOrderByDirection($data)) {
433 return strtolower($data);
434 }
435 break;
436
437 case 'MysqlOrderBy':
438 if (CRM_Utils_Rule::mysqlOrderBy($data)) {
439 return $data;
440 }
441 break;
442
443 default:
444 CRM_Core_Error::fatal("Cannot recognize $type for $data");
445 break;
446 }
447
448 if ($abort) {
449 $data = htmlentities($data);
450 CRM_Core_Error::fatal("$name (value: $data) is not of the type $type");
451 }
452
453 return NULL;
454 }
455
456 /**
457 * preg_replace_callback for MysqlOrderBy escape.
458 */
459 public static function mysqlOrderByCallback($matches) {
460 $output = '';
461 $matches = str_replace('`', '', $matches);
462
463 // Table name.
464 if (isset($matches[1]) && $matches[1]) {
465 $output .= '`' . $matches[1] . '`.';
466 }
467
468 // Column name.
469 if (isset($matches[2]) && $matches[2]) {
470 $output .= '`' . $matches[2] . '`';
471 }
472
473 // Sort order.
474 if (isset($matches[3]) && $matches[3]) {
475 $output .= ' ' . $matches[3];
476 }
477
478 return $output;
479 }
480
481 /**
482 * Get list of avaliable Data Tupes for Option Groups
483 *
484 * @return array
485 */
486 public static function dataTypes() {
487 $types = array(
488 'Integer',
489 'String',
490 'Date',
491 'Time',
492 'Timestamp',
493 'Money',
494 'Email',
495 );
496 return array_combine($types, $types);
497 }
498
499 }