mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-24 02:12:17 +00:00
Udf Component
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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>`
|
||||
}
|
||||
Reference in New Issue
Block a user