Udf Component

This commit is contained in:
cgfhtw
2024-03-28 15:38:50 +01:00
parent 46fff01eaa
commit b1c094383f
4 changed files with 471 additions and 0 deletions
@@ -0,0 +1,142 @@
<?php
/**
* Copyright (C) 2024 fhcomplete.org
*
* 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
* (at your option) 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 <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
* This controller operates between (interface) the JS (GUI) and the UDFLib (back-end)
* Provides data to the ajax get calls about the Udf component
* Listens to ajax post calls to change the Udf data
* This controller works with JSON calls on the HTTP GET or POST and the output is always JSON
*/
class Udf extends FHCAPI_Controller
{
/**
* Calls the parent's constructor and prepares the UDFLib
*/
public function __construct()
{
// TODO(chris): permissions
// NOTE: UdfLib has its own permissions checks
parent::__construct([
'load' => 'admin:r',#self::PERM_LOGGED,
'save' => 'admin:r'#self::PERM_LOGGED
]);
// Libraries
$this->load->library('form_validation');
$this->load->library('UDFLib');
// Models
$this->load->model($this->getTargetModelPath(), 'TargetModel');
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
/**
* Load all UDFs for a dataset
*
* @return void
*/
public function load()
{
$pks = $this->TargetModel->getPks();
foreach ($pks as $id)
$this->form_validation->set_rules($id, $id, 'required');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$id = [];
foreach ($pks as $pk)
$id[$pk] = $this->input->post($pk);
if (!is_array($this->TargetModel->getPk()))
$id = current($id);
$result = $this->udflib->getFieldArray($this->TargetModel, $id);
#$fields = $this->getDataOrTerminateWithError($result);
if (isError($result))
$this->terminateWithError(getError($result));
$fields = $result->retval;
$this->terminateWithSuccess($fields);
}
/**
* Saves UDFs to a dataset
*
* @return void
*/
public function save()
{
$pks = $this->TargetModel->getPks();
foreach ($pks as $id)
$this->form_validation->set_rules($id, $id, 'required');
$result = $this->udflib->getCiValidations($this->TargetModel, $this->input->post());
#$fieldValidations = $this->getDataOrTerminateWithError($result);
if (isError($result))
$this->terminateWithError(getError($result));
$fieldvalidations = $result->retval;
$this->form_validation->set_rules($fieldvalidations);
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$id = [];
$fields = $this->input->post();
foreach ($pks as $pk) {
$id[$pk] = $fields[$pk];
unset($fields[$pk]);
}
if (!is_array($this->TargetModel->getPk()))
$id = current($id);
$result = $this->TargetModel->update($id, $fields);
#$this->getDataOrTerminateWithError($result);
if (isError($result))
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
$this->terminateWithSuccess(array_fill_keys(array_keys($fields), ''));
}
//------------------------------------------------------------------------------------------------------------------
// Private methods
/**
* Get the path to the target model from the url
*
* @return string
*/
private function getTargetModelPath()
{
$ci_model_path = array_slice($this->uri->rsegments, 2);
if ($ci_model_path)
$ci_model_path[] = ucfirst(array_pop($ci_model_path)) . '_model';
return implode(DIRECTORY_SEPARATOR, $ci_model_path);
}
}
+17
View File
@@ -827,6 +827,23 @@ class DB_Model extends CI_Model
return $result;
}
public function getDbTable()
{
return $this->dbTable;
}
public function getPk()
{
return $this->pk;
}
public function getPks()
{
if (is_array($this->pk))
return $this->pk;
return [$this->pk];
}
// ------------------------------------------------------------------------------------------
// Protected methods
+156
View File
@@ -65,6 +65,8 @@ class UDFLib
private $_udfUniqueId; // Property that contains the UDF widget unique id
private $_definition_cache = [];
/**
* Gets CI instance
*/
@@ -613,6 +615,160 @@ class UDFLib
);
}
/**
* Gets the UDF definitions for a model
*
* @param DB_Model $targetModel
*
* @return stdClass
*/
public function getDefinitionForModel($targetModel)
{
$dbTable = $targetModel->getDbTable();
if (!isset($this->_definition_cache[$dbTable])) {
$this->_ci->load->model('system/UDF_model', 'UDFModel');
list($schema, $table) = explode('.', $dbTable);
$result = $this->_ci->UDFModel->loadWhere([
'schema' => $schema,
'table' => $table
]);
if (isError($result))
return $result;
$this->_definition_cache[$dbTable] = json_decode(current($result->retval)->jsons, true);
}
return success($this->_definition_cache[$dbTable]);
}
/**
* Gets the UDFs for db entry with translated params and resolved listValues for dropdowns
*
* @param DB_Model $targetModel
* @param mixed $id
*
* @return stdClass
*/
public function getFieldArray($targetModel, $id)
{
// Load Libraries
$this->_ci->load->library('PhrasesLib');
$this->_ci->load->library('PermissionLib');
$result = $this->getDefinitionForModel($targetModel);
if (isError($result))
return $result;
$definitions = getData($result);
usort($definitions, function ($a, $b) {
return $a[self::SORT] - $b[self::SORT];
});
$values = $targetModel->getUDFs($id);
$fields = [];
foreach ($definitions as $field) {
// check read permissions
if (!$this->_ci->permissionlib->hasAtLeastOne(
$field[self::REQUIRED_PERMISSIONS_PARAMETER],
self::PERMISSION_TABLE_METHOD,
self::PERMISSION_TYPE_READ
))
continue;
// set value
if (isset($values[$field[self::NAME]])) {
$field['value'] = $values[$field[self::NAME]];
} elseif (isset($field['defaultValue'])) {
$field['value'] = $field['defaultValue'];
} elseif (isset($field[self::TYPE]) && $field[self::TYPE] == 'checkbox') {
$field['value'] = false;
} else {
$field['value'] = '';
}
// translate params
foreach ([self::LABEL, self::TITLE, self::PLACEHOLDER] as $key) {
if (isset($field[$key])) {
$res = $this->_ci->phraseslib->getPhrases(self::PHRASES_APP_NAME, getUserLanguage(), $field[$key], null, null, 'no');
if (hasData($res))
$field[$key] = current(getData($res))->text;
}
}
// check write permissions
$field['disabled'] = !$this->_ci->permissionlib->hasAtLeastOne(
$field[self::REQUIRED_PERMISSIONS_PARAMETER],
self::PERMISSION_TABLE_METHOD,
self::PERMISSION_TYPE_WRITE
);
// set listValues for dropdowns
if (isset($field[self::LIST_VALUES])) {
if (isset($field[self::LIST_VALUES]['enum'])) {
$field['options'] = $field[self::LIST_VALUES]['enum'];
} elseif (isset($field[self::LIST_VALUES]['sql'])) {
$res = $this->_ci->UDFModel->execReadOnlyQuery($field[self::LIST_VALUES]['sql']);
$field['options'] = hasData($res) ? getData($res) : [];
}
}
// add to array
$fields[] = $field;
}
return success($fields);
}
/**
* Gets a validation config array for CI form_validation
*
* @param DB_Model $targetModel
* @param array (optional) $filter
*
* @return stdClass
*/
public function getCiValidations($targetModel, $filter = null)
{
$result = $this->getDefinitionForModel($targetModel);
if (isError($result))
return $result;
$definitions = getData($result);
$result = [];
foreach ($definitions as $def) {
if ($filter && !isset($filter[$def['name']]))
continue;
$validations = [];
if (isset($def['requiredPermissions']))
$validations[] = 'has_write_permissions[' . implode(',', $def['requiredPermissions']) . ']';
if (isset($def['required']))
$validations[] = 'required';
if (isset($def['validation'])) {
if (isset($def['validation']['max-value']))
$validations[] = 'less_than_equal_to[' . $def['validation']['max-value'] . ']';
if (isset($def['validation']['min-value']))
$validations[] = 'greater_than_equal_to[' . $def['validation']['min-value'] . ']';
if (isset($def['validation']['max-length']))
$validations[] = 'max_length[' . $def['validation']['max-length'] . ']';
if (isset($def['validation']['min-length']))
$validations[] = 'min_length[' . $def['validation']['min-length'] . ']';
if (isset($def['validation']['regex']) && is_array($def['validation']['regex'])) {
foreach ($def['validation']['regex'] as $regex) {
if ($regex['language'] == 'php') {
$validations[] = 'regex_match[' . $regex['expression'] . ']';
}
}
}
}
if ($validations)
$result[] = [
'field' => $def['name'],
'label' => $def['title'],
'rules' => $validations
];
}
return success($result);
}
// -------------------------------------------------------------------------------------------------
// Private methods
//
+156
View File
@@ -0,0 +1,156 @@
import { CoreFetchCmpt } from '../Fetch.js';
import FormInput from '../Form/Input.js';
export default {
components: {
CoreFetchCmpt,
FormInput
},
emits: [
'update:modelValue',
'load'
],
props: {
// CodeIgniter model (eg: crm/prestudent)
ciModel: {
type: String,
required: true
},
// Primarykey(s) of the record (eg: {prestudent_id: 12345})
pk: {
type: Object,
required: true
},
// The values as associative array
modelValue: Object,
// Show only fields with a name that exists in the filter
filter: [String, Array]
},
data() {
return {
fields: [],
backupModelValue: {}
};
},
computed: {
filterArray() {
if (!this.filter || Array.isArray(this.filter))
return this.filter;
return [this.filter];
},
filteredFields() {
if (!this.filterArray)
return this.fields;
return this.fields.filter(el => this.filterArray.includes(el.name));
},
filteredValues() {
return this.filteredFields.reduce((r,e) => (r[e.name] = this.internModelValue[e.name], r), {});
},
internModelValue: {
get() {
return this.modelValue || this.backupModelValue;
},
set(value) {
this.backupModelValue = value;
this.$emit('update:modelValue', value);
this.originalValues
}
}
},
watch: {
pk(n, o) {
if (!this.$refs.fetch)
return; // NOTE(chris): no initial load yet
if (Object.keys(o).length == Object.keys(n).length
&& Object.keys(o).every(key => n.hasOwnProperty(key) && o[key] === n[key]))
return; // NOTE(chris): old and new are the same
this.$nextTick(this.$refs.fetch.fetchData);
}
},
methods: {
loadF(params) {
// TODO(chris): move to fhcapi.factory
return this.$fhcApi
.post('/api/frontend/v1/udf/load/' + params.ciModel, params.pk);
},
init(result) {
const fields = result.map(el => {
switch (el.type) {
case 'textfield':
el.type = 'text';
break;
case 'date':
el.type = 'Datepicker';
el.clearable = el.hasOwnProperty('clearable') ? el.clearable : false
el.autoApply = el.hasOwnProperty('autoApply') ? el.autoApply : true
el.enableTimePicker = el.hasOwnProperty('enableTimePicker') ? el.enableTimePicker : false
el.format = el.hasOwnProperty('format') ? el.format : "dd.MM.yyyy"
el.previewFormat = el.hasOwnProperty('previewFormat') ? el.previewFormat : "dd.MM.yyyy"
el.teleport = el.hasOwnProperty('teleport') ? el.teleport : true
break;
case 'multipledropdown':
el.multiple = true;
case 'dropdown':
el.type = 'select';
el.options = el.options.map(item => {
if (Array.isArray(item))
return {
id: item[0],
description: item[1]
};
if (typeof item === 'object')
return item;
return {
id: item,
description: item
};
});
break;
}
return el;
});
const values = fields.reduce((a,c) => {
a[c.name] = c.value;
return a;
}, {});
this.internModelValue = {...this.internModelValue, ...values};
this.fields = fields;
this.$emit('load', values);
}
},
template: `
<div class="core-udf row">
<core-fetch-cmpt
ref="fetch"
:api-function="loadF"
:api-function-parameters="{ ciModel, pk }"
@data-fetched="init"
>
<template #default>
<div v-for="field in filteredFields" :key="field.name" class="col">
<form-input
v-model="internModelValue[field.name]"
:name="field.name"
:label="field.title"
:type="field.type"
:multiple="field.multiple"
:title="field.description"
:disabled="field.disabled"
:clearable="field.clearable"
:auto-apply="field.autoApply"
:enable-time-picker="field.enableTimePicker"
:format="field.format"
:preview-format="field.previewFormat"
:teleport="field.teleport"
>
<option v-for="value in field.options" :key="value.id" :value="value.id">{{value.description}}</option>
</form-input>
</div>
</template>
</core-fetch-cmpt>
</div>`
}