Skip to content

Commit

Permalink
NEW: API setup update extrafields from name, elementtype and json (#2…
Browse files Browse the repository at this point in the history
…9273)

* NEW: API setup update extrafields from name, elementtype and json

* default_value

* fix SQL injection

* more SQL injection prevention

* Sanitized

---------

Co-authored-by: Jon Bendtsen <[email protected]>
Co-authored-by: Laurent Destailleur <[email protected]>
  • Loading branch information
3 people authored Apr 10, 2024
1 parent 6c26562 commit f7eb0b9
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 3 deletions.
85 changes: 85 additions & 0 deletions htdocs/api/class/api_setup.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,91 @@ public function deleteExtrafieldsFromNames($attrname, $elementtype)
);
}

/**
* Update Extrafield object
*
* @param string $attrname extrafield attrname
* @param string $elementtype extrafield elementtype
* @param array $request_data Request datas
* @return int ID of extrafield
*
* @url PUT extrafields/{elementtype}/{attrname}
*
* @suppress PhanPluginUnknownArrayMethodParamType Luracast limitation
*
*/
public function updateExtrafields($attrname, $elementtype, $request_data = null)
{
if (!DolibarrApiAccess::$user->admin) {
throw new RestException(403, 'Only an admin user can create an extrafield');
}

$extrafields = new ExtraFields($this->db);

$result = $extrafields->fetch_name_optionals_label($elementtype, false, $attrname);
if (!$result) {
throw new RestException(404, 'Extrafield not found from attrname and elementtype');
}

foreach ($request_data as $field => $value) {
$extrafields->$field = $this->_checkValForAPI($field, $value, $extrafields);
}

// built in validation
$enabled = 1; // hardcoded because it seems to always be 1 in every row in the database
if ($request_data['entity']) {
$entity = $request_data['entity'];
} else {
throw new RestException(400, "Entity field absent");
}
if ($request_data['label']) {
$label = $request_data['label'];
} else {
throw new RestException(400, "label field absent");
}

$alwayseditable = $request_data['alwayseditable'];
$default_value = $request_data['default_value'];
$totalizable = $request_data['totalizable'];
$printable = $request_data['printable'];
$required = $request_data['required'];
$langfile = $request_data['langfile'];
$computed = $request_data['computed'];
$unique = $request_data['unique'];
$param = $request_data['param'];
$perms = $request_data['perms'];
$size = $request_data['size'];
$type = $request_data['type'];
$list = $request_data['list'];
$help = $request_data['help'];
$pos = $request_data['pos'];
$moreparams = array();

dol_syslog(get_class($this).'::updateExtraField', LOG_DEBUG);
if ( 0 > $extrafields->updateExtraField($attrname, $label, $type, $pos, $size, $elementtype, $unique, $required, $default_value, $param, $alwayseditable, $perms, $list, $help, $computed, $entity, $langfile, $enabled, $totalizable, $printable, $moreparams)) {
throw new RestException(500, 'Error updating extrafield', array_merge(array($extrafields->errno), $extrafields->errors));
}

$sql = "SELECT t.rowid as id";
$sql .= " FROM ".MAIN_DB_PREFIX."extrafields as t";
$sql .= " WHERE elementtype = '".$this->db->escape($elementtype)."'";
$sql .= " AND name = '".$this->db->escape($attrname)."'";

$resql = $this->db->query($sql);
if ($resql) {
if ($this->db->num_rows($resql)) {
$tab = $this->db->fetch_object($resql);
$id = (int) $tab->id;
} else {
$id = (int) -1;
}
} else {
$id = (int) -2;
}

return $id;
}

/**
* Get the list of towns.
*
Expand Down
84 changes: 81 additions & 3 deletions htdocs/core/class/extrafields.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,78 @@ public function addExtraField($attrname, $label, $type, $pos, $size, $elementtyp
}
}

/**
* Update an existing extra field parameter
*
* @param string $attrname Code of attribute
* @param string $label label of attribute
* @param string $type Type of attribute ('boolean','int','varchar','text','html','date','datetime','price', 'pricecy', 'phone','mail','password','url','select','checkbox','separate',...)
* @param int $pos Position of attribute
* @param string $size Size/length definition of attribute ('5', '24,8', ...). For float, it contains 2 numeric separated with a comma.
* @param string $elementtype Element type. Same value than object->table_element (Example 'member', 'product', 'thirdparty', ...)
* @param int $unique Is field unique or not
* @param int $required Is field required or not
* @param string $default_value Defaulted value (In database. use the default_value feature for default value on screen. Example: '', '0', 'null', 'avalue')
* @param array|string $param Params for field (ex for select list : array('options' => array(value'=>'label of option')) )
* @param int $alwayseditable Is attribute always editable regardless of the document status
* @param string $perms Permission to check
* @param string $list Visibility ('0'=never visible, '1'=visible on list+forms, '2'=list only, '3'=form only or 'eval string')
* @param string $help Text with help tooltip
* @param string $computed Computed value
* @param string $entity Entity of extrafields (for multicompany modules)
* @param string $langfile Language file
* @param string $enabled Condition to have the field enabled or not
* @param int $totalizable Is a measure. Must show a total on lists
* @param int $printable Is extrafield displayed on PDF
* @param array $moreparams More parameters. Example: array('css'=>, 'csslist'=>Css on list, 'cssview'=>...)
* @return int Return integer <=0 if KO, >0 if OK
*/
public function updateExtraField($attrname, $label, $type, $pos, $size, $elementtype, $unique = 0, $required = 0, $default_value = '', $param = '', $alwayseditable = 0, $perms = '', $list = '-1', $help = '', $computed = '', $entity = '', $langfile = '', $enabled = '1', $totalizable = 0, $printable = 0, $moreparams = array())
{
if (empty($attrname)) {
return -1;
}
if (empty($label)) {
return -1;
}

$result = 0;

if ($type == 'separator' || $type == 'separate') {
$type = 'separate';
$unique = 0;
$required = 0;
} // Force unique and not required if this is a separator field to avoid troubles.
if ($elementtype == 'thirdparty') {
$elementtype = 'societe';
}
if ($elementtype == 'contact') {
$elementtype = 'socpeople';
}

// Create field into database except for separator type which is not stored in database
if ($type != 'separate') {
dol_syslog(get_class($this).'::thisupdate', LOG_DEBUG);
$result = $this->update($attrname, $label, $type, $size, $elementtype, $unique, $required, $pos, $param, $alwayseditable, $perms, $list, $help, $default_value, $computed, $entity, $langfile, $enabled, $totalizable, $printable, $moreparams);
}
$err1 = $this->errno;
if ($result > 0 || $err1 == 'DB_ERROR_COLUMN_ALREADY_EXISTS' || $type == 'separate') {
// Add declaration of field into table
dol_syslog(get_class($this).'::thislabel', LOG_DEBUG);
$result2 = $this->update_label($attrname, $label, $type, $size, $elementtype, $unique, $required, $pos, $param, $alwayseditable, $perms, $list, $help, $default_value, $computed, $entity, $langfile, $enabled, $totalizable, $printable, $moreparams);
$err2 = $this->errno;
if ($result2 > 0 || ($err1 == 'DB_ERROR_COLUMN_ALREADY_EXISTS' && $err2 == 'DB_ERROR_RECORD_ALREADY_EXISTS')) {
$this->error = '';
$this->errno = '0';
return 1;
} else {
return -2;
}
} else {
return -1;
}
}

/**
* Add a new optional attribute.
* This is a private method. For public method, use addExtraField.
Expand Down Expand Up @@ -418,10 +490,11 @@ private function create_label($attrname, $label = '', $type = '', $pos = 0, $siz
$sql .= " ".($cssview ? "'".$this->db->escape($cssview)."'" : "null");
$sql .= ')';

dol_syslog(get_class($this)."::create_label", LOG_DEBUG);
if ($this->db->query($sql)) {
dol_syslog(get_class($this)."::create_label_success", LOG_DEBUG);
return 1;
} else {
dol_syslog(get_class($this)."::create_label_error", LOG_DEBUG);
$this->error = $this->db->lasterror();
$this->errno = $this->db->lasterrno();
return -1;
Expand Down Expand Up @@ -617,19 +690,23 @@ public function update($attrname, $label, $type, $length, $elementtype, $unique
}
}

dol_syslog(get_class($this).'::DDLUpdateField', LOG_DEBUG);
if ($type != 'separate') { // No table update when separate type
$result = $this->db->DDLUpdateField($this->db->prefix().$table, $attrname, $field_desc);
}
if ($result > 0 || $type == 'separate') {
if ($label) {
dol_syslog(get_class($this).'::update_label', LOG_DEBUG);
$result = $this->update_label($attrname, $label, $type, $length, $elementtype, $unique, $required, $pos, $param, $alwayseditable, $perms, $list, $help, $default, $computed, $entity, $langfile, $enabled, $totalizable, $printable, $moreparams);
}
if ($result > 0) {
$sql = '';
if ($unique) {
$sql = "ALTER TABLE ".$this->db->prefix().$table." ADD UNIQUE INDEX uk_".$table."_".$attrname." (".$attrname.")";
dol_syslog(get_class($this).'::update_unique', LOG_DEBUG);
$sql = "ALTER TABLE ".$this->db->prefix().$table." ADD UNIQUE INDEX uk_".$table."_".$this->db->sanitize($attrname)." (".$this->db->sanitize($attrname).")";
} else {
$sql = "ALTER TABLE ".$this->db->prefix().$table." DROP INDEX IF EXISTS uk_".$table."_".$attrname;
dol_syslog(get_class($this).'::update_common', LOG_DEBUG);
$sql = "ALTER TABLE ".$this->db->prefix().$table." DROP INDEX IF EXISTS uk_".$table."_".$this->db->sanitize($attrname);
}
dol_syslog(get_class($this).'::update', LOG_DEBUG);
$resql = $this->db->query($sql, 1, 'dml');
Expand All @@ -654,6 +731,7 @@ public function update($attrname, $label, $type, $length, $elementtype, $unique
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Modify description of personalized attribute
* This is a private method. For public method, use updateExtraField.
*
* @param string $attrname Name of attribute
* @param string $label Label of attribute
Expand Down

0 comments on commit f7eb0b9

Please sign in to comment.