drupal-civicrm/sites/all/modules/civicrm/CRM/Utils/Rule.php
2018-01-14 13:10:16 +00:00

932 lines
23 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
/**
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
require_once 'HTML/QuickForm/Rule/Email.php';
/**
* Class CRM_Utils_Rule
*/
class CRM_Utils_Rule {
/**
* @param $str
* @param int $maxLength
*
* @return bool
*/
public static function title($str, $maxLength = 127) {
// check length etc
if (empty($str) || strlen($str) > $maxLength) {
return FALSE;
}
// Make sure it include valid characters, alpha numeric and underscores
if (!preg_match('/^\w[\w\s\'\&\,\$\#\-\.\"\?\!]+$/i', $str)) {
return FALSE;
}
return TRUE;
}
/**
* @param $str
*
* @return bool
*/
public static function longTitle($str) {
return self::title($str, 255);
}
/**
* @param $str
*
* @return bool
*/
public static function variable($str) {
// check length etc
if (empty($str) || strlen($str) > 31) {
return FALSE;
}
// make sure it includes valid characters, alpha numeric and underscores
if (!preg_match('/^[\w]+$/i', $str)) {
return FALSE;
}
return TRUE;
}
/**
* Validate that a string is a valid MySQL column name or alias.
*
* @param $str
*
* @return bool
*/
public static function mysqlColumnNameOrAlias($str) {
// Check not empty.
if (empty($str)) {
return FALSE;
}
// Ensure $str conforms to expected format. Not a complete expression of
// what MySQL permits; this should permit the formats CiviCRM generates.
//
// * Table name prefix is optional.
// * Table & column names & aliases:
// * Composed of alphanumeric chars, underscore and hyphens.
// * Maximum length of 64 chars.
// * Optionally surrounded by backticks, in which case spaces also OK.
if (!preg_match('/^((`[\w- ]{1,64}`|[\w-]{1,64})\.)?(`[\w- ]{1,64}`|[\w-]{1,64})$/i', $str)) {
return FALSE;
}
return TRUE;
}
/**
* Validate that a string is ASC or DESC.
*
* Empty string should be treated as invalid and ignored => default = ASC.
*
* @param $str
* @return bool
*/
public static function mysqlOrderByDirection($str) {
if (!preg_match('/^(asc|desc)$/i', $str)) {
return FALSE;
}
return TRUE;
}
/**
* Validate that a string is valid order by clause.
*
* @param $str
* @return bool
*/
public static function mysqlOrderBy($str) {
$matches = array();
// Using the field function in order by is valid.
// Look for a string like field(contribution_status_id,3,4,6).
// or field(civicrm_contribution.contribution_status_id,3,4,6)
if (preg_match('/field\([a-z_.]+,[0-9,]+\)/', $str, $matches)) {
// We have checked these. Remove them as they will fail the next lot.
// Our check currently only permits numbers & no back ticks. If we get a
// need for strings or backticks we can add.
$str = str_replace($matches, '', $str);
}
$str = trim($str);
if (!empty($matches) && empty($str)) {
// nothing left to check after the field check.
return TRUE;
}
// Making a regex for a comma separated list is quite hard and not readable
// at all, so we split and loop over.
$parts = explode(',', $str);
foreach ($parts as $part) {
if (!preg_match('/^((`[\w-]{1,64}`|[\w-]{1,64})\.)?(`[\w-]{1,64}`|[\w-]{1,64})( (asc|desc))?$/i', trim($part))) {
return FALSE;
}
}
return TRUE;
}
/**
* @param $str
*
* @return bool
*/
public static function qfVariable($str) {
// check length etc
//if ( empty( $str ) || strlen( $str ) > 31 ) {
if (strlen(trim($str)) == 0 || strlen($str) > 31) {
return FALSE;
}
// make sure it includes valid characters, alpha numeric and underscores
// added (. and ,) option (CRM-1336)
if (!preg_match('/^[\w\s\.\,]+$/i', $str)) {
return FALSE;
}
return TRUE;
}
/**
* @param $phone
*
* @return bool
*/
public static function phone($phone) {
// check length etc
if (empty($phone) || strlen($phone) > 16) {
return FALSE;
}
// make sure it includes valid characters, (, \s and numeric
if (preg_match('/^[\d\(\)\-\.\s]+$/', $phone)) {
return TRUE;
}
return FALSE;
}
/**
* @param $query
*
* @return bool
*/
public static function query($query) {
// check length etc
if (empty($query) || strlen($query) < 3 || strlen($query) > 127) {
return FALSE;
}
// make sure it includes valid characters, alpha numeric and underscores
if (!preg_match('/^[\w\s\%\'\&\,\$\#]+$/i', $query)) {
return FALSE;
}
return TRUE;
}
/**
* @param $url
*
* @return bool
*/
public static function url($url) {
if (preg_match('/^\//', $url)) {
// allow relative URL's (CRM-15598)
$url = 'http://' . $_SERVER['HTTP_HOST'] . $url;
}
return (bool) filter_var($url, FILTER_VALIDATE_URL);
}
/**
* @param $url
*
* @return bool
*/
public static function urlish($url) {
if (empty($url)) {
return TRUE;
}
$url = Civi::paths()->getUrl($url, 'absolute');
return (bool) filter_var($url, FILTER_VALIDATE_URL);
}
/**
* @param $string
*
* @return bool
*/
public static function wikiURL($string) {
$items = explode(' ', trim($string), 2);
return self::url($items[0]);
}
/**
* @param $domain
*
* @return bool
*/
public static function domain($domain) {
// not perfect, but better than the previous one; see CRM-1502
if (!preg_match('/^[A-Za-z0-9]([A-Za-z0-9\.\-]*[A-Za-z0-9])?$/', $domain)) {
return FALSE;
}
return TRUE;
}
/**
* @param $value
* @param null $default
*
* @return null
*/
public static function date($value, $default = NULL) {
if (is_string($value) &&
preg_match('/^\d\d\d\d-?\d\d-?\d\d$/', $value)
) {
return $value;
}
return $default;
}
/**
* @param $value
* @param null $default
*
* @return null|string
*/
public static function dateTime($value, $default = NULL) {
$result = $default;
if (is_string($value) &&
preg_match('/^\d\d\d\d-?\d\d-?\d\d(\s\d\d:\d\d(:\d\d)?|\d\d\d\d(\d\d)?)?$/', $value)
) {
$result = $value;
}
return $result;
}
/**
* Check the validity of the date (in qf format)
* note that only a year is valid, or a mon-year is
* also valid in addition to day-mon-year. The date
* specified has to be beyond today. (i.e today or later)
*
* @param array $date
* @param bool $monthRequired
* Check whether month is mandatory.
*
* @return bool
* true if valid date
*/
public static function currentDate($date, $monthRequired = TRUE) {
$config = CRM_Core_Config::singleton();
$d = CRM_Utils_Array::value('d', $date);
$m = CRM_Utils_Array::value('M', $date);
$y = CRM_Utils_Array::value('Y', $date);
if (!$d && !$m && !$y) {
return TRUE;
}
// CRM-9017 CiviContribute/CiviMember form with expiration date format 'm Y'
if (!$m && !empty($date['m'])) {
$m = CRM_Utils_Array::value('m', $date);
}
$day = $mon = 1;
$year = 0;
if ($d) {
$day = $d;
}
if ($m) {
$mon = $m;
}
if ($y) {
$year = $y;
}
// if we have day we need mon, and if we have mon we need year
if (($d && !$m) ||
($d && !$y) ||
($m && !$y)
) {
return FALSE;
}
$result = FALSE;
if (!empty($day) || !empty($mon) || !empty($year)) {
$result = checkdate($mon, $day, $year);
}
if (!$result) {
return FALSE;
}
// ensure we have month if required
if ($monthRequired && !$m) {
return FALSE;
}
// now make sure this date is greater that today
$currentDate = getdate();
if ($year > $currentDate['year']) {
return TRUE;
}
elseif ($year < $currentDate['year']) {
return FALSE;
}
if ($m) {
if ($mon > $currentDate['mon']) {
return TRUE;
}
elseif ($mon < $currentDate['mon']) {
return FALSE;
}
}
if ($d) {
if ($day > $currentDate['mday']) {
return TRUE;
}
elseif ($day < $currentDate['mday']) {
return FALSE;
}
}
return TRUE;
}
/**
* Check the validity of a date or datetime (timestamp)
* value which is in YYYYMMDD or YYYYMMDDHHMMSS format
*
* Uses PHP checkdate() - params are ( int $month, int $day, int $year )
*
* @param string $date
*
* @return bool
* true if valid date
*/
public static function mysqlDate($date) {
// allow date to be null
if ($date == NULL) {
return TRUE;
}
if (checkdate(substr($date, 4, 2), substr($date, 6, 2), substr($date, 0, 4))) {
return TRUE;
}
return FALSE;
}
/**
* @param $value
*
* @return bool
*/
public static function integer($value) {
if (is_int($value)) {
return TRUE;
}
// CRM-13460
// ensure number passed is always a string numeral
if (!is_numeric($value)) {
return FALSE;
}
// note that is_int matches only integer type
// and not strings which are only integers
// hence we do this here
if (preg_match('/^\d+$/', $value)) {
return TRUE;
}
if ($value < 0) {
$negValue = -1 * $value;
if (is_int($negValue)) {
return TRUE;
}
}
return FALSE;
}
/**
* @param $value
*
* @return bool
*/
public static function positiveInteger($value) {
if (is_int($value)) {
return ($value < 0) ? FALSE : TRUE;
}
// CRM-13460
// ensure number passed is always a string numeral
if (!is_numeric($value)) {
return FALSE;
}
if (preg_match('/^\d+$/', $value)) {
return TRUE;
}
return FALSE;
}
/**
* @param $value
*
* @return bool
*/
public static function numeric($value) {
// lets use a php gatekeeper to ensure this is numeric
if (!is_numeric($value)) {
return FALSE;
}
return preg_match('/(^-?\d\d*\.\d*$)|(^-?\d\d*$)|(^-?\.\d\d*$)/', $value) ? TRUE : FALSE;
}
/**
* @param $value
* @param $noOfDigit
*
* @return bool
*/
public static function numberOfDigit($value, $noOfDigit) {
return preg_match('/^\d{' . $noOfDigit . '}$/', $value) ? TRUE : FALSE;
}
/**
* Strip thousand separator from a money string.
*
* Note that this should be done at the form layer. Once we are processing
* money at the BAO or processor layer we should be working with something that
* is already in a normalised format.
*
* @param string $value
*
* @return string
*/
public static function cleanMoney($value) {
// first remove all white space
$value = str_replace(array(' ', "\t", "\n"), '', $value);
$config = CRM_Core_Config::singleton();
//CRM-14868
$currencySymbols = CRM_Core_PseudoConstant::get(
'CRM_Contribute_DAO_Contribution',
'currency', array(
'keyColumn' => 'name',
'labelColumn' => 'symbol',
)
);
$value = str_replace($currencySymbols, '', $value);
if ($config->monetaryThousandSeparator) {
$mon_thousands_sep = $config->monetaryThousandSeparator;
}
else {
$mon_thousands_sep = ',';
}
// ugly fix for CRM-6391: do not drop the thousand separator if
// it looks like its separating decimal part (because a given
// value undergoes a second cleanMoney() call, for example)
// CRM-15835 - in case the amount/value contains 0 after decimal
// eg 150.5 the following if condition will pass
if ($mon_thousands_sep != '.' or (substr($value, -3, 1) != '.' && substr($value, -2, 1) != '.')) {
$value = str_replace($mon_thousands_sep, '', $value);
}
if ($config->monetaryDecimalPoint) {
$mon_decimal_point = $config->monetaryDecimalPoint;
}
else {
$mon_decimal_point = '.';
}
$value = str_replace($mon_decimal_point, '.', $value);
return $value;
}
/**
* @param $value
*
* @return bool
*/
public static function money($value) {
$config = CRM_Core_Config::singleton();
// only edge case when we have a decimal point in the input money
// field and not defined in the decimal Point in config settings
if ($config->monetaryDecimalPoint &&
$config->monetaryDecimalPoint != '.' &&
// CRM-7122 also check for Thousands Separator in config settings
$config->monetaryThousandSeparator != '.' &&
substr_count($value, '.')
) {
return FALSE;
}
$value = self::cleanMoney($value);
if (self::integer($value)) {
return TRUE;
}
// Allow values such as -0, 1.024555, -.1
// We need to support multiple decimal places here, not just the number allowed by locale
// otherwise tax calculations break when you want the inclusive amount to be a round number (eg. £10 inc. VAT requires 8.333333333 here).
return preg_match('/(^-?\d+\.?\d*$)|(^-?\.\d+$)/', $value) ? TRUE : FALSE;
}
/**
* @param $value
* @param int $maxLength
*
* @return bool
*/
public static function string($value, $maxLength = 0) {
if (is_string($value) &&
($maxLength === 0 || strlen($value) <= $maxLength)
) {
return TRUE;
}
return FALSE;
}
/**
* @param $value
*
* @return bool
*/
public static function boolean($value) {
return preg_match(
'/(^(1|0)$)|(^(Y(es)?|N(o)?)$)|(^(T(rue)?|F(alse)?)$)/i', $value
) ? TRUE : FALSE;
}
/**
* @param $value
*
* @return bool
*/
public static function email($value) {
return (bool) filter_var($value, FILTER_VALIDATE_EMAIL);
}
/**
* @param $list
*
* @return bool
*/
public static function emailList($list) {
$emails = explode(',', $list);
foreach ($emails as $email) {
$email = trim($email);
if (!self::email($email)) {
return FALSE;
}
}
return TRUE;
}
/**
* allow between 4-6 digits as postal code since india needs 6 and US needs 5 (or
* if u disregard the first 0, 4 (thanx excel!)
* FIXME: we need to figure out how to localize such rules
* @param $value
*
* @return bool
*/
public static function postalCode($value) {
if (preg_match('/^\d{4,6}(-\d{4})?$/', $value)) {
return TRUE;
}
return FALSE;
}
/**
* See how file rules are written in HTML/QuickForm/file.php
* Checks to make sure the uploaded file is ascii
*
* @param string $elementValue
*
* @return bool
* True if file has been uploaded, false otherwise
*/
public static function asciiFile($elementValue) {
if ((isset($elementValue['error']) && $elementValue['error'] == 0) ||
(!empty($elementValue['tmp_name']) && $elementValue['tmp_name'] != 'none')
) {
return CRM_Utils_File::isAscii($elementValue['tmp_name']);
}
return FALSE;
}
/**
* Checks to make sure the uploaded file is in UTF-8, recodes if it's not
*
* @param array $elementValue
*
* @return bool
* Whether file has been uploaded properly and is now in UTF-8.
*/
public static function utf8File($elementValue) {
$success = FALSE;
if ((isset($elementValue['error']) && $elementValue['error'] == 0) ||
(!empty($elementValue['tmp_name']) && $elementValue['tmp_name'] != 'none')
) {
$success = CRM_Utils_File::isAscii($elementValue['tmp_name']);
// if it's a file, but not UTF-8, let's try and recode it
// and then make sure it's an UTF-8 file in the end
if (!$success) {
$success = CRM_Utils_File::toUtf8($elementValue['tmp_name']);
if ($success) {
$success = CRM_Utils_File::isAscii($elementValue['tmp_name']);
}
}
}
return $success;
}
/**
* See how file rules are written in HTML/QuickForm/file.php
* Checks to make sure the uploaded file is html
*
* @param array $elementValue
*
* @return bool
* True if file has been uploaded, false otherwise
*/
public static function htmlFile($elementValue) {
if ((isset($elementValue['error']) && $elementValue['error'] == 0) ||
(!empty($elementValue['tmp_name']) && $elementValue['tmp_name'] != 'none')
) {
return CRM_Utils_File::isHtmlFile($elementValue['tmp_name']);
}
return FALSE;
}
/**
* Check if there is a record with the same name in the db.
*
* @param string $value
* The value of the field we are checking.
* @param array $options
* The daoName, fieldName (optional) and DomainID (optional).
*
* @return bool
* true if object exists
*/
public static function objectExists($value, $options) {
$name = 'name';
if (isset($options[2])) {
$name = $options[2];
}
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), CRM_Utils_Array::value(3, $options));
}
/**
* @param $value
* @param $options
*
* @return bool
*/
public static function optionExists($value, $options) {
return CRM_Core_OptionValue::optionExists($value, $options[0], $options[1], $options[2], CRM_Utils_Array::value(3, $options, 'name'), CRM_Utils_Array::value(4, $options, FALSE));
}
/**
* @param $value
* @param $type
*
* @return bool
*/
public static function creditCardNumber($value, $type) {
return Validate_Finance_CreditCard::number($value, $type);
}
/**
* @param $value
* @param $type
*
* @return bool
*/
public static function cvv($value, $type) {
return Validate_Finance_CreditCard::cvv($value, $type);
}
/**
* @param $value
*
* @return bool
*/
public static function currencyCode($value) {
static $currencyCodes = NULL;
if (!$currencyCodes) {
$currencyCodes = CRM_Core_PseudoConstant::currencyCode();
}
if (in_array($value, $currencyCodes)) {
return TRUE;
}
return FALSE;
}
/**
* @param $value
*
* @return bool
*/
public static function xssString($value) {
if (is_string($value)) {
return preg_match('!<(vb)?script[^>]*>.*</(vb)?script.*>!ims',
$value
) ? FALSE : TRUE;
}
else {
return TRUE;
}
}
/**
* @param $path
*
* @return bool
*/
public static function fileExists($path) {
return file_exists($path);
}
/**
* Determine whether the value contains a valid reference to a directory.
*
* Paths stored in the setting system may be absolute -- or may be
* relative to the default data directory.
*
* @param string $path
* @return bool
*/
public static function settingPath($path) {
return is_dir(Civi::paths()->getPath($path));
}
/**
* @param $value
* @param null $actualElementValue
*
* @return bool
*/
public static function validContact($value, $actualElementValue = NULL) {
if ($actualElementValue) {
$value = $actualElementValue;
}
return CRM_Utils_Rule::positiveInteger($value);
}
/**
* Check the validity of the date (in qf format)
* note that only a year is valid, or a mon-year is
* also valid in addition to day-mon-year
*
* @param array $date
*
* @return bool
* true if valid date
*/
public static function qfDate($date) {
$config = CRM_Core_Config::singleton();
$d = CRM_Utils_Array::value('d', $date);
$m = CRM_Utils_Array::value('M', $date);
$y = CRM_Utils_Array::value('Y', $date);
if (isset($date['h']) ||
isset($date['g'])
) {
$m = CRM_Utils_Array::value('M', $date);
}
if (!$d && !$m && !$y) {
return TRUE;
}
$day = $mon = 1;
$year = 0;
if ($d) {
$day = $d;
}
if ($m) {
$mon = $m;
}
if ($y) {
$year = $y;
}
// if we have day we need mon, and if we have mon we need year
if (($d && !$m) ||
($d && !$y) ||
($m && !$y)
) {
return FALSE;
}
if (!empty($day) || !empty($mon) || !empty($year)) {
return checkdate($mon, $day, $year);
}
return FALSE;
}
/**
* @param $key
*
* @return bool
*/
public static function qfKey($key) {
return ($key) ? CRM_Core_Key::valid($key) : FALSE;
}
/**
* Check if the values in the date range are in correct chronological order.
*
* @param array $fields
* Fields of the form.
* @param $fieldName
* Name of date range field.
* @param $errors
* The error array.
* @param $title
* Title of the date range to be displayed in the error message.
*/
public static function validDateRange($fields, $fieldName, &$errors, $title) {
$lowDate = strtotime($fields[$fieldName . '_low']);
$highDate = strtotime($fields[$fieldName . '_high']);
if ($lowDate > $highDate) {
$errors[$fieldName . '_range_error'] = ts('%1: Please check that your date range is in correct chronological order.', array(1 => $title));
}
}
/**
* @param string $key Extension Key to check
* @return bool
*/
public static function checkExtesnionKeyIsValid($key = NULL) {
if (!empty($key) && !preg_match('/^[0-9a-zA-Z._-]+$/', $key)) {
return FALSE;
}
return TRUE;
}
}