mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-01-08 19:21:40 +09:00
git-svn-id: http://xe-core.googlecode.com/svn/branches/luminous@11130 201d5d3c-b55e-5fd7-737f-ddc643e51545
584 lines
14 KiB
PHP
584 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Validator class
|
|
* @author NHN (developers@xpressengine.com)
|
|
* @package /classes/validator
|
|
* @version 0.1
|
|
*/
|
|
class Validator
|
|
{
|
|
/**
|
|
* cache directory
|
|
* @var string
|
|
*/
|
|
var $_cache_dir = '';
|
|
/**
|
|
* last error
|
|
* @var array
|
|
*/
|
|
var $_last_error;
|
|
/**
|
|
* xml ruleset object
|
|
* @var Xml_Node_ object
|
|
*/
|
|
var $_xml_ruleset = null;
|
|
/**
|
|
* rule list
|
|
* @var array
|
|
*/
|
|
var $_rules;
|
|
/**
|
|
* filter list
|
|
* @var array
|
|
*/
|
|
var $_filters;
|
|
/**
|
|
* Can usable status for multibyte string function
|
|
* @var boolean
|
|
*/
|
|
var $_has_mb_func;
|
|
/**
|
|
* validator version
|
|
* @var string
|
|
*/
|
|
var $_version = '1.0';
|
|
/**
|
|
* ruleset xml file path
|
|
* @var string
|
|
*/
|
|
var $_xml_path = '';
|
|
|
|
/**
|
|
* @constructor
|
|
* @param string $xml_path
|
|
* @return void
|
|
*/
|
|
function Validator($xml_path='') {
|
|
$this->__construct($xml_path);
|
|
}
|
|
|
|
/**
|
|
* @constructor
|
|
* @param string $xml_path
|
|
* @return void
|
|
*/
|
|
function __construct($xml_path='') {
|
|
$this->_rules = array();
|
|
$this->_filters = array();
|
|
$this->_xml_ruleset = null;
|
|
|
|
if($xml_path) $this->load($xml_path);
|
|
|
|
// predefined rules
|
|
$this->addRule(array(
|
|
'email' => '/^[\w-]+((?:\.|\+|\~)[\w-]+)*@[\w-]+(\.[\w-]+)+$/',
|
|
'userid' => '/^[a-z]+[\w-]*[a-z0-9_]+$/i',
|
|
'url' => '/^(https?|ftp|mms):\/\/[0-9a-z-]+(\.[_0-9a-z-]+)+(:\d+)?/',
|
|
'alpha' => '/^[a-z]*$/i',
|
|
'alpha_number' => '/^[a-z][a-z0-9_]*$/i',
|
|
'number' => '/^(?:[1-9]\\d*|0)$/'
|
|
));
|
|
|
|
$this->_has_mb_func = is_callable('mb_strlen');
|
|
$this->setCacheDir('./files/cache');
|
|
}
|
|
|
|
/**
|
|
* @destructor
|
|
* @return void
|
|
*/
|
|
function __destruct() {
|
|
$this->_rules = null;
|
|
$this->_filters = null;
|
|
}
|
|
|
|
/**
|
|
* Load a xml file
|
|
* @param string $xml_path A file name to be loaded
|
|
* @return boolean
|
|
*/
|
|
function load($xml_path) {
|
|
$this->_xml_ruleset = null;
|
|
|
|
$xml_path = realpath($xml_path);
|
|
if(!is_readable($xml_path)) return false;
|
|
|
|
$parser = new XmlParser();
|
|
$xml = $parser->loadXmlFile($xml_path);
|
|
if(!isset($xml->ruleset) || !isset($xml->ruleset->fields) || !isset($xml->ruleset->fields->field)) return false;
|
|
|
|
// custom rules
|
|
if(isset($xml->ruleset->customrules) && isset($xml->ruleset->customrules->rule)) {
|
|
$customrules = $xml->ruleset->customrules->rule;
|
|
if(!is_array($customrules)) $customrules = array($customrules);
|
|
|
|
$rules = array();
|
|
foreach($customrules as $rule) {
|
|
if(!isset($rule->attrs) || !isset($rule->attrs->name)) continue;
|
|
|
|
$rule = (array)$rule->attrs;
|
|
$name = $rule['name'];
|
|
unset($rule['name']);
|
|
|
|
$rules[$name] = $rule;
|
|
}
|
|
if(count($rules)) $this->addRule($rules);
|
|
}
|
|
|
|
// filters
|
|
$fields = $xml->ruleset->fields->field;
|
|
if(!is_array($fields)) $fields = array($fields);
|
|
|
|
$filters = array();
|
|
foreach($fields as $field) {
|
|
$name = '';
|
|
$filter = array();
|
|
|
|
if(!isset($field->attrs) || !isset($field->attrs->name)) continue;
|
|
$filter = (array)$field->attrs;
|
|
|
|
$name = $filter['name'];
|
|
unset($filter['name']);
|
|
|
|
// conditional statement
|
|
if(isset($field->if)) {
|
|
$if = $field->if;
|
|
if(!is_array($if)) $if = array($if);
|
|
foreach($if as $idx=>$cond) {
|
|
$if[$idx] = (array)$cond->attrs;
|
|
}
|
|
$filter['if'] = $if;
|
|
}
|
|
|
|
$filters[$name] = $filter;
|
|
}
|
|
|
|
$this->_xml_ruleset = $xml->ruleset;
|
|
$this->_filters = $filters;
|
|
$this->_xml_path = $xml_path;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Set root cache directory
|
|
* @param string $cache_dir Root cache directory
|
|
* @return void
|
|
*/
|
|
function setCacheDir($cache_dir){
|
|
if(is_dir($cache_dir)) {
|
|
$this->_cache_dir = preg_replace('@/$@', '', $cache_dir);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate the fields. If the fields aren't passed, validation will be execute on the Context variables.
|
|
* @param array $fields Target fields. The keys of the array represents field's name, its values represents field's value.
|
|
* @return boolean TRUE if it is valid, FALSE otherwise.
|
|
*/
|
|
function validate($fields_=null) {
|
|
if(is_array($fields_)) {
|
|
$fields = $fields_;
|
|
} else {
|
|
$args = array_keys($this->_filters);
|
|
$fields = (array)Context::getRequestVars();
|
|
}
|
|
|
|
if(!is_array($fields)) return true;
|
|
|
|
$filter_default = array(
|
|
'required' => 'false',
|
|
'default' => '',
|
|
'modifiers' => array(),
|
|
'length' => 0,
|
|
'equalto' => 0,
|
|
'rule' => 0,
|
|
'if' => array()
|
|
);
|
|
|
|
$fields = array_map(array($this, 'arrayTrim'), $fields);
|
|
$field_names = array_keys($fields);
|
|
|
|
$filters = array();
|
|
|
|
// get field names matching patterns
|
|
foreach($this->_filters as $key=>$filter) {
|
|
$names = array();
|
|
if($key{0} == '^') {
|
|
$names = preg_grep('/^'.preg_quote(substr($key,1)).'/', $field_names);
|
|
}elseif(substr($key,-2) == '[]'){
|
|
$filters[substr($key,0,-2)] = $filter;
|
|
unset($filters[$key]);
|
|
}
|
|
else
|
|
{
|
|
$filters[$key] = $filter;
|
|
}
|
|
|
|
if(!count($names)) continue;
|
|
|
|
foreach($names as $name) {
|
|
$filters[$name] = $filter;
|
|
}
|
|
|
|
unset($filters[$key]);
|
|
}
|
|
|
|
foreach($filters as $key=>$filter) {
|
|
$fname = preg_replace('/\[\]$/', '', $key);
|
|
$filter = array_merge($filter_default, $filter);
|
|
|
|
if(preg_match("/(^[a-z_]*)[\[](?:\'|\")?([a-z_]*)(?:\'|\")?[\]]$/i", $key, $matches))
|
|
{
|
|
$exists = array_key_exists($matches[1], $fields);
|
|
$value = $exists ? $fields[$matches[1]][$matches[2]] : null;
|
|
}
|
|
else
|
|
{
|
|
$exists = array_key_exists($key, $fields);
|
|
$value = $exists ? $fields[$fname] : null;
|
|
}
|
|
|
|
if(is_array($value)) {
|
|
if(!isset($value[tmp_name])){
|
|
$value = implode('', $value);
|
|
}else{
|
|
$value = $value['name'];
|
|
}
|
|
}
|
|
|
|
// conditional statement
|
|
foreach($filter['if'] as $cond) {
|
|
if(!isset($cond['test']) || !isset($cond['attr'])) continue;
|
|
|
|
$func_body = preg_replace('/\\$(\w+)/', '$c[\'$1\']', $cond['test']);
|
|
$func = create_function('$c', "return !!({$func_body});");
|
|
|
|
if($func($fields)) $filter[$cond['attr']] = $cond['value'];
|
|
}
|
|
|
|
// attr : default
|
|
if(!$value && strlen($default=trim($filter['default']))) {
|
|
$value = $default;
|
|
if(is_null($fields_)) Context::set($fname, $value);
|
|
else $fields_[$fname] = $value;
|
|
}
|
|
$value_len = strlen($value);
|
|
|
|
// attr : modifier
|
|
if(is_string($modifiers=$filter['modifiers'])) $modifiers = explode(',', trim($modifiers));
|
|
|
|
// attr : required
|
|
if($filter['required'] === 'true' && !$value_len) return $this->error($key, 'isnull');
|
|
|
|
// if the field wasn't passed, ignore this value
|
|
if(!$exists && !$value_len) continue;
|
|
|
|
// attr : length
|
|
if($length=$filter['length']){
|
|
list($min, $max) = explode(':', trim($length));
|
|
$is_min_b = (substr($min, -1) === 'b');
|
|
$is_max_b = (substr($max, -1) === 'b');
|
|
list($min, $max) = array((int)$min, (int)$max);
|
|
|
|
$strbytes = strlen($value);
|
|
if(!$is_min_b || !$is_max_b) {
|
|
$strlength = $this->_has_mb_func?mb_strlen($value,'utf-8'):$this->mbStrLen($value);
|
|
}
|
|
|
|
if(($min && $min > ($is_min_b?$strbytes:$strlength)) || ($max && $max < ($is_max_b?$strbytes:$strlength))) return $this->error($key, 'outofrange');
|
|
}
|
|
|
|
// equalto
|
|
if($equalto=$filter['equalto']){
|
|
if(!array_key_exists($equalto, $fields) || trim($fields[$equalto]) !== $value) return $this->error($key, 'equalto');
|
|
}
|
|
|
|
// rules
|
|
if($rules=$filter['rule']){
|
|
$rules = explode(',', $rules);
|
|
foreach($rules as $rule) {
|
|
$result = $this->applyRule($rule, $value);
|
|
// apply the 'not' modifier
|
|
if(in_array('not', $modifiers)) $result = !$result;
|
|
if(!$result) return $this->error($key, 'invalid_'.$rule);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* apply trim recursive
|
|
* @param string|array $array
|
|
* @return string|array
|
|
*/
|
|
function arrayTrim($array)
|
|
{
|
|
if (!is_array($array)) return trim($array);
|
|
|
|
foreach($array as $key => $value)
|
|
{
|
|
$array[$key] = $this->arrayTrim($value);
|
|
}
|
|
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Log an error
|
|
* @param $msg error message
|
|
* @return boolean always false
|
|
*/
|
|
function error($field, $msg){
|
|
$lang_filter = Context::getLang('filter');
|
|
$msg = isset($lang_filter->{$msg})?$lang_filter->{$msg}:$lang_filter->invalid;
|
|
$msg = sprintf($msg, Context::getLang($field));
|
|
|
|
$this->_last_error = array('field'=>$field, 'msg'=>$msg);
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the last error infomation including a field name and an error message.
|
|
* @return array The last error infomation
|
|
*/
|
|
function getLastError(){
|
|
return $this->_last_error;
|
|
}
|
|
|
|
/**
|
|
* Add a new rule
|
|
* @param string $name rule name
|
|
* @param mixed $rule
|
|
* @return void
|
|
*/
|
|
function addRule($name, $rule=''){
|
|
if(is_array($name)) $args = $name;
|
|
else $args = array($name=>$rule);
|
|
|
|
foreach($args as $name=>$rule){
|
|
if(!$rule) continue;
|
|
if(is_string($rule)) $rule = array('type'=>'regex', 'test'=>$rule);
|
|
|
|
if($rule['type'] == 'enum') {
|
|
$delim = isset($rule['delim'])?$rule['delim']:',';
|
|
$rule['test'] = explode($delim, $rule['test']);
|
|
}
|
|
|
|
$this->_rules[$name] = $rule;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a rule
|
|
* @param string $name rule name
|
|
* @return void
|
|
*/
|
|
function removeRule($name){
|
|
unset($this->_rules[$name]);
|
|
}
|
|
|
|
/**
|
|
* add filter to filter list
|
|
* @param string $name rule name
|
|
* @param string $filter filter
|
|
* @return void
|
|
*/
|
|
function addFilter($name, $filter='') {
|
|
if(is_array($name)) $args = $name;
|
|
else $args = array($name=>$filter);
|
|
|
|
foreach($args as $name=>$filter) {
|
|
if(!$filter) continue;
|
|
|
|
if(isset($filter['if'])) {
|
|
if(is_array($filter['if']) && count($filter['if'])) {
|
|
$key = key($filter['if']);
|
|
if(!is_int($key)) $filter['if'] = array($filter['if']);
|
|
} else {
|
|
unset($filter['if']);
|
|
}
|
|
}
|
|
|
|
$this->_filters[$name] = $filter;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* remove filter from filter list
|
|
* @param string $name rule name
|
|
* @return void
|
|
*/
|
|
function removeFilter($name) {
|
|
unset($this->_filters[$name]);
|
|
}
|
|
|
|
/**
|
|
* Find whether the field is valid with the rule
|
|
* @param string $name rule name
|
|
* @param string $value a value to be validated
|
|
* @return boolean TRUE if the field is valid, FALSE otherwise.
|
|
*/
|
|
function applyRule($name, $value){
|
|
$rule = $this->_rules[$name];
|
|
|
|
if (is_array($value) && isset($value['tmp_name']))
|
|
{
|
|
$value = $value['name'];
|
|
}
|
|
|
|
switch($rule['type']) {
|
|
case 'regex':
|
|
return (preg_match($rule['test'], $value) > 0);
|
|
case 'enum':
|
|
return in_array($value, $rule['test']);
|
|
case 'expr':
|
|
if(!$rule['func_test']) {
|
|
$rule['func_test'] = create_function('$a', 'return ('.preg_replace('/\$\$/', '$a', html_entity_decode($rule['test'])).');');
|
|
}
|
|
return $rule['func_test']($value);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* if not supported 'mb_strlen' function, this method can use.
|
|
* @param string $str
|
|
* @return int
|
|
*/
|
|
function mbStrLen($str){
|
|
$arr = count_chars($str);
|
|
for($i=0x80; $i < 0xc0; $i++) {
|
|
unset($arr[$i]);
|
|
}
|
|
return array_sum($arr);
|
|
}
|
|
|
|
/**
|
|
* Returns compiled javascript file path. The path begins from XE root directory.
|
|
* @return string Compiled JavaScript file path
|
|
*/
|
|
function getJsPath(){
|
|
if(!$this->_cache_dir) return false;
|
|
|
|
$dir = $this->_cache_dir.'/ruleset';
|
|
if(!is_dir($dir) && !mkdir($dir)) return false;
|
|
if(!$this->_xml_path) return false;
|
|
|
|
// current language
|
|
$lang_type = class_exists('Context')?Context::getLangType():'en';
|
|
|
|
// check the file
|
|
$filepath = $dir.'/'.md5($this->_version.' '.$this->_xml_path).".{$lang_type}.js";
|
|
if(is_readable($filepath) && filemtime($filepath) > filemtime($this->_xml_path)) return $filepath;
|
|
|
|
$content = $this->_compile2js();
|
|
if($content === false) return false;
|
|
|
|
if(is_callable('file_put_contents')) {
|
|
@file_put_contents($filepath, $content);
|
|
} else {
|
|
$fp = @fopen($filepath, 'w');
|
|
if(is_resource($fp)) {
|
|
fwrite($fp, $content);
|
|
fclose($fp);
|
|
}
|
|
}
|
|
|
|
return $filepath;
|
|
}
|
|
|
|
/**
|
|
* Compile a ruleset to a javascript file
|
|
* @return string
|
|
*/
|
|
function _compile2js() {
|
|
global $lang;
|
|
|
|
$ruleset = basename($this->_xml_path,'.xml');
|
|
$content = array();
|
|
|
|
if(preg_match('@(^|/)files/ruleset/\w+\.xml$@i', $this->_xml_path)) $ruleset = '@'.$ruleset;
|
|
|
|
list($ruleset) = explode('.', $ruleset);
|
|
|
|
// current language
|
|
$lang_type = class_exists('Context')?Context::getLangType():'en';
|
|
|
|
// custom rulesets
|
|
$addrules = array();
|
|
foreach($this->_rules as $name=>$rule) {
|
|
if(strpos('email,userid,url,alpha,alpha_number,number,', $name.',') !== false) continue;
|
|
switch($rule['type']) {
|
|
case 'regex':
|
|
$content[] = "v.cast('ADD_RULE', ['{$name}', {$rule['test']}]);";
|
|
break;
|
|
case 'enum':
|
|
$enums = '"'.implode('","', $rule['test']).'"';
|
|
$content[] = "v.cast('ADD_RULE', ['{$name}', function($$){ return ($.inArray($$,[{$enums}]) > -1); }]);";
|
|
break;
|
|
case 'expr':
|
|
$content[] = "v.cast('ADD_RULE', ['{$name}', function($$){ return ({$rule['test']}); }]);";
|
|
break;
|
|
}
|
|
}
|
|
$addrules = implode('', $addrules);
|
|
|
|
// filters
|
|
$content = array();
|
|
$messages = array();
|
|
foreach($this->_filters as $name=>$filter) {
|
|
$field = array();
|
|
|
|
// form filed name
|
|
if(isset($lang->{$name})) {
|
|
$field_lang = addslashes($lang->{$name});
|
|
$messages[] = "v.cast('ADD_MESSAGE',['{$name}','{$field_lang}']);";
|
|
}
|
|
|
|
if($filter['required'] == 'true') $field[] = 'required:true';
|
|
if($filter['rule']) $field[] = "rule:'{$filter['rule']}'";
|
|
if($filter['default']) $field[] = "default:'{$filter['default']}'";
|
|
if($filter['modifier']) $field[] = "modifier:'{$filter['modifier']}'";
|
|
if($filter['length']) {
|
|
list($min, $max) = explode(':', $filter['length']);
|
|
if($min) $field[] = "minlength:'{$min}'";
|
|
if($max) $field[] = "maxlength:'{$max}'";
|
|
}
|
|
if($filter['if']) {
|
|
$ifs = array();
|
|
if(!isset($filter['if'][0])) $filter['if'] = array($filter['if']);
|
|
foreach($filter['if'] as $if) {
|
|
$ifs[] = "{test:'".addslashes($if['test'])."', attr:'{$if['attr']}', value:'".addslashes($if['value'])."'}";
|
|
}
|
|
$field[] = "'if':[".implode(',', $ifs)."]";
|
|
}
|
|
if(count($field)) {
|
|
$field = '{'.implode(',', $field).'}';
|
|
$content[] = "'{$name}':{$field}";
|
|
}
|
|
}
|
|
|
|
if(!$content) return '/* Error : empty ruleset */';
|
|
|
|
// error messages
|
|
foreach($lang->filter as $key=>$text) {
|
|
if($text) {
|
|
$text = preg_replace('@\r?\n@', '\\n', addslashes($text));
|
|
$messages[] = "v.cast('ADD_MESSAGE',['{$key}','{$text}']);";
|
|
}
|
|
}
|
|
|
|
$content = implode(',', $content);
|
|
$messages = implode("\n", $messages);
|
|
|
|
return "(function($,v){\nv=xe.getApp('validator')[0];if(!v)return;\n{$addrules}\nv.cast('ADD_FILTER',['{$ruleset}', {{$content}}]);\n{$messages}\n})(jQuery);";
|
|
}
|
|
}
|
|
|
|
/* End of file Validator.class.php */
|
|
/* Location: ./classes/validator/Validator.class.php */
|