Merge branch 'feature-40128/Mehrere_Suchergebnisse_für_selbe_Person' into rc1_FHC4_C4

This commit is contained in:
Harald Bamberger
2025-08-01 16:19:23 +02:00
44 changed files with 4624 additions and 677 deletions
+874
View File
@@ -0,0 +1,874 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
$config['person'] = [
'primarykey' => 'person_id',
'table' => 'public.tbl_person',
'searchfields' => [
'uid' => [
'comparison' => 'equals',
'field' => 'uid',
'join' => [
'table' => "public.tbl_benutzer",
'using' => "person_id"
],
'1-n' => true
],
'vorname' => [
'alias' => ['firstname'],
'comparison' => 'similar',
'field' => 'vorname'
],
'nachname' => [
'alias' => ['lastname', 'surename'],
'comparison' => 'similar',
'field' => 'nachname'
],
'name' => [
'comparison' => 'similar',
'field' => "(vorname || ' ' || nachname)"
],
'email' => [
'comparison' => 'similar',
'field' => 'kontakt',
'join' => [
'table' => "public.tbl_kontakt",
'on' => "kontakttyp = 'email' AND tbl_kontakt.person_id = tbl_person.person_id"
],
"1-n" => true
],
'tel' => [
'alias' => ['phone', 'telefon'],
'comparison' => 'similar',
'field' => 'kontakt',
'join' => [
'table' => "public.tbl_kontakt",
'on' => "kontakttyp IN ('telefon', 'so.tel', 'mobil') AND tbl_kontakt.person_id = tbl_person.person_id"
],
"1-n" => true
],
'preid' => [
'alias' => ['prestudent_id'],
'comparison' => 'equal-int',
'field' => 'prestudent_id',
'join' => [
'table' => "public.tbl_prestudent",
'using' => "person_id"
],
'1-n' => true
],
'pid' => [
'alias' => ['person_id'],
'comparison' => 'equal-int',
'field' => 'person_id'
]
],
'resultfields' => [
"ARRAY( SELECT uid FROM public.tbl_benutzer WHERE person_id = p.person_id ) AS uids",
"p.person_id",
"(p.vorname || ' ' || p.nachname) AS name",
"ARRAY( SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp = 'email' AND person_id=p.person_id ) AS email",
"CASE
WHEN p.foto IS NOT NULL THEN 'data:image/jpeg' || CONVERT_FROM(DECODE('3b','hex'), 'UTF8') || 'base64,' || p.foto
ELSE NULL END
AS photo_url"
],
'resultjoin' => "
JOIN public.tbl_person p USING (person_id)"
];
$config['student'] = [
'primarykey' => 'student_uid',
'table' => 'public.tbl_student',
'searchfields' => [
'uid' => [
'comparison' => 'equals',
'field' => 'student_uid'
],
'vorname' => [
'alias' => ['firstname'],
'comparison' => 'similar',
'field' => 'vorname',
'join' => [
[
'table' => "public.tbl_prestudent",
'using' => "prestudent_id"
],
[
'table' => "public.tbl_person",
'using' => "person_id"
]
]
],
'nachname' => [
'alias' => ['lastname', 'surename'],
'comparison' => 'similar',
'field' => 'nachname',
'join' => [
[
'table' => "public.tbl_prestudent",
'using' => "prestudent_id"
],
[
'table' => "public.tbl_person",
'using' => "person_id"
]
]
],
'name' => [
'comparison' => 'similar',
'field' => "(vorname || ' ' || nachname)",
'join' => [
[
'table' => "public.tbl_prestudent",
'using' => "prestudent_id"
],
[
'table' => "public.tbl_person",
'using' => "person_id"
]
]
],
'email' => [
'comparison' => 'similar',
'field' => 'kontakt',
'join' => [
[
'table' => "public.tbl_prestudent",
'using' => "prestudent_id"
],
[
'table' => "public.tbl_kontakt",
'on' => "kontakttyp = 'email' AND tbl_kontakt.person_id = tbl_prestudent.person_id"
]
],
"1-n" => true
],
'tel' => [
'alias' => ['phone', 'telefon'],
'comparison' => 'similar',
'field' => 'kontakt',
'join' => [
[
'table' => "public.tbl_prestudent",
'using' => "prestudent_id"
],
[
'table' => "public.tbl_kontakt",
'on' => "kontakttyp IN ('telefon', 'so.tel', 'mobil') AND tbl_kontakt.person_id = tbl_prestudent.person_id"
]
],
"1-n" => true
],
'stg' => [
'alias' => ['studiengang'],
'comparison' => 'equals',
'field' => "typ || kurzbz",
'join' => [
[
'table' => "public.tbl_prestudent",
'using' => "prestudent_id"
],
[
'table' => "public.tbl_studiengang",
'on' => "tbl_studiengang.studiengang_kz = tbl_prestudent.studiengang_kz"
]
]
],
'preid' => [
'alias' => ['prestudent_id'],
'comparison' => 'equal-int',
'field' => 'prestudent_id'
],
'pid' => [
'alias' => ['person_id'],
'comparison' => 'equal-int',
'field' => 'person_id',
'join' => [
'table' => "public.tbl_prestudent",
'using' => "prestudent_id"
]
]
],
'resultfields' => [
"s.student_uid AS uid",
"s.matrikelnr",
"p.person_id",
"(p.vorname || ' ' || p.nachname) AS name",
"(s.student_uid || '@" . DOMAIN . "') || ARRAY( SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp = 'email' AND person_id=p.person_id ) AS email",
"CASE
WHEN p.foto IS NOT NULL THEN 'data:image/jpeg' || CONVERT_FROM(DECODE('3b','hex'), 'UTF8') || 'base64,' || p.foto
ELSE NULL END
AS photo_url",
"b.aktiv"
],
'resultjoin' => "
JOIN public.tbl_student s USING (student_uid)
JOIN public.tbl_benutzer b ON(b.uid = s.student_uid)
JOIN public.tbl_person p USING(person_id)"
];
$prestudent_sort = [
"Student",
"Incoming",
"Outgoing",
"Diplomand",
"Unterbrecher",
"Aufgenommener",
"Wartender",
"Bewerber",
"Interessent",
"Abgewiesener",
"Absolvent",
"Abbrecher",
"Ausserordentlicher",
"Praktikant"
];
$prestudent_sort_array = "array['" . implode("','", $prestudent_sort) . "']";
$config['prestudent'] = [
'primarykey' => 'prestudent_id',
'table' => 'public.tbl_prestudent',
'searchfields' => [
'uid' => [
'comparison' => 'equals',
'field' => 'student_uid',
'join' => [
'table' => "public.tbl_student",
'using' => "prestudent_id"
]
],
'vorname' => [
'alias' => ['firstname'],
'comparison' => 'similar',
'field' => 'vorname',
'join' => [
'table' => "public.tbl_person",
'using' => "person_id"
]
],
'nachname' => [
'alias' => ['lastname', 'surename'],
'comparison' => 'similar',
'field' => 'nachname',
'join' => [
'table' => "public.tbl_person",
'using' => "person_id"
]
],
'name' => [
'comparison' => 'similar',
'field' => "(vorname || ' ' || nachname)",
'join' => [
'table' => "public.tbl_person",
'using' => "person_id"
]
],
'email' => [
'comparison' => 'similar',
'field' => 'kontakt',
'join' => [
'table' => "public.tbl_kontakt",
'on' => "kontakttyp = 'email' AND tbl_kontakt.person_id = tbl_prestudent.person_id"
],
"1-n" => true
],
'tel' => [
'alias' => ['phone', 'telefon'],
'comparison' => 'similar',
'field' => 'kontakt',
'join' => [
'table' => "public.tbl_kontakt",
'on' => "kontakttyp IN ('telefon', 'so.tel', 'mobil') AND tbl_kontakt.person_id = tbl_prestudent.person_id"
],
"1-n" => true
],
'stg' => [
'alias' => ['studiengang'],
'comparison' => 'equals',
'field' => "typ || kurzbz",
'join' => [
'table' => "public.tbl_studiengang",
'using' => "studiengang_kz"
]
],
'preid' => [
'alias' => ['prestudent_id'],
'comparison' => 'equal-int',
'field' => 'prestudent_id'
],
'pid' => [
'alias' => ['person_id'],
'comparison' => 'equal-int',
'field' => 'person_id',
'join' => [
'table' => "public.tbl_person",
'using' => "person_id"
]
]
],
'resultfields' => [
"ps.prestudent_id",
"ps.studiengang_kz",
"s.matrikelnr",
"p.person_id",
"b.uid",
"(p.vorname || ' ' || p.nachname) AS name",
"(b.uid || '@" . DOMAIN . "') || ARRAY( SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp = 'email' AND person_id=p.person_id ) AS email",
"CASE
WHEN p.foto IS NOT NULL THEN 'data:image/jpeg' || CONVERT_FROM(DECODE('3b','hex'), 'UTF8') || 'base64,' || p.foto
ELSE NULL END
AS photo_url",
"UPPER(sg.typ || sg.kurzbz) AS stg_kuerzel",
"sg.bezeichnung",
"(
SELECT bezeichnung_mehrsprachig[(TABLE lang)]
FROM public.tbl_status
WHERE status_kurzbz = public.get_rolle_prestudent(ps.prestudent_id, NULL)
LIMIT 1
) AS status",
"COALESCE(
(
SELECT COALESCE(plan.orgform_kurzbz, pss.orgform_kurzbz)
FROM public.tbl_prestudentstatus pss
LEFT JOIN lehre.tbl_studienplan plan USING (studienplan_id)
WHERE pss.prestudent_id=ps.prestudent_id
ORDER BY pss.datum DESC, pss.insertamum DESC, pss.ext_id DESC
LIMIT 1
),
sg.orgform_kurzbz
) AS orgform",
"b.aktiv",
"array_position(" . $prestudent_sort_array . ", public.get_rolle_prestudent(ps.prestudent_id, NULL)) AS sort"
],
'resultjoin' => "
LEFT JOIN public.tbl_prestudent ps USING (prestudent_id)
LEFT JOIN public.tbl_student s ON (ps.prestudent_id = s.prestudent_id)
LEFT JOIN public.tbl_benutzer b ON (b.uid = s.student_uid)
JOIN public.tbl_person p ON (p.person_id = ps.person_id)
LEFT JOIN public.tbl_studiengang sg ON (sg.studiengang_kz = ps.studiengang_kz)"
];
$config['employee'] = [
'alias' => ['ma', 'mitarbeiter'],
'primarykey' => 'mitarbeiter_uid',
'table' => 'public.tbl_mitarbeiter',
'searchfields' => [
'uid' => [
'alias' => ['mitarbeiter_uid'],
'comparison' => 'equals',
'field' => "mitarbeiter_uid"
],
'vorname' => [
'alias' => ['firstname'],
'comparison' => 'similar',
'field' => "vorname",
'join' => [
[
'table' => "public.tbl_benutzer",
'on' => "uid = mitarbeiter_uid"
],
[
'table' => "public.tbl_person",
'using' => "person_id"
]
]
],
'nachname' => [
'alias' => ['lastname', 'surename'],
'comparison' => 'similar',
'field' => "nachname",
'join' => [
[
'table' => "public.tbl_benutzer",
'on' => "uid = mitarbeiter_uid"
],
[
'table' => "public.tbl_person",
'using' => "person_id"
]
]
],
'name' => [
'comparison' => 'similar',
'field' => "(vorname || ' ' || nachname)",
'join' => [
[
'table' => "public.tbl_benutzer",
'on' => "uid = mitarbeiter_uid"
],
[
'table' => "public.tbl_person",
'using' => "person_id"
]
]
],
'email' => [
'comparison' => 'similar',
'field' => "COALESCE(alias, uid) || '" . '@' . DOMAIN . "'",
'join' => [
'table' => "public.tbl_benutzer",
'on' => "uid = mitarbeiter_uid"
]
],
'tel' => [
'alias' => ['phone', 'telefon'],
'comparison' => 'similar',
'field' => "TRIM(COALESCE(kontakt, '') || ' ' || COALESCE(telefonklappe, ''))",
'join' => [
'table' => "public.tbl_kontakt",
'on' => "kontakttyp = 'telefon' AND tbl_kontakt.standort_id = tbl_mitarbeiter.standort_id"
],
"1-n" => true
],
'pid' => [
'alias' => ['person_id'],
'comparison' => 'equal-int',
'field' => "person_id",
'join' => [
'table' => "public.tbl_benutzer",
'on' => "uid = mitarbeiter_uid"
]
],
'oe' => [
'alias' => ['ou', 'organisationseinheit', 'organisationunit'],
'comparison' => 'vector',
'field' => "fts_bezeichnung",
'join' => [
[
'table' => "public.tbl_benutzerfunktion",
'on' => "mitarbeiter_uid = uid
AND funktion_kurzbz = 'oezuordnung'
AND (datum_von IS NULL OR datum_von <= NOW())
AND (datum_bis IS NULL OR datum_bis >= NOW())"
],
[
'table' => "public.tbl_organisationseinheit",
'using' => "oe_kurzbz"
]
],
'1-n' => true
],
'kst' => [
'comparison' => 'vector',
'field' => "fts_bezeichnung",
'join' => [
[
'table' => "public.tbl_benutzerfunktion",
'on' => "mitarbeiter_uid = uid
AND funktion_kurzbz = 'kstzuordnung'
AND (datum_von IS NULL OR datum_von <= NOW())
AND (datum_bis IS NULL OR datum_bis >= NOW())"
],
[
'table' => "public.tbl_organisationseinheit",
'using' => "oe_kurzbz"
]
],
'1-n' => true
]
],
'resultfields' => [
"b.uid",
"p.person_id",
"(p.vorname || ' ' || p.nachname) AS name",
"ARRAY(
SELECT
'[' || ot.bezeichnung || '] ' || o.bezeichnung AS bezeichnung
FROM public.tbl_benutzerfunktion bf
JOIN public.tbl_organisationseinheit o USING(oe_kurzbz)
JOIN public.tbl_organisationseinheittyp ot USING(organisationseinheittyp_kurzbz)
WHERE bf.funktion_kurzbz = 'oezuordnung'
AND (bf.datum_von IS NULL OR bf.datum_von <= NOW())
AND (bf.datum_bis IS NULL OR bf.datum_bis >= NOW())
AND bf.uid = b.uid
GROUP BY o.bezeichnung, ot.bezeichnung
) AS organisationunit_name",
"COALESCE(b.alias, b.uid) || '" . '@' . DOMAIN . "' AS email",
"TRIM(COALESCE(k.kontakt, '') || ' ' || COALESCE(m.telefonklappe, '')) AS phone",
"'" . base_url("/cis/public/bild.php?src=person&person_id=") . "' || p.person_id AS photo_url",
"ARRAY(
SELECT
'[' || ot.bezeichnung || '] ' || o.bezeichnung AS bezeichnung
FROM public.tbl_benutzerfunktion bf
JOIN public.tbl_organisationseinheit o USING(oe_kurzbz)
JOIN public.tbl_organisationseinheittyp ot USING(organisationseinheittyp_kurzbz)
WHERE bf.funktion_kurzbz = 'kstzuordnung'
AND (bf.datum_von IS NULL OR bf.datum_von <= NOW())
AND (bf.datum_bis IS NULL OR bf.datum_bis >= NOW())
AND bf.uid = b.uid
GROUP BY o.bezeichnung, ot.bezeichnung
) AS standardkostenstelle"
],
'resultjoin' => "
JOIN public.tbl_mitarbeiter m USING (mitarbeiter_uid)
JOIN public.tbl_benutzer b ON (b.uid = m.mitarbeiter_uid)
JOIN public.tbl_person p USING(person_id)
LEFT JOIN (
SELECT kontakt, standort_id
FROM public.tbl_kontakt
WHERE kontakttyp = 'telefon'
) k ON (k.standort_id = m.standort_id)"
];
// TODO(chris): move to searchpv21.php
$config['unassigned_employee'] = $config['employee'];
$config['unassigned_employee']['alias'] = ['mitarbeiter_ohne_zuordnung'];
$config['unassigned_employee']['prepare'] = "unassigned_employee AS (
SELECT tbl_mitarbeiter.*
FROM public.tbl_mitarbeiter
LEFT JOIN public.tbl_benutzerfunktion ON (
uid = mitarbeiter_uid
AND funktion_kurzbz = 'kstzuordnung'
AND (datum_von IS NULL OR datum_von <= NOW())
AND (datum_bis IS NULL OR datum_bis >= NOW())
)
WHERE tbl_benutzerfunktion.bezeichnung IS NULL
UNION
SELECT tbl_mitarbeiter.*
FROM public.tbl_mitarbeiter
LEFT JOIN public.tbl_benutzerfunktion ON (
uid = mitarbeiter_uid
AND funktion_kurzbz = 'oezuordnung'
AND (datum_von IS NULL OR datum_von <= NOW())
AND (datum_bis IS NULL OR datum_bis >= NOW())
)
WHERE tbl_benutzerfunktion.bezeichnung IS NULL
)";
$config['unassigned_employee']['table'] = "unassigned_employee";
$config['unassigned_employee']['searchfields']['tel']['join']['on'] = "
kontakttyp = 'telefon'
AND tbl_kontakt.standort_id = unassigned_employee.standort_id
";
$config['unassigned_employee']['renderer'] = 'employee';
$config['organisationunit'] = [
'alias' => ['ou', 'organisationseinheit', 'oe'],
'primarykey' => 'oe_kurzbz',
'table' => 'public.tbl_organisationseinheit',
'searchfields' => [
'uid' => [
'comparison' => 'equals',
'field' => 'uid',
'prepare' => "organisationunit_leader(oe_kurzbz, uid, vorname, nachname) AS (
SELECT oe_kurzbz, vorname, nachname, uid
FROM public.tbl_benutzerfunktion
JOIN public.tbl_benutzer USING (uid)
JOIN public.tbl_person USING (person_id)
WHERE funktion_kurzbz = 'Leitung'
AND (datum_von IS NULL OR datum_von <= NOW())
AND (datum_bis IS NULL OR datum_bis >= NOW())
AND tbl_benutzer.aktiv = TRUE
)",
'join' => [
'table' => "organisationunit_leader",
'using' => "oe_kurzbz"
],
'1-n' => true
],
'vorname' => [
'alias' => ['firstname'],
'comparison' => 'similar',
'field' => 'vorname',
'prepare' => "organisationunit_leader(oe_kurzbz, uid, vorname, nachname) AS (
SELECT oe_kurzbz, vorname, nachname, uid
FROM public.tbl_benutzerfunktion
JOIN public.tbl_benutzer USING (uid)
JOIN public.tbl_person USING (person_id)
WHERE funktion_kurzbz = 'Leitung'
AND (datum_von IS NULL OR datum_von <= NOW())
AND (datum_bis IS NULL OR datum_bis >= NOW())
AND tbl_benutzer.aktiv = TRUE
)",
'join' => [
'table' => "organisationunit_leader",
'using' => "oe_kurzbz"
],
'1-n' => true
],
'nachname' => [
'alias' => ['lastname', 'surename'],
'comparison' => 'similar',
'field' => 'nachname',
'prepare' => "organisationunit_leader(oe_kurzbz, uid, vorname, nachname) AS (
SELECT oe_kurzbz, vorname, nachname, uid
FROM public.tbl_benutzerfunktion
JOIN public.tbl_benutzer USING (uid)
JOIN public.tbl_person USING (person_id)
WHERE funktion_kurzbz = 'Leitung'
AND (datum_von IS NULL OR datum_von <= NOW())
AND (datum_bis IS NULL OR datum_bis >= NOW())
AND tbl_benutzer.aktiv = TRUE
)",
'join' => [
'table' => "organisationunit_leader",
'using' => "oe_kurzbz"
],
'1-n' => true
],
'name' => [
'comparison' => 'similar',
'field' => "(vorname || ' ' || nachname)",
'prepare' => "organisationunit_leader(oe_kurzbz, uid, vorname, nachname) AS (
SELECT oe_kurzbz, vorname, nachname, uid
FROM public.tbl_benutzerfunktion
JOIN public.tbl_benutzer USING (uid)
JOIN public.tbl_person USING (person_id)
WHERE funktion_kurzbz = 'Leitung'
AND (datum_von IS NULL OR datum_von <= NOW())
AND (datum_bis IS NULL OR datum_bis >= NOW())
AND tbl_benutzer.aktiv = TRUE
)",
'join' => [
'table' => "organisationunit_leader",
'using' => "oe_kurzbz"
],
'1-n' => true
],
'oe' => [
'alias' => ['ou', 'organisationseinheit', 'organisationunit'],
'comparison' => 'vector',
'field' => "fts_bezeichnung"
],
'kurzbz' => [
'alias' => ['oe_kurzbz'],
'comparison' => 'equals',
'field' => "oe_kurzbz"
]
],
'resultfields' => [
"oe.oe_kurzbz",
"('[' || type.bezeichnung || '] ' || oe.bezeichnung) AS name",
"oe_parent.oe_kurzbz AS parentoe_kurzbz",
"(CASE WHEN oe_parent.bezeichnung IS NOT NULL THEN '[' || type_parent.bezeichnung || '] ' || oe_parent.bezeichnung END) AS parentoe_name",
"ARRAY(
SELECT JSON_BUILD_OBJECT('uid', b.uid, 'vorname', p.vorname, 'nachname', p.nachname, 'name', (p.vorname || ' ' || p.nachname))
FROM public.tbl_benutzerfunktion bf
JOIN public.tbl_benutzer b USING (uid)
JOIN public.tbl_person p USING (person_id)
WHERE funktion_kurzbz = 'Leitung'
AND (datum_von IS NULL OR datum_von <= NOW())
AND (datum_bis IS NULL OR datum_bis >= NOW())
AND b.aktiv = TRUE
AND oe_kurzbz = oe.oe_kurzbz
) AS leaders",
"(
SELECT COUNT(*)
FROM public.tbl_benutzerfunktion
WHERE funktion_kurzbz = 'oezuordnung'
AND (datum_von IS NULL OR datum_von <= NOW())
AND (datum_bis IS NULL OR datum_bis >= NOW())
AND oe_kurzbz = oe.oe_kurzbz
) AS number_of_people",
"(CASE WHEN oe.mailverteiler THEN oe.oe_kurzbz || '" . '@' . DOMAIN . "' END) AS mailgroup"
],
'resultjoin' => "
JOIN public.tbl_organisationseinheit oe
USING (oe_kurzbz)
JOIN public.tbl_organisationseinheittyp type
USING (organisationseinheittyp_kurzbz)
LEFT JOIN public.tbl_organisationseinheit oe_parent
ON (oe_parent.oe_kurzbz = oe.oe_parent_kurzbz)
LEFT JOIN public.tbl_organisationseinheittyp type_parent
ON (oe_parent.organisationseinheittyp_kurzbz = type_parent.organisationseinheittyp_kurzbz)"
];
$config['room'] = [
'alias' => ['raum'],
'primarykey' => 'ort_kurzbz',
'table' => 'public.tbl_ort',
'searchfields' => [
'name' => [
'comparison' => 'similar',
'field' => 'ort_kurzbz'
]
],
'resultfields' => [
"ort.ort_kurzbz",
"ort.gebteil AS building",
"ort.ausstattung AS equipment",
"ort.stockwerk AS floor",
"ort.dislozierung AS room_number",
"ort.content_id",
"address.ort AS city",
"address.plz AS zip",
"address.strasse AS street",
"ort.max_person",
"ort.arbeitsplaetze AS workplaces"
],
'resultjoin' => "
JOIN public.tbl_ort ort
USING (ort_kurzbz)
LEFT JOIN public.tbl_standort
USING (standort_id)
LEFT JOIN public.tbl_adresse address
USING (adresse_id)"
];
$sprache = getUserLanguage();
$config['cms'] = [
'primarykey' => 'contentsprache_id',
'table' => 'campus.tbl_contentsprache',
'prepare' => "
cms_auth (content_id) AS (
SELECT content_id
FROM campus.tbl_content c
WHERE NOT EXISTS (SELECT 1 FROM campus.tbl_contentgruppe g WHERE g.content_id=c.content_id)
UNION
SELECT content_id
FROM public.vw_gruppen g
JOIN campus.tbl_contentgruppe c USING (gruppe_kurzbz)
WHERE uid = (TABLE auth)
),
cms_active (content_id, template_kurzbz) AS (
SELECT content_id, template_kurzbz
FROM cms_auth
JOIN campus.tbl_content USING (content_id)
WHERE aktiv = TRUE
),
cms_active_redirect (content_id) AS (
SELECT content_id
FROM cms_active
WHERE template_kurzbz = 'redirect'
),
cms_active_redirect_linked (content_id) AS (
SELECT content_id
FROM cms_active_redirect
JOIN campus.tbl_contentsprache USING (content_id)
WHERE LEFT((xpath('string(/content/url)', content))[1]::text, 1) <> '#'
),
cms_active_others (content_id) AS (
SELECT content_id
FROM cms_active
WHERE template_kurzbz IN ('contentmittitel', 'contentohnetitel', 'contentmittitel_filterwidget')
)
",
'searchfields' => [
'content' => [
'alias' => ['inhalt'],
'comparison' => "vector",
'field' => "(
setweight(to_tsvector('simple', COALESCE(titel, '')), 'A')
||
setweight(to_tsvector('simple', COALESCE(content, '')::text), 'B')
)"
],
'content_id' => [
'alias' => ['id'],
'comparison' => "equal-int",
'field' => "content_id"
],
'lang' => [
'alias' => ['language', 'sprache'],
'comparison' => "equals",
'field' => "sprache"
]
],
'resultfields' => [
"contentsprache.content_id",
"content.template_kurzbz",
"contentsprache.version",
"contentsprache.sprache AS language",
"contentsprache.titel AS title",
"contentsprache.content",
"(xpath('string(/content/url)', contentsprache.content))[1] AS content_url"
],
'resultjoin' => "
JOIN campus.tbl_contentsprache contentsprache
USING (contentsprache_id)
JOIN campus.tbl_content content
USING (content_id)
WHERE content_id IN (
SELECT content_id
FROM cms_active_redirect_linked
UNION
SELECT content_id
FROM cms_active_others
)
AND version = campus.get_highest_content_version(content_id)
AND contentsprache.sprache = '{$sprache}'"
];
$config['dms'] = [
'primarykey' => 'dms_id, version',
'table' => 'campus.tbl_dms_version',
'searchfields' => [
'keywords' => [
'alias' => ['keyword', 'keywords', 'schlagwort', 'schlagworte'],
'comparison' => "vector",
'field' => "(to_tsvector('simple', COALESCE(schlagworte, '')))"
]
],
'resultfields' => [
"v.dms_id",
"v.version",
"v.filename",
"v.mimetype",
"v.name",
"v.beschreibung AS description",
"v.schlagworte AS keywords"
],
'resultjoin' => "
JOIN campus.tbl_dms_version v
USING (dms_id, version)
WHERE cis_suche = TRUE
AND version=(SELECT MAX(version) FROM campus.tbl_dms_version WHERE dms_id=v.dms_id)
AND NOT EXISTS (
SELECT
1
FROM
fue.tbl_projekt_dokument p
WHERE p.dms_id = v.dms_id
) AND (
NOT EXISTS (
WITH RECURSIVE categories (kategorie_kurzbz) AS (
SELECT
kategorie_kurzbz
FROM
campus.tbl_dms c
WHERE c.dms_id = v.dms_id
UNION ALL
SELECT
cat.parent_kategorie_kurzbz AS kategorie_kurzbz
FROM
categories
JOIN campus.tbl_dms_kategorie cat USING (kategorie_kurzbz)
)
SELECT
1
FROM
categories
JOIN campus.tbl_dms_kategorie_gruppe USING (kategorie_kurzbz)
UNION
SELECT
1
FROM
categories
JOIN campus.tbl_dms_kategorie USING (kategorie_kurzbz)
WHERE
berechtigung_kurzbz IS NOT NULL
) OR EXISTS (
WITH RECURSIVE categories (kategorie_kurzbz) AS (
SELECT
kategorie_kurzbz
FROM
campus.tbl_dms c
WHERE c.dms_id = v.dms_id
UNION ALL
SELECT
cat.parent_kategorie_kurzbz AS kategorie_kurzbz
FROM
categories
JOIN campus.tbl_dms_kategorie cat USING (kategorie_kurzbz)
)
SELECT
1
FROM
categories
JOIN campus.tbl_dms_kategorie_gruppe USING (kategorie_kurzbz)
JOIN public.tbl_benutzergruppe USING(gruppe_kurzbz)
WHERE
uid = (TABLE auth)
)
)"
];
+35
View File
@@ -0,0 +1,35 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
$CI =& get_instance();
$config['employee'] = $CI->config->item('employee', 'search');
$config['student'] = $CI->config->item('student', 'search');
unset($config['student']['searchfields']['email']);
unset($config['student']['searchfields']['tel']);
$config['student']['resultfields'] = [
"s.student_uid AS uid",
"s.matrikelnr",
"p.person_id",
"(p.vorname || ' ' || p.nachname) AS name",
"ARRAY[s.student_uid || '@' || '" . DOMAIN . "'] AS email",
"CASE
WHEN p.foto IS NOT NULL THEN 'data:image/jpeg' || CONVERT_FROM(DECODE('3b','hex'), 'UTF8') || 'base64,' || p.foto
ELSE NULL END
AS photo_url",
"b.aktiv"
];
$config['organisationunit'] = $CI->config->item('organisationunit', 'search');
$config['organisationunit']['prepare'] = 'active_organisationseinheit AS (SELECT * FROM public.tbl_organisationseinheit WHERE aktiv = true)';
$config['organisationunit']['table'] = 'active_organisationseinheit';
$config['room'] = $CI->config->item('room', 'search');
$config['cms'] = $CI->config->item('cms', 'search');
$config['dms'] = $CI->config->item('dms', 'search');
+35
View File
@@ -0,0 +1,35 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/*
* TODO: 2025-07-21 ma0080 bare integer comparision results in db error
* if searchstring is numeric but out of range of int, think about
* more sophisticated solution using column data type - quick fix
* convert field and search string to text
*/
$config['equal-int'] = [
'priority' => 4,
'rank' => "0",
'compare' => "{field}::text = {word}::text",
'force_integer' => true
];
$config['equals'] = [
'priority' => 3,
'rank' => "0",
'compare' => "LOWER({field}) = {word}"
];
$config['similar'] = [
'priority' => 2,
'rank' => "(COALESCE({field}, '') <->> {word})",
'compare' => "COALESCE({field}, '') %> {word}",
'compare_boolean' => "COALESCE({field}, '') ILIKE {like:word}"
];
$config['vector'] = [
'priority' => 1,
'rank' => "ts_rank({field}, to_tsquery('simple', {word}))",
'compare' => "to_tsquery('simple', {word}) @@ {field}"
];
+11
View File
@@ -0,0 +1,11 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
$CI =& get_instance();
$config['student'] = $CI->config->item('student', 'search');
$config['prestudent'] = $CI->config->item('prestudent', 'search');
@@ -0,0 +1,47 @@
<?php
/**
* Copyright (C) 2024 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
* This controller operates between (interface) the JS (GUI) and the back-end
* Provides data to the ajax get calls about languages
* This controller works with JSON calls on the HTTP GET and the output is always JSON
*/
class Language extends FHCAPI_Controller
{
public function __construct()
{
parent::__construct([
'get' => self::PERM_LOGGED
]);
// Load models
$this->load->model('system/Sprache_model', 'SpracheModel');
}
public function get()
{
$this->SpracheModel->addOrder('sprache');
$result = $this->SpracheModel->load();
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
}
@@ -35,11 +35,10 @@ class Searchbar extends FHCAPI_Controller
{
// NOTE(chris): additional permission checks will be done in SearchBarLib
parent::__construct([
'search' => self::PERM_LOGGED
'search' => self::PERM_LOGGED,
'searchCis' => self::PERM_LOGGED,
'searchStv' => self::PERM_LOGGED
]);
// Load the library SearchBarLib
$this->load->library('SearchBarLib');
}
//------------------------------------------------------------------------------------------------------------------
@@ -50,6 +49,7 @@ class Searchbar extends FHCAPI_Controller
*/
public function search()
{
$this->load->library('SearchBarLib');
$this->load->library('form_validation');
// Checks if the searchstr and the types parameters are in the POSTed JSON
@@ -63,7 +63,53 @@ class Searchbar extends FHCAPI_Controller
$result = $this->searchbarlib->search($this->input->post(self::SEARCHSTR_PARAM), $this->input->post(self::TYPES_PARAM));
if (property_exists($result, 'error'))
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
$this->terminateWithSuccess($result);
$this->addMeta('mode', 'simple');
$this->terminateWithSuccess($result->data);
}
/**
* Gets a JSON body via HTTP POST and provides the parameters
*/
public function searchCis()
{
return $this->searchAdvanced([ 'config' => 'searchcis' ]);
}
/**
* Gets a JSON body via HTTP POST and provides the parameters
*/
public function searchStv()
{
return $this->searchAdvanced([ 'config' => 'searchstv' ]);
}
/**
* Gets a JSON body via HTTP POST and provides the parameters
*/
private function searchAdvanced($config)
{
$this->load->library('SearchLib', $config);
$this->load->library('form_validation');
// Checks if the searchstr and the types parameters are in the POSTed JSON
$this->form_validation->set_rules(self::SEARCHSTR_PARAM, null, 'required');
$this->form_validation->set_rules(self::TYPES_PARAM . '[]', null, 'required');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
// Convert to json the result from searchlib->search
$result = $this->searchlib->search($this->input->post(self::SEARCHSTR_PARAM), $this->input->post(self::TYPES_PARAM));
$data = $this->getDataOrTerminateWithError($result);
$this->addMeta('time', $result->meta['time']);
$this->addMeta('searchstring', $result->meta['searchstring']);
$this->addMeta('mode', 'advanced');
$this->terminateWithSuccess($data);
}
}
+7 -2
View File
@@ -109,10 +109,15 @@ class FHCAPI_Controller extends Auth_Controller
$error = [];
if (is_array($data)) {
if ($type == self::ERROR_TYPE_VALIDATION)
if ($type == self::ERROR_TYPE_VALIDATION) {
$error['messages'] = $data;
else
} elseif (array_is_list($data)) {
foreach ($data as $d)
$this->addError($d, $type);
return;
} else {
$error = $data;
}
} elseif (is_object($data)) {
$error = (array)$data;
} else {
+17
View File
@@ -424,6 +424,23 @@ function isValidDate($dateString)
}
// ------------------------------------------------------------------------
// PHP functions that don't exist in older versions
// ------------------------------------------------------------------------
/**
* Returns true if the given array is sequential
*/
if (!function_exists('array_is_list')) {
function array_is_list(array $arr)
{
if ($arr === []) {
return true;
}
return array_keys($arr) === range(0, count($arr) - 1);
}
}
// ------------------------------------------------------------------------
// Collection of utility functions for form validation purposes
// ------------------------------------------------------------------------
File diff suppressed because it is too large Load Diff
+100 -1
View File
@@ -14,6 +14,35 @@
--fhc-searchbar-results-btn-color: var(--bs-btn-color, white);
}
.searchbar_input_clear {
/* 1rem * 5 / 8 = width of icon */
margin-inline-start: calc(-1rem * 5 / 8 - var(--bs-btn-padding-x) * 2 - var(--bs-border-width)) !important;
border: 0;
margin-inline-end: 1px;
z-index: 1;
}
.searchbar_input_clear:hover,
.searchbar_input_clear:focus {
color: var(--bs-body-color);
background-color: transparent;
}
.searchbar_input:focus,
.searchbar_input:focus ~ .searchbar_input_clear {
z-index: 5; /* same as bootstrap .input-group > .form-control:focus */
}
.searchbar_setting_btn {
--bs-btn-color: var(--bs-body-color);
--bs-btn-bg: var(--bs-tertiary-bg);
--bs-btn-border-color: var(--bs-border-color);
}
.searchbar_searchbox.open {
z-index: 10000;
}
.searchbar_settings {
position: absolute;
z-index: 9999;
@@ -120,6 +149,76 @@
list-style: none;
}
/* new variant with template/frame */
.searchbar-result-template-frame {
border-bottom: 1px solid lightgrey;
margin-bottom: 1rem;
padding-bottom: 1rem;
}
.searchbar-result-template-actions {
display: flex;
flex-wrap: wrap;
gap: .5rem;
padding: 0;
margin: 1rem 0 0;
}
.searchbar-square-image {
position: relative;
display: block;
height: 0;
padding-bottom: 100%;
}
.searchbar-square-image > * {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
.searchbar-square-image > img {
object-fit: cover;
}
.searchbar-square-image > div {
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
color: rgba(var(--bs-white-rgb),1);
background-color: rgba(var(--bs-primary-rgb), 1);
}
.searchbar-square-image.rounded-circle > img,
.searchbar-square-image.rounded-circle > div {
border-radius: 50%;
}
.searchbar-rounded-image {
display: block;
width: 100%;
}
.searchbar-rounded-image > img {
width: 100%;
}
.searchbar-rounded-image > div {
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
color: rgba(var(--bs-white-rgb),1);
background-color: rgba(var(--bs-primary-rgb), 1);
height: calc((100px - .75em) * 1.3);
}
.searchbar-rounded-image > img,
.searchbar-rounded-image > div {
border-radius: .25rem;
}
.no-margin-paragraphs p {
margin: 0;
}
.non-selectable {
user-select: none;
-webkit-user-select: none; /* Safari */
@@ -129,4 +228,4 @@
.searchbar_inaktiv {
opacity: .6;
}
}
+25
View File
@@ -0,0 +1,25 @@
/**
* Copyright (C) 2025 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 {
getAll() {
return {
method: 'get',
url: '/api/frontend/v1/language/get'
};
}
};
+14
View File
@@ -22,5 +22,19 @@ export default {
url: '/api/frontend/v1/searchbar/search',
params
};
},
searchCis(params) {
return {
method: 'post',
url: '/api/frontend/v1/searchbar/searchCis',
params
};
},
searchStv(params) {
return {
method: 'post',
url: '/api/frontend/v1/searchbar/searchStv',
params
};
}
};
+2
View File
@@ -40,6 +40,7 @@ import menu from "./menu.js";
import dashboard from "./dashboard.js";
import authinfo from "./authinfo.js";
import studium from "./studium.js";
import language from "./language.js";
export default {
search,
@@ -68,4 +69,5 @@ export default {
menu,
authinfo,
studium,
language
};
+22
View File
@@ -0,0 +1,22 @@
/**
* Copyright (C) 2024 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
getAll() {
return this.$fhcApi.get('/api/frontend/v1/language/get');
}
};
+9 -1
View File
@@ -16,8 +16,16 @@
*/
export default {
search(searchsettings) {
search(searchsettings, config) {
const url = '/api/frontend/v1/searchbar/search';
return this.$fhcApi.post(url, searchsettings, config);
},
searchAdvanced(searchsettings, config) {
const url = '/api/frontend/v1/searchbar/searchAdvanced';
return this.$fhcApi.post(url, searchsettings, config);
},
searchdummy(searchsettings) {
const url = 'public/js/apps/api/dummyapi.php/Search';
return this.$fhcApi.post(url, searchsettings);
}
};
+34 -9
View File
@@ -16,12 +16,14 @@ const app = Vue.createApp({
origin: "cis",
cssclass: "",
calcheightonly: true,
types: [
"mitarbeiter",
"student",
"raum",
"organisationunit"
],
types: {
employee: Vue.computed(() => this.$p.t("search/type_employee")),
student: Vue.computed(() => this.$p.t("search/type_student")),
room: Vue.computed(() => this.$p.t("search/type_room")),
organisationunit: Vue.computed(() => this.$p.t("search/type_organisationunit")),
cms: Vue.computed(() => this.$p.t("search/type_cms")),
dms: Vue.computed(() => this.$p.t("search/type_dms"))
},
actions: {
employee: {
defaultaction: {
@@ -44,7 +46,7 @@ const app = Vue.createApp({
},
childactions: []
},
raum: {
room: {
defaultaction: {
type: "link",
renderif: function(data) {
@@ -106,14 +108,37 @@ const app = Vue.createApp({
}
},
childactions: []
}
},
cms: {
defaultaction: {
type: "link",
action: function (data) {
const link = FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/CisVue/Cms/content/' + data.content_id;
return link;
}
},
childactions: []
},
dms: {
defaultaction: {
type: "link",
action: function (data) {
const link = FHC_JS_DATA_STORAGE_OBJECT.app_root +
'cms/dms.php?id=' + data.dms_id;
return link;
}
},
childactions: []
}
}
}
};
},
methods: {
searchfunction: function(searchsettings) {
return this.$api.call(ApiSearchbar.search(searchsettings));
return this.$api.call(ApiSearchbar.searchCis(searchsettings));
}
}
});
@@ -75,12 +75,12 @@ export default {
return {
selected: [],
searchbaroptions: {
cssclass: "position-relative",
origin: 'stv',
calcheightonly: true,
types: [
"studentStv",
"prestudent"
],
types: {
student: Vue.computed(() => this.$p.t('search/type_student')),
prestudent: Vue.computed(() => this.$p.t('search/type_prestudent'))
},
actions: {
student: {
defaultaction: {
@@ -101,8 +101,28 @@ export default {
},
childactions: [
]
},
mergedPerson: {
defaultaction: {
type: "link",
action: data => this.$fhcApi.getUri('/studentenverwaltung/person/' + data.person_id)
},
defaultactionstudent: {
type: "link",
action: data => {
if (data.prestudent_id) {
return this.$fhcApi.getUri('/studentenverwaltung/prestudent/' + data.prestudent_id);
} else if (data.uid) {
return this.$fhcApi.getUri('/studentenverwaltung/student/' + data.uid);
} else {
return this.$fhcApi.getUri('/studentenverwaltung/person/' + data.person_id);
}
}
},
childactions: []
}
}
},
mergeResults: 'person'
},
studiengangKz: undefined,
studiensemesterKurzbz: this.defaultSemester,
@@ -128,8 +148,8 @@ export default {
reloadList() {
this.$refs.stvList.reload();
},
searchfunction(params) {
return this.$api.call(ApiSearchbar.search(params));
searchfunction(params, config) {
return this.$api.call(ApiSearchbar.searchStv(params), config);
}
},
created() {
@@ -217,7 +237,11 @@ export default {
<header class="navbar navbar-expand-lg navbar-dark bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-4 col-lg-3 col-xl-2 me-0 px-3" :href="stvRoot">FHC 4.0</a>
<button class="navbar-toggler d-md-none m-1 collapsed" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" :aria-label="$p.t('ui/toggle_nav')"><span class="navbar-toggler-icon"></span></button>
<core-searchbar :searchoptions="searchbaroptions" :searchfunction="searchfunction" class="searchbar w-100"></core-searchbar>
<core-searchbar
:searchoptions="searchbaroptions"
:searchfunction="searchfunction"
class="searchbar position-relative w-100"
></core-searchbar>
</header>
<div class="container-fluid overflow-hidden">
<div class="row h-100">
-44
View File
@@ -1,44 +0,0 @@
export default {
props: {
res: {
type: Object
},
action: {
type: Object
},
cssclass: {
type: String,
default: ''
}
},
emits: [ 'actionexecuted' ],
template: `
<template v-if="this.renderif()">
<a :class="this.cssclass" :href="this.getactionhref()"
:alt="$p.t('ui','aktion')"
@click="this.execaction()">
<slot>Action</slot>
</a>
</template>
<template v-else>
<slot>Action</slot>
</template>
`,
methods: {
getactionhref: function() {
return (this.action.type === 'link') ? this.action.action(this.res)
: 'javascript:void(0);';
},
execaction: function() {
this.action.action(this.res);
this.$emit('actionexecuted');
},
renderif: function() {
if(this.action?.renderif === undefined) {
return true;
}
return this.action.renderif(this.res);
}
}
};
-37
View File
@@ -1,37 +0,0 @@
import action from "./action.js";
export default {
props: [ "res", "actions" ],
components: {
action: action
},
emits: [ 'actionexecuted' ],
template: `
<ul class="searchbar_actions" v-if="this.actions.length > 0">
<template v-for="(action, index) in this.actions" :key="action.label">
<li v-if="this.renderif(action)">
<action :res="this.res" :action="action" :cssclass="'btn btn-primary btn-sm'" @actionexecuted="$emit('actionexecuted')">
<i v-if="this.hasicon(index)" :class="this.geticonclass(index)"></i>
<span class="p-2">{{ action.label }}</span>
</action>
</li>
</template>
</ul>
<div class="mb-3" v-else=""></div>
`,
methods: {
hasicon: function(index) {
return (typeof this.actions[index].icon !== "undefined");
},
geticonclass: function(index) {
return this.actions[index].icon;
},
renderif: function(action) {
if(action?.renderif === undefined) {
return true;
}
return action.renderif(this.res);
}
}
};
@@ -1,89 +0,0 @@
import action from "./action.js";
import actions from "./actions.js";
export default {
props: [ "res", "actions" ],
components: {
action: action,
actions: actions
},
emits: [ 'actionexecuted' ],
template: `
<div class="searchbar_result searchbar_employee">
<div class="searchbar_grid">
<div class="searchbar_icon">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<img v-if="(typeof res.photo_url !== 'undefined') && (res.photo_url !== null)" :alt="$p.t('profilUpdate','profilBild')" :src="res.photo_url"
class="rounded" style="max-height: 120px; max-width: 90px;" />
<i v-else class="fas fa-user-circle fa-5x"></i>
</action>
</div>
<div class="searchbar_data">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<span class="fw-bold">{{ res.name }}</span>
</action>
<div class="mb-3"></div>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Standard-Kostenstelle</div>
<div class="searchbar_tablecell searchbar_value">
<ul class="searchbar_inline_ul" v-if="res.standardkostenstelle.length > 0">
<li v-for="(stdkst, idx) in res.standardkostenstelle" :key="idx">{{ stdkst }}</li>
</ul>
<span v-else="">keine</span>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Organisations-Einheit</div>
<div class="searchbar_tablecell searchbar_value">
<ul class="searchbar_inline_ul" v-if="res.organisationunit_name.length > 0">
<li v-for="(oe, idx) in res.organisationunit_name" :key="idx">{{ oe }}</li>
</ul>
<span v-else="">keine</span>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">EMail</div>
<div class="searchbar_tablecell searchbar_value">
<a :href="this.mailtourl">
{{ res.email }}
</a>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Telefon</div>
<div class="searchbar_tablecell searchbar_value">
<a :href="this.telurl">
{{ res.phone }}
</a>
</div>
</div>
</div>
<actions :res="this.res" :actions="this.actions.childactions" @actionexecuted="$emit('actionexecuted')"></actions>
</div>
</div>
</div>
`,
methods: {
},
computed: {
mailtourl: function() {
return 'mailto:' + this.res.email;
},
telurl: function() {
return 'tel:' + this.res.phone;
}
}
};
@@ -1,83 +0,0 @@
import action from "./action.js";
import actions from "./actions.js";
export default {
props: [ "res", "actions" ],
components: {
action: action,
actions: actions
},
emits: [ 'actionexecuted' ],
template: `
<div class="searchbar_result searchbar_organisationunit">
<div class="searchbar_grid">
<div class="searchbar_icon">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<i class="fas fa-sitemap fa-4x" aria-hidden="true"></i>
</action>
</div>
<div class="searchbar_data">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<span class="fw-bold">{{ res.name }}</span>
</action>
<div class="mb-3"></div>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">übergeordnete OrgEinheit</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.parentoe_name }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Gruppen-EMail</div>
<div class="searchbar_tablecell searchbar_value">
<a :href="this.mailtourl" v-if="res.mailgroup">
{{ res.mailgroup }}
</a>
<span v-else>-</span>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Leiter</div>
<div class="searchbar_tablecell searchbar_value">
<ul class="searchbar_inline_ul" v-if="res.leaders.length > 0">
<li v-for="(leader, idx) in res.leaders" :key="idx">{{ leader.name }}</li>
</ul>
<span v-else="">N.N.</span>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Mitarbeiter-Anzahl</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.number_of_people }}
</div>
</div>
</div>
<actions :res="this.res" :actions="this.actions.childactions" @actionexecuted="$emit('actionexecuted')"></actions>
</div>
</div>
</div>
`,
methods: {
},
computed: {
mailtourl: function() {
return 'mailto:' + this.res.mailgroup;
},
telurl: function() {
return 'tel:' + this.res.phone;
}
}
};
-56
View File
@@ -1,56 +0,0 @@
import action from "./action.js";
import actions from "./actions.js";
export default {
props: [ "res", "actions" ],
components: {
action: action,
actions: actions
},
emits: [ 'actionexecuted' ],
template: `
<div class="searchbar_result searchbar_person">
<div class="searchbar_grid">
<div class="searchbar_icon">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<img v-if="(typeof res.foto !== 'undefined') && (res.foto !== null)"
:alt="$p.t('profilUpdate','profilBild')" :src="res.foto"
class="rounded" style="max-height: 120px; max-width: 90px;" />
<i v-else class="fas fa-user-circle fa-5x"></i>
</action>
</div>
<div class="searchbar_data">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<span class="fw-bold">{{ res.gn }} {{ res.sn }}</span>
</action>
<div class="mb-3"></div>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">EMail</div>
<div class="searchbar_tablecell searchbar_value">
<a :href="this.mailtourl">
{{ res.mail }}
</a>
</div>
</div>
</div>
<actions :res="this.res" :actions="this.actions.childactions" @actionexecuted="$emit('actionexecuted')"></actions>
</div>
</div>
</div>
`,
methods: {
},
computed: {
mailtourl: function() {
return 'mailto:' + this.res.mail;
}
}
};
@@ -1,89 +0,0 @@
import action from "./action.js";
import actions from "./actions.js";
export default {
props: [ "res", "actions" ],
components: {
action: action,
actions: actions
},
emits: [ 'actionexecuted' ],
template: `
<div class="searchbar_result searchbar_prestudent">
<div class="searchbar_grid">
<div class="searchbar_icon">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<img v-if="(typeof res.foto !== 'undefined') && (res.foto !== null)"
:alt="$p.t('profilUpdate','profilBild')"
:src="'data:image/jpeg;base64,' + res.foto"
class="rounded-circle" height="100"/>
<i v-else class="fas fa-user-circle fa-5x"></i>
</action>
</div>
<div class="searchbar_data">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<span class="fw-bold">{{ res.name }}</span>
</action>
<div class="mb-3"></div>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Prestudent_id</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.prestudent_id }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Student_uid</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.uid }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Person_id</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.person_id }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Studiengang</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.bezeichnung }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">EMail</div>
<div class="searchbar_tablecell searchbar_value">
<a :href="this.mailtourl">
{{ res.email }}
</a>
</div>
</div>
</div>
<actions :res="this.res" :actions="this.actions.childactions"
@actionexecuted="$emit('actionexecuted')"></actions>
</div>
</div>
</div>
`,
methods: {
},
computed: {
mailtourl: function() {
return 'mailto:' + this.res.email;
}
}
};
-54
View File
@@ -1,54 +0,0 @@
import action from "./action.js";
import actions from "./actions.js";
export default {
props: [ "res", "actions" ],
components: {
action: action,
actions: actions
},
emits: [ 'actionexecuted' ],
template: /*html*/`
<div class="searchbar_result searchbar_raum">
<div class="searchbar_grid">
<div class="searchbar_icon">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<i class="fas fa-door-open fa-4x" aria-hidden="true"></i>
</action>
</div>
<div class="searchbar_data">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<span class="fw-bold">{{ res.ort_kurzbz }}</span>
</action>
<div class="mb-3"></div>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Standort</div>
<div class="searchbar_tablecell searchbar_value">{{ res.standort }}</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Sitzplätze</div>
<div class="searchbar_tablecell searchbar_value">{{ res.sitzplaetze }}</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Gebäude</div>
<div class="searchbar_tablecell searchbar_value">{{ res.building }}</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Zusatz Informationen</div>
<div class="searchbar_tablecell searchbar_value"><div v-html="res.austattung.replace('<br />','')"></div></div>
</div>
</div>
<actions :res="this.res" :actions="this.actions.childactions" @actionexecuted="$emit('actionexecuted')"></actions>
</div>
</div>
</div>
`,
methods: {
}
};
@@ -0,0 +1,75 @@
import TemplateFrame from "./template/frame.js";
export default {
name: 'SearchbarResultCms',
components: {
TemplateFrame
},
emits: [ 'actionexecuted' ],
props: {
res: Object,
actions: Object
},
inject: [
'query'
],
computed: {
preview() {
if (this.res.template_kurzbz != 'redirect') {
let text = this.res.content.replace(/<!\[CDATA\[|\]\]>/ig, '').replace(/<[^>]+>/ig, '').replace(/^\s+|\s+$/g, '');
if (text.length > 1000) {
// NOTE(chris): focus on searched text!
let lower = text.toLowerCase();
let firstOccurence = Math.min(this.query.split(' ').reduce((a, c) => {
// NOTE(chris): filter query for words that affects the content field and get the lowest index of them
if (c == 'or')
return a;
let i = c.indexOf(':');
if (i < 0 || (i > 0 && ['content', 'inhalt'].includes(c.split(':')[0]))) {
let posInText = lower.indexOf(c);
if (posInText >= 0)
a.push(posInText);
}
return a;
}, []));
if (firstOccurence) {
if (firstOccurence + 997 >= text.length) {
firstOccurence = text.length - 997;
if (firstOccurence > 0)
return '...' + text.substr(firstOccurence, 997);
} else {
return '...' + text.substr(firstOccurence, 994) + '...';
}
}
text = text.substr(0, 997) + '...';
}
return text;
}
let url = this.res.content_url;
if (url.substr(0, 16) == '../index.ci.php/')
url = this.$fhcApi.getUri(url.substr(16));
else if (url.substr(0, 3) == '../')
url = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/\/+$/, '') + url.substr(2);
return '<a href="' + url + '">' + url + '</a>';
}
},
template: `
<template-frame
class="searchbar-result-cms"
:res="res"
:actions="actions"
:title="res.title"
image-fallback="fas fa-newspaper fa-4x"
@actionexecuted="$emit('actionexecuted')"
>
<div v-if="preview" class="searchbar_table" v-html="preview"></div>
<div v-else class="searchbar_table text-muted">
{{ $p.t('search/result_content_none') }}
</div>
</template-frame>`
};
@@ -0,0 +1,75 @@
import TemplateFrame from "./template/frame.js";
export default {
name: 'SearchbarResultDms',
components: {
TemplateFrame
},
emits: [ 'actionexecuted' ],
props: {
res: Object,
actions: Object
},
computed: {
icon() {
switch (this.res.mimetype) {
case 'application/pdf':
return 'file-pdf';
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
case 'application/msword':
return 'file-word';
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
case 'application/mspowerpoint':
return 'file-powerpoint';
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
case 'application/vnd.ms-excel':
return 'file-excel';
case 'application/x-zip':
case 'application/zip':
return 'file-zipper';
case 'image/jpeg':
case 'image/gif':
case 'image/png':
return 'file-image';
default:
return 'file';
}
}
},
template: `
<template-frame
class="searchbar-result-dms"
:res="res"
:actions="actions"
:title="res.name"
:image-fallback="'fas fa-' + icon + ' fa-4x'"
@actionexecuted="$emit('actionexecuted')"
>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_dms_id') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.dms_id }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_version') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.version }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_keywords') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.keywords }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('global/beschreibung') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.description }}
</div>
</div>
</div>
</template-frame>`
};
@@ -0,0 +1,60 @@
import TemplateFrame from "./template/frame.js";
export default {
name: 'SearchbarResultEmployee',
components: {
TemplateFrame
},
emits: [ 'actionexecuted' ],
props: {
res: Object,
actions: Object
},
template: `
<template-frame
class="searchbar-result-student"
:res="res"
:actions="actions"
:title="res.name"
:image="res.photo_url"
image-fallback="fas fa-user fa-4x"
@actionexecuted="$emit('actionexecuted')"
>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_stdkst') }}</div>
<div class="searchbar_tablecell searchbar_value">
<ul class="searchbar_inline_ul" v-if="res.standardkostenstelle.length > 0">
<li v-for="(stdkst, idx) in res.standardkostenstelle" :key="idx">{{ stdkst }}</li>
</ul>
<span v-else="">{{ $p.t('search/result_stdkst_none') }}</span>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/organisationseinheit') }}</div>
<div class="searchbar_tablecell searchbar_value">
<ul class="searchbar_inline_ul" v-if="res.organisationunit_name.length > 0">
<li v-for="(oe, idx) in res.organisationunit_name" :key="idx">{{ oe }}</li>
</ul>
<span v-else="">{{ $p.t('search/result_oe_none') }}</span>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_emails') }}</div>
<div class="searchbar_tablecell searchbar_value">
<a :href="'mailto:' + res.email" class="d-block">
{{ res.email }}
</a>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('person/telefon') }}</div>
<div class="searchbar_tablecell searchbar_value">
<a :href="'tel:' + res.phone" class="d-block">
{{ res.phone }}
</a>
</div>
</div>
</div>
</template-frame>`
};
@@ -0,0 +1,209 @@
import TemplateFrame from "./template/frame.js";
import TemplateAction from "./template/action.js";
export default {
name: 'SearchbarResultMergedperson',
components: {
TemplateFrame,
TemplateAction
},
emits: [ 'actionexecuted' ],
props: {
mode: String,
res: Object,
actions: Object
},
computed: {
person() {
// Cummulate all emails
const email = this.res.list.reduce((a, c) => [...a, ...(Array.isArray(c.email) ? c.email : [c.email])], []);
// Use person entry if available (with cummulated emails)
const person = this.res.list.find(item => item.type == 'person');
if (person)
return {...person, email};
// Those properties should be the same in all entries
const { person_id, name } = this.res.list[0];
// Get first photo (prefer student photo if available)
let photo_url;
if (this.mode == 'simple') {
let foto = (this.students ? this.students.find(el => el.foto) : null)?.foto;
if (foto)
foto = 'data:image/jpeg;base64,' + foto;
photo_url = foto || this.employee?.photo_url;
} else
photo_url = ((this.students ? this.students.find(el => el.photo_url) : null) || this.employee)?.photo_url;
return { person_id, name, photo_url, email };
},
employee() {
return this.res.list.find(item => [
'employee',
'unassigned_employee',
'mitarbeiter',
'mitarbeiter_ohne_zuordnung'
].includes(item.type)) || null;
},
students() {
const students = this.res.list.filter(item => [
'student',
'prestudent',
'studentcis',
'studentStv'
].includes(item.type))
.filter((item, idx, arr) => {
if (item.type === 'prestudent') {
return true;
}
let prestudentwithsameuidexists = arr.some(tmpitem => {
return tmpitem.uid === item.uid && tmpitem.type === 'prestudent';
});
if (prestudentwithsameuidexists) {
return false;
}
return true;
}).sort((a, b) => (a.sort || 0) - (b.sort || 0));
return students.length ? students : null;
},
emails() {
// Remove duplicates
return new Set(this.person.email);
},
telurl() {
return 'tel:' + this.employee?.phone;
},
inaktiv() {
return this.res.list.some(item => item?.aktiv === false);
}
},
template: `
<template-frame
class="searchbar-result-mergedperson"
:class="(inaktiv) ? 'searchbar_inaktiv' : ''"
:res="person"
:actions="actions"
:title="person.name"
:image="person.photo_url"
image-fallback="fas fa-user fa-4x"
@actionexecuted="$emit('actionexecuted')"
>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('person/person_id') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ person.person_id }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_emails') }}</div>
<div class="searchbar_tablecell searchbar_value">
<a v-for="email in emails" :key="email" :href="'mailto:' + email" class="d-block">
{{ email }}
</a>
</div>
</div>
<template v-if="employee">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell">
<template-action
:res="employee"
:action="actions.defaultactionemployee || actions.defaultaction"
@actionexecuted="$emit('actionexecuted')"
>
{{ $p.t('search/result_employee') }}
</template-action>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label ps-3">{{ $p.t('search/result_stdkst') }}</div>
<div class="searchbar_tablecell searchbar_value">
<ul class="searchbar_inline_ul" v-if="employee.standardkostenstelle.length > 0">
<li
v-for="(stdkst, idx) in employee.standardkostenstelle"
:key="idx"
>
{{ stdkst }}
</li>
</ul>
<span v-else="">{{ $p.t('search/result_stdkst_none') }}</span>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label ps-3">{{ $p.t('lehre/organisationseinheit') }}</div>
<div class="searchbar_tablecell searchbar_value">
<ul class="searchbar_inline_ul" v-if="employee.organisationunit_name.length > 0">
<li
v-for="(oe, idx) in employee.organisationunit_name"
:key="idx"
>
{{ oe }}
</li>
</ul>
<span v-else="">{{ $p.t('search/result_oe_none') }}</span>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label ps-3">{{ $p.t('person/telefon') }}</div>
<div class="searchbar_tablecell searchbar_value">
<a :href="telurl">
{{ employee.phone }}
</a>
</div>
</div>
</template>
<template v-if="students">
<template v-for="student in students">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell">
<template-action
v-if="actions.defaultaction"
:res="student"
:action="actions.defaultactionstudent || actions.defaultaction"
@actionexecuted="$emit('actionexecuted')"
>
<template v-if="mode == 'simple'">
{{ student.studiengang_kz }}
</template>
<template v-else-if="student.status && student.stg_kuerzel">
{{ student.status }} ({{ student.stg_kuerzel }})
</template>
<template v-else>
{{ $p.t('person/student') }}
</template>
</template-action>
</div>
</div>
<div v-if="student.bezeichnung" class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label ps-3">{{ $p.t('lehre/studiengang') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ student.bezeichnung }} {{ student.orgform ? '(' + student.orgform + ')' : '' }}
</div>
</div>
<div v-if="student.prestudent_id" class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label ps-3">{{ $p.t('search/result_prestudent_id') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ student.prestudent_id }}
</div>
</div>
<div v-if="student.uid" class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label ps-3">{{ $p.t('search/result_student_uid') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ student.uid }}
</div>
</div>
<div v-if="student.matrikelnr" class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label ps-3">{{ $p.t('person/matrikelnummer') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ student.matrikelnr }}
</div>
</div>
</template>
</template>
</div>
</template-frame>`
};
@@ -0,0 +1,39 @@
import ResultPrestudent from "./prestudent.js";
import ResultStudent from "./student.js";
export default {
name: 'SearchbarResultMergedstudent',
components: {
ResultPrestudent,
ResultStudent
},
emits: [ 'actionexecuted' ],
props: {
mode: String,
res: Object,
actions: Object
},
computed: {
prestudent() {
const prestudent = this.res.list.filter(item => item.type == 'prestudent');
return prestudent.pop();
}
},
template: `
<result-prestudent
v-if="prestudent"
:mode="mode"
:res="prestudent"
:actions="actions"
@actionexecuted="$emit('actionexecuted')"
class="searchbar-result-mergedstudent"
></result-prestudent>
<result-student
v-else
:mode="mode"
:res="res.list[0]"
:actions="actions"
@actionexecuted="$emit('actionexecuted')"
class="searchbar-result-mergedstudent"
></result-student>`
};
@@ -0,0 +1,58 @@
import TemplateFrame from "./template/frame.js";
export default {
name: 'SearchbarResultOrganistationunit',
components: {
TemplateFrame
},
emits: [ 'actionexecuted' ],
props: {
res: Object,
actions: Object
},
template: `
<template-frame
class="searchbar-result-organisationunit"
:res="res"
:actions="actions"
:title="res.name"
image-fallback="fas fa-sitemap fa-4x"
@actionexecuted="$emit('actionexecuted')"
>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_parent_oe') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.parentoe_name }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_group_emails') }}</div>
<div class="searchbar_tablecell searchbar_value">
<a v-if="res.mailgroup" :href="'mailto:' + res.mailgroup">
{{ res.mailgroup }}
</a>
<template v-else>-</template>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_leader') }}</div>
<div class="searchbar_tablecell searchbar_value">
<ul class="searchbar_inline_ul" v-if="res.leaders.length > 0">
<li v-for="(leader, idx) in res.leaders" :key="idx">{{ leader.name }}</li>
</ul>
<span v-else="">{{ $p.t('search/result_leader_none') }}</span>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_number_of_employees') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.number_of_people }}
</div>
</div>
</div>
</template-frame>`
};
@@ -0,0 +1,45 @@
import TemplateFrame from "./template/frame.js";
export default {
name: 'SearchbarResultPerson',
components: {
TemplateFrame
},
emits: [ 'actionexecuted' ],
props: {
res: Object,
actions: Object
},
computed: {
emails() {
return new Set(this.res.email);
}
},
template: `
<template-frame
class="searchbar-result-student"
:res="res"
:actions="actions"
:title="res.name"
:image="res.photo_url"
image-fallback="fas fa-user fa-4x"
@actionexecuted="$emit('actionexecuted')"
>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('person/person_id') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.person_id }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_emails') }}</div>
<div class="searchbar_tablecell searchbar_value">
<a v-for="email in emails" :key="email" :href="'mailto:' + email" class="d-block">
{{ email }}
</a>
</div>
</div>
</div>
</template-frame>`
};
@@ -0,0 +1,84 @@
import TemplateFrame from "./template/frame.js";
export default {
name: 'SearchbarResultPrestudent',
components: {
TemplateFrame
},
emits: [ 'actionexecuted' ],
props: {
mode: String,
res: Object,
actions: Object
},
computed: {
title() {
if (this.mode == 'simple')
return this.res.name;
return this.res.name + ' (' + this.res.status + ' ' + this.res.stg_kuerzel + ')';
},
photo_url() {
if (this.mode != 'simple')
return this.res.photo_url;
if (this.res.foto)
return 'data:image/jpeg;base64,' + this.res.foto;
return null;
},
emails() {
if (this.mode == 'simple')
return new Set([this.res.email]);
return new Set(this.res.email);
}
},
template: `
<template-frame
class="searchbar-result-prestudent"
:res="res"
:actions="actions"
:title="title"
:image="photo_url"
image-fallback="fas fa-user fa-4x"
@actionexecuted="$emit('actionexecuted')"
>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('person/person_id') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.person_id }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_emails') }}</div>
<div class="searchbar_tablecell searchbar_value">
<a v-for="email in emails" :key="email" :href="'mailto:' + email" class="d-block">
{{ email }}
</a>
</div>
</div>
<div v-if="res.uid" class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_student_uid') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.uid }}
</div>
</div>
<div v-if="res.matrikelnr" class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('person/matrikelnummer') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.matrikelnr }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_prestudent_id') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.prestudent_id }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/studiengang') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.bezeichnung }} {{ res.orgform ? '(' + res.orgform + ')' : '' }}
</div>
</div>
</div>
</template-frame>`
};
@@ -0,0 +1,80 @@
import TemplateFrame from "./template/frame.js";
export default {
name: 'SearchbarResultRoom',
components: {
TemplateFrame
},
emits: [ 'actionexecuted' ],
props: {
mode: String,
res: Object,
actions: Object
},
computed: {
equipment() {
if (!this.res.equipment)
return "";
return this.res.equipment.replace(new RegExp('<br />', 'ig'), '');
},
address() {
let address = this.res.zip || '';
if (this.res.city)
address += (address ? ' ' : '') + this.res.city;
if (this.res.street)
address += (address ? ', ' : '') + this.res.street;
if (this.res.floor)
address += (address ? ' / ' : '') + this.$p.t('search/result_address_floor', this.res);
return address || this.$p.t('search/result_address_none');
}
},
template: `
<template-frame
class="searchbar-result-room"
:res="res"
:actions="actions"
:title="res.ort_kurzbz"
image-fallback="fas fa-door-open fa-4x"
@actionexecuted="$emit('actionexecuted')"
>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_room_address') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ address }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_workplaces') }}</div>
<div v-if="mode == 'simple'" class="searchbar_tablecell searchbar_value">
{{ res.sitzplaetze }}
</div>
<div v-else class="searchbar_tablecell searchbar_value">
<template v-if="res.max_person !== null && res.workplaces !== null">
{{ $p.t('search/result_workplaces_pc', res) }}
</template>
<template v-else>
{{ $p.t('search/result_workplaces_none') }}
</template>
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_building') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.building }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_equipment') }}</div>
<div class="searchbar_tablecell searchbar_value">
<div v-if="mode == 'simple'" class="no-margin-paragraphs" v-html="res.austattung.replace('<br />','')"></div>
<div v-else class="no-margin-paragraphs" v-html="equipment"></div>
</div>
</div>
</div>
</template-frame>`
};
@@ -0,0 +1,67 @@
import TemplateFrame from "./template/frame.js";
export default {
name: 'SearchbarResultStudent',
components: {
TemplateFrame
},
emits: [ 'actionexecuted' ],
props: {
mode: String,
res: Object,
actions: Object
},
computed: {
photo_url() {
if (this.mode != 'simple')
return this.res.photo_url;
if (this.res.foto)
return 'data:image/jpeg;base64,' + this.res.foto;
return null;
},
emails() {
if (this.mode == 'simple')
return new Set([this.res.email]);
return new Set(this.res.email);
}
},
template: `
<template-frame
class="searchbar-result-student"
:res="res"
:actions="actions"
:title="res.name"
:image="photo_url"
image-fallback="fas fa-user fa-4x"
@actionexecuted="$emit('actionexecuted')"
>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_student_uid') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.uid }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('person/person_id') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.person_id }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('person/matrikelnummer') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.matrikelnr }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('search/result_emails') }}</div>
<div class="searchbar_tablecell searchbar_value">
<a v-for="email in emails" :key="email" :href="'mailto:' + email" class="d-block">
{{ email }}
</a>
</div>
</div>
</div>
</template-frame>`
};
@@ -0,0 +1,29 @@
export default {
name: 'SearchbarResultTemplateAction',
emits: [ 'actionexecuted' ],
props: {
res: Object,
action: Object
},
computed: {
actionHref() {
if (this.action.type !== 'link')
return 'javascript:void(0);';
return typeof this.action.action === 'function'
? this.action.action(this.res)
: this.action.action;
}
},
methods: {
actionFunc() {
if (this.action.type !== 'function')
return;
this.action.action(this.res);
this.$emit('actionexecuted');
}
},
template: `
<a class="searchbar-result-template-action" :href="actionHref" @click="actionFunc">
<slot>{{ $p.t('search/action_default_label') }}</slot>
</a>`
};
@@ -0,0 +1,27 @@
import ResultAction from "./action.js";
export default {
name: 'SearchbarResultTemplateActions',
components: {
ResultAction
},
emits: [ 'actionexecuted' ],
props: {
res: Object,
actions: Array
},
template: `
<div v-if="actions.length" class="searchbar-result-template-actions">
<result-action
v-for="(action, index) in actions"
:key="action.label"
:res="res"
:action="action"
class="btn btn-primary btn-sm"
@actionexecuted="$emit('actionexecuted')"
>
<i v-if="action.icon" :class="action.icon"></i>
<span class="p-2">{{ action.label }}</span>
</result-action>
</div>`
};
@@ -0,0 +1,59 @@
import ResultAction from "./action.js";
import ResultActions from "./actions.js";
export default {
name: 'SearchbarResultTemplateFrame',
components: {
ResultAction,
ResultActions
},
emits: [ 'actionexecuted' ],
props: {
res: Object,
actions: Object,
title: String,
image: String,
imageFallback: String
},
template: `
<div class="searchbar-result-template-frame">
<div class="searchbar_grid">
<div class="searchbar_icon">
<result-action
:res="res"
:action="actions.defaultaction"
@actionexecuted="$emit('actionexecuted')"
class="searchbar-rounded-image"
>
<img
v-if="image"
:src="image"
/>
<div v-else>
<i :class="imageFallback"></i>
</div>
</result-action>
</div>
<div class="searchbar_data">
<result-action
:res="res"
:action="actions.defaultaction"
@actionexecuted="$emit('actionexecuted')"
class="mb-3"
>
<span class="fw-bold">{{ title }}</span>
</result-action>
<slot></slot>
<result-actions
:res="res"
:actions="actions.childactions"
@actionexecuted="$emit('actionexecuted')"
>
</result-actions>
</div>
</div>
</div>`
};
+238 -105
View File
@@ -1,137 +1,206 @@
import person from "./person.js";
import raum from "./raum.js";
import employee from "./employee.js";
import organisationunit from "./organisationunit.js";
import student from "./student.js";
import prestudent from "./prestudent.js";
import person from "./result/person.js";
import room from "./result/room.js";
import employee from "./result/employee.js";
import organisationunit from "./result/organisationunit.js";
import student from "./result/student.js";
import prestudent from "./result/prestudent.js";
import dms from "./result/dms.js";
import cms from "./result/cms.js";
import mergedStudent from "./result/mergedstudent.js";
import mergedPerson from "./result/mergedperson.js";
export default {
name: "FhcSearchbar",
components: {
person,
room,
employee,
organisationunit,
student,
prestudent,
dms,
cms,
mergedStudent,
mergedPerson
},
props: [ "searchoptions", "searchfunction" ],
provide() {
return {
query: Vue.computed(() => this.lastQuery)
};
},
data: function() {
return {
searchtimer: null,
hidetimer: null,
searchsettings: {
searchstr: this.getSearchStr(),
types: this.getSearchTypes(),
types: this.getInitiallySelectedTypes(),
},
searchresult: [],
searchmode: '',
showresult: false,
searching: false,
error: null,
settingsDropdown:null,
abortController: null,
settingsDropdown: null,
lastQuery: ''
};
},
components: {
person: person,
raum: raum,
employee: employee,
organisationunit: organisationunit,
student: student,
prestudent: prestudent
},
template: /*html*/`
<form
ref="searchform"
class="d-flex me-3"
:class="searchoptions.cssclass"
action="javascript:void(0);"
@focusin="searchfocusin"
@focusout="searchfocusout"
>
<div ref="searchbox" class="h-100 input-group me-2">
computed:{
searchTypesPlaceholder() {
if (!this.searchsettings.types.length) {
return Object.values(this.typeLabels).join(' / ');
}
return this.searchsettings.types.map(type => this.typeLabels[type]).join(' / ');
},
types() {
if (!this.searchoptions.types)
return [];
if (Array.isArray(this.searchoptions.types))
return this.searchoptions.types;
return Object.keys(this.searchoptions.types);
},
typeLabels() {
if (!this.searchoptions.types)
return {};
if (Array.isArray(this.searchoptions.types)) {
return this.searchoptions.types.reduce((res, type) => {
res[type] = type;
return res
}, {});
}
return this.searchoptions.types;
}
},
template: /*html*/`
<form
ref="searchform"
class="d-flex me-3"
action="javascript:void(0);"
@focusin="searchfocusin"
@focusout="searchfocusout"
>
<div
ref="searchbox"
class="h-100 input-group me-2 searchbar_searchbox"
:class="showresult ? 'open' : 'closed'"
>
<span class="input-group-text">
<i class="fa-solid fa-magnifying-glass"></i>
</span>
<input
ref="input"
@keyup="search"
@focus="showsearchresult"
v-model="searchsettings.searchstr"
class="form-control"
class="form-control searchbar_input"
type="search"
:placeholder="'Search: '+ search_types_string"
aria-label="Search"
:placeholder="$p.t('search/input_search_label', { types: searchTypesPlaceholder })"
:aria-label="$p.t('search/input_search_label', { types: searchTypesPlaceholder })"
>
<span
<button
v-if="searchsettings.searchstr"
class="searchbar_input_clear btn btn-outline-secondary"
@click="clearInput"
>
<i class="fas fa-close"></i>
</button>
<button
data-bs-toggle="collapse"
data-bs-target="#searchSettings"
aria-expanded="false"
aria-controls="searchSettings"
ref="settingsbutton"
class="input-group-text"
class="searchbar_setting_btn btn btn-secondary"
type="button"
:title="$p.t('search/button_filter_label')"
:aria-label="$p.t('search/button_filter_label')"
>
<i class="fas fa-cog"></i>
</span>
</button>
</div>
<div v-show="this.showresult"
<div v-show="showresult"
class="searchbar_results" tabindex="-1">
<div class="searchbar_results_scroller" ref="result">
<div class="searchbar_results_wrapper" ref="results">
<div v-if="this.searching">
<div v-if="searching">
<i class="fas fa-spinner fa-spin fa-2x"></i>
</div>
<div v-else-if="this.error !== null">{{ this.error }}</div>
<div v-else-if="searchresult.length < 1">Es wurden keine Ergebnisse gefunden.</div>
<template v-else="" v-for="res in searchresult">
<person v-if="res.type === 'person'" :res="res" :actions="this.searchoptions.actions.person" @actionexecuted="this.hideresult"></person>
<student v-else-if="res.type === 'student' || res.type === 'studentStv'" :res="res" :actions="this.searchoptions.actions.student" @actionexecuted="this.hideresult"></student>
<prestudent v-else-if="res.type === 'prestudent'" :res="res" :actions="this.searchoptions.actions.prestudent" @actionexecuted="this.hideresult"></prestudent>
<employee v-else-if="res.type === 'mitarbeiter' || res.type === 'mitarbeiter_ohne_zuordnung'" :res="res" :actions="this.searchoptions.actions.employee" @actionexecuted="this.hideresult"></employee>
<organisationunit v-else-if="res.type === 'organisationunit'" :res="res" :actions="this.searchoptions.actions.organisationunit" @actionexecuted="this.hideresult"></organisationunit>
<raum v-else-if="res.type === 'raum'" :res="res" :actions="this.searchoptions.actions.raum" @actionexecuted="this.hideresult"></raum>
<div v-else="">Unbekannter Ergebnistyp: '{{ res.type }}'.</div>
<div v-else-if="this.error !== null">{{ error }}</div>
<div v-else-if="searchresult.length < 1">{{ $p.t('search/error_no_results') }}</div>
<template v-else v-for="res in searchresult">
<component
v-if="isValidRenderer(res.renderer)"
:is="res.renderer"
:mode="searchmode"
:res="res"
:actions="searchoptions.actions[dash2camelCase(res.renderer)]"
@actionexecuted="hideresult"
></component>
<div v-else class="searchbar-result text-danger fw-bold">{{ $p.t('search/error_unknown_type', res) }}</div>
</template>
</div>
</div>
</div>
<div id="searchSettings" ref="settings"
<div
id="searchSettings"
ref="settings"
@[\`shown.bs.collapse\`]="handleShowSettings"
@[\`hide.bs.collapse\`]="handleHideSettings"
class="top-100 end-0 searchbar_settings text-white collapse" tabindex="-1">
<div class="d-flex flex-column m-3" v-if="this.searchoptions.types.length > 0">
<span class="fw-light mb-2">Suche filtern nach:</span>
<template v-for="(type, index) in this.searchoptions.types" :key="type">
<div class="form-check form-switch">
<input class="fhc-switches form-check-input" type="checkbox" role="switch" :id="this.$.uid + 'search_type_' + index" :value="type" v-model="searchsettings.types" />
<label class="ps-2 form-check-label non-selectable" :for="this.$.uid + 'search_type_' + index">{{ type }}</label>
</div>
</template>
</div>
class="top-100 end-0 searchbar_settings text-white collapse"
tabindex="-1"
>
<div
v-if="types.length > 0"
class="d-flex flex-column m-3"
>
<span class="fw-light mb-2">
{{ $p.t('search/applyfilter_label') }}
</span>
<template
v-for="(label, value) in typeLabels"
:key="value"
>
<div class="form-check form-switch">
<input
class="fhc-switches form-check-input"
type="checkbox"
role="switch"
:id="$.uid + 'search_type_' + value"
:value="value"
v-model="searchsettings.types"
>
<label
class="ps-2 form-check-label non-selectable"
:for="$.uid + 'search_type_' + value"
>
{{ label }}
</label>
</div>
</template>
</div>
</div>
</form>
</form>
`,
watch:{
'searchsettings.searchstr': function (newSearchValue, oldSearchValue) {
'searchsettings.searchstr': function (newSearchValue) {
if(this.searchoptions.origin){
sessionStorage.setItem(`${this.searchoptions.origin}_searchstr`,newSearchValue);
}
},
},
computed:{
search_types_string(){
if (Array.isArray(this.searchsettings.types) && this.searchsettings.types.length > 0){
return this.searchsettings.types.join(' / ');
}else{
return JSON.stringify(this.searchsettings.types);
}
},
},
beforeMount: function() {
this.$watch('searchsettings.types', (newValue, oldValue) => {
if (Array.isArray(newValue) && newValue.length === 0){
this.searchsettings.types = this.allSearchTypes();
'searchsettings.types'(newValue) {
if (Array.isArray(newValue) && newValue.length === 0) {
this.searchsettings.types = [...this.types];
}
// stores the search types in the localstorage, only if the newValue is also an array
if(Array.isArray(newValue) && this.searchoptions.origin){
if (Array.isArray(newValue) && this.searchoptions.origin) {
localStorage.setItem(`${this.searchoptions.origin}_searchtypes`, JSON.stringify(newValue));
}
this.search();
});
}
},
mounted(){
this.settingsDropdown = new bootstrap.Collapse(this.$refs.settings, {
@@ -150,22 +219,26 @@ export default {
}
},
methods: {
getSearchTypes: function () {
let result = this.allSearchTypes();
clearInput() {
this.searchsettings.searchstr = "";
this.hideresult();
this.$refs.input.focus()
},
getInitiallySelectedTypes() {
let result = false;
if (this.searchoptions.origin) {
let localStorageValue = localStorage.getItem(`${this.searchoptions.origin}_searchtypes`);
if (localStorageValue) {
result = JSON.parse(localStorageValue);
}
}
return result;
},
allSearchTypes() {
let allTypes = [];
for (const idx in this.searchoptions.types) {
allTypes.push(this.searchoptions.types[idx]);
};
return allTypes;
if (result)
return result;
if (!this.searchoptions.types)
return [];
if (Array.isArray(this.searchoptions.types))
return [...this.searchoptions.types];
return Object.keys(this.searchoptions.types);
},
getSearchStr: function(){
if (!this.searchoptions.origin)
@@ -187,12 +260,6 @@ export default {
// removes the event listener checkSettingsVisibility when the collapsible is hidden
document.removeEventListener("click", this.checkSettingsVisibility);
},
allSearchOptions: function() {
this.searchsettings.types = [];
for( const idx in this.searchoptions.types ) {
this.searchsettings.types.push(this.searchoptions.types[idx]);
}
},
calcSearchResultHeight: function() {
const rect = this.$refs.results.getBoundingClientRect();
if( rect.height > 0 && rect.height < (window.innerHeight * 0.8) ) {
@@ -218,6 +285,10 @@ export default {
if( this.searchtimer !== null ) {
clearTimeout(this.searchtimer);
}
if (this.abortController) {
this.abortController.abort();
this.abortController = null;
}
if( this.searchsettings.searchstr.length >= 2 ) {
this.calcSearchResultExtent();
this.searchtimer = setTimeout(
@@ -233,27 +304,82 @@ export default {
this.searchresult.splice(0, this.searchresult.length);
this.searching = true;
this.showsearchresult();
if(this.searchsettings.types.length === 0) {
this.error = 'Kein Ergebnistyp ausgewählt. Bitte mindestens einen Ergebnistyp auswählen.';
this.searching = false;
return;
}
this.searchfunction(this.searchsettings)
if(this.searchsettings.types.length === 0) {
this.error = this.$p.t('search/error_missing_type');
this.searching = false;
return;
}
if (this.abortController)
this.abortController.abort();
this.abortController = new AbortController();
this.searchfunction(this.searchsettings, { timeout: 50000, signal: this.abortController.signal })
.then(response=>{
if( response.data?.error === 1 ) {
this.error = 'Bei der Suche ist ein Fehler aufgetreten.';
if (!response.data) {
this.error = this.$p.t('search/error_general');
} else {
for(let element of response.data.data){
this.searchresult.push(element);
let res = response.data.map(el => el.data ? {...el, ...JSON.parse(el.data)} : el);
this.lastQuery = response.meta.searchstring;
if (this.searchoptions.mergeResults) {
let counter = 0;
let mergeTypes = [];
let mergedType = 'merged-';
let mergeKey = '';
switch (this.searchoptions.mergeResults) {
case 'student':
mergeTypes = ['student', 'prestudent'];
mergedType += this.searchoptions.mergeResults;
mergeKey = 'uid';
break;
case 'person':
mergeTypes = ['person', 'employee', 'student', 'prestudent'];
mergedType += this.searchoptions.mergeResults;
mergeKey = 'person_id';
break;
}
if (mergeTypes.length) {
res = Object.values(res.reduce((a, c) => {
if (!mergeTypes.includes(c.renderer)) {
a['nomerge' + counter++] = c;
} else if (c[mergeKey] === null) {
a['nomerge' + counter++] = c;
} else if (a[c[mergeKey]] === undefined) {
a[c[mergeKey]] = {
rank: c.rank,
renderer: mergedType,
type: mergedType,
list: [c]
};
} else {
a[c[mergeKey]].list.push(c);
if (c.rank > a[c[mergeKey]].rank)
a[c[mergeKey]].rank = c.rank;
}
return a;
}, {})).sort((a, b) => b.rank - a.rank);
}
}
this.searchresult = res;
this.searchmode = response.meta.mode;
}
this.searching = false;
this.retry = 0;
})
.catch(error=> {
this.error = 'Bei der Suche ist ein Fehler aufgetreten.'
+ ' ' + error.message;
})
.finally(()=> {
if (error.code == "ERR_CANCELED") {
return this.retry = 0;
}
if (error.code == "ECONNABORTED" && this.retry) {
this.retry--;
return this.callsearchapi();
}
this.error = this.$p.t('search/error_general', error);
this.searching = false;
this.retry = 0;
});
},
refreshsearch: function() {
@@ -285,6 +411,13 @@ export default {
this.hideresult,
100
);
},
dash2camelCase(string) {
return string.replace(/-([a-z])/g, g => g[1].toUpperCase());
},
isValidRenderer(renderer) {
const camelCaseRenderer = this.dash2camelCase(renderer);
return Object.keys(this.$.components).includes(camelCaseRenderer);
}
}
};
-92
View File
@@ -1,92 +0,0 @@
import action from "./action.js";
import actions from "./actions.js";
export default {
props: [ "res", "actions" ],
components: {
action: action,
actions: actions
},
emits: [ 'actionexecuted' ],
template: `
<div class="searchbar_result searchbar_student" :class="(!res?.aktiv) ? 'searchbar_inaktiv' : ''">
<div class="searchbar_grid">
<div class="searchbar_icon">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<img v-if="(typeof res.foto !== 'undefined') && (res.foto !== null)"
:src="studentImage"
:alt="$p.t('personUpdate','profilBild')"
class="rounded" style="max-height: 120px; max-width: 90px;" />
<i v-else class="fas fa-user-circle fa-5x"></i>
</action>
</div>
<div class="searchbar_data">
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
<span class="fw-bold">{{ res.name }}</span>
</action>
<div class="mb-3"></div>
<div class="searchbar_table">
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Student UID</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.uid }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{$p.t('lehre','studiengang')}}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.studiengang }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">Verband</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.verband }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{$p.t('person','personenkennzeichen')}}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.matrikelnr }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{$p.t('person','email')}}</div>
<div class="searchbar_tablecell searchbar_value">
<a :href="this.mailtourl">
{{ res.email }}
</a>
</div>
</div>
</div>
<actions :res="this.res" :actions="this.actions.childactions"
@actionexecuted="$emit('actionexecuted')"></actions>
</div>
</div>
</div>
`,
methods: {
},
computed: {
mailtourl: function() {
return 'mailto:' + this.res.email;
},
studentImage: function () {
if (!this.res.foto) return;
return 'data:image/jpeg;base64,'.concat(this.res.foto);
},
}
};
+2
View File
@@ -50,6 +50,8 @@ export default {
function _clean_return_value(response) {
const result = response.data;
delete response.data;
if (!result)
return {meta: {response}, data: null};
if (!result.meta)
result.meta = {response};
else
+2
View File
@@ -44,6 +44,8 @@ export default {
function _clean_return_value(response) {
const result = response.data;
delete response.data;
if (!result)
return {meta: {response}, data: null};
if (!result.meta)
result.meta = {response};
else
+1 -1
View File
@@ -76,7 +76,7 @@ require_once('dbupdate_3.4/55289_pep_fine_tuning.php');
require_once('dbupdate_3.4/55614_perm_verwaltetoe.php');
require_once('dbupdate_3.4/25999_C4_dashboard.php');
require_once('dbupdate_3.4/61730_Dashboard_Anpassungen.php');
require_once('dbupdate_3.4/40128_search.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
+222
View File
@@ -0,0 +1,222 @@
<?php
if (!defined('DB_NAME')) exit('No direct script access allowed');
// Activate module pg_trgm
if (!$db->db_num_rows(@$db->db_query("SELECT 1
FROM pg_extension WHERE extname = 'pg_trgm' LIMIT 1;")))
{
$qry = "CREATE extension pg_trgm;";
if (!$db->db_query($qry))
echo '<strong>Module pg_trgm ' . $db->db_last_error() . '</strong><br>';
else
echo 'Module pg_trgm: activated<br>';
}
// Add additional computed columns
// Add column fts_bezeichnung to public.tbl_organisationseinheit
if (!@$db->db_query("SELECT fts_bezeichnung FROM public.tbl_organisationseinheit LIMIT 1"))
{
$qry = "ALTER TABLE public.tbl_organisationseinheit ADD COLUMN fts_bezeichnung tsvector;";
$qry .= "COMMENT ON COLUMN public.tbl_organisationseinheit.fts_bezeichnung IS 'used for search - auto generated w triggers';";
if (!$db->db_query($qry))
echo '<strong> public.tbl_organisationseinheit ' . $db->db_last_error() . '</strong><br>';
else
echo 'public.tbl_organisationseinheit: new column "fts_bezeichnung" added<br>';
}
// Add function tr_update_tbl_organisationseinheit_fts_bezeichnung to public
if (!$db->db_num_rows(@$db->db_query("SELECT 1 FROM pg_proc WHERE proname = 'tr_update_tbl_organisationseinheit_fts_bezeichnung' LIMIT 1;")))
{
$qry = "CREATE FUNCTION tr_update_tbl_organisationseinheit_fts_bezeichnung()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
IF TG_TABLE_NAME = 'tbl_organisationseinheit' THEN
NEW.fts_bezeichnung := to_tsvector('simple', COALESCE((
SELECT bezeichnung
FROM public.tbl_organisationseinheittyp
WHERE organisationseinheittyp_kurzbz = NEW.organisationseinheittyp_kurzbz
), '') || ' ' || COALESCE(NEW.bezeichnung, ''));
ELSIF TG_TABLE_NAME = 'tbl_organisationseinheittyp' THEN
UPDATE public.tbl_organisationseinheit
SET fts_bezeichnung = to_tsvector('simple', COALESCE(NEW.bezeichnung, '') || ' ' || COALESCE(bezeichnung, ''))
WHERE organisationseinheittyp_kurzbz = NEW.organisationseinheittyp_kurzbz;
END IF;
RETURN NEW;
END;
$$";
if (!$db->db_query($qry))
echo '<strong> public.tr_update_tbl_organisationseinheit_fts_bezeichnung ' . $db->db_last_error() . '</strong><br>';
else
echo 'public.tr_update_tbl_organisationseinheit_fts_bezeichnung: function created<br>';
}
$update_column = false;
// Add trigger tr_organisationseinheit_update_organisationseinheittyp_kurzbz to public.tbl_organisationseinheit
if (!$db->db_num_rows(@$db->db_query("
SELECT 1
FROM information_schema.triggers
WHERE event_object_table ='tbl_organisationseinheit'
AND trigger_name = 'tr_organisationseinheit_update_organisationseinheittyp_kurzbz'
LIMIT 1;
")))
{
$qry = "CREATE TRIGGER tr_organisationseinheit_update_organisationseinheittyp_kurzbz
BEFORE UPDATE OF organisationseinheittyp_kurzbz OR INSERT
ON public.tbl_organisationseinheit
FOR EACH ROW
EXECUTE FUNCTION tr_update_tbl_organisationseinheit_fts_bezeichnung();";
if (!$db->db_query($qry))
echo '<strong> public.tbl_organisationseinheit ' . $db->db_last_error() . '</strong><br>';
else {
echo 'public.tbl_organisationseinheit: trigger "tr_organisationseinheit_update_organisationseinheittyp_kurzbz" created<br>';
$update_column = true;
}
}
// Add trigger tr_organisationseinheittyp_update_bezeichnung to public.tbl_organisationseinheittyp
if (!$db->db_num_rows(@$db->db_query("
SELECT 1
FROM information_schema.triggers
WHERE event_object_table ='tbl_organisationseinheittyp'
AND trigger_name = 'tr_organisationseinheittyp_update_bezeichnung'
LIMIT 1;
")))
{
$qry = "CREATE TRIGGER tr_organisationseinheittyp_update_bezeichnung
BEFORE UPDATE OF bezeichnung
ON public.tbl_organisationseinheittyp
FOR EACH ROW
EXECUTE FUNCTION tr_update_tbl_organisationseinheit_fts_bezeichnung();";
if (!$db->db_query($qry))
echo '<strong> public.tbl_organisationseinheittyp ' . $db->db_last_error() . '</strong><br>';
else {
echo 'public.tbl_organisationseinheittyp: trigger "tr_organisationseinheittyp_update_bezeichnung" created<br>';
$update_column = true;
}
}
// Update fts_bezeichnung on tbl_organisationseinheit with new triggers
if ($update_column)
{
$qry = "UPDATE public.tbl_organisationseinheittyp SET bezeichnung = bezeichnung;";
if (!$db->db_query($qry))
echo '<strong> public.tbl_organisationseinheit ' . $db->db_last_error() . '</strong><br>';
else
echo 'public.tbl_organisationseinheit: column "fts_bezeichnung" updated<br>';
}
// Add Trigram Indexes
// Add index for kontakt to public.tbl_kontakt
if ($result = @$db->db_query("SELECT 1
FROM pg_indexes WHERE indexname = 'idx_tbl_kontakt_kontakt_trgm';"))
{
if ($db->db_num_rows($result) == 0)
{
$qry = "CREATE INDEX idx_tbl_kontakt_kontakt_trgm ON public.tbl_kontakt USING GIN (COALESCE(kontakt, '') gin_trgm_ops);";
if (!$db->db_query($qry))
echo '<strong>public.tbl_kontakt ' . $db->db_last_error() . '</strong><br>';
else
echo 'public.tbl_kontakt: added index "idx_tbl_kontakt_kontakt_trgm"<br>';
}
}
// Add index for vorname to public.tbl_person
if ($result = @$db->db_query("SELECT 1
FROM pg_indexes WHERE indexname = 'idx_tbl_person_vorname_trgm';"))
{
if ($db->db_num_rows($result) == 0)
{
$qry = "CREATE INDEX idx_tbl_person_vorname_trgm ON public.tbl_person USING GIN (COALESCE(vorname, '') gin_trgm_ops);";
if (!$db->db_query($qry))
echo '<strong>public.tbl_person ' . $db->db_last_error() . '</strong><br>';
else
echo 'public.tbl_person: added index "idx_tbl_person_vorname_trgm"<br>';
}
}
// Add index for nachname to public.tbl_person
if ($result = @$db->db_query("SELECT 1
FROM pg_indexes WHERE indexname = 'idx_tbl_person_nachname_trgm';"))
{
if ($db->db_num_rows($result) == 0)
{
$qry = "CREATE INDEX idx_tbl_person_nachname_trgm ON public.tbl_person USING GIN (COALESCE(nachname, '') gin_trgm_ops);";
if (!$db->db_query($qry))
echo '<strong>public.tbl_person ' . $db->db_last_error() . '</strong><br>';
else
echo 'public.tbl_person: added index "idx_tbl_person_nachname_trgm"<br>';
}
}
// Add index for vorname || ' ' || nachname to public.tbl_person
if ($result = @$db->db_query("SELECT 1
FROM pg_indexes WHERE indexname = 'idx_tbl_person_name_trgm';"))
{
if ($db->db_num_rows($result) == 0)
{
$qry = "CREATE INDEX idx_tbl_person_name_trgm ON public.tbl_person USING GIN (COALESCE((vorname || ' ' || nachname), '') gin_trgm_ops);";
if (!$db->db_query($qry))
echo '<strong>public.tbl_person ' . $db->db_last_error() . '</strong><br>';
else
echo 'public.tbl_person: added index "idx_tbl_person_name_trgm"<br>';
}
}
// Add Vector Indexes
// Add index for fts_bezeichnung to public.tbl_organisationseinheit
if (!$db->db_num_rows(@$db->db_query("SELECT 1
FROM pg_indexes WHERE indexname = 'idx_tbl_organisationseinheit_fts_bezeichnung_vector' LIMIT 1;")))
{
$qry = "CREATE INDEX idx_tbl_organisationseinheit_fts_bezeichnung_vector ON public.tbl_organisationseinheit USING GIN (fts_bezeichnung);";
if (!$db->db_query($qry))
echo '<strong>public.tbl_organisationseinheit ' . $db->db_last_error() . '</strong><br>';
else
echo 'public.tbl_organisationseinheit: added index "idx_tbl_organisationseinheit_fts_bezeichnung_vector"<br>';
}
// Add index for titel || ' ' || content to campus.tbl_contentsprache
if (!$db->db_num_rows(@$db->db_query("SELECT 1
FROM pg_indexes WHERE indexname = 'idx_tbl_contentsprache_fts_titel_content_vector' LIMIT 1;")))
{
$qry = "
CREATE INDEX idx_tbl_contentsprache_fts_titel_content_vector
ON campus.tbl_contentsprache
USING GIN ((
setweight(to_tsvector('simple', COALESCE(titel, '')), 'A')
|| setweight(to_tsvector('simple', COALESCE(content, '')::text), 'B')
));
";
if (!$db->db_query($qry))
echo '<strong>campus.tbl_contentsprache ' . $db->db_last_error() . '</strong><br>';
else
echo 'campus.tbl_contentsprache: added index "idx_tbl_contentsprache_fts_titel_content_vector"<br>';
}
// Add index for schlagworte to campus.tbl_dms_version
if (!$db->db_num_rows(@$db->db_query("SELECT 1
FROM pg_indexes WHERE indexname = 'idx_tbl_dms_version_fts_schlagworte_vector' LIMIT 1;")))
{
$qry = "
CREATE INDEX idx_tbl_dms_version_fts_schlagworte_vector
ON campus.tbl_dms_version
USING GIN ((to_tsvector('simple', COALESCE(schlagworte, ''))));
";
if (!$db->db_query($qry))
echo '<strong>campus.tbl_dms_version ' . $db->db_last_error() . '</strong><br>';
else
echo 'campus.tbl_contentsprache: added index "idx_tbl_dms_version_fts_schlagworte_vector"<br>';
}
+845
View File
@@ -46678,6 +46678,851 @@ and represent the current state of research on the topic. The prescribed citatio
)
),
// FHC-4 Finetuning END
//**************************** CORE/search
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'input_search_label',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Suche: {types}',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Search: {types}',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'type_employee',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Mitarbeiter',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'employees',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'type_student',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Studenten',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'students',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'type_prestudent',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Prestudenten',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'prestudents',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'type_room',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Räume',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'rooms',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'type_organisationunit',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Organisationseinheiten',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'organisation units',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'type_cms',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Inhalte',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'sites',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'type_dms',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Dokumente',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'documents',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'button_filter_label',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Filter',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Filter',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'applyfilter_label',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Suche filtern nach:',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Filter search for:',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'action_default_label',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Standard Aktion',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Default Action',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_student_uid',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Student UID',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Student UID',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_prestudent_id',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Prestudent ID',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Prestudent ID',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_emails',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Emails',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Emails',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_group_emails',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Gruppen-Email',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Group-Email',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_employee',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'MitarbeiterIn',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Employee',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_stdkst',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Standard-Kostenstelle',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Standard cost center',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_stdkst_none',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'keine',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'none',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_parent_oe',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Übergeordnete OE',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Parent OU',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_oe_none',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'keine',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'none',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_room_address',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Standort',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Site',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_address_floor',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => '{floor} Stockwerk',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => '{floor} floor',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_address_none',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'N/A',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'N/A',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_workplaces',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Sitzplätze',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Seats',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_workplaces_pc',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => '{max_person}, davon {workplaces} PC-Plätze',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => '{max_person}, of which {workplaces} PC-Workstations',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_workplaces_none',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'N/A',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'N/A',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_building',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Gebäude',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Building',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_equipment',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Zusatz Informationen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Additional information',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_leader',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Leiter',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Leader',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_leader_none',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'N.N.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'N/A',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_number_of_employees',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Mitarbeiter-Anzahl',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Number of employees',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_dms_id',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'DMS ID',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'DMS ID',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_version',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Version',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Version',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_keywords',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Schlagwörter',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Keywords',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'result_content_none',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Kein Inhalt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'No Content',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'error_missing_type',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Kein Ergebnistyp ausgewählt. Bitte mindestens einen Ergebnistyp auswählen.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'No result type selected. Please select at least one result type.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'error_no_results',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Es wurden keine Ergebnisse gefunden.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'No results were found.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'error_unknown_type',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Unbekannter Ergebnistyp: '{type}'.",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "Unknown resulttype: '{type}'.",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'error_general',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Bei der Suche ist ein Fehler aufgetreten. {message}',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'An error occurred while searching. {message}',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'error_missing_config',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Such-Konfiguration $config["{type}"] nicht gefunden.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Search config $config["{type}"] not found.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'error_invalid_config',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Such-Konfiguration für $config["{type}"] ist ungültig: Feld "{field}" fehlt, ist leer oder hat einen ungültigen Typ.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Search config for $config["{type}"] is invalid: field {field} is missing, empty or has an invalid type.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'search',
'phrase' => 'error_invalid_config_searchfield',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Such-Konfiguration ' .
'$config["{type}"]["searchfields"]["{searchfield}"] ' .
'ist ungültig: Feld "{field}" fehlt oder ist unglültig.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Search config $config["{type}"]["searchfields"]["{searchfield}"] is invalid: field {field} is missing or invalid.',
'description' => '',
'insertvon' => 'system'
)
)
),
//**************************** CORE/search end
);