Merge pull request #3837 from eileenmcnaughton/CRM-15113
[civicrm-core.git] / CRM / Utils / Rule.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.4 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
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-2013
32 * $Id$
33 *
34 */
35
36 require_once 'HTML/QuickForm/Rule/Email.php';
37
38 class CRM_Utils_Rule {
39
40 static function title($str, $maxLength = 127) {
41
42 // check length etc
43 if (empty($str) || strlen($str) > $maxLength) {
44 return FALSE;
45 }
46
47 // Make sure it include valid characters, alpha numeric and underscores
48 if (!preg_match('/^\w[\w\s\'\&\,\$\#\-\.\"\?\!]+$/i', $str)) {
49 return FALSE;
50 }
51
52 return TRUE;
53 }
54
55 static function longTitle($str) {
56 return self::title($str, 255);
57 }
58
59 static function variable($str) {
60 // check length etc
61 if (empty($str) || strlen($str) > 31) {
62 return FALSE;
63 }
64
65 // make sure it include valid characters, alpha numeric and underscores
66 if (!preg_match('/^[\w]+$/i', $str)) {
67 return FALSE;
68 }
69
70 return TRUE;
71 }
72
73 static function qfVariable($str) {
74 // check length etc
75 //if ( empty( $str ) || strlen( $str ) > 31 ) {
76 if (strlen(trim($str)) == 0 || strlen($str) > 31) {
77 return FALSE;
78 }
79
80 // make sure it include valid characters, alpha numeric and underscores
81 // added (. and ,) option (CRM-1336)
82 if (!preg_match('/^[\w\s\.\,]+$/i', $str)) {
83 return FALSE;
84 }
85
86 return TRUE;
87 }
88
89 static function phone($phone) {
90 // check length etc
91 if (empty($phone) || strlen($phone) > 16) {
92 return FALSE;
93 }
94
95 // make sure it include valid characters, (, \s and numeric
96 if (preg_match('/^[\d\(\)\-\.\s]+$/', $phone)) {
97 return TRUE;
98 }
99 return FALSE;
100 }
101
102 static function query($query) {
103 // check length etc
104 if (empty($query) || strlen($query) < 3 || strlen($query) > 127) {
105 return FALSE;
106 }
107
108 // make sure it include valid characters, alpha numeric and underscores
109 if (!preg_match('/^[\w\s\%\'\&\,\$\#]+$/i', $query)) {
110 return FALSE;
111 }
112
113 return TRUE;
114 }
115
116 static function url($url) {
117 return (bool) filter_var($url, FILTER_VALIDATE_URL);
118 }
119
120 static function wikiURL($string) {
121 $items = explode(' ', trim($string), 2);
122 return self::url($items[0]);
123 }
124
125 static function domain($domain) {
126 // not perfect, but better than the previous one; see CRM-1502
127 if (!preg_match('/^[A-Za-z0-9]([A-Za-z0-9\.\-]*[A-Za-z0-9])?$/', $domain)) {
128 return FALSE;
129 }
130 return TRUE;
131 }
132
133 static function date($value, $default = NULL) {
134 if (is_string($value) &&
135 preg_match('/^\d\d\d\d-?\d\d-?\d\d$/', $value)
136 ) {
137 return $value;
138 }
139 return $default;
140 }
141
142 static function dateTime($value, $default = NULL) {
143 $result = $default;
144 if (is_string($value) &&
145 preg_match('/^\d\d\d\d-?\d\d-?\d\d(\s\d\d:\d\d(:\d\d)?|\d\d\d\d(\d\d)?)?$/', $value)
146 ) {
147 $result = $value;
148 }
149
150 return $result;
151 }
152
153 /**
154 * check the validity of the date (in qf format)
155 * note that only a year is valid, or a mon-year is
156 * also valid in addition to day-mon-year. The date
157 * specified has to be beyond today. (i.e today or later)
158 *
159 * @param array $date
160 * @param bool $monthRequired check whether month is mandatory
161 *
162 * @return bool true if valid date
163 * @static
164 * @access public
165 */
166 static function currentDate($date, $monthRequired = TRUE) {
167 $config = CRM_Core_Config::singleton();
168
169 $d = CRM_Utils_Array::value('d', $date);
170 $m = CRM_Utils_Array::value('M', $date);
171 $y = CRM_Utils_Array::value('Y', $date);
172
173 if (!$d && !$m && !$y) {
174 return TRUE;
175 }
176
177 // CRM-9017 CiviContribute/CiviMember form with expiration date format 'm Y'
178 if (!$m && CRM_Utils_Array::value('m', $date)) {
179 $m = CRM_Utils_Array::value('m', $date);
180 }
181
182 $day = $mon = 1;
183 $year = 0;
184 if ($d) {
185 $day = $d;
186 }
187 if ($m) {
188 $mon = $m;
189 }
190 if ($y) {
191 $year = $y;
192 }
193
194 // if we have day we need mon, and if we have mon we need year
195 if (($d && !$m) ||
196 ($d && !$y) ||
197 ($m && !$y)
198 ) {
199 return FALSE;
200 }
201
202 $result = FALSE;
203 if (!empty($day) || !empty($mon) || !empty($year)) {
204 $result = checkdate($mon, $day, $year);
205 }
206
207 if (!$result) {
208 return FALSE;
209 }
210
211 // ensure we have month if required
212 if ($monthRequired && !$m) {
213 return FALSE;
214 }
215
216 // now make sure this date is greater that today
217 $currentDate = getdate();
218 if ($year > $currentDate['year']) {
219 return TRUE;
220 }
221 elseif ($year < $currentDate['year']) {
222 return FALSE;
223 }
224
225 if ($m) {
226 if ($mon > $currentDate['mon']) {
227 return TRUE;
228 }
229 elseif ($mon < $currentDate['mon']) {
230 return FALSE;
231 }
232 }
233
234 if ($d) {
235 if ($day > $currentDate['mday']) {
236 return TRUE;
237 }
238 elseif ($day < $currentDate['mday']) {
239 return FALSE;
240 }
241 }
242
243 return TRUE;
244 }
245
246 /**
247 * check the validity of a date or datetime (timestamp)
248 * value which is in YYYYMMDD or YYYYMMDDHHMMSS format
249 *
250 * Uses PHP checkdate() - params are ( int $month, int $day, int $year )
251 *
252 * @param string $date
253 *
254 * @return bool true if valid date
255 * @static
256 * @access public
257 */
258 static function mysqlDate($date) {
259 // allow date to be null
260 if ($date == NULL) {
261 return TRUE;
262 }
263
264 if (checkdate(substr($date, 4, 2), substr($date, 6, 2), substr($date, 0, 4))) {
265 return TRUE;
266 }
267
268 return FALSE;
269 }
270
271 static function integer($value) {
272 if (is_int($value)) {
273 return TRUE;
274 }
275
276 // CRM-13460
277 // ensure number passed is always a string numeral
278 if (!is_numeric($value)) {
279 return FALSE;
280 }
281
282 // note that is_int matches only integer type
283 // and not strings which are only integers
284 // hence we do this here
285 if (preg_match('/^\d+$/', $value)) {
286 return TRUE;
287 }
288
289 if ($value < 0) {
290 $negValue = -1 * $value;
291 if (is_int($negValue)) {
292 return TRUE;
293 }
294 }
295
296 return FALSE;
297 }
298
299 static function positiveInteger($value) {
300 if (is_int($value)) {
301 return ($value < 0) ? FALSE : TRUE;
302 }
303
304 // CRM-13460
305 // ensure number passed is always a string numeral
306 if (!is_numeric($value)) {
307 return FALSE;
308 }
309
310 if (preg_match('/^\d+$/', $value)) {
311 return TRUE;
312 }
313
314 return FALSE;
315 }
316
317 static function numeric($value) {
318 // lets use a php gatekeeper to ensure this is numeric
319 if (!is_numeric($value)) {
320 return FALSE;
321 }
322
323 return preg_match('/(^-?\d\d*\.\d*$)|(^-?\d\d*$)|(^-?\.\d\d*$)/', $value) ? TRUE : FALSE;
324 }
325
326 static function numberOfDigit($value, $noOfDigit) {
327 return preg_match('/^\d{' . $noOfDigit . '}$/', $value) ? TRUE : FALSE;
328 }
329
330 static function cleanMoney($value) {
331 // first remove all white space
332 $value = str_replace(array(' ', "\t", "\n"), '', $value);
333
334 $config = CRM_Core_Config::singleton();
335
336 if ($config->monetaryThousandSeparator) {
337 $mon_thousands_sep = $config->monetaryThousandSeparator;
338 }
339 else {
340 $mon_thousands_sep = ',';
341 }
342
343 // ugly fix for CRM-6391: do not drop the thousand separator if
344 // it looks like it’s separating decimal part (because a given
345 // value undergoes a second cleanMoney() call, for example)
346 if ($mon_thousands_sep != '.' or substr($value, -3, 1) != '.') {
347 $value = str_replace($mon_thousands_sep, '', $value);
348 }
349
350 if ($config->monetaryDecimalPoint) {
351 $mon_decimal_point = $config->monetaryDecimalPoint;
352 }
353 else {
354 $mon_decimal_point = '.';
355 }
356 $value = str_replace($mon_decimal_point, '.', $value);
357
358 return $value;
359 }
360
361 static function money($value) {
362 $config = CRM_Core_Config::singleton();
363
364 //only edge case when we have a decimal point in the input money
365 //field and not defined in the decimal Point in config settings
366 if ($config->monetaryDecimalPoint &&
367 $config->monetaryDecimalPoint != '.' &&
368 /* CRM-7122 also check for Thousands Separator in config settings */
369 $config->monetaryThousandSeparator != '.' &&
370 substr_count($value, '.')
371 ) {
372 return FALSE;
373 }
374
375 $value = self::cleanMoney($value);
376
377 if (self::integer($value)) {
378 return TRUE;
379 }
380
381 return preg_match('/(^-?\d+\.\d?\d?$)|(^-?\.\d\d?$)/', $value) ? TRUE : FALSE;
382 }
383
384 static function string($value, $maxLength = 0) {
385 if (is_string($value) &&
386 ($maxLength === 0 || strlen($value) <= $maxLength)
387 ) {
388 return TRUE;
389 }
390 return FALSE;
391 }
392
393 static function boolean($value) {
394 return preg_match(
395 '/(^(1|0)$)|(^(Y(es)?|N(o)?)$)|(^(T(rue)?|F(alse)?)$)/i', $value
396 ) ? TRUE : FALSE;
397 }
398
399 static function email($value) {
400 return (bool) filter_var($value, FILTER_VALIDATE_EMAIL);
401 }
402
403 static function emailList($list) {
404 $emails = explode(',', $list);
405 foreach ($emails as $email) {
406 $email = trim($email);
407 if (!self::email($email)) {
408 return FALSE;
409 }
410 }
411 return TRUE;
412 }
413
414 // allow between 4-6 digits as postal code since india needs 6 and US needs 5 (or
415 // if u disregard the first 0, 4 (thanx excel!)
416 // FIXME: we need to figure out how to localize such rules
417 static function postalCode($value) {
418 if (preg_match('/^\d{4,6}(-\d{4})?$/', $value)) {
419 return TRUE;
420 }
421 return FALSE;
422 }
423
424 /**
425 * see how file rules are written in HTML/QuickForm/file.php
426 * Checks to make sure the uploaded file is ascii
427 *
428 * @param array Uploaded file info (from $_FILES)
429 * @access private
430 *
431 * @return bool true if file has been uploaded, false otherwise
432 */
433 static function asciiFile($elementValue) {
434 if ((isset($elementValue['error']) && $elementValue['error'] == 0) ||
435 (!empty($elementValue['tmp_name']) && $elementValue['tmp_name'] != 'none')
436 ) {
437 return CRM_Utils_File::isAscii($elementValue['tmp_name']);
438 }
439 return FALSE;
440 }
441
442 /**
443 * Checks to make sure the uploaded file is in UTF-8, recodes if it's not
444 *
445 * @param array Uploaded file info (from $_FILES)
446 * @access private
447 *
448 * @return bool whether file has been uploaded properly and is now in UTF-8
449 */
450 static function utf8File($elementValue) {
451 $success = FALSE;
452
453 if ((isset($elementValue['error']) && $elementValue['error'] == 0) ||
454 (!empty($elementValue['tmp_name']) && $elementValue['tmp_name'] != 'none')
455 ) {
456
457 $success = CRM_Utils_File::isAscii($elementValue['tmp_name']);
458
459 // if it's a file, but not UTF-8, let's try and recode it
460 // and then make sure it's an UTF-8 file in the end
461 if (!$success) {
462 $success = CRM_Utils_File::toUtf8($elementValue['tmp_name']);
463 if ($success) {
464 $success = CRM_Utils_File::isAscii($elementValue['tmp_name']);
465 }
466 }
467 }
468 return $success;
469 }
470
471 /**
472 * see how file rules are written in HTML/QuickForm/file.php
473 * Checks to make sure the uploaded file is html
474 *
475 * @param array Uploaded file info (from $_FILES)
476 * @access private
477 *
478 * @return bool true if file has been uploaded, false otherwise
479 */
480 static function htmlFile($elementValue) {
481 if ((isset($elementValue['error']) && $elementValue['error'] == 0) ||
482 (!empty($elementValue['tmp_name']) && $elementValue['tmp_name'] != 'none')
483 ) {
484 return CRM_Utils_File::isHtmlFile($elementValue['tmp_name']);
485 }
486 return FALSE;
487 }
488
489 /**
490 * Check if there is a record with the same name in the db
491 *
492 * @param string $value the value of the field we are checking
493 * @param array $options the daoName and fieldName (optional )
494 *
495 * @return boolean true if object exists
496 * @access public
497 * @static
498 */
499 static function objectExists($value, $options) {
500 $name = 'name';
501 if (isset($options[2])) {
502 $name = $options[2];
503 }
504
505 return CRM_Core_DAO::objectExists($value, CRM_Utils_Array::value(0, $options), CRM_Utils_Array::value(1, $options), CRM_Utils_Array::value(2, $options, $name));
506 }
507
508 static function optionExists($value, $options) {
509 return CRM_Core_OptionValue::optionExists($value, $options[0], $options[1], $options[2], CRM_Utils_Array::value(3, $options, 'name'));
510 }
511
512 static function creditCardNumber($value, $type) {
513 require_once 'Validate/Finance/CreditCard.php';
514 return Validate_Finance_CreditCard::number($value, $type);
515 }
516
517 static function cvv($value, $type) {
518 require_once 'Validate/Finance/CreditCard.php';
519
520 return Validate_Finance_CreditCard::cvv($value, $type);
521 }
522
523 static function currencyCode($value) {
524 static $currencyCodes = NULL;
525 if (!$currencyCodes) {
526 $currencyCodes = CRM_Core_PseudoConstant::currencyCode();
527 }
528 if (in_array($value, $currencyCodes)) {
529 return TRUE;
530 }
531 return FALSE;
532 }
533
534 static function xssString($value) {
535 if (is_string($value)) {
536 return preg_match('!<(vb)?script[^>]*>.*</(vb)?script.*>!ims',
537 $value
538 ) ? FALSE : TRUE;
539 }
540 else {
541 return TRUE;
542 }
543 }
544
545 static function fileExists($path) {
546 return file_exists($path);
547 }
548
549 static function autocomplete($value, $options) {
550 if ($value) {
551 $selectOption = CRM_Core_BAO_CustomOption::valuesByID($options['fieldID'], $options['optionGroupID']);
552
553 if (!in_array($value, $selectOption)) {
554 return FALSE;
555 }
556 }
557 return TRUE;
558 }
559
560 static function validContact($value, $actualElementValue = NULL) {
561 if ($actualElementValue) {
562 $value = $actualElementValue;
563 }
564
565 if ($value && !is_numeric($value)) {
566 return FALSE;
567 }
568 return TRUE;
569 }
570
571 /**
572 * check the validity of the date (in qf format)
573 * note that only a year is valid, or a mon-year is
574 * also valid in addition to day-mon-year
575 *
576 * @param array $date
577 *
578 * @return bool true if valid date
579 * @static
580 * @access public
581 */
582 static function qfDate($date) {
583 $config = CRM_Core_Config::singleton();
584
585 $d = CRM_Utils_Array::value('d', $date);
586 $m = CRM_Utils_Array::value('M', $date);
587 $y = CRM_Utils_Array::value('Y', $date);
588 if (isset($date['h']) ||
589 isset($date['g'])
590 ) {
591 $m = CRM_Utils_Array::value('M', $date);
592 }
593
594 if (!$d && !$m && !$y) {
595 return TRUE;
596 }
597
598 $day = $mon = 1;
599 $year = 0;
600 if ($d) {
601 $day = $d;
602 }
603 if ($m) {
604 $mon = $m;
605 }
606 if ($y) {
607 $year = $y;
608 }
609
610 // if we have day we need mon, and if we have mon we need year
611 if (($d && !$m) ||
612 ($d && !$y) ||
613 ($m && !$y)
614 ) {
615 return FALSE;
616 }
617
618 if (!empty($day) || !empty($mon) || !empty($year)) {
619 return checkdate($mon, $day, $year);
620 }
621 return FALSE;
622 }
623
624 static function qfKey($key) {
625 return ($key) ? CRM_Core_Key::valid($key) : FALSE;
626 }
627 }
628