Merge branch 'feature-30660/FHC4_StudierendenGUI_Prototyp' of github.com:FH-Complete/FHC-Core into feature-30660/FHC4_StudierendenGUI_Prototyp

This commit is contained in:
ma0068
2024-03-14 15:53:39 +01:00
29 changed files with 1596 additions and 202 deletions
+255
View File
@@ -0,0 +1,255 @@
<?php
if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
* Controller using JSON
*/
class FHCAPI_Controller extends FHC_Controller
{
/**
* Response status
* @see https://github.com/omniti-labs/jsend
*/
const STATUS_SUCCESS = 'success';
const STATUS_FAIL = 'fail';
const STATUS_ERROR = 'error';
/**
* Error types
*/
const ERROR_TYPE_PHP = 'php'; // TODO(chris): php types from severity?
const ERROR_TYPE_EXCEPTION = 'exception';
const ERROR_TYPE_GENERAL = 'general';
const ERROR_TYPE_404 = '404';
const ERROR_TYPE_DB = 'db';
const ERROR_TYPE_VALIDATION = 'validation';
/**
* Return Object
*
* @var array
*/
private $returnObj = [];
/**
* Constructor
*
* @param array $requiredPermissions
* @return void
*/
public function __construct($requiredPermissions = [])
{
if (is_cli())
show_404();
parent::__construct();
$this->config->set_item('error_views_path', VIEWPATH.'errors'.DIRECTORY_SEPARATOR.'json'.DIRECTORY_SEPARATOR);
global $g_result;
$g_result = $this;
ob_start(function ($content) {
$http_response_code = http_response_code();
// NOTE(chris): For security reasons 404 will be displayed the same everywhere
if ($http_response_code == REST_Controller::HTTP_NOT_FOUND)
return $content;
header('Content-Type: application/json; charset=utf-8');
if (!isset($this->returnObj['meta']) || !isset($this->returnObj['meta']['status'])) {
switch ($http_response_code) {
case 200:
$this->setStatus(self::STATUS_SUCCESS);
break;
case 400:
$this->setStatus(self::STATUS_FAIL);
break;
default:
$this->setStatus(self::STATUS_ERROR);
break;
}
}
#$this->returnObj['test'] = implode('/n', headers_list());
return json_encode($this->returnObj);
});
// Load libraries
$this->load->library('AuthLib');
$this->load->library('PermissionLib');
// Checks if the caller is allowed to access to this content
$this->_isAllowed($requiredPermissions);
// For JSON Requests (as opposed to multipart/form-data) get the $_POST variable from the input stream instead
if ($this->input->get_request_header('Content-Type', true) == 'application/json')
$_POST = json_decode($this->security->xss_clean($this->input->raw_input_stream), true);
elseif (isset($_POST['_jsondata'])) {
$_POST = array_merge($_POST, json_decode($_POST['_jsondata'], true));
unset($_POST['_jsondata']);
}
}
// ---------------------------------------------------------------
// Handle Output object
// ---------------------------------------------------------------
/**
* @param array $data
* @param string $type (optional)
* @return void
*/
public function addError($data, $type = null)
{
if (!isset($this->returnObj['errors']))
$this->returnObj['errors'] = [];
$error = [];
if (is_array($data)) {
if ($type == self::ERROR_TYPE_VALIDATION)
$error['messages'] = $data;
else
$error = $data;
} else {
$error['message'] = $data;
}
if ($type)
$error['type'] = $type;
$this->returnObj['errors'][] = $error;
}
/**
* @param mixed $data
* @return void
*/
public function setData($data)
{
$this->returnObj['data'] = $data;
}
/**
* @param string $status
* @return void
*/
public function setStatus($status)
{
if (!isset($this->returnObj['meta']))
$this->returnObj['meta'] = [];
$this->returnObj['meta']['status'] = $status;
}
// ---------------------------------------------------------------
// Handle Output object - Shortcut functions
// ---------------------------------------------------------------
/**
* @param array $errors
* @return void
*/
protected function terminateWithValidationErrors($errors)
{
$this->output->set_status_header(REST_Controller::HTTP_BAD_REQUEST);
$this->addError($errors, self::ERROR_TYPE_VALIDATION);
$this->setStatus(self::STATUS_FAIL);
exit(EXIT_ERROR);
}
/**
* @param mixed $data (optional)
* @return void
*/
protected function terminateWithSuccess($data = null)
{
$this->setData($data);
$this->setStatus(self::STATUS_SUCCESS);
exit;
}
/**
* @param array $error
* @param string $type (optional)
* @return void
*/
protected function terminateWithError($error, $type = null)
{
$this->output->set_status_header(REST_Controller::HTTP_INTERNAL_SERVER_ERROR);
$this->addError($error, $type);
$this->setStatus(self::STATUS_ERROR);
exit;
}
/**
* @param stdclass $result
* @param string $errortype
* @return void
*/
protected function checkForErrors($result, $errortype = self::ERROR_TYPE_GENERAL)
{
// TODO(chris): IMPLEMENT!
if (isError($result)) {
$this->terminateWithError(getError($result), $errortype);
}
return $result->retval;
}
// TODO(chris): complete list
// ---------------------------------------------------------------
// Security
// ---------------------------------------------------------------
/**
* Checks if the caller is allowed to access to this content with the given permissions
* If it is not allowed will set the HTTP header with code 401
* Wrapper for permissionlib->isEntitled
*
* @param array $requiredPermissions
* @return void
*/
protected function _isAllowed($requiredPermissions)
{
// Checks if this user is entitled to access to this content
if (!$this->permissionlib->isEntitled($requiredPermissions, $this->router->method))
{
$this->output->set_status_header(isLogged() ? REST_Controller::HTTP_FORBIDDEN : REST_Controller::HTTP_UNAUTHORIZED);
$this->addError([
'message' => 'You are not allowed to access to this content',
'controller' => $this->router->class,
'method' => $this->router->method,
'required_permissions' => $this->_rpsToString($requiredPermissions, $this->router->method)
]);
exit; // immediately terminate the execution
}
}
/**
* Converts an array of permissions to a string that contains them as a comma separated list
* Ex: "<permission 1>, <permission 2>, <permission 3>"
*
* @param array $requiredPermissions
* @param string $method
* @return void
*/
protected function _rpsToString($requiredPermissions, $method)
{
if (!isset($requiredPermissions[$method]))
return '';
if (!is_array($requiredPermissions[$method]))
return $requiredPermissions[$method];
return implode(', ', $requiredPermissions[$method]);
}
}
@@ -0,0 +1,65 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
// NOTE(chris): For security reasons 404 will be displayed the same everywhere
?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>404 Page Not Found</title>
<style type="text/css">
::selection { background-color: #E13300; color: white; }
::-moz-selection { background-color: #E13300; color: white; }
body {
background-color: #fff;
margin: 40px;
font: 13px/20px normal Helvetica, Arial, sans-serif;
color: #4F5155;
}
a {
color: #003399;
background-color: transparent;
font-weight: normal;
}
h1 {
color: #444;
background-color: transparent;
border-bottom: 1px solid #D0D0D0;
font-size: 19px;
font-weight: normal;
margin: 0 0 14px 0;
padding: 14px 15px 10px 15px;
}
code {
font-family: Consolas, Monaco, Courier New, Courier, monospace;
font-size: 12px;
background-color: #f9f9f9;
border: 1px solid #D0D0D0;
color: #002166;
display: block;
margin: 14px 0 14px 0;
padding: 12px 10px 12px 10px;
}
#container {
margin: 10px;
border: 1px solid #D0D0D0;
box-shadow: 0 0 8px #D0D0D0;
}
p {
margin: 12px 15px 12px 15px;
}
</style>
</head>
<body>
<div id="container">
<h1><?php echo $heading; ?></h1>
<?php echo $message; ?>
</div>
</body>
</html>
@@ -0,0 +1,49 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
global $g_result;
// NOTE(chris): remove p tags from CI_Exceptions::show_error() function
$msg = substr($message, 3);
$msg = substr($msg, 0, -4);
$msg = explode('</p><p>', $msg);
$msgs = [];
$error = [
'heading' => $heading
];
/** NOTE(chris): extract Error Number and SQL
* @see: DB_driver.php:692
*/
if (substr(current($msg), 0, 14) == 'Error Number: ') {
$code = substr(array_shift($msg), 14);
if ($code)
$error['code'] = (int)$code;
$msgs[] = array_shift($msg);
$error['sql'] = array_shift($msg);
}
/** NOTE(chris): extract Line Number and Filename
* @see: DB_driver.php:1782
* @see: DB_driver.php:1783
*/
if (count($msg) >= 2) {
if (substr(end($msg), 0, 13) == 'Line Number: ' && substr(prev($msg), 0, 10) == 'Filename: ') {
$error['line'] = (int)substr(array_pop($msg), 13);
$error['filename'] = substr(array_pop($msg), 10);
}
}
foreach ($msg as $m)
$msgs[] = $m;
if (count($msgs) == 1)
$error['message'] = current($msgs);
else
$error['messages'] = $msgs;
$g_result->addError($error, FHCAPI_Controller::ERROR_TYPE_DB);
$g_result->setStatus(FHCAPI_Controller::STATUS_ERROR);
@@ -0,0 +1,27 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
global $g_result;
$error = [
'message' => $message,
'class' => get_class($exception),
'filename' => $exception->getFile(),
'line' => $exception->getLine()
];
if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE === true) {
$error['backtrace'] = [];
foreach (debug_backtrace() as $err) {
if (isset($err['file']) && strpos($err['file'], realpath(BASEPATH)) !== 0) {
$error['backtrace'][] = [
'file' => $err['file'],
'line' => $err['line'],
'function' => $err['function']
];
}
}
}
$g_result->addError($error, FHCAPI_Controller::ERROR_TYPE_EXCEPTION);
$g_result->setStatus(FHCAPI_Controller::STATUS_ERROR);
@@ -0,0 +1,20 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
global $g_result;
// NOTE(chris): remove p tags from CI_Exceptions::show_error() function
$msg = substr($message, 3);
$msg = substr($msg, 0, -4);
$msg = explode('</p><p>', $msg);
$error = [
'heading' => $heading
];
if (count($msg) == 1)
$error['message'] = current($msg);
else
$error['messages'] = $msg;
$g_result->addError($error, FHCAPI_Controller::ERROR_TYPE_GENERAL);
$g_result->setStatus(FHCAPI_Controller::STATUS_ERROR);
@@ -0,0 +1,31 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
global $g_result;
$error = [
'message' => $message,
'severity' => $severity,
'filename' => $filepath,
'line' => $line
];
if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE === true) {
$error['backtrace'] = [];
foreach (debug_backtrace() as $err) {
if (isset($err['file']) && strpos($err['file'], realpath(BASEPATH)) !== 0) {
$error['backtrace'][] = [
'file' => $err['file'],
'line' => $err['line'],
'function' => $err['function']
];
}
}
}
// TODO(chris): change type with severity
$g_result->addError($error, 'php');
if (((E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $severity) === $severity) {
$g_result->setStatus('error');
}
+56 -24
View File
@@ -1,68 +1,100 @@
.fhc-header {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
align-items: baseline;
margin-bottom: 3rem;
}
.fhc-header > h1:first-child {
font-size: calc(1.325rem + .9vw);
}
.fhc-header > :first-child > small {
color: var(--bs-secondary);
font-size: .65em;
padding-inline-start: 1em;
}
.fhc-alert.p-toast-center {
width: 35rem;
max-width: 100vw;
}
.fhc-alert.p-toast-top-right {
max-width: calc(100vw - 40px);
}
.fhc-alert.p-toast-top-right .p-toast-detail,
.fhc-alert.p-toast-center .p-toast-message-text .card {
white-space: pre-wrap;
}
.text-preline {
white-space: pre-line;
}
.text-prewrap {
white-space: pre-line;
white-space: pre-wrap;
}
.btn-p-0 {
padding: 0 .375rem;
padding: 0 .375rem;
}
.z-1 {
z-index: 1;
z-index: 1;
}
.input-group > .input-group-item {
position: relative;
flex: 1 1 auto;
width: 1%;
min-width: 0;
position: relative;
flex: 1 1 auto;
width: 1%;
min-width: 0;
}
.input-group > .input-group-item .form-control:focus,
.input-group > .input-group-item .form-select:focus {
z-index: 3;
position: relative;
z-index: 3;
position: relative;
}
.input-group-lg > .input-group-item .form-control,
.input-group-lg > .input-group-item .form-select {
padding: 0.5rem 1rem;
font-size: 1.25rem;
border-radius: 0.3rem;
padding: 0.5rem 1rem;
font-size: 1.25rem;
border-radius: 0.3rem;
}
.input-group-sm > .input-group-item .form-control,
.input-group-sm > .input-group-item .form-select {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
border-radius: 0.2rem;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
border-radius: 0.2rem;
}
.input-group-lg > .input-group-item .form-select,
.input-group-sm > .input-group-item .form-select {
padding-right: 3rem;
padding-right: 3rem;
}
.input-group:not(.has-validation) > .input-group-item:not(:last-child) .form-control,
.input-group:not(.has-validation) > .input-group-item:not(:last-child) .form-select {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-group.has-validation > .input-group-item:nth-last-child(n+3) .form-control,
.input-group.has-validation > .input-group-item:nth-last-child(n+3) .form-select {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-group > .input-group-item:not(:first-child) .form-control,
.input-group > .input-group-item:not(:first-child) .form-select {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.form-control-color.is-invalid,
.was-validated .form-control-color:invalid,
.form-control-color.is-valid,
.was-validated .form-control-color:valid {
padding-right: .375rem;
background-image: none;
padding-right: .375rem;
background-image: none;
}
+6 -5
View File
@@ -43,13 +43,14 @@
font-size: 1rem;
}
.tabulator-row.tabulator-selectable:focus {
box-shadow: 0 0 0 .24rem rgba(13,110,253,.25);
z-index: 1;
outline: 0;
.tabulator-cell .btn {
padding: 0 .5rem;
max-height: 22px;
min-width: 30px;
}
.tabulator-row.tabulator-selectable:focus {
box-shadow: 0 0 0 .24rem rgba(13,110,253,.25);
z-index: 1;
outline: 0;
}
@@ -64,3 +64,12 @@
margin-top: 20px;
}
.tabulator {
font-size: 1rem;
}
.tabulator-cell .btn {
padding: 0 .375rem;
font-size: .875rem;
border-radius: .2rem;
}
@@ -82,6 +82,7 @@
/*
* To be moved outside
*/
.navbar.navbar-left-side ~ *,
#content {
position: inherit;
margin: 0 0 0 250px;
@@ -24,6 +24,8 @@ import {CoreRESTClient} from '../../RESTClient.js';
import {CoreFetchCmpt} from '../../components/Fetch.js';
import {BismeldestichtagAPIs} from './API.js';
import Phrasen from '../../plugin/Phrasen.js';
const bismeldestichtagApp = Vue.createApp({
data: function() {
return {
@@ -187,4 +189,4 @@ const bismeldestichtagApp = Vue.createApp({
}
});
bismeldestichtagApp.mount('#main');
bismeldestichtagApp.use(Phrasen).mount('#main');
+3 -1
View File
@@ -21,6 +21,8 @@ import {LogsViewerTabulatorEventHandlers} from './TabulatorSetup.js';
import {CoreFilterCmpt} from '../../components/filter/Filter.js';
import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js';
import Phrasen from '../../plugin/Phrasen.js';
const logsViewerApp = Vue.createApp({
data: function() {
return {
@@ -40,5 +42,5 @@ const logsViewerApp = Vue.createApp({
}
});
logsViewerApp.mount('#main');
logsViewerApp.use(Phrasen).mount('#main');
+4 -4
View File
@@ -18,8 +18,8 @@
import FhcStudentenverwaltung from "../components/Stv/Studentenverwaltung.js";
import fhcapifactory from "./api/fhcapifactory.js";
import FhcAlert from "../plugin/FhcAlert.js";
import FhcPhrasen from "../plugin/Phrasen.js";
import FhcApi from "../plugin/FhcApi.js";
import Phrasen from "../plugin/Phrasen.js";
//import PrimeVue form "../../../../index.ci.php/public/js/components/primevue/config/config.esm.min.js");
//const PrimeVue = await import(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/public/js/components/primevue/config/config.esm.min.js").default;
@@ -47,7 +47,7 @@ import(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_route
overlay: 1100
}
})
.use(FhcAlert)
.use(FhcPhrasen)
.use(FhcApi)
.use(Phrasen)
.mount('#main');
});
+30 -44
View File
@@ -9,7 +9,8 @@ export default {
$registerToForm: component => {
if (this.inputs.indexOf(component) < 0)
this.inputs.push(component);
}
},
$clearValidationForName: this.clearValidationForName
};
},
props: {
@@ -40,9 +41,33 @@ export default {
return a;
}, {});
},
factory() {
const factory = Object.create(Object.getPrototypeOf(this.$fhcApi.factory), Object.getOwnPropertyDescriptors(this.$fhcApi.factory));
factory.$fhcApi = {
get: this.get,
post: this.post
};
return factory;
}
},
methods: {
get(...args) {
if (typeof args[0] == 'object' && args[0].clearValidation && args[0].setFeedback)
args[0] = this;
else
args.unshift(this);
return this.$fhcApi.get(...args);
},
post(...args) {
if (typeof args[0] == 'object' && args[0].clearValidation && args[0].setFeedback)
args[0] = this;
else
args.unshift(this);
return this.$fhcApi.post(...args);
},
_sendFeedbackToInput(inputs, feedback, valid) {
if (inputs.length) {
inputs.forEach(input => input.setFeedback(valid, feedback));
@@ -86,52 +111,13 @@ export default {
clearValidation() {
this.inputs.forEach(input => input.clearValidation());
},
send(promise) {
return new Promise((resolve, reject) => {
promise.then(result => {
if (result?.status == 200 && result.data) {
if (typeof result.data !== 'object' || !result.data.hasOwnProperty('retval'))
// TODO(chris): IMPLEMENT! Error in API
return reject(result);
if (result.data.error)
// TODO(chris): IMPLEMENT! Error in API
return reject(result);
const data = result.data.retval;
// TODO(chris): check for something better/add new standardized return value
if (result.data.code == 1)
this.setFeedback(true, data);
return resolve(data);
}
// TODO(chris): IMPLEMENT! Wrong result object
reject(result);
}).catch(result => {
if (result?.response?.status == 400 && result.response.data) {
if (typeof result.response.data !== 'object' || !result.response.data.hasOwnProperty('retval'))
// TODO(chris): IMPLEMENT! Error in API
return reject(result);
this.clearValidation();
const remaining = this.setFeedback(
false,
result.response.data.retval
);
if (remaining) {
result.response.data.retval = remaining;
return reject(result);
}
} else if (result?.response?.status == 500) {
if (this.$fhcAlert)
this.$fhcAlert.handleSystemError(result);
else
return reject(result);
} else {
return reject(result);
}
});
});
clearValidationForName(name) {
(this.sortedInputs[name.split('.')[0] + name.split('.').slice(1).map(p => `[${p}]`).join("")] || this.sortedInputs['_default'] || [])
.forEach(input => input.clearValidation());
}
},
template: `
<component :is="tag || 'FhcFragment'" v-bind="$attrs">
<slot></slot>
</component>`
}
}
+61 -23
View File
@@ -7,9 +7,16 @@ export default {
components: {
FhcFragment
},
inject: [
'$registerToForm'
],
inject: {
registerToForm: {
from: '$registerToForm',
default: null
},
clearValidationForName: {
from: '$clearValidationForName',
default: null
}
},
props: {
bsFeedback: Boolean,
noAutoClass: Boolean,
@@ -17,12 +24,14 @@ export default {
inputGroup: Boolean,
type: String,
name: String,
containerClass: [String, Array, Object]
containerClass: [String, Array, Object],
label: String
},
data() {
return {
valid: undefined,
feedback: []
feedback: [],
modelValueDummy: undefined
}
},
computed: {
@@ -31,8 +40,9 @@ export default {
return true;
if (this.containerClass)
return true;
if (this.autoContainerClass)
return true;
for (const prop in this.autoContainerClass)
if (Object.hasOwn(this.autoContainerClass, prop))
return true;
return false;
},
acc() {
@@ -111,13 +121,19 @@ export default {
if (!c.includes('form-check-input') && !c.includes('btn-check'))
classes.push('form-check-input');
break;
case 'color':
if (!c.includes('form-control-color'))
classes.push('form-control-color');
if (!c.includes('form-control'))
classes.push('form-control');
break;
case 'autocomplete':
case 'datepicker':
classes.push('p-0');
classes.push('border-0');
case 'color':
if (!c.includes('form-control-color'))
classes.push('form-control-color');
if (!c.includes('form-control'))
classes.push('form-control');
break;
case 'text':
case 'number':
case 'password':
@@ -145,9 +161,13 @@ export default {
},
modelValueCmp: {
get() {
if (this.$attrs.modelValue === undefined)
return this.modelValueDummy;
return this.$attrs.modelValue;
},
set(v) {
if (this.$attrs.modelValue === undefined)
this.modelValueDummy = v;
this.$emit('update:modelValue', v);
}
},
@@ -155,7 +175,7 @@ export default {
let uuid = this.$attrs.id;
if (this.lcType == 'datepicker')
uuid = this.$attrs.uid;
if (!uuid && this.$attrs.label)
if (!uuid && this.label)
uuid = 'fhc-form-input';
if (!uuid)
return undefined;
@@ -171,12 +191,26 @@ export default {
this.valid = undefined;
this.feedback = [];
},
clearValidationForThisName() {
if (this.valid === undefined)
return;
if (this.clearValidationForName && this.name)
this.clearValidationForName(this.name);
else
this.clearValidation();
},
setFeedback(valid, feedback) {
if (!feedback)
feedback = [];
if (!Array.isArray(feedback))
feedback = [feedback];
this.valid = valid;
// NOTE(chris): On a list of radios/checkboxes only add the feedback message to the last item
if (this.name && (this.lcType == 'radio' || this.lcType == 'checkbox')) {
const selector = 'input[type="' + this.lcType + '"][name="' + this.name + '"]';
if ([...this.$el.parentNode.querySelectorAll(selector)].pop() != this.$refs.input)
return;
}
this.feedback = feedback;
},
_loadComponents() {
@@ -198,19 +232,20 @@ export default {
this._loadComponents();
},
mounted() {
if (this.$registerToForm)
this.$registerToForm(this);
if (this.registerToForm)
this.registerToForm(this);
},
template: `
<component :is="!hasContainer ? 'FhcFragment' : 'div'" class="position-relative" :class="autoContainerClass">
<label v-if="$attrs.label && lcType != 'radio' && lcType != 'checkbox'" :for="idCmp">{{$attrs.label}}</label>
<input v-if="tag == 'input'" :type="lcType" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidation(); $emit('input', $event)">
<textarea v-else-if="tag == 'textarea'" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidation(); $emit('input', $event)"></textarea>
<select v-else-if="tag == 'select'" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidation(); $emit('input', $event)">
<label v-if="label && lcType != 'radio' && lcType != 'checkbox'" :for="idCmp">{{label}}</label>
<input v-if="tag == 'input'" :type="lcType" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidationForThisName(); $emit('input', $event)">
<textarea v-else-if="tag == 'textarea'" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidationForThisName(); $emit('input', $event)"></textarea>
<select v-else-if="tag == 'select'" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidationForThisName(); $emit('input', $event)">
<slot></slot>
</select>
<component
v-else-if="tag == 'VueDatePicker'"
ref="input"
:is="tag"
:type="type"
v-model="modelValueCmp"
@@ -220,12 +255,13 @@ export default {
:class="validationClass"
:input-class-name=
"[...Object.entries({'form-control': !noAutoClass, 'is-valid': valid === true, 'is-invalid': valid === false}).reduce((a,[k,v]) => {if(v) a.push(k);return a}, []), ...($attrs['input-class-name'] ? $attrs['input-class-name'].split(' ') : [])].join(' ')"
@update:model-value="clearValidation"
@update:model-value="clearValidationForThisName"
>
<slot></slot>
</component>
<component
v-else-if="tag == 'PvAutocomplete'"
ref="input"
:is="tag"
:type="type"
v-model="modelValueCmp"
@@ -234,12 +270,13 @@ export default {
:input-props="{name}"
:class="validationClass"
:input-class="[...Object.entries({'form-control': !noAutoClass, 'is-valid': valid === true, 'is-invalid': valid === false}).reduce((a,[k,v]) => {if(v) a.push(k);return a}, []), ...($attrs['input-class'] ? $attrs['input-class'].split(' ') : [])].join(' ')"
@update:model-value="clearValidation"
@update:model-value="clearValidationForThisName"
>
<slot></slot>
</component>
<component
v-else-if="tag == 'UploadDms'"
ref="input"
:is="tag"
:type="type"
v-model="modelValueCmp"
@@ -249,12 +286,13 @@ export default {
:class="validationClass"
:input-class="validationClass"
:no-list="inputGroup"
@update:model-value="clearValidation"
@update:model-value="clearValidationForThisName"
>
<slot></slot>
</component>
<component
v-else
ref="input"
:is="tag"
:type="type"
v-model="modelValueCmp"
@@ -262,11 +300,11 @@ export default {
:id="idCmp"
:name="name"
:class="validationClass"
@update:model-value="clearValidation"
@update:model-value="clearValidationForThisName"
>
<slot></slot>
</component>
<label v-if="$attrs.label && (lcType == 'radio' || lcType == 'checkbox')" :for="idCmp" :class="!noAutoClass && 'form-check-label'">{{$attrs.label}}</label>
<label v-if="label && (lcType == 'radio' || lcType == 'checkbox')" :for="idCmp" :class="!noAutoClass && 'form-check-label'">{{label}}</label>
<div v-if="valid !== undefined && feedback.length && !noFeedback" :class="feedbackClass">
<template v-for="(msg, i) in feedback" :key="i">
<hr v-if="i" class="m-0">
@@ -275,4 +313,4 @@ export default {
</div>
</component>
`
}
}
+2 -2
View File
@@ -75,7 +75,7 @@ export default {
<div class="form-upload-dms">
<input ref="upload" class="form-control" :class="inputClass" :id="id" :name="name" :multiple="multiple" type="file" @change="addFiles">
<ul v-if="modelValue.length && multiple && !noList" class="list-unstyled m-0">
<li v-for="(file, index) in modelValue" :key="index" class="d-flex mx-1 mt-1">
<li v-for="(file, index) in modelValue" :key="index" class="d-flex mx-1 mt-1 align-items-start">
<span class="col-auto"><i class="fa fa-file me-1"></i></span>
<span class="col">{{ file.name }}</span>
<button class="col-auto btn btn-outline-secondary btn-p-0" @click="removeFile(index)">
@@ -84,4 +84,4 @@ export default {
</li>
</ul>
</div>`
}
}
+12 -9
View File
@@ -1,26 +1,29 @@
const FEEDBACK_DEFAULT = {
success: [],
danger: []
};
export default {
inject: [
'$registerToForm'
],
data() {
return {
feedback: FEEDBACK_DEFAULT
feedback: {
success: [],
danger: []
}
};
},
methods: {
clearValidation() {
this.feedback = FEEDBACK_DEFAULT;
this.feedback = {
success: [],
danger: []
};
},
setFeedback(valid, feedback) {
if (!feedback)
feedback = [];
if (!Array.isArray(feedback))
feedback = [feedback];
this.feedback[valid ? 'success' : 'danger'] = feedback;
const ts = Date.now();
this.feedback[valid ? 'success' : 'danger'] = feedback.map(msg => [msg, ts]);
}
},
mounted() {
@@ -29,10 +32,10 @@ export default {
},
template: `
<template v-for="(arr, key) in feedback" :key="key">
<div v-for="msg in arr" :key="msg" class="alert alert-dismissible fade show" :class="'alert-' + key" role="alert">
<div v-for="[msg, ts] in arr" :key="ts + msg" class="alert alert-dismissible fade show" :class="'alert-' + key" role="alert">
{{msg}}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</template>
`
};
};
@@ -112,11 +112,14 @@ export default {
this.lastSelected = first ? undefined : this.selected;
if (url)
url = CoreRESTClient._generateRouterURI(url);
if (!this.$refs.table.tableBuilt)
this.$refs.table.tabulator.on("tableBuilt", () => {
this.$refs.table.tabulator.setData(url);
});
else
if (!this.$refs.table.tableBuilt) {
if (!this.$refs.table.tabulator) {
this.tabulatorOptions.ajaxURL = url;
} else
this.$refs.table.tabulator.on("tableBuilt", () => {
this.$refs.table.tabulator.setData(url);
});
} else
this.$refs.table.tabulator.setData(url);
},
onKeydown(e) { // TODO(chris): this should be in the filter component
+5 -6
View File
@@ -11,14 +11,14 @@ export default {
'changed'
],
props: {
// TODO(chris): rename to config?
config: {
type: [String, Object],
required: true
},
default: String,
modelValue: [String, Number, Boolean, Array, Object, Date, Function, Symbol],
vertical: Boolean
vertical: Boolean,
border: Boolean
},
data() {
return {
@@ -62,7 +62,6 @@ export default {
.then(this.initConfig)
.catch(this.$fhcAlert.handleSystemError);
console.log(config);
const tabs = {};
if (Array.isArray(config)) {
@@ -104,7 +103,7 @@ export default {
this.initConfig(this.config);
},
template: `
<div class="fhc-tabs d-flex" :class="vertical ? 'align-items-stretch' : 'flex-column'" v-if="Object.keys(tabs).length">
<div class="fhc-tabs d-flex" :class="vertical ? 'align-items-stretch gap-3' : (border ? 'flex-column' : 'flex-column gap-3')" v-if="Object.keys(tabs).length">
<div class="nav" :class="vertical ? 'nav-pills flex-column' : 'nav-tabs'">
<div
v-for="tab in tabs"
@@ -118,10 +117,10 @@ export default {
{{tab.title}}
</div>
</div>
<div :style="vertical ? '' : 'flex: 1 1 0%; height: 0%'" class="overflow-auto p-3" :class="vertical ? '' : 'border-bottom border-start border-end'">
<div :style="vertical ? '' : 'flex: 1 1 0%; height: 0%'" class="overflow-auto flex-grow-1" :class="vertical || !border ? '' : 'p-3 border-bottom border-start border-end'">
<keep-alive>
<component ref="current" :is="currentTab.component" v-model="value" :config="currentTab.config"></component>
</keep-alive>
</div>
</div>`
};
};
+18 -7
View File
@@ -135,9 +135,9 @@ export const CoreFilterCmpt = {
for (let col of columns)
{
// If the column has to be displayed or not
col.visible = selectedFields.indexOf(col.field) >= 0;
if (col.formatter == 'rowSelection')
col.visible = true;
/* fields.indexOf(col.field) == -1; ensures displaying formatter colums
e.g. column with rowSelection checkboxes or with custom formatted action buttons */
col.visible = selectedFields.indexOf(col.field) >= 0 || fields.indexOf(col.field) == -1;
if (col.hasOwnProperty('resizable'))
col.resizable = col.visible;
@@ -184,13 +184,23 @@ export const CoreFilterCmpt = {
else
this.getFilter();
},
initTabulator() {
async initTabulator() {
let placeholder = '< Phrasen Plugin not loaded! >';
if (this.$p) {
await this.$p.loadCategory('ui');
placeholder = this.$p.t('ui/keineDatenVorhanden');
}
// Define a default tabulator options in case it was not provided
let tabulatorOptions = {...{
height: 500,
layout: "fitColumns",
layout: "fitDataStretch",
movableColumns: true,
reactiveData: true
columnDefaults:{
tooltip: true,
},
placeholder,
reactiveData: true,
persistence: true
}, ...(this.tabulatorOptions || {})};
if (!this.tableOnly) {
@@ -264,6 +274,7 @@ export const CoreFilterCmpt = {
this.filterName = data.filterName;
this.dataset = data.dataset;
this.datasetMetadata = data.datasetMetadata;
this.fields = data.fields;
this.selectedFields = data.selectedFields;
this.notSelectedFields = this.fields.filter(x => this.selectedFields.indexOf(x) === -1);
@@ -434,7 +445,7 @@ export const CoreFilterCmpt = {
*
*/
handlerRemoveCustomFilter: function(event) {
filterId = event.currentTarget.getAttribute("href").substring(1);
let filterId = event.currentTarget.getAttribute("href").substring(1);
if (filterId === this.selectedFilter)
this.selectedFilter = null;
//
+54
View File
@@ -0,0 +1,54 @@
/**
* Copyright (C) 2022 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/>.
*/
export default {
props: {
title: '',
subtitle: '',
mainCols: {
type: Array,
default: []
},
asideCols: {
type: Array,
default: []
},
},
computed: {
mainGridCols() {
return this.mainCols.length > 0 ? `col-md-${this.mainCols[0]}` : "col-md-12";
},
asideGridCols() {
return this.asideCols.length > 0 ? `col-md-${this.asideCols[0]}` : "";
}
},
template: `
<div class="overflow-hidden">
<header v-if="title">
<h1 class="h2 mb-5">{{ title }}<span class="fhc-subtitle">{{ subtitle }}</span></h1>
</header>
<div class="row gx-5">
<main :class="mainGridCols">
<slot name="main">{{ mainGridCols }}</slot>
</main>
<aside v-if="asideCols.length > 0" :class="asideGridCols">
<slot name="aside">{{ asideGridCols }}</slot>
</aside>
</div>
</div>
`
};
+12 -13
View File
@@ -133,9 +133,8 @@ const helperApp = Vue.createApp({
helperAppContainer.parentElement.removeChild(helperAppContainer);
},
template: `
<pv-toast ref="toast" :base-z-index="99999"></pv-toast>
<pv-toast ref="alert" :base-z-index="99999" position="center">
<pv-toast ref="toast" class="fhc-alert" :base-z-index="99999"></pv-toast>
<pv-toast ref="alert" class="fhc-alert" :base-z-index="99999" position="center">
<template #message="slotProps">
<i class="fa fa-circle-exclamation fa-2xl mt-3"></i>
<div class="p-toast-message-text">
@@ -162,7 +161,7 @@ const helperApp = Vue.createApp({
</a>
</div>
<div ref="messageCard" :id="'fhcAlertCollapseMessageCard' + slotProps.message.id" class="collapse mt-3">
<div class="card card-body text-body small" style="white-space: pre-wrap">
<div class="card card-body text-body small">
{{slotProps.message.detail}}
</div>
</div>
@@ -238,7 +237,7 @@ export default {
alertMultiple(messageArray, severity = 'info', title = 'Info', sticky = false){
// Check, if array has only string values
if (messageArray.every(message => typeof message === 'string')) {
messageArray.every(message => this.alertDefault(severity, title, message, sticky));
messageArray.forEach(message => this.alertDefault(severity, title, message, sticky));
return true;
}
return false;
@@ -282,21 +281,21 @@ export default {
handleSystemMessage(message) {
// Message is string
if (typeof message === 'string')
return fhcerror.alertWarning(message);
return $fhcAlert.alertWarning(message);
// Message is array of strings
if (Array.isArray(message)) {
// If Array has only Strings
if (message.every(msg => typeof msg === 'string'))
return message.every(fhcerror.alertWarning);
return message.every($fhcAlert.alertWarning);
// If Array has only Objects
if (message.every(msg => typeof msg === 'object') && msg !== null) {
return message.every(msg => {
if (msg.hasOwnProperty('data') && msg.data.hasOwnProperty('retval')) {
fhcerror.alertWarning(JSON.stringify(msg.data.retval));
$fhcAlert.alertWarning(JSON.stringify(msg.data.retval));
} else {
fhcerror.alertSystemError(JSON.stringify(msg));
$fhcAlert.alertSystemError(JSON.stringify(msg));
}
});
}
@@ -306,15 +305,15 @@ export default {
if (typeof message === 'object' && message !== null){
if (message.hasOwnProperty('data') && message.data.hasOwnProperty('retval')) {
// NOTE(chris): changed: alertSystemError => alertWarning
fhcerror.alertWarning(JSON.stringify(message.data.retval));
$fhcAlert.alertWarning(JSON.stringify(message.data.retval));
} else {
fhcerror.alertSystemError(JSON.stringify(message));
$fhcAlert.alertSystemError(JSON.stringify(message));
}
return;
}
// Fallback
fhcerror.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method);
$fhcAlert.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method);
},
resetFormValidation(form) {
const event = new Event('fhc-form-reset');
@@ -392,4 +391,4 @@ export default {
};
app.config.globalProperties.$fhcAlert = $fhcAlert;
}
}
}
+282
View File
@@ -0,0 +1,282 @@
import FhcAlert from './FhcAlert.js';
import FhcApiFactory from '../apps/api/fhcapifactory.js';
export default {
install: (app, options) => {
app.use(FhcAlert);
function _get_config(form, uri, data, config) {
if (typeof form == 'string' && config === undefined) {
[uri, data, config] = [form, uri, data];
form = undefined;
} else if (form) {
if (typeof form != 'object')
throw new TypeError('Parameter 1 of _get_config must be an object or a string');
if (uri === undefined && data === undefined && config === undefined) {
config = form;
form = undefined;
}
}
if (form) {
// NOTE(chris): check if form is fhc-form
if (!form.clearValidation || !form.setFeedback)
throw new TypeError("'form' is not a Form Component");
form = {
clearValidation: form.clearValidation,
setFeedback: form.setFeedback
};
if (config)
config.form = form;
else
config = {form};
}
return [uri, data, config];
}
function _clean_return_value(response) {
const result = response.data;
delete response.data;
if (!result.meta)
result.meta = {response};
else
result.meta.response = response;
return result;
}
const fhcApiAxios = axios.create({
timeout: 5000,
baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/"
});
fhcApiAxios.interceptors.request.use(config => {
if (config.method != 'post' || !config.data)
return config;
if (config.data instanceof FormData)
return config;
if (!Object.values(config.data).every(item => {
if (item instanceof FileList)
return false;
if (Array.isArray(item))
return item.every(i => !(i instanceof File));
return true;
})) {
const newData = Object.entries(config.data).reduce((nd, [key, item]) => {
if (item instanceof FileList) {
for (const file of item)
nd.FormData.append(key + (item.length > 1 ? '[]' : ''), file);
} else if (Array.isArray(item)) {
if (item.every(i => !(i instanceof File))) {
nd.jsondata[key] = item;
} else {
item.forEach(file => nd.FormData.append(key + (item.length > 1 ? '[]' : ''), file));
}
} else {
nd.jsondata[key] = item;
}
return nd;
}, {
FormData: new FormData(),
jsondata: {}
});
newData.FormData.append('_jsondata', JSON.stringify(newData.jsondata));
config.data = newData.FormData;
}
return config;
});
fhcApiAxios.interceptors.response.use(response => {
if (response.config?.errorHandling == 'off'
|| response.config?.errorHandling === false
|| response.config?.errorHandling == 'fail')
return _clean_return_value(response);
// NOTE(chris): loop through errors
if (response.data.errors)
response.data.errors = response.data.errors.filter(
err => (response.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$fhcApi._defaultErrorHandlers[err.type])(err, response.config.form)
);
return _clean_return_value(response);
}, error => {
if (error.code == 'ERR_CANCELED')
return new Promise(() => {});
if (error.config?.errorHandling == 'off'
|| error.config?.errorHandling === false
|| error.config?.errorHandling == 'success')
return Promise.reject(error);
if (error.response) {
if (error.response.status == 404) {
app.config.globalProperties.$fhcAlert.alertDefault('error', error.message, error.request.responseURL, true);
return new Promise(() => {});
}
// NOTE(chris): loop through errors
error.response.data.errors = error.response.data.errors.filter(
err => (error.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$fhcApi._defaultErrorHandlers[err.type])(err, error.config.form)
);
if (!error.response.data.errors.length)
return new Promise(() => {});
} else if (error.request) {
app.config.globalProperties.$fhcAlert.alertDefault('error', error.message, error.request.responseURL);
return new Promise(() => {});
} else {
app.config.globalProperties.$fhcAlert.alertError(error.message);
return new Promise(() => {});
}
return Promise.reject(error);
});
app.config.globalProperties.$fhcApi = {
get(form, uri, params, config) {
[uri, params, config] = _get_config(form, uri, params, config);
if (params) {
if (config)
config.params = params;
else
config = {params};
}
return fhcApiAxios.get(uri, config);
},
post(form, uri, data, config) {
[uri, data, config] = _get_config(form, uri, data, config);
return fhcApiAxios.post(uri, data, config);
},
_defaultErrorHandlers: {
validation(error, form) {
const $fhcAlert = app.config.globalProperties.$fhcAlert;
if (form) {
form.clearValidation();
form.setFeedback(false, error.messages);
return false;
}
if (Array.isArray(error.messages)) {
error.messages.forEach($fhcAlert.alertError);
return false;
} else if (typeof error.messages == 'object') {
Object.entries(error.messages).forEach(
([key, value]) => $fhcAlert.alertDefault('error', key, value, true)
);
return false;
}
return true;
},
general(error, form) {
const $fhcAlert = app.config.globalProperties.$fhcAlert;
if (form)
form.setFeedback(false, error.message);
else
$fhcAlert.alertError(error.message);
},
php(error) {
const $fhcAlert = app.config.globalProperties.$fhcAlert;
var message = '';
message += 'Message: ' + error.message + '\n\n';
message += 'Filename: ' + error.filename + '\n';
message += 'Line Number: ' + error.line + '\n';
if (error.backtrace && error.backtrace.length) {
message += '\nBacktrace: ';
error.backtrace.forEach(err => {
message += '\n\tFile: ' + err.file + '\n';
message += '\tLine: ' + err.line + '\n';
message += '\tFunction: ' + err.function + '\n';
});
}
switch (error.severity) {
case 'Warning':
case 'Core Warning':
case 'Compile Warning':
case 'User Warning':
$fhcAlert.alertDefault('warn', 'PHP ' + error.severity, message, true);
break;
case 'Notice':
case 'User Notice':
case 'Runtime Notice':
$fhcAlert.alertDefault('info', 'PHP ' + error.severity, message, true);
break;
default:
message = 'Type: PHP ' + error.severity + '\n\n' + message;
$fhcAlert.alertSystemError(message);
break;
}
},
exception(error) {
const $fhcAlert = app.config.globalProperties.$fhcAlert;
var message = '';
message += 'Type: ' + error.class + '\n\n';
message += 'Message: ' + error.message + '\n\n';
message += 'Filename: ' + error.filename + '\n';
message += 'Line Number: ' + error.line + '\n';
if (error.backtrace && error.backtrace.length) {
message += '\nBacktrace: ';
error.backtrace.forEach(err => {
message += '\n\tFile: ' + err.file + '\n';
message += '\tLine: ' + err.line + '\n';
message += '\tFunction: ' + err.function + '\n';
});
}
$fhcAlert.alertSystemError(message);
},
db(error) {
const $fhcAlert = app.config.globalProperties.$fhcAlert;
var message = '';
if (error.heading !== undefined)
message += error.heading + '\n\n';
if (error.code !== undefined)
message += 'Code: ' + error.code + '\n\n';
if (error.sql !== undefined)
message += 'SQL: ' + error.sql + '\n\n';
if (error.message !== undefined)
message += 'Message: ' + error.message + '\n\n';
else if (error.messages !== undefined)
message += 'Messages: ' + error.messages.join('\n\t') + '\n\n';
if (error.filename !== undefined)
message += 'Filename: ' + error.filename + '\n';
if (error.line !== undefined)
message += 'Line Number: ' + error.line + '\n';
$fhcAlert.alertSystemError(message);
}
}
};
class FhcApiFactoryWrapper {
constructor(factorypart, root) {
if (root === undefined)
this.$fhcApi = app.config.globalProperties.$fhcApi;
else
Object.defineProperty(this, '$fhcApi', {
get() {
return (root || this).$fhcApi;
}
})
Object.keys(factorypart).forEach(key => {
Object.defineProperty(this, key, {
get() {
if (typeof factorypart[key] == 'function')
return factorypart[key].bind(this);
return new FhcApiFactoryWrapper(factorypart[key], root || this);
}
});
});
}
}
app.config.globalProperties.$fhcApi.factory = new FhcApiFactoryWrapper(FhcApiFactory);
}
};
+2
View File
@@ -51,6 +51,8 @@ require_once('dbupdate_3.4/29835_uhstat1_erfassung_der_uhstat1_daten_ueber_das_b
require_once('dbupdate_3.4/33714_erhoehter_studienbeitrag_fuer_drittsaatenangehoerig.php');
require_once('dbupdate_3.4/36275_zeitaufzeichnung_karenz.php');
require_once('dbupdate_3.4/21620_neues_feld_zum_erfassen_des_ESI.php');
require_once('dbupdate_3.4/36530_bis_internationsalisierung_codextabelle_neuerungen.php');
require_once('dbupdate_3.4/34543_ux_template.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
+16
View File
@@ -0,0 +1,16 @@
<?php
if (! defined('DB_NAME')) exit('No direct script access allowed');
// add app fhctemplate
if($result = $db->db_query("SELECT 1 FROM system.tbl_app WHERE app='fhctemplate'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO system.tbl_app (app) VALUES('fhctemplate');";
if(!$db->db_query($qry))
echo '<strong>System Tabelle app: '.$db->db_last_error().'</strong><br>';
else
echo '<br>app fhctemplate hinzugefuegt';
}
}
@@ -0,0 +1,30 @@
<?php
if (! defined('DB_NAME')) exit('No direct script access allowed');
if($result = @$db->db_query("SELECT 1 FROM bis.tbl_mobilitaetsprogramm WHERE mobilitaetsprogramm_code IN (43, 44, 45, 46, 47, 48, 49, 50, 51, 52)"))
{
if($db->db_num_rows($result) < 10)
{
$qry = "
INSERT INTO bis.tbl_mobilitaetsprogramm(mobilitaetsprogramm_code, kurzbz, beschreibung, sichtbar, sichtbar_outgoing) VALUES
(43, 'AMinKunst', 'Auslandsstipendium des Bundesministeriums für Kunst', TRUE, TRUE),
(44, 'BundeslandProg', 'Bundesland-Programm', TRUE, TRUE),
(45, 'ERASMUSSMS', 'ERASMUS+ (SMS) - Studienaufenthalte', TRUE, TRUE),
(46, 'ERASMUSSMT', 'ERASMUS+ (SMT) - Studierendenpraktika', TRUE, TRUE),
(47, 'ERASMUSMundus', 'Erasmus Mundus Joint Master Degrees / Erasmus Mundus Joint Master', FALSE, FALSE),
(48, 'EUGrad', 'Mobilitätsprogramm für Graduierte im EU-Bereich', TRUE, TRUE),
(49, 'ÖStiftung', 'Stipendienstiftung der Republik Österreich', TRUE, FALSE),
(50, 'ÖAkadWiss', 'Stipendienabkommen der Österreichischen Akademie der Wissenschaften', TRUE, TRUE),
(51, 'FondWissForsch', 'Stipendium des Fonds zur Förderung der wissenschaftlichen Forschung', TRUE, TRUE),
(52, 'SEMP', 'Swiss-European Mobility Programme (SEMP)', TRUE, TRUE)
ON CONFLICT (mobilitaetsprogramm_code) DO NOTHING;
";
if(!$db->db_query($qry))
echo '<strong>bis.tbl_mobilitaetsprogramm: '.$db->db_last_error().'</strong><br>';
else
echo ' bis.tbl_mobilitaetsprogramm: Mobilitätsprogramme hinzugefuegt<br>';
}
}
+26
View File
@@ -1279,6 +1279,32 @@ $filters = array(
}
',
'oe_kurzbz' => null,
),
array(
'app' => 'fhctemplate',
'dataset_name' => 'exampledata',
'filter_kurzbz' => 'exampledata',
'description' => '{Beispieldaten Filter}',
'sort' => 1,
'default_filter' => true,
'filter' => '
{
"name": "Alle Beispieldaten",
"columns": [
{"name": "uid"},
{"name": "stringval"},
{"name": "integerval"},
{"name": "dateval"},
{"name": "booleanval"},
{"name": "moneyval"},
{"name": "dokument_bezeichnung"},
{"name": "textval"},
{"name": "examplestatus_kurzbz"}
],
"filters": []
}
',
'oe_kurzbz' => null
)
);
+495 -49
View File
@@ -2104,7 +2104,6 @@ $phrases = array(
array(
'app' => 'core',
'category' => 'person',
<<<<<<< HEAD
'phrase' => 'bankverbindungen',
'insertvon' => 'system',
'phrases' => array(
@@ -2245,8 +2244,6 @@ $phrases = array(
array(
'app' => 'core',
'category' => 'person',
=======
>>>>>>> master
'phrase' => 'nation',
'insertvon' => 'system',
'phrases' => array(
@@ -2284,26 +2281,6 @@ $phrases = array(
)
)
),
array(
'app' => 'core',
'category' => 'person',
'phrase' => 'plz',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'plz',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'code',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'person',
@@ -2324,6 +2301,26 @@ $phrases = array(
)
)
),
array(
'app' => 'core',
'category' => 'person',
'phrase' => 'plz',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'PLZ',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'zip',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'person',
@@ -2331,7 +2328,7 @@ $phrases = array(
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German'
'sprache' => 'German',
'text' => 'Zustellung',
'description' => '',
'insertvon' => 'system'
@@ -11558,6 +11555,26 @@ Any unusual occurrences
)
)
),
array(
'app' => 'core',
'category' => 'anrechnung',
'phrase' => 'anrechnung',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Anrechnung',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Exemption',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'anrechnung',
@@ -24318,7 +24335,7 @@ array(
),
array(
'sprache' => 'English',
'text' => 'or'
'text' => 'or',
'description' => '',
'insertvon' => 'system'
)
@@ -24505,20 +24522,431 @@ array(
)
)
),
array(
'app' => 'lehrauftrag',
'category' => 'ui',
'phrase' => 'hinweistextLehrauftrag',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => '<strong>Hinweis:</strong> Das Akzeptieren von Lehraufträgen ersetzt alle vorhergehenden Lehraufträge dieses Studiensemesters.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => '<strong>Note:</strong> Accepting teaching assignments replaces all previous teaching assignments for this study semester.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'person',
'phrase' => 'standort',
'category' => 'global',
'phrase' => 'geloescht',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Standort',
'text' => 'Gelöscht',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'location',
'text' => 'Deleted',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'global',
'phrase' => 'aenderungGespeichert',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Änderung gespeichert',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Saved changes',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'fhctemplate',
'category' => 'global',
'phrase' => 'datensatz',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Datensatz',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Dataset',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'fhctemplate',
'category' => 'global',
'phrase' => 'datensatzGenehmigen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Datensatz genehmigen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Approve dataset',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'fhctemplate',
'category' => 'global',
'phrase' => 'datensatzAblehnen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Datensatz ablehnen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Reject dataset',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'fhctemplate',
'category' => 'global',
'phrase' => 'datensatzAnlegen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Datensatz anlegen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Add dataset',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'fhctemplate',
'category' => 'global',
'phrase' => 'datensatzBearbeiten',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Datensatz bearbeiten',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Edit dataset',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'global',
'phrase' => 'alleGenehmigt',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Alle genehmigt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'All accepted',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'global',
'phrase' => 'alleAbgelehnt',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Alle abgelehnt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'All rejected',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'global',
'phrase' => 'dokument',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Dokument',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Document',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'global',
'phrase' => 'aktionen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Aktionen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Actions',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'notiz',
'phrase' => 'document',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Anhänge',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Attachments',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'notiz',
'phrase' => 'notiz_new',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Neue Notiz',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'New Note',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'notiz',
'phrase' => 'notiz_edit',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Notiz bearbeiten',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Edit Note',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'notiz',
'phrase' => 'verfasser',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Verfasser*in',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'author',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'notiz',
'phrase' => 'bearbeiter',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Bearbeiter*in',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'editor',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'notiz',
'phrase' => 'erledigt',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'erledigt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'completed',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'notiz',
'phrase' => 'letzte_aenderung',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'letzte Änderung',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Last updated',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'error_fieldRequired',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Das Eingabefeld {field} ist erforderlich',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'The Input Field {field} is required',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'error_fieldNotNumeric',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Das Eingabefeld {field} darf nur Zahlen enthalten.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'The Field {Field} must contain only numbers.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'error_fieldNoValidEmail',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Das Eingabefeld {field} muss eine gültige Email-Adresse enthalten.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'The field {field} must contain a valid email address.',
'description' => '',
'insertvon' => 'system'
)
@@ -24562,6 +24990,25 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'person',
'phrase' => 'standort',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Standort',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'location',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'person',
@@ -24600,6 +25047,25 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'person',
'phrase' => 'adresse_delete',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Adresse löschen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'delete address',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'person',
@@ -24885,26 +25351,6 @@ array(
)
)
),
array(
'app' => 'lehrauftrag',
'category' => 'ui',
'phrase' => 'hinweistextLehrauftrag',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => '<strong>Hinweis:</strong> Das Akzeptieren von Lehraufträgen ersetzt alle vorhergehenden Lehraufträge dieses Studiensemesters.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => '<strong>Note:</strong> Accepting teaching assignments replaces all previous teaching assignments for this study semester.',
'description' => '',
'insertvon' => 'system'
)
)
)
);
+14 -9
View File
@@ -47,17 +47,23 @@ $tmp_gemeinde_ar = array();
if (isset($_FILES['parsefile']) && $_FILES['parsefile']['error'] == 0)
{
$rows = array_map('str_getcsv', file( $_FILES['parsefile']['tmp_name'] ));
$header = array_shift($rows);
$rows = file( $_FILES['parsefile']['tmp_name'] );
// all entries of csv
$header = explode(";",array_shift($rows)); // first row
$header = array_map('trim',$header);
$data = array();
foreach ($rows as $row)
{
$data[] = array_combine($header, $row);
$data[] = array_combine(preg_replace('/\xEF\xBB\xBF/','',$header), explode(";",$row));
}
foreach ($data as $gemeinde_details)
{
//Wenn nicht gültig dann überspringen
if ($gemeinde_details['Gültig'] == 'Nein') continue;
@@ -65,17 +71,16 @@ if (isset($_FILES['parsefile']) && $_FILES['parsefile']['error'] == 0)
$plzs = explode(' ', trim($gemeinde_details['PLZ']));
foreach ($plzs as $plz)
{
$tmp_obj_gemeinde = null;
{
$tmp_obj_gemeinde = new gemeinde();
$tmp_obj_gemeinde->plz = $plz;
$tmp_obj_gemeinde->kennziffer = $gemeinde_details["Gemeindekennziffer"];
$tmp_obj_gemeinde->name = $gemeinde_details['Gemeindename'];
$tmp_obj_gemeinde->ortschaftskennziffer = $gemeinde_details['Ortschaftskennziffer'];
$tmp_obj_gemeinde->ortschaftsname = $gemeinde_details['Ortschaftsname'];
$tmp_obj_gemeinde->bulacode = $gemeinde_details['BULA_Code'];
$tmp_obj_gemeinde->bulabez = $gemeinde_details['BULA_Bez'];
$tmp_obj_gemeinde->kennziffer = $gemeinde_details['Gemeindekennziffer'];
$tmp_obj_gemeinde->save();
$tmp_gemeinde_ar[] = $tmp_obj_gemeinde;
}