diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Status/Dropdown.js b/public/js/components/Stv/Studentenverwaltung/Details/Status/Dropdown.js
index 8c2071c51..1f6f41900 100644
--- a/public/js/components/Stv/Studentenverwaltung/Details/Status/Dropdown.js
+++ b/public/js/components/Stv/Studentenverwaltung/Details/Status/Dropdown.js
@@ -151,7 +151,17 @@ export default {
ApiStvStatus.addStudent(prestudent_id, data),
{ errorHeader: prestudent_id }
]))
- .then(() => {
+ .then(result => {
+ const messagesSuccessful = result.filter(res => res.status == 'fulfilled');
+ if (messagesSuccessful.length) {
+ this.$fhcAlert.alertDefault(
+ 'info',
+ 'Feedback',
+ messagesSuccessful.length + " erfolgreiche Statusänderung(en) durchgeführt", // TODO(chris): translate
+ false,
+ true
+ );
+ }
this.$emit('reloadTable');
this.$reloadList();
});
diff --git a/public/js/plugins/Api.js b/public/js/plugins/Api.js
index 43c2b3064..0b8e2fd17 100644
--- a/public/js/plugins/Api.js
+++ b/public/js/plugins/Api.js
@@ -10,7 +10,232 @@ export default {
if (!app.config.globalProperties.$fhcAlert)
app.use(FhcAlert);
- function _get_config(form, uri, data, config) {
+ const $fhcAlert = app.config.globalProperties.$fhcAlert;
+
+ function _send_array_or_object(errors, func) {
+ if (Array.isArray(errors))
+ errors.forEach(func);
+ else
+ Object.entries(errors).forEach(
+ ([title, errs]) => errs.forEach(
+ error => func(error, title)
+ )
+ );
+ }
+ let DEFAULT_ERROR_CONFIG = {
+ success: true,
+ fail: true,
+ combine: {
+ form: ['validation', 'general'],
+ toast: ['validation', 'general', 'not_found', 'site_failed']
+ },
+ handler: {
+ form(form, errors) {
+ form.clearValidation();
+ errors.forEach(err => form.setFeedback(
+ false,
+ err.messages || err.message
+ ));
+ },
+ async toast(errors) {
+ const $p = app.config.globalProperties.$p;
+ if (!$p)
+ return Promise.reject('Phrasen plugin not loaded!');
+
+ async function _format_toast(errors) {
+ errors = errors.reduce((result, err) => {
+ switch (err.type) {
+ case 'not_found':
+ case 'site_failed':
+ if (err.message)
+ result[err.message] = [err.url];
+ else
+ result._default = [err.url];
+ break;
+ case 'general':
+ if (!result._default)
+ result._default = [];
+ result._default.push(err.message);
+ break;
+ case 'validation':
+ Object.entries(err.messages)
+ .forEach(([field, msg]) => {
+ if (!result[field])
+ result[field] = [];
+ if (Array.isArray(msg))
+ result[field].push(...msg);
+ else
+ result[field].push(msg);
+ });
+ break;
+ }
+ return result;
+ }, {});
+ let counter = 0;
+ const msgs = await Promise.all(Object.entries(errors)
+ .sort((a, b) => ['_default'].indexOf(b[0]) - ['_default'].indexOf(a[0])) // sort _default first
+ .map(async ([field, msgs]) => {
+ if (field == '_default') {
+ await $p.loadCategory('dashboard');
+ const general = $p.t('dashboard/general');
+ field = '
' + general + '';
+ } else {
+ field = '' + field + '';
+ }
+ counter += msgs.length;
+ return field
+ + ''
+ + msgs.join('')
+ + '';
+ }));
+ return {
+ counter,
+ msgs
+ }
+ }
+
+ let counter, msgs;
+ if (Array.isArray(errors)) {
+ ({ counter, msgs } = await _format_toast(errors));
+ } else {
+ ({ counter, msgs } = await Object.entries(errors)
+ .reduce(async (res, [title, errs]) => {
+ const result = await res;
+ const { counter, msgs } = await _format_toast(errs);
+ result.counter += counter;
+ result.msgs.push(''
+ + title
+ + ''
+ + msgs.join('')
+ + '
');
+ return result;
+ }, Promise.resolve({ counter: 0, msgs: []})));
+ }
+
+ await $p.loadCategory('ui');
+ const n_errors = $p.t('ui/n_errors', { n: counter });
+
+ $fhcAlert.alertDefault(
+ 'error',
+ n_errors,
+ '' + msgs.join('') + '
',
+ true,
+ true
+ );
+ },
+ php(errors) {
+ _send_array_or_object(errors, (error, title) => {
+ 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':
+ if (title)
+ title += ': PHP ' + error.severity;
+ else
+ title = 'PHP ' + error.severity;
+ $fhcAlert.alertDefault('warn', title, message, true);
+ break;
+ case 'Notice':
+ case 'User Notice':
+ case 'Runtime Notice':
+ if (title)
+ title += ': PHP ' + error.severity;
+ else
+ title = 'PHP ' + error.severity;
+ $fhcAlert.alertDefault('info', title, message, true);
+ break;
+ default:
+ message = 'Type: PHP ' + error.severity + '\n\n' + message;
+ if (title)
+ message = title + '\n\n' + message;
+ $fhcAlert.alertSystemError(message);
+ break;
+ }
+ });
+ },
+ exception(errors) {
+ _send_array_or_object(errors, (error, title) => {
+ var message = '';
+ if (title)
+ message += title + '\n\n';
+ 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(errors) {
+ _send_array_or_object(errors, (error, title) => {
+ var message = '';
+ if (title)
+ message += title + '\n\n';
+ 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);
+ });
+ },
+ auth(errors) {
+ _send_array_or_object(errors, (error, title) => {
+ if (title)
+ title += ': ' + error.message;
+ else
+ title = error.message;
+
+ var message = '';
+ message += 'Controller name: ' + error.controller + '\n';
+ message += 'Method name: ' + error.method + '\n';
+ message += 'Required permissions: ' + error.required_permissions;
+
+ $fhcAlert.alertDefault(
+ 'error',
+ title,
+ message,
+ true
+ );
+ });
+ }
+ }
+ };
+
+ if (options?.errorHandling !== undefined)
+ DEFAULT_ERROR_CONFIG = _merge_error_config(options.errorHandling);
+
+ function get_config(form, uri, data, config) {
if (typeof form == 'string' && config === undefined) {
[uri, data, config] = [form, uri, data];
form = undefined;
@@ -40,10 +265,9 @@ export default {
return [uri, data, config];
}
-
- function _clean_return_value(response) {
+ function clean_return_value(response) {
if (typeof response.data === 'string' || response.data instanceof String)
- return _clean_return_value({ data: response });
+ return clean_return_value({ data: response });
const result = response.data;
delete response.data;
@@ -55,10 +279,111 @@ export default {
result.meta.response = response;
return result;
}
- const baseURL = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/";
+ function _merge_error_config(config) {
+ if (config === false || config === 'off')
+ return { ...DEFAULT_ERROR_CONFIG, success: false, fail: false };
+
+ if (!config || config === true)
+ return { ...DEFAULT_ERROR_CONFIG };
+
+ if (config === 'success')
+ return { ...DEFAULT_ERROR_CONFIG, fail: false };
+
+ if (config === 'fail')
+ return { ...DEFAULT_ERROR_CONFIG, success: false };
+
+ const { success, fail, handler, combine } = config;
+
+ config = { ...DEFAULT_ERROR_CONFIG };
+
+ Object.entries({ fail, success }).forEach(([key, value]) => {
+ if (value !== undefined)
+ config[key] = value;
+ });
+ Object.entries({ handler, combine }).forEach(([key, value]) => {
+ if (value !== undefined)
+ config[key] = { ...config[key], ...value };
+ });
+
+ return config;
+ }
+ function get_error_handler(config) {
+ const result = _merge_error_config(config?.errorHandling);
+
+ if (!config?.form) {
+ result.combine = { ...result.combine, form: [] };
+ } else {
+ const formHandler = result.handler.form;
+ result.handler = { ...result.handler, form: errors => formHandler(config.form, errors) };
+ }
+
+ return result;
+ }
+ function get_error_list(error) {
+ if (error.response) {
+ if (error.response.status == 404) {
+ return [{
+ type: 'not_found',
+ message: error.message,
+ url: error.request.responseURL
+ }];
+ } else {
+ return error.response.data.errors;
+ }
+ } else if (error.request) {
+ return [{
+ type: 'site_failed',
+ message: error.message,
+ url: error.request.responseURL
+ }];
+ } else {
+ return [{
+ type: 'script',
+ message: error.message
+ }];
+ }
+ }
+ function popHandleableErrors(errorHandling, errors) {
+ const result = {};
+ const copy = [];
+ while (errors.length)
+ copy.push(errors.pop());
+ for (var error of copy) {
+ let type = error.type;
+ let newType = null;
+ for (var t in errorHandling.combine) {
+ let newTypeCombinesType = errorHandling
+ .combine[t]
+ .includes(type);
+ let newTypeHasHandler = errorHandling.handler[t];
+ if (newTypeCombinesType && newTypeHasHandler) {
+ newType = t;
+ if (newType == 'form')
+ break;
+ }
+ }
+ if (newType)
+ type = newType;
+ const handler = errorHandling.handler[type];
+ if (handler) {
+ if (!result[type])
+ result[type] = [];
+ if (Array.isArray(error))
+ result[type].push(...error);
+ else
+ result[type].push(error);
+ continue;
+ }
+ errors.push(error);
+ }
+ return result;
+ }
+
const fhcApiAxios = axios.create({
timeout: 500000,
- baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/"
+ baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root
+ + FHC_JS_DATA_STORAGE_OBJECT.ci_router
+ + "/"
});
fhcApiAxios.interceptors.request.use(config => {
@@ -100,57 +425,51 @@ export default {
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.$api._defaultErrorHandlers[err.type])(err, response.config)
- );
-
- return _clean_return_value(response);
- }, error => {
- if (error.code == 'ERR_CANCELED')
- return Promise.reject({...{handled: true}, ...error});
-
- 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 Promise.reject({...{handled: true}, ...error});
- }
+ 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
- error.response.data.errors = error.response.data.errors.filter(
- err => (error.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$api._defaultErrorHandlers[err.type])(err, error.config)
- );
- if (!error.response.data.errors.length)
- return Promise.reject({...{handled: true}, ...error});
- } else if (error.request) {
- app.config.globalProperties.$fhcAlert.alertDefault('error', error.message, error.request.responseURL);
- return Promise.reject({...{handled: true}, ...error});
- } else {
- app.config.globalProperties.$fhcAlert.alertError(error.message);
- return Promise.reject({...{handled: true}, ...error});
- }
+ if (response.data.errors)
+ response.data.errors = response.data.errors.filter(
+ err => (response.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$api._defaultErrorHandlers[err.type])(err, response.config)
+ );
- return Promise.reject(error);
- });
+ return clean_return_value(response);
+ },
+ error => {
+ if (error.code == 'ERR_CANCELED')
+ return Promise.reject({ handled: true, ...error });
+
+ const errorConfig = get_error_handler(error.config);
+
+ if (!errorConfig.fail)
+ return Promise.reject(error);
+
+ const remaining = get_error_list(error);
+
+ const errors = popHandleableErrors(errorConfig, remaining);
+
+ for (var type in errors) {
+ errorConfig.handler[type](errors[type]);
+ }
+
+ if (remaining.length)
+ return Promise.reject(error);
+
+ return Promise.reject({ handled: true, ...error });
+ }
+ );
app.config.globalProperties.$api = {
getUri(url) {
return fhcApiAxios.getUri({url});
},
get(form, uri, params, config) {
- [uri, params, config] = _get_config(form, uri, params, config);
+ [uri, params, config] = get_config(form, uri, params, config);
if (params) {
if (config)
config.params = params;
@@ -160,82 +479,58 @@ export default {
return fhcApiAxios.get(uri, config);
},
post(form, uri, data, config) {
- [uri, data, config] = _get_config(form, uri, data, config);
+ [uri, data, config] = get_config(form, uri, data, config);
return fhcApiAxios.post(uri, data, config);
},
call(factory, configoverwrite, form) {
if (Array.isArray(factory)) {
- const $fhcAlert = app.config.globalProperties.$fhcAlert;
const $api = app.config.globalProperties.$api;
- Promise
+ return Promise
.allSettled(factory.map((config, index) => {
- if (Array.isArray(config))
- return $api.call(config[1], {
- errorHeader: config[0],
- errorHandling: false
- });
- else
- return $api.call(config, {
- errorHeader: '#' + index,
- errorHandling: false
- });
- }))
- .then(res => {
- // TODO(chris): obey form & configoverwrite
- let messagesError = [];
- let messagesSuccessful = [];
-
- res.forEach(result => {
- if (result.status === 'fulfilled') {
- //console.log(JSON.parse(result.value.data));
- const successTitle = "" + result.value.data + "";
- messagesSuccessful.push(successTitle + "ok");
- } else {
- const errorTitle = "" + result.reason.config.errorHeader + "";
- const errorMsg = JSON.parse(result.reason.request.response);
- const fullMessage = errorMsg.errors.map(error => {
- if (error.type == 'validation') {
- // TODO(chris): do we want the keys?
- return '' + Object.values(error.messages).join("") + '';
- }
- // TODO(chris): other types
- if (error.message)
- return '' + error.message + '';
- if (error.messages)
- return '' + error.messages.join("\n") + '';
- // TODO(chris): what to do here
- return '' + "Generic Error" + ''; // TODO(chris): translate
- }).join("\n");
- messagesError.push(errorTitle + fullMessage);
- }
+ if (!Array.isArray(config))
+ config = ['#' + index, config];
+ return $api.call(config[1], {
+ errorHeader: config[0],
+ errorHandling: false
});
+ }))
+ .then(result => {
+ const [ , , config ] = get_config(form, undefined, undefined, configoverwrite || {});
+ const errorConfig = get_error_handler(config);
- if (messagesError.length)
- {
- const test = document.createElement('b');
- $fhcAlert.alertDefault(
- 'error',
- messagesError.length + " Fehler", // TODO(chris): translate
- '' + messagesError.join("") + '
',
- true,
- true
- );
+ if (!errorConfig.success && !errorConfig.fail) {
+ return result;
}
- if (messagesSuccessful.length)
- {
- const test = document.createElement('b');
- $fhcAlert.alertDefault(
- 'info',
- 'Feedback',
- messagesSuccessful.length + " erfolgreiche Statusänderung(en) durchgeführt", // TODO(chris): translate
- false,
- true
- );
+
+ const typedErrors = {};
+ for (var res of result) {
+ const [ allowed, item ] = res.status === 'fulfilled'
+ ? [ errorConfig.success, res.value ]
+ : [ errorConfig.fail, res.reason ];
+ if (!allowed)
+ return;
+
+ const errors = popHandleableErrors(errorConfig, get_error_list(item));
+
+ for (var type in errors) {
+ if (!typedErrors[type])
+ typedErrors[type] = {
+ [item.config.errorHeader]: errors[type]
+ };
+ else
+ typedErrors[type][item.config.errorHeader] = errors[type];
+ }
+ };
+
+ for (var errType in typedErrors) {
+ errorConfig.handler[errType](typedErrors[errType]);
}
+
+ return result;
});
}
- let {method, url, params, config} = factory;
+ let { method, url, params, config } = factory;
if (configoverwrite !== undefined) {
config = configoverwrite;
}
@@ -251,141 +546,6 @@ export default {
} else {
console.error("FhcApi: method not allowed:", method);
}
- },
- _defaultErrorHandlers: {
- validation(error, config) {
- const $fhcAlert = app.config.globalProperties.$fhcAlert;
-
- if (config?.form) {
- config.form.clearValidation();
- config.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') {
- if (config?.errorHeader)
- Object.values(error.messages).forEach(
- value => $fhcAlert.alertDefault(
- 'error',
- Array.isArray(config.errorHeader) ? app.config.globalProperties.$p.t.apply(null, config.errorHeader) : config.errorHeader,
- value,
- true
- )
- );
- else
- Object.entries(error.messages).forEach(
- ([key, value]) => $fhcAlert.alertDefault('error', key, value, true)
- );
- return false;
- }
- return true;
- },
- general(error, config) {
- const $fhcAlert = app.config.globalProperties.$fhcAlert;
-
- if (config?.form)
- config.form.setFeedback(false, error.message);
- else if (config?.errorHeader)
- $fhcAlert.alertDefault(
- 'error',
- Array.isArray(config.errorHeader) ? app.config.globalProperties.$p.t.apply(null, config.errorHeader) : config.errorHeader,
- error.message,
- true
- );
- 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);
- },
- auth(error, config) {
- const $fhcAlert = app.config.globalProperties.$fhcAlert;
-
- var message = '';
- message += 'Controller name: ' + error.controller + '\n';
- message += 'Method name: ' + error.method + '\n';
- message += 'Required permissions: ' + error.required_permissions;
- if (config?.errorHeader)
- $fhcAlert.alertDefault(
- 'error',
- Array.isArray(config.errorHeader) ? app.config.globalProperties.$p.t.apply(null, config.errorHeader) : config.errorHeader,
- error.message,
- true
- );
- else
- $fhcAlert.alertDefault('error', error.message, message);
- }
}
};
diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php
index 82ea03ba0..84126dfd7 100644
--- a/system/phrasesupdate.php
+++ b/system/phrasesupdate.php
@@ -1475,6 +1475,26 @@ $phrases = array(
)
)
),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'n_errors',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => '{n} Fehler',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => '{n} Error(s)',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
array(
'app' => 'core',
'category' => 'ui',