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 {
|