diff --git a/htdocs/core/actions_addupdatedelete.inc.php b/htdocs/core/actions_addupdatedelete.inc.php index f871ed73de8..d2d22d74e3c 100644 --- a/htdocs/core/actions_addupdatedelete.inc.php +++ b/htdocs/core/actions_addupdatedelete.inc.php @@ -105,6 +105,15 @@ if ($action == 'add' && !empty($permissiontoadd)) { $error++; setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv($val['label'])), null, 'errors'); } + + // Validation of fields values + if ($conf->global->MAIN_FEATURE_LEVEL >= 2 || !empty($conf->global->MAIN_ACTIVATE_VALIDATION_RESULT)) { + if (!$error && !empty($val['validate']) && is_callable(array($object, 'validateField'))) { + if (!$object->validateField($object->fields, $key, $value)) { + $error++; + } + } + } } // Fill array 'array_options' with data from add form @@ -204,6 +213,15 @@ if ($action == 'update' && !empty($permissiontoadd)) { $error++; setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv($val['label'])), null, 'errors'); } + + // Validation of fields values + if ($conf->global->MAIN_FEATURE_LEVEL >= 2 || !empty($conf->global->MAIN_ACTIVATE_VALIDATION_RESULT)) { + if (!$error && !empty($val['validate']) && is_callable(array($object, 'validateField'))) { + if (!$object->validateField($object->fields, $key, $value)) { + $error++; + } + } + } } // Fill array 'array_options' with data from add form diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index d01b972e0c5..52e27d89c87 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -73,6 +73,11 @@ abstract class CommonObject */ public $errors = array(); + /** + * @var array To store error results of ->validateField() + */ + public $validateFieldsErrors = array(); + /** * @var string ID to identify managed object */ @@ -123,14 +128,12 @@ abstract class CommonObject */ protected $table_ref_field = ''; - /** - * 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user - * @var integer + /** + * @var integer 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user */ public $restrictiononfksoc = 0; - // Following vars are used by some objects only. We keep this property here in CommonObject to be able to provide common method using them. /** @@ -6422,6 +6425,16 @@ abstract class CommonObject $val = $this->fields[$key]; } + // Validation tests and output + $fieldValidationErrorMsg = ''; + $validationClass = ''; + $fieldValidationErrorMsg = $this->getFieldError($key); + if (!empty($fieldValidationErrorMsg)) { + $validationClass = ' --error'; // the -- is use as class state in css : .--error can't be be defined alone it must be define with another class like .my-class.--error or input.--error + } else { + $validationClass = ' --success'; // the -- is use as class state in css : .--success can't be be defined alone it must be define with another class like .my-class.--success or input.--success + } + $out = ''; $type = ''; $isDependList=0; @@ -6513,6 +6526,11 @@ abstract class CommonObject } } + // Add validation state class + if (!empty($validationClass)) { + $morecss.= ' '.$validationClass; + } + if (in_array($type, array('date'))) { $tmp = explode(',', $size); $newsize = $tmp[0]; @@ -6979,6 +6997,12 @@ abstract class CommonObject if ($type == 'date') $out.=' (YYYY-MM-DD)'; elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)'; */ + + // Display error message for field + if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) { + $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg); + } + return $out; } @@ -7318,6 +7342,228 @@ abstract class CommonObject return $out; } + /** + * clear validation message result for a field + * + * @param string $fieldKey Key of attribute to clear + * @return null + */ + public function clearFieldError($fieldKey) + { + $this->error = ''; + unset($this->validateFieldsErrors[$fieldKey]); + } + + /** + * set validation error message a field + * + * @param string $fieldKey Key of attribute + * @param string $msg the field error message + * @return null + */ + public function setFieldError($fieldKey, $msg = '') + { + global $langs; + if (empty($msg)) { $msg = $langs->trans("UnknowError"); } + + $this->error = $this->validateFieldsErrors[$fieldKey] = $msg; + } + + /** + * get field error message + * + * @param string $fieldKey Key of attribute + * @return string + */ + public function getFieldError($fieldKey) + { + if (!empty($this->validateFieldsErrors[$fieldKey])) { + return $this->validateFieldsErrors[$fieldKey]; + } + return ''; + } + + /** + * Return validation test result for a field + * + * @param array $val Array of properties of field to show + * @param string $fieldKey Key of attribute + * @param string $fieldValue value of attribute + * @return bool return false if fail true on success, see $this->error for error message + */ + public function validateField($val, $fieldKey, $fieldValue) + { + global $langs; + + if (!class_exists('Validate')) { require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php'; } + + $this->clearFieldError($fieldKey); + + if (!isset($val[$fieldKey])) { + $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject')); + return false; + } + + $param = array(); + $param['options'] = array(); + $type = $val[$fieldKey]['type']; + + $required = false; + if (isset($val[$fieldKey]['notnull']) && $val[$fieldKey]['notnull'] === 1) { + // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0). + $required = true; + } + + $maxSize = 0; + $minSize = 0; + + // + // PREPARE Elements + // + + // Convert var to be able to share same code than showOutputField of extrafields + if (preg_match('/varchar\((\d+)\)/', $type, $reg)) { + $type = 'varchar'; // convert varchar(xx) int varchar + $maxSize = $reg[1]; + } elseif (preg_match('/varchar/', $type)) { + $type = 'varchar'; // convert varchar(xx) int varchar + } + + if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) { + $type = 'select'; + } + + if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) { + $type = 'link'; + } + + if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) { + $param['options'] = $val['arrayofkeyval']; + } + + if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) { + $type = 'link'; + $param['options'] = array($reg[1].':'.$reg[2]=>$reg[1].':'.$reg[2]); + } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) { + $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N'); + $type = 'sellist'; + } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) { + $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N'); + $type = 'sellist'; + } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) { + $param['options'] = array($reg[1].':'.$reg[2] => 'N'); + $type = 'sellist'; + } + + // + // TEST Value + // + + // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse + $validate = new Validate($this->db, $langs); + + + // little trick : to perform tests with good performances sort tests by quick to low + + // + // COMMON TESTS + // + + // Required test and empty value + if ($required && !$validate->isNotEmptyString($fieldValue)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) { + // if no value sent and the field is not mandatory, no need to perform tests + return true; + } + + // MAX Size test + if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } + + // MIN Size test + if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } + + // + // TESTS for TYPE + // + + if (in_array($type, array('date', 'datetime', 'timestamp'))) { + if (!$validate->isTimestamp($fieldValue)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } else { return true; } + } elseif ($type == 'duration') { + if (!$validate->isDuration($fieldValue)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } else { return true; } + } elseif (in_array($type, array('double', 'real', 'price'))) { + // is numeric + if (!$validate->isDuration($fieldValue)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } else { return true; } + } elseif ($type == 'boolean') { + if (!$validate->isBool($fieldValue)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } else { return true; } + } elseif ($type == 'mail') { + if (!$validate->isEmail($fieldValue)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } + } elseif ($type == 'url') { + if (!$validate->isUrl($fieldValue)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } else { return true; } + } elseif ($type == 'phone') { + if (!$validate->isPhone($fieldValue)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } else { return true; } + } elseif ($type == 'select' || $type == 'radio') { + if (!isset($param['options'][$fieldValue])) { + $this->error = $langs->trans('RequireValidValue'); + return false; + } else { return true; } + } elseif ($type == 'sellist' || $type == 'chkbxlst') { + $param_list = array_keys($param['options']); + $InfoFieldList = explode(":", $param_list[0]); + $value_arr = explode(',', $fieldValue); + $value_arr = array_map(array($this->db, 'escape'), $value_arr); + + $selectkey = "rowid"; + if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) { + $selectkey = $InfoFieldList[2]; + } + + if (!isInDb($value_arr, $InfoFieldList[0], $selectkey)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } else { return true; } + } elseif ($type == 'link') { + $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath' + $InfoFieldList = explode(":", $param_list[0]); + $classname = $InfoFieldList[0]; + $classpath = $InfoFieldList[1]; + if (!$validate->isFetchable($fieldValue, $classname, $classpath)) { + $this->setFieldError($fieldKey, $validate->error); + return false; + } else { return true; } + } + + // if no test failled all is ok + return true; + } /** * Function to show lines of extrafields with output datas. diff --git a/htdocs/core/class/validate.class.php b/htdocs/core/class/validate.class.php new file mode 100644 index 00000000000..1738034545d --- /dev/null +++ b/htdocs/core/class/validate.class.php @@ -0,0 +1,309 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/core/class/validate.class.php + * \ingroup core + * \brief File for Utils class + */ + + +/** + * Class toolbox to validate values + */ +class Validate +{ + + /** + * @var DoliDb Database handler (result of a new DoliDB) + */ + public $db; + + /** + * @var Translate $outputLang + */ + public $outputLang; + + /** + * @var string Error string + * @see $errors + */ + public $error; + + + /** + * Constructor + * + * @param DoliDB $db Database handler + * @param Translate $outputLang output lang for error + * @return null + */ + public function __construct($db, $outputLang = false) + { + global $langs; + + if ($outputLang) { + $this->outputLang = $langs; + } else { + $this->outputLang = $outputLang; + } + + $outputLang->load('validate'); + + $this->db = $db; + } + + /** + * Use to clear errors msg or other ghost vars + * @return null + */ + protected function clear() + { + $this->error = ''; + } + + /** + * Use to clear errors msg or other ghost vars + * + * @param string $errMsg your error message + * @return null + */ + protected function setError($errMsg) + { + $this->error = $errMsg; + } + + /** + * Check for e-mail validity + * + * @param string $email e-mail address to validate + * @param int $maxLength string max length + * @return boolean Validity is ok or not + */ + public function isEmail($email, $maxLength = false) + { + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $this->error = $this->outputLang->trans('RequireValidEmail'); + return false; + } + return true; + } + + /** + * Check for price validity + * + * @param string $price Price to validate + * @return boolean Validity is ok or not + */ + public function isPrice($price) + { + if (!preg_match('/^[0-9]{1,10}(\.[0-9]{1,9})?$/ui', $price)) { + $this->error = $this->outputLang->trans('RequireValidValue'); + return false; + } + return true; + } + + /** + * Check for timestamp validity + * + * @param string|int $stamp timestamp to validate + * @return boolean Validity is ok or not + */ + public function isTimestamp($stamp) + { + if (!is_numeric($stamp) && (int) $stamp == $stamp) { + $this->error = $this->outputLang->trans('RequireValidDate'); + return false; + } + return true; + } + + /** + * Check for phone validity + * + * @param string $phone Phone string to validate + * @return boolean Validity is ok or not + */ + public function isPhone($phone) + { + if (!preg_match('/^[+0-9. ()-]*$/ui', $phone)) { + $this->error = $this->outputLang->trans('RequireValidPhone'); + return false; + } + return true; + } + + /** + * Check for string max length validity + * + * @param string $string to validate + * @param int $length max length + * @return boolean Validity is ok or not + */ + public function isMaxLength($string, $length) + { + if (strlen($string) > $length) { + $this->error = $this->outputLang->trans('RequireMaxLength', $length); + return false; + } + return true; + } + + /** + * Check for string not empty + * + * @param string $string to validate + * @return boolean Validity is ok or not + */ + public function isNotEmptyString($string) + { + if (!strlen($string)) { + $this->error = $this->outputLang->trans('RequireANotEmptyValue'); + return false; + } + return true; + } + + /** + * Check for string min length validity + * + * @param string $string to validate + * @param int $length max length + * @return boolean Validity is ok or not + */ + public function isMinLength($string, $length) + { + if (!strlen($string) < $length) { + $this->error = $this->outputLang->trans('RequireMinLength', $length); + return false; + } + return true; + } + + /** + * Check url validity + * + * @param string $url to validate + * @return boolean Validity is ok or not + */ + public function isUrl($url) + { + if (!filter_var($url, FILTER_VALIDATE_URL)) { + $this->error = $this->outputLang->trans('RequireValidUrl'); + return false; + } + return true; + } + + /** + * Check Duration validity + * + * @param string $duration to validate + * @return boolean Validity is ok or not + */ + public function isDuration($duration) + { + if (!is_int($duration) && $duration >= 0) { + $this->error = $this->outputLang->trans('RequireValidDuration'); + return false; + } + return true; + } + + /** + * Check for boolean validity + * + * @param boolean $bool Boolean to validate + * @return boolean Validity is ok or not + */ + public function isBool($bool) + { + if (!(is_null($bool) || is_bool($bool) || preg_match('/^[0|1]{1}$/ui', $bool))) { + $this->error = $this->outputLang->trans('RequireValidBool'); + return false; + } + return true; + } + + /** + * Check for all values in db + * + * @param array $values Boolean to validate + * @param string $table the db table name without MAIN_DB_PREFIX + * @param string $col the target col + * @return boolean Validity is ok or not + * @throws Exception + */ + public function isInDb($values, $table, $col) + { + if (!is_array($values)) { + $value_arr = array($values); + } else { + $value_arr = $values; + } + + if (!count($value_arr)) { + $this->error = $this->outputLang->trans('RequireValue'); + return false; + } + + foreach ($value_arr as $val) { + $sql = 'SELECT ' . $col . ' FROM ' . MAIN_DB_PREFIX . $table . " WHERE " . $col ." = '" . $this->db->escape($val) . "'"; // nore quick than count(*) to check existing of a row + $resql = $this->db->getRow($sql); + if ($resql) { + continue; + } else { + $this->error = $this->outputLang->trans('RequireValidExistingElement'); + return false; + } + } + + return true; + } + + /** + * Check for all values in db + * + * @param array $values Boolean to validate + * @param string $classname the class name + * @param string $classpath the class path + * @return boolean Validity is ok or not + * @throws Exception + */ + public function isFetchable($values, $classname, $classpath) + { + if (!empty($classpath)) { + if (dol_include_once($classpath)) { + if ($classname && class_exists($classname)) { + /** @var CommonObject $object */ + $object = new $classname($this->db); + + if (!is_callable(array($object, 'fetch')) || !is_callable(array($object, 'isExistingObject'))) { + $this->error = $this->outputLang->trans('BadSetupOfFieldFetchNotCallable'); + return false; + } + + if (!empty($object->table_element) && $object->isExistingObject($object->table_element, $values)) { + return true; + } else { $this->error = $this->outputLang->trans('RequireValidExistingElement'); } + } else { $this->error = $this->outputLang->trans('BadSetupOfFieldClassNotFoundForValidation'); } + } else { $this->error = $this->outputLang->trans('BadSetupOfFieldFileNotFound'); } + } else { $this->error = $this->outputLang->trans('BadSetupOfField'); } + return false; + } +} diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index f692f4944f8..365fa4af6bf 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -9921,6 +9921,24 @@ function dolGetButtonTitleSeparator($moreClass = "") return ''; } +/** + * get field error icon + * + * @param string $fieldValidationErrorMsg message to add in tooltip + * @return string html output + */ +function getFieldErrorIcon($fieldValidationErrorMsg) +{ + $out = ''; + if (!empty($fieldValidationErrorMsg)) { + $out.= ''; // role alert is used for accessibility + $out.= ''; // For accessibility icon is separated and aria-hidden + $out.= ''; + } + + return $out; +} + /** * Function dolGetButtonTitle : this kind of buttons are used in title in list * diff --git a/htdocs/core/lib/modulebuilder.lib.php b/htdocs/core/lib/modulebuilder.lib.php index 27a4ea7a9ab..a5589679b50 100644 --- a/htdocs/core/lib/modulebuilder.lib.php +++ b/htdocs/core/lib/modulebuilder.lib.php @@ -176,6 +176,9 @@ function rebuildObjectClass($destdir, $module, $objectname, $newmask, $readdir = } $texttoinsert .= "),"; } + if ($val['validate']) { + $texttoinsert .= " 'validate'=>'".$val['validate']."',"; + } if ($val['comment']) { $texttoinsert .= " 'comment'=>\"".preg_replace('/"/', '', $val['comment'])."\""; } diff --git a/htdocs/langs/en_US/modulebuilder.lang b/htdocs/langs/en_US/modulebuilder.lang index 7ba539d3bd4..5d6b51d5fe4 100644 --- a/htdocs/langs/en_US/modulebuilder.lang +++ b/htdocs/langs/en_US/modulebuilder.lang @@ -143,4 +143,5 @@ AsciiToHtmlConverter=Ascii to HTML converter AsciiToPdfConverter=Ascii to PDF converter TableNotEmptyDropCanceled=Table not empty. Drop has been canceled. ModuleBuilderNotAllowed=The module builder is available but not allowed to your user. -ImportExportProfiles=Import and export profiles \ No newline at end of file +ImportExportProfiles=Import and export profiles +ValidateModBuilderDesc=Put 1 if this field need to be validated with $this->validateField() or 0 if validation required diff --git a/htdocs/langs/en_US/validate.lang b/htdocs/langs/en_US/validate.lang new file mode 100644 index 00000000000..bd25b6c0c74 --- /dev/null +++ b/htdocs/langs/en_US/validate.lang @@ -0,0 +1,19 @@ +# Dolibarr language file - Source file is en_US - users +RequireValidValue = Value not valid +RequireAtLeastXString = Requires at least % character(s) +RequireXStringMax = Requires % character(s) max +RequireAtLeastXDigits = Requires at least % digit(s) +RequireXDigitsMax = Requires % digit(s) max +RequireValidEmail = Email address is not valid +RequireMaxLength = Length must be less than %s chars +RequireMinLength = Length must be more than %s char(s) +RequireValidUrl = Require valid URL +RequireValidDate = Require a valid date +RequireANotEmptyValue = Is required +RequireValidDuration = Require a valid duration +RequireValidExistingElement = Require an existing value +RequireValidBool = Require a valid boolean +BadSetupOfField = Error bad setup of field +BadSetupOfFieldClassNotFoundForValidation = Error bad setup of field : Class not found for validation +BadSetupOfFieldFileNotFound = Error bad setup of field : File not found for inclusion +BadSetupOfFieldFetchNotCallable = Error bad setup of field : Fetch not callable on class diff --git a/htdocs/modulebuilder/index.php b/htdocs/modulebuilder/index.php index f8ca876eafc..0a05266b6ff 100644 --- a/htdocs/modulebuilder/index.php +++ b/htdocs/modulebuilder/index.php @@ -1303,7 +1303,8 @@ if ($dirins && $action == 'addproperty' && empty($cancel) && !empty($module) && 'visible'=>GETPOST('propvisible', 'int'), 'enabled'=>GETPOST('propenabled', 'int'), 'position'=>GETPOST('propposition', 'int'), 'notnull'=>GETPOST('propnotnull', 'int'), 'index'=>GETPOST('propindex', 'int'), 'searchall'=>GETPOST('propsearchall', 'int'), 'isameasure'=>GETPOST('propisameasure', 'int'), 'comment'=>GETPOST('propcomment', 'alpha'), 'help'=>GETPOST('prophelp', 'alpha'), - 'css'=>GETPOST('propcss', 'aZ09'), 'cssview'=>GETPOST('propcssview', 'aZ09'), 'csslist'=>GETPOST('propcsslist', 'aZ09') + 'css'=>GETPOST('propcss', 'aZ09'), 'cssview'=>GETPOST('propcssview', 'aZ09'), 'csslist'=>GETPOST('propcsslist', 'aZ09'), + 'validate' => GETPOST('propvalidate', 'int') ); if (!empty($addfieldentry['arrayofkeyval']) && !is_array($addfieldentry['arrayofkeyval'])) { @@ -2680,6 +2681,7 @@ if ($module == 'initmodule') { print ''.$langs->trans("KeyForTooltip").''; print ''.$langs->trans("ShowOnCombobox").''; //print ''.$langs->trans("Disabled").''; + print ''.$form->textwithpicto($langs->trans("Validate"), $langs->trans("ValidateModBuilderDesc")).''; print ''.$langs->trans("Comment").''; print ''; print ''; @@ -2712,6 +2714,7 @@ if ($module == 'initmodule') { print ''; print ''; //print ''; + print ''; print ''; print ''; print ''; @@ -2753,6 +2756,7 @@ if ($module == 'initmodule') { $prophelp = $propval['help']; $propshowoncombobox = $propval['showoncombobox']; //$propdisabled=$propval['disabled']; + $propvalidate = $propval['validate']; $propcomment = $propval['comment']; print ''; @@ -2823,6 +2827,9 @@ if ($module == 'initmodule') { print ''; print ''; print ''; + print ''; + print ''; + print ''; print ''; print ''; print ''; @@ -2888,6 +2895,9 @@ if ($module == 'initmodule') { /*print ''; print $propdisabled?$propdisabled:''; print '';*/ + print ''; + print $propvalidate ? dol_escape_htmltag($propvalidate) : ''; + print ''; print ''; print ''; print dol_escape_htmltag($propcomment); diff --git a/htdocs/modulebuilder/template/class/myobject.class.php b/htdocs/modulebuilder/template/class/myobject.class.php index 65af15cffa8..86546aa05a7 100644 --- a/htdocs/modulebuilder/template/class/myobject.class.php +++ b/htdocs/modulebuilder/template/class/myobject.class.php @@ -91,7 +91,7 @@ class MyObject extends CommonObject * 'arrayofkeyval' to set a list of values if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel"). Note that type can be 'integer' or 'varchar' * 'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1. * 'comment' is not used. You can store here any text of your choice. It is not used by application. - * + * 'validate' is 1 if need to validate with $this->validateField() * Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor. */ @@ -102,15 +102,15 @@ class MyObject extends CommonObject public $fields = array( 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'noteditable'=>1, 'notnull'=> 1, 'index'=>1, 'position'=>1, 'comment'=>'Id', 'css'=>'left'), 'entity' => array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'notnull'=> 1, 'default'=>1, 'index'=>1, 'position'=>10), - 'ref' => array('type'=>'varchar(128)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'noteditable'=>0, 'default'=>'', 'notnull'=> 1, 'showoncombobox'=>1, 'index'=>1, 'position'=>20, 'searchall'=>1, 'comment'=>'Reference of object'), - 'label' => array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>1, 'visible'=>1, 'position'=>30, 'searchall'=>1, 'css'=>'minwidth300', 'cssview'=>'wordbreak', 'help'=>'Help text', 'showoncombobox'=>2), - 'amount' => array('type'=>'price', 'label'=>'Amount', 'enabled'=>1, 'visible'=>1, 'default'=>'null', 'position'=>40, 'searchall'=>0, 'isameasure'=>1, 'help'=>'Help text for amount'), - 'qty' => array('type'=>'real', 'label'=>'Qty', 'enabled'=>1, 'visible'=>1, 'default'=>'0', 'position'=>45, 'searchall'=>0, 'isameasure'=>1, 'help'=>'Help text for quantity', 'css'=>'maxwidth75imp'), - 'fk_soc' => array('type'=>'integer:Societe:societe/class/societe.class.php:1:status=1 AND entity IN (__SHARED_ENTITIES__)', 'picto'=>'company', 'label'=>'ThirdParty', 'visible'=> 1, 'enabled'=>1, 'position'=>50, 'notnull'=>-1, 'index'=>1, 'help'=>'LinkToThirparty'), - 'fk_project' => array('type'=>'integer:Project:projet/class/project.class.php:1', 'label'=>'Project', 'picto'=>'project', 'enabled'=>1, 'visible'=>-1, 'position'=>52, 'notnull'=>-1, 'index'=>1), - 'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>3, 'position'=>60), - 'note_public' => array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>61), - 'note_private' => array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>62), + 'ref' => array('type'=>'varchar(128)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'noteditable'=>0, 'default'=>'', 'notnull'=> 1, 'showoncombobox'=>1, 'index'=>1, 'position'=>20, 'searchall'=>1, 'comment'=>'Reference of object', 'validate'=>1), + 'label' => array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>1, 'visible'=>1, 'position'=>30, 'searchall'=>1, 'css'=>'minwidth300', 'cssview'=>'wordbreak', 'help'=>'Help text', 'showoncombobox'=>2, 'validate'=>1), + 'amount' => array('type'=>'price', 'label'=>'Amount', 'enabled'=>1, 'visible'=>1, 'default'=>'null', 'position'=>40, 'searchall'=>0, 'isameasure'=>1, 'help'=>'Help text for amount', 'validate'=>1), + 'qty' => array('type'=>'real', 'label'=>'Qty', 'enabled'=>1, 'visible'=>1, 'default'=>'0', 'position'=>45, 'searchall'=>0, 'isameasure'=>1, 'help'=>'Help text for quantity', 'css'=>'maxwidth75imp', 'validate'=>1), + 'fk_soc' => array('type'=>'integer:Societe:societe/class/societe.class.php:1:status=1 AND entity IN (__SHARED_ENTITIES__)', 'picto'=>'company', 'label'=>'ThirdParty', 'visible'=> 1, 'enabled'=>1, 'position'=>50, 'notnull'=>-1, 'index'=>1, 'help'=>'LinkToThirparty', 'validate'=>1), + 'fk_project' => array('type'=>'integer:Project:projet/class/project.class.php:1', 'label'=>'Project', 'picto'=>'project', 'enabled'=>1, 'visible'=>-1, 'position'=>52, 'notnull'=>-1, 'index'=>1, 'validate'=>1), + 'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>3, 'position'=>60, 'validate'=>1), + 'note_public' => array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>61, 'validate'=>1), + 'note_private' => array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>62, 'validate'=>1), 'date_creation' => array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'notnull'=> 1, 'position'=>500), 'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'notnull'=> 0, 'position'=>501), //'date_validation ' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502), @@ -120,7 +120,7 @@ class MyObject extends CommonObject 'last_main_doc' => array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>0, 'notnull'=>0, 'position'=>600), 'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'index'=>0, 'position'=>1000), 'model_pdf' => array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'notnull'=>-1, 'position'=>1010), - 'status' => array('type'=>'smallint', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=> 1, 'default'=>0, 'index'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Validated', 9=>'Canceled')), + 'status' => array('type'=>'smallint', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=> 1, 'default'=>0, 'index'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Validated', 9=>'Canceled'), 'validate'=>1), ); /** diff --git a/htdocs/theme/eldy/global.inc.php b/htdocs/theme/eldy/global.inc.php index 0f4ac71fd54..311f6a2e584 100644 --- a/htdocs/theme/eldy/global.inc.php +++ b/htdocs/theme/eldy/global.inc.php @@ -234,10 +234,12 @@ input.button.massactionconfirmed { margin: 4px; } -input:invalid, select:invalid { +input:invalid, select:invalid, input.--error , select.--error { border-color: #ea1212; } +.field-error-icon { color: #ea1212; !important; } + /* Focus definitions must be after standard definition */ textarea:focus { border: 1px solid #aaa !important; @@ -435,7 +437,7 @@ input.pageplusone { transform: scale(-1, 1); } -select:invalid { +select:invalid, select.--error { color: gray; } input:disabled, textarea:disabled, select[disabled='disabled'] @@ -2097,7 +2099,7 @@ span.widthpictotitle.pictotitle { vertical-align: middle; margin-top: -3px } -.pictowarning, .pictoerror, .pictopreview { +.pictowarning, .pictoerror, .pictopreview, .picto.error { padding-: 3px; } .pictowarning {