mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-22 09:22:22 +00:00
CMS Search
This commit is contained in:
@@ -671,3 +671,97 @@ $config['room'] = [
|
||||
LEFT JOIN public.tbl_adresse address
|
||||
USING (adresse_id)"
|
||||
];
|
||||
|
||||
$config['cms'] = [
|
||||
'primarykey' => 'contentsprache_id',
|
||||
'table' => 'cms',
|
||||
'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')
|
||||
),
|
||||
cms (contentsprache_id) AS (
|
||||
SELECT contentsprache_id
|
||||
FROM campus.tbl_contentsprache
|
||||
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)
|
||||
)
|
||||
",
|
||||
'searchfields' => [
|
||||
'content' => [
|
||||
'alias' => ['inhalt'],
|
||||
'comparison' => "vector",
|
||||
'field' => "(setweight(to_tsvector('simple', COALESCE(titel, '')), 'A') || setweight(to_tsvector('simple', COALESCE(content, '')::text), 'B'))",
|
||||
'join' => [
|
||||
'table' => "campus.tbl_contentsprache",
|
||||
'using' => "contentsprache_id"
|
||||
]
|
||||
],
|
||||
'content_id' => [
|
||||
'alias' => ['id'],
|
||||
'comparison' => "equal-int",
|
||||
'field' => "content_id",
|
||||
'join' => [
|
||||
'table' => "campus.tbl_contentsprache",
|
||||
'using' => "contentsprache_id"
|
||||
]
|
||||
],
|
||||
'lang' => [
|
||||
'alias' => ['language', 'sprache'],
|
||||
'comparison' => "equals",
|
||||
'field' => "sprache",
|
||||
'join' => [
|
||||
'table' => "campus.tbl_contentsprache",
|
||||
'using' => "contentsprache_id"
|
||||
]
|
||||
]
|
||||
],
|
||||
'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)"
|
||||
];
|
||||
|
||||
@@ -25,7 +25,7 @@ $config['similar'] = [
|
||||
|
||||
$config['vector'] = [
|
||||
'priority' => 1,
|
||||
'rank' => "ts_rank_cd({field}, to_tsquery('simple', {word}))",
|
||||
'rank' => "ts_rank({field}, to_tsquery('simple', {word}))",
|
||||
'compare' => "to_tsquery('simple', {word}) @@ {field}"
|
||||
];
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -168,6 +168,9 @@ class SearchBarLib
|
||||
FROM public.tbl_sprache
|
||||
WHERE sprache=" . $this->_ci->db->escape($lang) . "
|
||||
LIMIT 1
|
||||
),
|
||||
auth (uid) AS (
|
||||
SELECT " . $this->_ci->db->escape(getAuthUID()) . " AS uid
|
||||
)";
|
||||
|
||||
if ($sql_with) {
|
||||
@@ -248,6 +251,9 @@ class SearchBarLib
|
||||
WHERE sprache=" . $this->_ci->db->escape($lang) . "
|
||||
LIMIT 1
|
||||
)");
|
||||
array_unshift($with, "auth (uid) AS (
|
||||
SELECT " . $this->_ci->db->escape(getAuthUID()) . " AS uid
|
||||
)");
|
||||
|
||||
return success("
|
||||
WITH " . implode(", ", $with) . "
|
||||
|
||||
@@ -23,6 +23,7 @@ import studstatus from "./studstatus.js";
|
||||
import stv from "./stv.js";
|
||||
import notiz from "./notiz.js";
|
||||
import betriebsmittel from "./betriebsmittel.js";
|
||||
import language from "./language.js";
|
||||
|
||||
export default {
|
||||
search,
|
||||
@@ -32,5 +33,6 @@ export default {
|
||||
studstatus,
|
||||
stv,
|
||||
notiz,
|
||||
betriebsmittel
|
||||
betriebsmittel,
|
||||
language
|
||||
};
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
import TemplateFrame from "./template/frame.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TemplateFrame
|
||||
},
|
||||
emits: [ 'actionexecuted' ],
|
||||
props: {
|
||||
res: Object,
|
||||
actions: Object
|
||||
},
|
||||
inject: [
|
||||
'languages',
|
||||
'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>';
|
||||
},
|
||||
flag() {
|
||||
if (!this.languages || !this.languages[this.res.language])
|
||||
return "";
|
||||
return "data:image/jpeg;base64," + this.languages[this.res.language].flagge;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<template-frame
|
||||
class="searchbar-result-cms"
|
||||
:res="res"
|
||||
:actions="actions"
|
||||
:title="res.title"
|
||||
image-fallback="fas fa-newspaper fa-4x p-4 text-white bg-primary"
|
||||
@actionexecuted="$emit('actionexecuted')"
|
||||
>
|
||||
<img v-if="flag" :src="flag" class="ms-2">
|
||||
<div v-if="preview" class="searchbar_table" v-html="preview"></div>
|
||||
<div v-else class="searchbar_table text-muted">
|
||||
No Content
|
||||
</div>
|
||||
</template-frame>`
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import ResultPrestudent from "./result/prestudent.js";
|
||||
import ResultEmployee from "./result/employee.js";
|
||||
import ResultOrganisationunit from "./result/organisationunit.js";
|
||||
import ResultRoom from "./result/room.js";
|
||||
import ResultCms from "./result/cms.js";
|
||||
import ResultMergedperson from "./result/mergedperson.js";
|
||||
import ResultMergedstudent from "./result/mergedstudent.js";
|
||||
|
||||
@@ -17,10 +18,17 @@ export default {
|
||||
ResultEmployee,
|
||||
ResultOrganisationunit,
|
||||
ResultRoom,
|
||||
ResultCms,
|
||||
ResultMergedperson,
|
||||
ResultMergedstudent
|
||||
},
|
||||
props: [ "searchoptions", "searchfunction" ],
|
||||
provide() {
|
||||
return {
|
||||
languages: Vue.computed(() => this.languages),
|
||||
query: Vue.computed(() => this.lastQuery)
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchtimer: null,
|
||||
@@ -35,9 +43,22 @@ export default {
|
||||
searching: false,
|
||||
error: null,
|
||||
abortController: null,
|
||||
retry: 5
|
||||
retry: 0,
|
||||
languages: null,
|
||||
lastQuery: ''
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$fhcApi.factory
|
||||
.language.getAll()
|
||||
.then(result => {
|
||||
this.languages = result.data.reduce((a, c) => {
|
||||
a[c.sprache] = c;
|
||||
return a;
|
||||
}, {});
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
beforeMount() {
|
||||
this.updateSearchOptions();
|
||||
},
|
||||
@@ -82,12 +103,14 @@ export default {
|
||||
this.abortController = new AbortController();
|
||||
|
||||
this
|
||||
.searchfunction(this.searchsettings, { signal: this.abortController.signal })
|
||||
.searchfunction(this.searchsettings, { timeout: 50000, signal: this.abortController.signal })
|
||||
.then(response => {
|
||||
if (!response.data) {
|
||||
this.error = 'Bei der Suche ist ein Fehler aufgetreten.';
|
||||
} else {
|
||||
let res = response.data.map(el => ({...el, ...JSON.parse(el.data)}));
|
||||
this.lastQuery = response.meta.searchstring;
|
||||
|
||||
if (this.searchoptions.mergeResults) {
|
||||
let counter = 0;
|
||||
let mergeTypes = [];
|
||||
@@ -131,11 +154,11 @@ export default {
|
||||
this.searchresult = res;
|
||||
}
|
||||
this.searching = false;
|
||||
this.retry = 5;
|
||||
this.retry = 0;
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.code == "ERR_CANCELED") {
|
||||
return this.retry = 5;
|
||||
return this.retry = 0;
|
||||
}
|
||||
if (error.code == "ECONNABORTED" && this.retry) {
|
||||
this.retry--;
|
||||
@@ -144,7 +167,7 @@ export default {
|
||||
|
||||
this.error = 'Bei der Suche ist ein Fehler aufgetreten.' + ' ' + error.message;
|
||||
this.searching = false;
|
||||
this.retry = 5;
|
||||
this.retry = 0;
|
||||
});
|
||||
},
|
||||
refreshsearch() {
|
||||
@@ -239,6 +262,7 @@ export default {
|
||||
<result-employee v-else-if="res.type === 'unassigned_employee'" :res="res" :actions="searchoptions.actions.employee" @actionexecuted="hideresult"></result-employee>
|
||||
<result-organisationunit v-else-if="res.type === 'organisationunit'" :res="res" :actions="searchoptions.actions.organisationunit" @actionexecuted="hideresult"></result-organisationunit>
|
||||
<result-room v-else-if="res.type === 'room'" :res="res" :actions="searchoptions.actions.room" @actionexecuted="hideresult"></result-room>
|
||||
<result-cms v-else-if="res.type === 'cms'" :res="res" :actions="searchoptions.actions.cms" @actionexecuted="hideresult"></result-cms>
|
||||
<result-mergedperson v-else-if="res.type === 'mergedperson'" :res="res" :actions="searchoptions.actions.mergedperson" @actionexecuted="hideresult"></result-mergedperson>
|
||||
<result-mergedstudent v-else-if="res.type === 'mergedstudent'" :res="res" :actions="searchoptions.actions.mergedstudent" @actionexecuted="hideresult"></result-mergedstudent>
|
||||
<div v-else>Unbekannter Ergebnistyp: '{{ res.type }}'.</div>
|
||||
|
||||
@@ -169,3 +169,18 @@ FROM pg_indexes WHERE indexname = 'idx_tbl_organisationseinheit_fts_bezeichnung_
|
||||
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>';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user