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