Compare commits

..
8 changed files with 207 additions and 22 deletions
@@ -101,6 +101,9 @@ class Widget extends FHCAPI_Controller
$tmpsetup = json_decode($widget->setup); $tmpsetup = json_decode($widget->setup);
$tmpsetup->file = absoluteJsImportUrl($tmpsetup->file); $tmpsetup->file = absoluteJsImportUrl($tmpsetup->file);
$widget->setup = $tmpsetup; $widget->setup = $tmpsetup;
$widget->permitted = empty($widget->berechtigung_kurzbz)
|| $this->permissionlib->isBerechtigt($widget->berechtigung_kurzbz);
return $widget; return $widget;
}, $widgets); }, $widgets);
+13 -2
View File
@@ -1,5 +1,6 @@
import BsModal from "../Bootstrap/Modal.js"; import BsModal from "../Bootstrap/Modal.js";
import HeightTransition from "../Tranistion/HeightTransition.js"; import HeightTransition from "../Tranistion/HeightTransition.js";
import MissingPermissionWidget from "../DashboardWidget/MissingPermission.js";
import { enableDragDropTouch } from "../../../../vendor/drag-drop-touch-js/dragdroptouch/dist/drag-drop-touch.esm.min.js"; import { enableDragDropTouch } from "../../../../vendor/drag-drop-touch-js/dragdroptouch/dist/drag-drop-touch.esm.min.js";
@@ -113,6 +114,12 @@ export default {
isPinned() { isPinned() {
return this.place?.pinned ? true : false; return this.place?.pinned ? true : false;
}, },
permitted() {
// widgets without a linked permission (or before the template loaded)
// are considered permitted; the backend sets permitted === false only
// when the user is missing the widget's required permission
return this.widgetTemplate?.permitted !== false;
},
ready() { ready() {
return this.component && this.arguments !== null; return this.component && this.arguments !== null;
}, },
@@ -178,7 +185,11 @@ export default {
&& this.widgetTemplate.widget_id && this.widgetTemplate.widget_id
&& this.widgetTemplate.arguments && this.widgetTemplate.arguments
) { ) {
let component = (await import(this.widgetTemplate.setup.file)).default; // render the "missing permission" screen instead of the actual
// widget when the user does not hold the widget's linked permission
let component = this.permitted
? (await import(this.widgetTemplate.setup.file)).default
: MissingPermissionWidget;
this.$options.components["widget" + this.widgetTemplate.widget_id] = component; this.$options.components["widget" + this.widgetTemplate.widget_id] = component;
this.component = "widget" + this.widgetTemplate.widget_id; this.component = "widget" + this.widgetTemplate.widget_id;
this.arguments = { ...this.widgetTemplate.arguments, ...this.config }; this.arguments = { ...this.widgetTemplate.arguments, ...this.config };
@@ -280,7 +291,7 @@ export default {
</template> </template>
<!-- widget link --> <!-- widget link -->
<a <a
v-if="widgetTemplate.setup.cis4link" v-if="widgetTemplate.setup.cis4link && permitted"
:href="getWidgetC4Link(widgetTemplate)" :href="getWidgetC4Link(widgetTemplate)"
class="col-auto ms-auto" class="col-auto ms-auto"
:aria-label="$p.t('dashboard/widget_link')" :aria-label="$p.t('dashboard/widget_link')"
@@ -12,6 +12,12 @@ export default {
data: () => ({ data: () => ({
callbacks: {} callbacks: {}
}), }),
computed: {
// filter widgets away the user has no permissions for
availableWidgets() {
return (this.widgets || []).filter(widget => widget.permitted !== false);
}
},
methods: { methods: {
getWidget() { getWidget() {
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
@@ -37,17 +43,17 @@ export default {
<bs-modal <bs-modal
ref="modal" ref="modal"
class="fade" class="fade"
:dialog-class="{ 'modal-fullscreen-sm-down': 1, 'modal-xl': widgets && widgets.length > 0 }" :dialog-class="{ 'modal-fullscreen-sm-down': 1, 'modal-xl': availableWidgets.length > 0 }"
@hiddenBsModal="close" @hiddenBsModal="close"
> >
<template v-slot:title>{{ $p.t('dashboard/createWidget') }}</template> <template v-slot:title>{{ $p.t('dashboard/createWidget') }}</template>
<template v-slot:default> <template v-slot:default>
<div v-if="widgets" class="row g-2"> <div v-if="widgets" class="row g-2">
<div v-if="!widgets.length"> <div v-if="!availableWidgets.length">
{{ $p.t('dashboard/noWidgetsAvailable') }} {{ $p.t('dashboard/noWidgetsAvailable') }}
</div> </div>
<div <div
v-for="widget in widgets" v-for="widget in availableWidgets"
:key="widget.widget_id" :key="widget.widget_id"
class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2" class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2"
> >
@@ -0,0 +1,21 @@
import AbstractWidget from './Abstract.js';
/**
* Fallback screen rendered in place of a widget when the current user is missing
* the permission (berechtigung_kurzbz) linked to that widget. Used for widgets
* that are already on a user's dashboard but whose required permission the user
* no longer holds.
*/
export default {
mixins: [
AbstractWidget
],
created() {
this.$emit('setConfig', false);
},
template: /*html*/`
<div class="dashboard-widget-missing-permission d-flex flex-column justify-content-center align-items-center text-center h-100 p-3 text-body-secondary">
<i class="fa-solid fa-lock fa-2x mb-2" aria-hidden="true"></i>
<p class="mb-0">{{ $p.t('dashboard/widget_missing_permission') }}</p>
</div>`
}
+3 -17
View File
@@ -4,7 +4,6 @@ import FormInput from '../Form/Input.js';
import ApiUdf from '../../api/udf.js'; import ApiUdf from '../../api/udf.js';
export default { export default {
name: 'CoreUdfCmpt',
components: { components: {
CoreFetchCmpt, CoreFetchCmpt,
FormInput FormInput
@@ -27,8 +26,7 @@ export default {
// The values as associative array // The values as associative array
modelValue: Object, modelValue: Object,
// Show only fields with a name that exists in the filter // Show only fields with a name that exists in the filter
filter: [String, Array], filter: [String, Array]
readonly: true
}, },
data() { data() {
return { return {
@@ -37,13 +35,6 @@ export default {
}; };
}, },
computed: { computed: {
isPkValid() {
// If the provided pk is _not_ valid
return !Object.keys(this.pk).every(key => !this.pk[key]);
},
getReadonly() {
return this.readonly;
},
filterArray() { filterArray() {
if (!this.filter || Array.isArray(this.filter)) if (!this.filter || Array.isArray(this.filter))
return this.filter; return this.filter;
@@ -70,10 +61,6 @@ export default {
}, },
watch: { watch: {
pk(n, o) { pk(n, o) {
// If the provided pk is _not_ valid
if (Object.keys(n).every(key => !n[key])) return;
if (!this.$refs.fetch) if (!this.$refs.fetch)
return; // NOTE(chris): no initial load yet return; // NOTE(chris): no initial load yet
@@ -137,7 +124,6 @@ export default {
template: ` template: `
<div class="core-udf row"> <div class="core-udf row">
<core-fetch-cmpt <core-fetch-cmpt
v-if="isPkValid"
ref="fetch" ref="fetch"
:api-function="loadF" :api-function="loadF"
:api-function-parameters="{ ciModel, pk }" :api-function-parameters="{ ciModel, pk }"
@@ -152,7 +138,7 @@ export default {
:type="field.type" :type="field.type"
:multiple="field.multiple" :multiple="field.multiple"
:title="field.description" :title="field.description"
:disabled="field.disabled || getReadonly" :disabled="field.disabled"
:clearable="field.clearable" :clearable="field.clearable"
:auto-apply="field.autoApply" :auto-apply="field.autoApply"
:enable-time-picker="field.enableTimePicker" :enable-time-picker="field.enableTimePicker"
@@ -166,4 +152,4 @@ export default {
</template> </template>
</core-fetch-cmpt> </core-fetch-cmpt>
</div>` </div>`
} }
+1
View File
@@ -98,6 +98,7 @@ require_once('dbupdate_3.4/75888_reihungstest_mehrfachdurchfuehrung.php');
require_once('dbupdate_3.4/76150_perm_other_lv_plan.php'); require_once('dbupdate_3.4/76150_perm_other_lv_plan.php');
require_once('dbupdate_3.4/68957_dashboard_bookmark_neue_Spalte_sort.php'); require_once('dbupdate_3.4/68957_dashboard_bookmark_neue_Spalte_sort.php');
require_once('dbupdate_3.4/68530_Dashboard_Cleanup.php'); require_once('dbupdate_3.4/68530_Dashboard_Cleanup.php');
require_once('dbupdate_3.4/77757_widgets_berechtigungen.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen // *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>'; echo '<H2>Pruefe Tabellen und Attribute!</H2>';
@@ -0,0 +1,57 @@
<?php
/* Copyright (C) 2026 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Beschreibung:
* Dashboard Widgets: Verknüpfung einer (einzelnen) Berechtigung pro Widget.
* Neue Spalte dashboard.tbl_widget.berechtigung_kurzbz mit FK auf
* system.tbl_berechtigung. Ist die Spalte NULL, erfordert das Widget keine
* gesonderte Berechtigung. Benutzer denen die verknüpfte Berechtigung fehlt,
* bekommen das Widget nicht mehr zur Auswahl angezeigt bzw. sehen (falls bereits
* am Dashboard) einen "Fehlende Berechtigung"-Screen.
*/
if (! defined('DB_NAME')) exit('No direct script access allowed');
// Add column dashboard.tbl_widget.berechtigung_kurzbz (FK => system.tbl_berechtigung)
if ($result = @$db->db_query("
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'dashboard'
AND table_name = 'tbl_widget'
AND column_name = 'berechtigung_kurzbz';"))
{
if ($db->db_num_rows($result) == 0)
{
$qry = "
ALTER TABLE dashboard.tbl_widget
ADD COLUMN berechtigung_kurzbz VARCHAR(32) NULL;
ALTER TABLE dashboard.tbl_widget
ADD CONSTRAINT tbl_widget_berechtigung_fk
FOREIGN KEY (berechtigung_kurzbz)
REFERENCES system.tbl_berechtigung (berechtigung_kurzbz)
ON UPDATE CASCADE ON DELETE SET NULL;";
if (!$db->db_query($qry))
{
echo '<strong>dashboard.tbl_widget: '.$db->db_last_error().'</strong><br>';
}
else
{
echo 'dashboard.tbl_widget: Spalte berechtigung_kurzbz (FK system.tbl_berechtigung) hinzugefuegt<br>';
}
}
}
+100
View File
@@ -32514,6 +32514,86 @@ array(
) )
) )
), ),
array(
'app' => 'anwesenheiten',
'category' => 'global',
'phrase' => 'anwKontrolle',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Anwesenheitskontrolle',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Attendance check',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'anwesenheiten',
'category' => 'global',
'phrase' => 'anwKontrolleOeffnen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Anwesenheitskontrolle öffnen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Open attendance check',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'anwesenheiten',
'category' => 'global',
'phrase' => 'anwKeinUnterricht',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Kein Unterricht in diesem Zeitraum',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'No lessons in this period',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'anwesenheiten',
'category' => 'global',
'phrase' => 'anwJetzt',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Jetzt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Now',
'description' => '',
'insertvon' => 'system'
)
)
),
array( array(
'app' => 'anwesenheiten', 'app' => 'anwesenheiten',
'category' => 'global', 'category' => 'global',
@@ -48681,6 +48761,26 @@ array(
) )
) )
), ),
array(
'app' => 'core',
'category' => 'dashboard',
'phrase' => 'widget_missing_permission',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Ihnen fehlt die erforderliche Berechtigung, um dieses Widget anzuzeigen.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'You do not have the permission required to display this widget.',
'description' => '',
'insertvon' => 'system'
)
)
),
// CIS4 phrases from legacy code end // CIS4 phrases from legacy code end
// FHC4 Phrases Abschlusspruefung // FHC4 Phrases Abschlusspruefung
array( array(