mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-10-12 15:04:04 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7275d6e897 | ||
|
|
349a0641cd | ||
|
|
304870f18c | ||
|
|
71e3cdb78d | ||
|
|
a0ea54d0c0 | ||
|
|
de627171d5 | ||
|
|
6f3c0fb174 | ||
|
|
76130bbf2c | ||
|
|
708ffac47e | ||
|
|
ab854067eb | ||
|
|
b59c962f60 | ||
|
|
d57985b90a |
@@ -61,6 +61,15 @@ $route['api/v1/organisation/[O|o]rganisationseinheit/(:any)'] = 'api/v1/organisa
|
||||
$route['api/v1/ressource/[B|b]etriebsmittelperson/(:any)'] = 'api/v1/ressource/betriebsmittelperson2/$1';
|
||||
$route['api/v1/system/[S|s]prache/(:any)'] = 'api/v1/system/sprache2/$1';
|
||||
|
||||
// Legacy CMS news routes
|
||||
$route['api/frontend/v1/Cms/getNewsItem/(:num)'] = 'api/frontend/v1/NewsAdministrationAPI/getNewsItem/$1';
|
||||
$route['api/frontend/v1/Cms/storeNewsItem'] = 'api/frontend/v1/NewsAdministrationAPI/storeNewsItem';
|
||||
$route['api/frontend/v1/Cms/updateNewsItem/(:num)'] = 'api/frontend/v1/NewsAdministrationAPI/updateNewsItem/$1';
|
||||
$route['api/frontend/v1/Cms/deleteNewsItem/(:num)'] = 'api/frontend/v1/NewsAdministrationAPI/deleteNewsItem/$1';
|
||||
|
||||
// Keep legacy uppercase URLs refreshable while using the canonical controller method casing
|
||||
$route['CisVue/Cms/NewsAdministration'] = 'CisVue/Cms/newsAdministration';
|
||||
|
||||
$route['Cis/LvPlan/.*'] = 'Cis/LvPlan/index/$1';
|
||||
$route['Cis/MyLvPlan/.*'] = 'Cis/MyLvPlan/index/$1';
|
||||
$route['Cis/MyLv/.*'] = 'Cis/MyLv/index/$1';
|
||||
|
||||
@@ -17,14 +17,15 @@ class Cms extends Auth_Controller
|
||||
array(
|
||||
'content' => 'basis/cis:r',
|
||||
'getNews' => 'basis/cis:r',
|
||||
'getNewsRowCount' => 'basis/cis:r',
|
||||
'getRoomInformation' => 'basis/cis:r',
|
||||
'news' => 'basis/cis:r'
|
||||
'news' => 'basis/cis:r',
|
||||
'newsAdministration' => ['basis/news:r'],
|
||||
)
|
||||
);
|
||||
|
||||
// Loads Libraries
|
||||
$this->load->library('CmsLib');
|
||||
$this->load->library('PermissionLib');
|
||||
|
||||
// Loads phrases system
|
||||
$this->loadPhrases([
|
||||
@@ -84,9 +85,29 @@ class Cms extends Auth_Controller
|
||||
public function news($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true)
|
||||
{
|
||||
$viewData = array();
|
||||
$this->load->view('CisRouterView/CisRouterView.php', ['viewData'=>$viewData, 'route' => 'News']);
|
||||
$this->load->view('CisRouterView/CisRouterView.php', [
|
||||
'viewData'=>$viewData,
|
||||
'route' => 'News',
|
||||
'permissions' => [
|
||||
'basis/news_r' => $this->permissionlib->isBerechtigt('basis/news', 's'),
|
||||
'basis/news_w' => $this->permissionlib->isBerechtigt('basis/news', 'suid'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function newsAdministration()
|
||||
{
|
||||
$viewData = array();
|
||||
$this->load->view('CisRouterView/CisRouterView.php', [
|
||||
'viewData'=>$viewData,
|
||||
'route' => 'NewsAdministration',
|
||||
'permissions' => [
|
||||
'basis/news_r' => $this->permissionlib->isBerechtigt('basis/news', 's'),
|
||||
'basis/news_w' => $this->permissionlib->isBerechtigt('basis/news', 'suid'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getRoomInformation($ort_kurzbz)
|
||||
{
|
||||
// Load Config
|
||||
|
||||
@@ -36,14 +36,15 @@ class Cms extends FHCAPI_Controller
|
||||
'ContentID' => self::PERM_LOGGED,
|
||||
'getOrtKurzbzContent' => self::PERM_LOGGED,
|
||||
'content' => self::PERM_LOGGED,
|
||||
'news' => self::PERM_LOGGED,
|
||||
'getNewsRowCount' => self::PERM_LOGGED,
|
||||
'newsForWidget' => self::PERM_LOGGED,
|
||||
'getNews' => self::PERM_LOGGED,
|
||||
|
||||
]);
|
||||
|
||||
$this->load->model('content/News_model', 'NewsModel');
|
||||
|
||||
$this->load->model('crm/Student_model', 'StudentModel');
|
||||
$this->load->model('crm/Prestudent_model', 'PrestudentModel');
|
||||
|
||||
// setting up the papgination_size
|
||||
$this->page_size = 10;
|
||||
|
||||
@@ -59,7 +60,6 @@ class Cms extends FHCAPI_Controller
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Private methods
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
@@ -106,7 +106,7 @@ class Cms extends FHCAPI_Controller
|
||||
$this->terminateWithSuccess($content_id);
|
||||
}
|
||||
|
||||
public function news()
|
||||
public function newsForWidget()
|
||||
{
|
||||
|
||||
// form validation
|
||||
@@ -118,7 +118,7 @@ class Cms extends FHCAPI_Controller
|
||||
$this->load->model('content/news_model', 'NewsModel');
|
||||
|
||||
$limit = $this->input->get('limit',TRUE);
|
||||
|
||||
|
||||
//query the news
|
||||
$news = $this->NewsModel->getAll($limit);
|
||||
|
||||
@@ -157,23 +157,7 @@ class Cms extends FHCAPI_Controller
|
||||
|
||||
}
|
||||
|
||||
public function getNewsRowCount($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $fachbereich_kurzbz = null, $maxalter = 0, $edit = false, $sichtbar = true, $page = 1, $page_size = 10)
|
||||
{
|
||||
list($studiengang_kz, $semester) = $this->cmslib->getStgAndSem($studiengang_kz, $semester);
|
||||
$all = $edit;
|
||||
|
||||
$this->load->model('content/News_model','NewsModel');
|
||||
|
||||
$num_rows = $this->NewsModel->countNewsWithContent(getSprache(), $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen);
|
||||
|
||||
$num_rows = $this->getDataOrTerminateWithError($num_rows);
|
||||
|
||||
$this->terminateWithSuccess($num_rows);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true)
|
||||
public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = false, $titel = '', $edit = false, $sichtbar = true)
|
||||
{
|
||||
//form validation
|
||||
$this->load->library('form_validation');
|
||||
@@ -193,15 +177,45 @@ class Cms extends FHCAPI_Controller
|
||||
|
||||
// default value for the page_size is 10
|
||||
$page_size = $page_size ?? 10;
|
||||
|
||||
$passedSichtbar = $this->input->get('published', true);
|
||||
if($passedSichtbar !== null)
|
||||
{
|
||||
$sichtbar = $passedSichtbar;
|
||||
}
|
||||
|
||||
$news = $this->cmslib->getNews($infoscreen, $studiengang_kz, $semester, $mischen, $titel, $edit, $sichtbar, $page, $page_size, $sprache);
|
||||
$filterForDegreePrograms = false;
|
||||
$entitledDegreePrograms = [];
|
||||
$student = $this->StudentModel->loadWhere(['student_uid' => getAuthUID()]);
|
||||
if(isError($student))
|
||||
{
|
||||
$this->terminateWithError(getError($student));
|
||||
}
|
||||
if(hasData($student))
|
||||
{
|
||||
$student = current(getData($student));
|
||||
|
||||
$degreeProgramSemesterDataResult = $this->PrestudentModel->getStudiengaengAndAusbildungssemesterByStudentUid(getAuthUID());
|
||||
$degreeProgramSemesterData = $this->getDataOrTerminateWithError($degreeProgramSemesterDataResult)[0];
|
||||
if ($degreeProgramSemesterData) {
|
||||
$studiengang_kz = isset($degreeProgramSemesterData->studiengang_kz) ? $degreeProgramSemesterData->studiengang_kz : null;
|
||||
$semester = isset($degreeProgramSemesterData->ausbildungssemester) ? $degreeProgramSemesterData->ausbildungssemester : null;
|
||||
}
|
||||
|
||||
$mischen = true;
|
||||
} else {
|
||||
$filterForDegreePrograms = true;
|
||||
$entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news");
|
||||
array_push($entitledDegreePrograms, 0);
|
||||
}
|
||||
|
||||
$news = $this->cmslib->getNews($infoscreen, $studiengang_kz, $semester, $mischen, $titel, $edit, $sichtbar, $page, $page_size, $sprache, $filterForDegreePrograms, $entitledDegreePrograms, true);
|
||||
$news = $this->getDataOrTerminateWithError($news);
|
||||
|
||||
$this->addMeta('phrases', json_decode($this->p->getJson()));
|
||||
$this->terminateWithSuccess($news);
|
||||
$this->addMeta('row_count', $news["row_count"] ?? 0);
|
||||
$this->terminateWithSuccess($news["content"]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,937 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 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');
|
||||
|
||||
class NewsAdministrationAPI extends FHCAPI_Controller
|
||||
{
|
||||
/**
|
||||
* News API constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct([
|
||||
'getNews' => 'basis/news:r',
|
||||
'getNewsItem' => 'basis/news:r',
|
||||
'storeNewsItem' => 'basis/news:w',
|
||||
'updateNewsItem' => 'basis/news:w',
|
||||
'deleteNewsItem' => 'basis/news:w'
|
||||
]);
|
||||
|
||||
$this->load->model('content/Content_model', 'ContentModel');
|
||||
$this->load->model('content/Contentsprache_model', 'ContentspracheModel');
|
||||
$this->load->model('content/News_model', 'NewsModel');
|
||||
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
|
||||
$this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
|
||||
$this->load->model('system/Sprache_model', 'SpracheModel');
|
||||
$this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
|
||||
|
||||
$this->loadPhrases([
|
||||
'global'
|
||||
]);
|
||||
|
||||
$this->load->library('CmsLib');
|
||||
|
||||
$this->load->helper('hlp_sancho_helper');
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $titel = '', $active = true, $sichtbar = true)
|
||||
{
|
||||
$this->load->library('form_validation');
|
||||
$this->form_validation->set_data($_GET);
|
||||
$this->form_validation->set_rules('page','Page','required|is_natural');
|
||||
$this->form_validation->set_rules('page_size', 'PageSize', 'is_natural');
|
||||
if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
|
||||
|
||||
$page = intval($this->input->get('page', true));
|
||||
$page_size = intval($this->input->get('page_size', true));
|
||||
$sprache = $this->input->get('sprache', true);
|
||||
if(!$sprache)
|
||||
{
|
||||
$sprache = getUserLanguage();
|
||||
}
|
||||
|
||||
// default value for the page_size is 10
|
||||
$page_size = $page_size ?? 10;
|
||||
|
||||
$passedSichtbar = $this->input->get('published', true);
|
||||
if($passedSichtbar !== null)
|
||||
{
|
||||
$sichtbar = $passedSichtbar;
|
||||
}
|
||||
|
||||
$passedIsActive = $this->input->get('isActive', true);
|
||||
if ($passedIsActive !== null)
|
||||
{
|
||||
$isActive = filter_var(
|
||||
$passedIsActive,
|
||||
FILTER_VALIDATE_BOOLEAN,
|
||||
FILTER_NULL_ON_FAILURE
|
||||
);
|
||||
if ($isActive !== null)
|
||||
{
|
||||
$active = $isActive;
|
||||
}
|
||||
}
|
||||
|
||||
$degreeProgramShortCode = $this->input->get('degreeProgramShortCode', true);
|
||||
if ($degreeProgramShortCode !== null && $degreeProgramShortCode !== '')
|
||||
{
|
||||
if (!ctype_digit((string)$degreeProgramShortCode))
|
||||
{
|
||||
$this->terminateWithValidationErrors([
|
||||
'degreeProgramShortCode' => 'A valid degree program is required'
|
||||
]);
|
||||
}
|
||||
$studiengang_kz = $degreeProgramShortCode;
|
||||
}
|
||||
|
||||
$passedSemester = $this->input->get('semester', true);
|
||||
if ($passedSemester !== null && $passedSemester !== '')
|
||||
{
|
||||
if (
|
||||
!ctype_digit((string)$passedSemester)
|
||||
|| (int)$passedSemester < 1
|
||||
|| (int)$passedSemester > 8
|
||||
)
|
||||
{
|
||||
$this->terminateWithValidationErrors([
|
||||
'semester' => 'A valid semester is required'
|
||||
]);
|
||||
}
|
||||
$semester = filter_var($passedSemester, FILTER_VALIDATE_INT);
|
||||
}
|
||||
|
||||
$entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news");
|
||||
|
||||
$news = $this->cmslib->getNews($infoscreen, $studiengang_kz, $semester, false, $titel, true, $sichtbar, $page, $page_size, $sprache, true, $entitledDegreePrograms, $active);
|
||||
$news = $this->getDataOrTerminateWithError($news);
|
||||
|
||||
$this->addMeta('row_count', $news["row_count"] ?? 0);
|
||||
$this->terminateWithSuccess($news["content"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one news entry with all available language versions for editing.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getNewsItem($newsId)
|
||||
{
|
||||
if (!is_numeric($newsId) || (int)$newsId < 1)
|
||||
{
|
||||
$this->terminateWithValidationErrors([
|
||||
'newsId' => 'A valid news ID is required'
|
||||
]);
|
||||
}
|
||||
|
||||
$newsId = (int)$newsId;
|
||||
$newsResult = $this->NewsModel->load($newsId);
|
||||
|
||||
if (isError($newsResult))
|
||||
{
|
||||
$this->terminateWithError(getError($newsResult), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
if (!hasData($newsResult))
|
||||
{
|
||||
$this->terminateWithValidationErrors([
|
||||
'newsId' => 'The requested news entry does not exist'
|
||||
]);
|
||||
}
|
||||
|
||||
$news = current(getData($newsResult));
|
||||
$entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news");
|
||||
|
||||
if (!in_array($news->studiengang_kz, $entitledDegreePrograms)) {
|
||||
$this->terminateWithError('You are not entitled to access this news entry', self::ERROR_TYPE_PERMISSION);
|
||||
}
|
||||
$translationsResult = $this->ContentspracheModel->loadWhere([
|
||||
'content_id' => $news->content_id
|
||||
]);
|
||||
|
||||
if (isError($translationsResult))
|
||||
{
|
||||
$this->terminateWithError(getError($translationsResult), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$languages = [];
|
||||
|
||||
foreach ((array)getData($translationsResult) as $translation)
|
||||
{
|
||||
$languages[$translation->sprache] = true;
|
||||
}
|
||||
|
||||
$translations = [];
|
||||
|
||||
foreach (array_keys($languages) as $language)
|
||||
{
|
||||
$contentResult = $this->ContentModel->getContent(
|
||||
$news->content_id,
|
||||
$language,
|
||||
null,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
if (isError($contentResult))
|
||||
{
|
||||
$this->terminateWithError(getError($contentResult), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$content = getData($contentResult);
|
||||
$document = new DOMDocument('1.0', 'UTF-8');
|
||||
|
||||
if ($content->content !== '' && $document->loadXML($content->content) === false)
|
||||
{
|
||||
$this->terminateWithError(
|
||||
'The stored news content is invalid',
|
||||
self::ERROR_TYPE_GENERAL
|
||||
);
|
||||
}
|
||||
|
||||
$author = $document->getElementsByTagName('verfasser')->item(0);
|
||||
$title = $document->getElementsByTagName('betreff')->item(0);
|
||||
$text = $document->getElementsByTagName('text')->item(0);
|
||||
|
||||
$translations[] = [
|
||||
'language' => $language,
|
||||
'author' => $author ? $author->nodeValue : '',
|
||||
'title' => $title ? $title->nodeValue : '',
|
||||
'text' => $text ? str_replace('dms.php', APP_ROOT . 'cms/dms.php', $text->nodeValue) : '',
|
||||
'isPublished' => (bool)$content->sichtbar
|
||||
];
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess([
|
||||
'newsId' => (int)$news->news_id,
|
||||
'visibleFrom' => $news->datum,
|
||||
'visibleTo' => $news->datum_bis,
|
||||
'degreeProgramShortCode' => $news->studiengang_kz,
|
||||
'semester' => $news->semester,
|
||||
'translations' => $translations
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a news entry and all supplied translations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function storeNewsItem()
|
||||
{
|
||||
$this->load->library('form_validation');
|
||||
$postData = $this->input->post(null, true);
|
||||
$this->form_validation->set_data(is_array($postData) ? $postData : []);
|
||||
$this->form_validation->set_rules('visibleFrom', 'Visible from', 'required|is_valid_date');
|
||||
$this->form_validation->set_rules('visibleTo', 'Visible to', 'is_valid_date');
|
||||
$this->form_validation->set_rules(
|
||||
'degreeProgramShortCode',
|
||||
'Degree program',
|
||||
'required|is_natural'
|
||||
);
|
||||
$this->form_validation->set_rules(
|
||||
'semester',
|
||||
'Semester',
|
||||
'is_natural_no_zero|less_than_equal_to[8]'
|
||||
);
|
||||
|
||||
$errors = $this->form_validation->run() === false
|
||||
? $this->form_validation->error_array()
|
||||
: [];
|
||||
$visibleFrom = $this->input->post('visibleFrom', true);
|
||||
$visibleTo = $this->input->post('visibleTo', true);
|
||||
$translations = $this->input->post('translations', true);
|
||||
|
||||
if (
|
||||
is_string($visibleFrom)
|
||||
&& is_string($visibleTo)
|
||||
&& $visibleTo !== ''
|
||||
&& is_valid_date($visibleFrom)
|
||||
&& is_valid_date($visibleTo)
|
||||
&& new DateTime($visibleTo) < new DateTime($visibleFrom)
|
||||
)
|
||||
{
|
||||
$errors['visibleTo'] = 'Visible to must not be before Visible from';
|
||||
}
|
||||
|
||||
if (!is_array($translations) || count($translations) === 0)
|
||||
{
|
||||
$errors['translations'] = 'At least one translation is required';
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ($translations as $index => $translation)
|
||||
{
|
||||
if (!is_array($translation))
|
||||
{
|
||||
$errors['translations.' . $index] = 'Each translation must be an object';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($errors) > 0)
|
||||
{
|
||||
$this->terminateWithValidationErrors($errors);
|
||||
}
|
||||
|
||||
|
||||
$this->_ensureUserIsEntitledForDegreeProgram($this->input->post('degreeProgramShortCode', true));
|
||||
|
||||
$degreeProgramShortCode = $this->input->post('degreeProgramShortCode', true);
|
||||
$semester = $this->input->post('semester', true);
|
||||
$semester = $semester === '' ? null : $semester;
|
||||
$visibleTo = $visibleTo === '' ? null : $visibleTo;
|
||||
|
||||
$studiengang = $this->_getStudiengang($degreeProgramShortCode);
|
||||
|
||||
$data = [
|
||||
'studiengang_kz' => $degreeProgramShortCode,
|
||||
'semester' => $semester,
|
||||
'datum' => $visibleFrom,
|
||||
'datum_bis' => $visibleTo,
|
||||
'translations' => []
|
||||
];
|
||||
|
||||
foreach ($translations as $index => $translation)
|
||||
{
|
||||
$data['translations'][$index] = [
|
||||
'sprache' => $translation['language'] ?? '',
|
||||
'verfasser' => $translation['author'] ?? '',
|
||||
'betreff' => $translation['title'] ?? '',
|
||||
'text' => $translation['text'] ?? '',
|
||||
'sichtbar' => isset($translation['isPublished']) ? (bool)$translation['isPublished'] : false
|
||||
];
|
||||
}
|
||||
|
||||
$this->_validateLanguages($data['translations']);
|
||||
|
||||
foreach ($data['translations'] as $index => $translation)
|
||||
{
|
||||
$data['translations'][$index]['content'] = $this->_buildNewsXml($translation);
|
||||
}
|
||||
|
||||
$uid = getAuthUID();
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
if ($this->db->trans_begin() === false)
|
||||
{
|
||||
$this->terminateWithError($this->db->error(), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$contentResult = $this->ContentModel->insert([
|
||||
'template_kurzbz' => 'news',
|
||||
'oe_kurzbz' => $studiengang->oe_kurzbz,
|
||||
'aktiv' => true,
|
||||
'menu_open' => false,
|
||||
'insertamum' => $now,
|
||||
'insertvon' => $uid
|
||||
]);
|
||||
$contentId = (int)$this->_getInsertedIdOrRollback($contentResult);
|
||||
|
||||
$newsResult = $this->NewsModel->insert([
|
||||
'uid' => $uid,
|
||||
'studiengang_kz' => $data['studiengang_kz'],
|
||||
'semester' => $data['semester'],
|
||||
'datum' => $data['datum'],
|
||||
'datum_bis' => $data['datum_bis'],
|
||||
'content_id' => $contentId,
|
||||
'insertamum' => $now,
|
||||
'insertvon' => $uid,
|
||||
'updateamum' => $now,
|
||||
'updatevon' => $uid
|
||||
]);
|
||||
$newsId = (int)$this->_getInsertedIdOrRollback($newsResult);
|
||||
|
||||
foreach ($data['translations'] as $translation)
|
||||
{
|
||||
$translation['content'] = $this->formatContentURLsToRelative($translation['content']);
|
||||
|
||||
$translationResult = $this->ContentspracheModel->insert([
|
||||
'sprache' => $translation['sprache'],
|
||||
'content_id' => $contentId,
|
||||
'version' => 1,
|
||||
'sichtbar' => $translation['sichtbar'],
|
||||
'content' => $translation['content'],
|
||||
'titel' => $translation['betreff'],
|
||||
'insertamum' => $now,
|
||||
'insertvon' => $uid,
|
||||
'updateamum' => $now,
|
||||
'updatevon' => $uid
|
||||
]);
|
||||
|
||||
$this->_getInsertedIdOrRollback($translationResult);
|
||||
}
|
||||
|
||||
$newsItemResult = $this->NewsModel->load($newsId);
|
||||
$newsItem = $this->getDataOrTerminateWithError($newsItemResult);
|
||||
$newsItem = $newsItem[0];
|
||||
|
||||
$this->_sendNewsItemTranslationRequiredMail(
|
||||
$newsItem
|
||||
);
|
||||
|
||||
if ($this->db->trans_status() === false)
|
||||
{
|
||||
$error = $this->db->error();
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError($error, self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
if ($this->db->trans_commit() === false)
|
||||
{
|
||||
$error = $this->db->error();
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError($error, self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess($newsId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a news entry and its supplied translations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateNewsItem($newsId)
|
||||
{
|
||||
if (!is_numeric($newsId) || (int)$newsId < 1)
|
||||
{
|
||||
$this->terminateWithValidationErrors([
|
||||
'newsId' => 'A valid news ID is required'
|
||||
]);
|
||||
}
|
||||
|
||||
$newsId = (int)$newsId;
|
||||
$existingNewsResult = $this->NewsModel->load($newsId);
|
||||
|
||||
if (isError($existingNewsResult))
|
||||
{
|
||||
$this->terminateWithError(getError($existingNewsResult), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
if (!hasData($existingNewsResult))
|
||||
{
|
||||
$this->terminateWithValidationErrors([
|
||||
'newsId' => 'The requested news entry does not exist'
|
||||
]);
|
||||
}
|
||||
|
||||
$existingNews = current(getData($existingNewsResult));
|
||||
|
||||
$this->_ensureUserIsEntitledForDegreeProgram($existingNews->studiengang_kz);
|
||||
|
||||
$this->load->library('form_validation');
|
||||
$postData = $this->input->post(null, true);
|
||||
$this->form_validation->set_data(is_array($postData) ? $postData : []);
|
||||
$this->form_validation->set_rules('visibleFrom', 'Visible from', 'required|is_valid_date');
|
||||
$this->form_validation->set_rules('visibleTo', 'Visible to', 'is_valid_date');
|
||||
$this->form_validation->set_rules(
|
||||
'degreeProgramShortCode',
|
||||
'Degree program',
|
||||
'required|is_natural'
|
||||
);
|
||||
$this->form_validation->set_rules(
|
||||
'semester',
|
||||
'Semester',
|
||||
'is_natural_no_zero|less_than_equal_to[8]'
|
||||
);
|
||||
|
||||
$errors = $this->form_validation->run() === false
|
||||
? $this->form_validation->error_array()
|
||||
: [];
|
||||
$visibleFrom = $this->input->post('visibleFrom', true);
|
||||
$visibleTo = $this->input->post('visibleTo', true);
|
||||
$translations = $this->input->post('translations', true);
|
||||
|
||||
if (
|
||||
is_string($visibleFrom)
|
||||
&& is_string($visibleTo)
|
||||
&& $visibleTo !== ''
|
||||
&& is_valid_date($visibleFrom)
|
||||
&& is_valid_date($visibleTo)
|
||||
&& new DateTime($visibleTo) < new DateTime($visibleFrom)
|
||||
)
|
||||
{
|
||||
$errors['visibleTo'] = 'Visible to must not be before Visible from';
|
||||
}
|
||||
|
||||
if (!is_array($translations) || count($translations) === 0)
|
||||
{
|
||||
$errors['translations'] = 'At least one translation is required';
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ($translations as $index => $translation)
|
||||
{
|
||||
if (!is_array($translation))
|
||||
{
|
||||
$errors['translations.' . $index] = 'Each translation must be an object';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($errors) > 0)
|
||||
{
|
||||
$this->terminateWithValidationErrors($errors);
|
||||
}
|
||||
|
||||
$degreeProgramShortCode = $this->input->post('degreeProgramShortCode', true);
|
||||
$semester = $this->input->post('semester', true);
|
||||
$semester = $semester === '' ? null : $semester;
|
||||
$visibleTo = $visibleTo === '' ? null : $visibleTo;
|
||||
|
||||
$studiengang = $this->_getStudiengang($degreeProgramShortCode);
|
||||
|
||||
$data = [
|
||||
'studiengang_kz' => $degreeProgramShortCode,
|
||||
'semester' => $semester,
|
||||
'datum' => $visibleFrom,
|
||||
'datum_bis' => $visibleTo,
|
||||
'translations' => []
|
||||
];
|
||||
|
||||
foreach ($translations as $index => $translation)
|
||||
{
|
||||
$data['translations'][$index] = [
|
||||
'sprache' => $translation['language'] ?? '',
|
||||
'verfasser' => $translation['author'] ?? '',
|
||||
'betreff' => $translation['title'] ?? '',
|
||||
'text' => $translation['text'] ?? '',
|
||||
'sichtbar' => isset($translation['isPublished']) ? (bool)$translation['isPublished'] : false
|
||||
];
|
||||
}
|
||||
|
||||
$this->_validateLanguages($data['translations']);
|
||||
|
||||
foreach ($data['translations'] as $index => $translation)
|
||||
{
|
||||
$data['translations'][$index]['content'] = $this->_buildNewsXml($translation);
|
||||
}
|
||||
|
||||
$uid = getAuthUID();
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
if ($this->db->trans_begin() === false)
|
||||
{
|
||||
$this->terminateWithError($this->db->error(), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$contentResult = $this->ContentModel->update($existingNews->content_id, [
|
||||
'oe_kurzbz' => $studiengang->oe_kurzbz,
|
||||
'updateamum' => $now,
|
||||
'updatevon' => $uid
|
||||
]);
|
||||
|
||||
if (isError($contentResult))
|
||||
{
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError(getError($contentResult), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$newsResult = $this->NewsModel->update($newsId, [
|
||||
'studiengang_kz' => $data['studiengang_kz'],
|
||||
'semester' => $data['semester'],
|
||||
'datum' => $data['datum'],
|
||||
'datum_bis' => $data['datum_bis'],
|
||||
'updateamum' => $now,
|
||||
'updatevon' => $uid
|
||||
]);
|
||||
|
||||
if (isError($newsResult))
|
||||
{
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError(getError($newsResult), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$existingTranslationsResult = $this->ContentspracheModel->loadWhere([
|
||||
'content_id' => $existingNews->content_id
|
||||
]);
|
||||
|
||||
if (isError($existingTranslationsResult))
|
||||
{
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError(getError($existingTranslationsResult), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$existingTranslations = [];
|
||||
|
||||
foreach ((array)getData($existingTranslationsResult) as $existingTranslation)
|
||||
{
|
||||
$language = $existingTranslation->sprache;
|
||||
|
||||
if (
|
||||
!isset($existingTranslations[$language])
|
||||
|| (int)$existingTranslation->version > (int)$existingTranslations[$language]->version
|
||||
)
|
||||
{
|
||||
$existingTranslations[$language] = $existingTranslation;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data['translations'] as $translation)
|
||||
{
|
||||
$translation['content'] = $this->formatContentURLsToRelative($translation['content']);
|
||||
$translationData = [
|
||||
'sichtbar' => $translation['sichtbar'],
|
||||
'content' => $translation['content'],
|
||||
'titel' => $translation['betreff'],
|
||||
'updateamum' => $now,
|
||||
'updatevon' => $uid
|
||||
];
|
||||
|
||||
$existingTranslation = $existingTranslations[$translation['sprache']] ?? null;
|
||||
|
||||
if ($existingTranslation)
|
||||
{
|
||||
$translationResult = $this->ContentspracheModel->update(
|
||||
$existingTranslation->contentsprache_id,
|
||||
$translationData
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$translationResult = $this->ContentspracheModel->insert([
|
||||
'sprache' => $translation['sprache'],
|
||||
'content_id' => $existingNews->content_id,
|
||||
'version' => 1,
|
||||
'insertamum' => $now,
|
||||
'insertvon' => $uid
|
||||
] + $translationData);
|
||||
}
|
||||
|
||||
$this->_getInsertedIdOrRollback($translationResult);
|
||||
}
|
||||
|
||||
if ($this->db->trans_status() === false)
|
||||
{
|
||||
$error = $this->db->error();
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError($error, self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
if ($this->db->trans_commit() === false)
|
||||
{
|
||||
$error = $this->db->error();
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError($error, self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess($newsId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes one news entry and its linked content.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteNewsItem($newsId)
|
||||
{
|
||||
if (!is_numeric($newsId) || (int)$newsId < 1)
|
||||
{
|
||||
$this->terminateWithValidationErrors([
|
||||
'newsId' => 'A valid news ID is required'
|
||||
]);
|
||||
}
|
||||
|
||||
$newsId = (int)$newsId;
|
||||
$newsResult = $this->NewsModel->load($newsId);
|
||||
|
||||
if (isError($newsResult))
|
||||
{
|
||||
$this->terminateWithError(getError($newsResult), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
if (!hasData($newsResult))
|
||||
{
|
||||
$this->terminateWithValidationErrors([
|
||||
'newsId' => 'The requested news entry does not exist'
|
||||
]);
|
||||
}
|
||||
|
||||
$news = current(getData($newsResult));
|
||||
|
||||
|
||||
$this->_ensureUserIsEntitledForDegreeProgram($news->studiengang_kz);
|
||||
|
||||
if ($this->db->trans_begin() === false)
|
||||
{
|
||||
$this->terminateWithError($this->db->error(), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$newsDeleteResult = $this->NewsModel->delete($newsId);
|
||||
|
||||
if (isError($newsDeleteResult))
|
||||
{
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError(getError($newsDeleteResult), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$this->db->where('content_id', $news->content_id);
|
||||
|
||||
if ($this->db->delete('campus.tbl_contentsprache') === false)
|
||||
{
|
||||
$error = $this->db->error();
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError($error, self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$contentDeleteResult = $this->ContentModel->delete($news->content_id);
|
||||
|
||||
if (isError($contentDeleteResult))
|
||||
{
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError(getError($contentDeleteResult), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
if ($this->db->trans_status() === false)
|
||||
{
|
||||
$error = $this->db->error();
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError($error, self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
if ($this->db->trans_commit() === false)
|
||||
{
|
||||
$error = $this->db->error();
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError($error, self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess($newsId);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Private methods
|
||||
|
||||
/**
|
||||
* Loads the requested Studiengang or reports a validation error.
|
||||
*
|
||||
* @param integer $studiengangKz
|
||||
* @return stdClass
|
||||
*/
|
||||
private function _getStudiengang($studiengangKz)
|
||||
{
|
||||
$result = $this->StudiengangModel->load($studiengangKz);
|
||||
|
||||
if (isError($result))
|
||||
{
|
||||
$this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
if (!hasData($result))
|
||||
{
|
||||
$this->terminateWithValidationErrors([
|
||||
'studiengang_kz' => 'The requested Studiengang does not exist'
|
||||
]);
|
||||
}
|
||||
|
||||
$studiengaenge = getData($result);
|
||||
|
||||
return current($studiengaenge);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that all supplied languages exist in tbl_sprache.
|
||||
*
|
||||
* @param array $translations
|
||||
* @return void
|
||||
*/
|
||||
private function _validateLanguages($translations)
|
||||
{
|
||||
$languages = [];
|
||||
|
||||
foreach ($translations as $translation)
|
||||
{
|
||||
$languages[] = $translation['sprache'];
|
||||
}
|
||||
|
||||
$result = $this->SpracheModel->loadMultiple($languages);
|
||||
|
||||
if (isError($result))
|
||||
{
|
||||
$this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
$existingLanguages = [];
|
||||
|
||||
foreach ((array)getData($result) as $language)
|
||||
{
|
||||
$existingLanguages[$language->sprache] = true;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
|
||||
foreach ($translations as $index => $translation)
|
||||
{
|
||||
if (!isset($existingLanguages[$translation['sprache']]))
|
||||
{
|
||||
$errors['translations.' . $index . '.sprache'] = 'The requested language does not exist';
|
||||
}
|
||||
}
|
||||
|
||||
if (count($errors) > 0)
|
||||
{
|
||||
$this->terminateWithValidationErrors($errors);
|
||||
}
|
||||
}
|
||||
|
||||
private function _ensureUserIsEntitledForDegreeProgram($degreeProgramShortCode)
|
||||
{
|
||||
$entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news:w");
|
||||
if (!is_array($entitledDegreePrograms))
|
||||
{
|
||||
$this->terminateWithError('Failed to retrieve entitled degree programs for the user', self::ERROR_TYPE_GENERAL);
|
||||
}
|
||||
|
||||
if (!in_array($degreeProgramShortCode, $entitledDegreePrograms)) {
|
||||
$this->terminateWithError("User is not entitled for the specified degree program");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns an inserted identifier or rolls the transaction back on error.
|
||||
*
|
||||
* @param stdClass $result
|
||||
* @return mixed
|
||||
*/
|
||||
private function _getInsertedIdOrRollback($result)
|
||||
{
|
||||
if (isError($result))
|
||||
{
|
||||
$this->db->trans_rollback();
|
||||
$this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
|
||||
}
|
||||
|
||||
return getData($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the XML format consumed by existing news readers.
|
||||
*
|
||||
* @param array $translation
|
||||
* @return string
|
||||
*/
|
||||
private function _buildNewsXml($translation)
|
||||
{
|
||||
$document = new DOMDocument('1.0', 'UTF-8');
|
||||
$news = $document->createElement('news');
|
||||
$document->appendChild($news);
|
||||
|
||||
$this->_appendCdataElement($document, $news, 'verfasser', $translation['verfasser']);
|
||||
$this->_appendCdataElement($document, $news, 'betreff', $translation['betreff']);
|
||||
$this->_appendCdataElement($document, $news, 'text', $translation['text']);
|
||||
|
||||
return $document->saveXML($news);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a CDATA element and safely preserves CDATA terminators.
|
||||
*
|
||||
* @param DOMDocument $document
|
||||
* @param DOMElement $parent
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @return void
|
||||
*/
|
||||
private function _appendCdataElement($document, $parent, $name, $value)
|
||||
{
|
||||
$element = $document->createElement($name);
|
||||
$parts = explode(']]>', $value);
|
||||
$lastIndex = count($parts) - 1;
|
||||
|
||||
foreach ($parts as $index => $part)
|
||||
{
|
||||
if ($index > 0)
|
||||
{
|
||||
$part = '>' . $part;
|
||||
}
|
||||
|
||||
if ($index < $lastIndex)
|
||||
{
|
||||
$part .= ']]';
|
||||
}
|
||||
|
||||
$element->appendChild($document->createCDATASection($part));
|
||||
}
|
||||
|
||||
$parent->appendChild($element);
|
||||
}
|
||||
|
||||
private function _sendNewsItemTranslationRequiredMail($newsItem)
|
||||
{
|
||||
$translatorsResult = $this->BenutzerfunktionModel->getBenutzerFunktionen("translate", $newsItem->studiengang_kz);
|
||||
$translators = $this->getDataOrTerminateWithError($translatorsResult);
|
||||
|
||||
$newsItemContentResult = $this->ContentspracheModel->loadWhere([
|
||||
'content_id' => $newsItem->content_id,
|
||||
'sprache' => 'German'
|
||||
]);
|
||||
$newsItemContentEntry = getData($newsItemContentResult)[0] ?? null;
|
||||
if ($newsItemContentEntry) {
|
||||
$newsItem->sichtbar = $newsItemContentEntry->sichtbar ?? false;
|
||||
$newsItem->content = $newsItemContentEntry->content;
|
||||
}
|
||||
$content = getData(
|
||||
$this->cmslib->createNewsContent(
|
||||
$this->cmslib->createNewsWrapper($newsItem, true),
|
||||
false,
|
||||
""
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($translators as $translator) {
|
||||
sendSanchoMail(
|
||||
"Sancho_Mail_News_Translation_Req",
|
||||
[
|
||||
"newsItemUrl" => APP_ROOT . "cis.php/CisVue/Cms/newsAdministration?newsId=" . $newsItem->news_id,
|
||||
"newsItemAuthor" => $newsItem->verfasser,
|
||||
"newsItemSubject" => $newsItem->betreff,
|
||||
"newsItemContent" => $content
|
||||
],
|
||||
$translator->uid . "@" . DOMAIN,
|
||||
"Übersetzung erforderlich / Translation required"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function formatContentURLsToRelative($content)
|
||||
{
|
||||
$content = preg_replace(
|
||||
'#https?://[^"\'>\s]*/(dms\.php(?:\?[^"\'>\s]*)?)#i',
|
||||
'$1',
|
||||
$content
|
||||
);
|
||||
|
||||
$content = preg_replace(
|
||||
'#(?:\.\./)+[^"\'>\s]*/(dms\.php(?:\?[^"\'>\s]*)?)#i',
|
||||
'$1',
|
||||
$content
|
||||
);
|
||||
|
||||
$content = preg_replace(
|
||||
'#/[^"\'>\s]*/(dms\.php(?:\?[^"\'>\s]*)?)#i',
|
||||
'$1',
|
||||
$content
|
||||
);
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class Studgang extends FHCAPI_Controller
|
||||
{
|
||||
parent::__construct([
|
||||
'getStudiengangInfo'=> self::PERM_LOGGED,
|
||||
|
||||
'getDegreePrograms'=> self::PERM_LOGGED,
|
||||
]);
|
||||
|
||||
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
|
||||
@@ -43,6 +43,22 @@ class Studgang extends FHCAPI_Controller
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
public function getDegreePrograms() {
|
||||
$entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news");
|
||||
if (!$entitledDegreePrograms) {
|
||||
$this->terminateWithError("User is not entitled for any degree program");
|
||||
}
|
||||
|
||||
$activeDegreePrograms = $this->StudiengangModel->getDegreePrograms();
|
||||
$activeDegreePrograms= $this->getDataOrTerminateWithError($activeDegreePrograms);
|
||||
|
||||
$activeDegreePrograms = array_values(array_filter($activeDegreePrograms, function($degreeProgram) use ($entitledDegreePrograms) {
|
||||
return in_array($degreeProgram->studiengang_kz, $entitledDegreePrograms);
|
||||
}));
|
||||
|
||||
$this->terminateWithSuccess($activeDegreePrograms);
|
||||
}
|
||||
|
||||
public function getStudiengangInfo(){
|
||||
$isMitarbeiter = $this->MitarbeiterModel->isMitarbeiter(getAuthUID());
|
||||
$isMitarbeiter = $this->getDataOrTerminateWithError($isMitarbeiter);
|
||||
|
||||
@@ -4,6 +4,7 @@ if (!defined('BASEPATH'))
|
||||
exit('No direct script access allowed');
|
||||
|
||||
use \DateTime as DateTime;
|
||||
use \DateInterval as DateInterval;
|
||||
use \DOMDocument as DOMDocument;
|
||||
use \XSLTProcessor as XSLTProcessor;
|
||||
|
||||
@@ -141,63 +142,6 @@ class CmsLib
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param stdClass $stg_obj
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function getNewsExtras($stg_obj, $semester)
|
||||
{
|
||||
$this->ci->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
|
||||
|
||||
$stg_ltg = $this->ci->StudiengangModel->getLeitungDetailed($stg_obj->studiengang_kz);
|
||||
if (isError($stg_ltg))
|
||||
return $stg_ltg;
|
||||
$stg_ltg = getData($stg_ltg) ?: [];
|
||||
|
||||
$gf_ltg = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('gLtg', $stg_obj->oe_kurzbz);
|
||||
if (isError($gf_ltg))
|
||||
return $gf_ltg;
|
||||
$gf_ltg = getData($gf_ltg) ?: [];
|
||||
|
||||
$stv_ltg = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('stvLtg', $stg_obj->oe_kurzbz);
|
||||
if (isError($stv_ltg))
|
||||
return $stv_ltg;
|
||||
$stv_ltg = getData($stv_ltg) ?: [];
|
||||
|
||||
$ass = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('ass', $stg_obj->oe_kurzbz);
|
||||
if (isError($ass))
|
||||
return $ass;
|
||||
$ass = getData($ass) ?: [];
|
||||
|
||||
$hochschulvertr = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('hsv');
|
||||
if (isError($hochschulvertr))
|
||||
return $hochschulvertr;
|
||||
$hochschulvertr = getData($hochschulvertr) ?: [];
|
||||
|
||||
$stdv = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('stdv', $stg_obj->oe_kurzbz);
|
||||
if (isError($stdv))
|
||||
return $stdv;
|
||||
$stdv = getData($stdv) ?: [];
|
||||
|
||||
$jahrgangsvertr = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('jgv', $stg_obj->oe_kurzbz, $semester);
|
||||
if (isError($jahrgangsvertr))
|
||||
return $jahrgangsvertr;
|
||||
$jahrgangsvertr = getData($jahrgangsvertr) ?: [];
|
||||
|
||||
return success($this->ci->load->view('Cis/Cms/News/Xml/NewsExtras', [
|
||||
'studiengang' => $stg_obj,
|
||||
'semester' => $semester,
|
||||
'stg_ltg' => $stg_ltg,
|
||||
'gf_ltg' => $gf_ltg,
|
||||
'stv_ltg' => $stv_ltg,
|
||||
'ass' => $ass,
|
||||
'hochschulvertr' => $hochschulvertr,
|
||||
'stdv' => $stdv,
|
||||
'jahrgangsvertr' => $jahrgangsvertr
|
||||
], true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $studiengang_kz
|
||||
* @param string $semester
|
||||
@@ -233,64 +177,102 @@ class CmsLib
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true, $page = 1, $page_size = 10, $sprache)
|
||||
public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true, $page = 1, $page_size = 10, $sprache, $filterForDegreePrograms = false, $allowedDegreePrograms = [], $active = true)
|
||||
|
||||
{
|
||||
$this->ci->load->model('organisation/Studiengang_model', 'StudiengangModel');
|
||||
list($studiengang_kz, $semester) = $this->getStgAndSem($studiengang_kz, $semester);
|
||||
$all = $edit;
|
||||
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?><content>';
|
||||
|
||||
$this->ci->load->model('content/News_model', 'NewsModel');
|
||||
$news = $this->ci->NewsModel->getNewsWithContent($sprache, $studiengang_kz, $semester, null, $sichtbar, 0, $page, $page_size, $all, $mischen);
|
||||
|
||||
if (isError($news))
|
||||
return $news;
|
||||
|
||||
$news = getData($news);
|
||||
|
||||
|
||||
$news = $this->ci->NewsModel->getNewsWithContent($sprache, $studiengang_kz, $semester, null, $sichtbar, $page, $page_size, $active, $mischen, $filterForDegreePrograms, $allowedDegreePrograms);
|
||||
$news = getData($news) ?? [];
|
||||
$newsWrappers = [];
|
||||
|
||||
foreach ($news as $newsobj) {
|
||||
if ($studiengang_kz && $edit && !$newsobj->studiengang_kz)
|
||||
continue;
|
||||
$date = new DateTime($newsobj->datum);
|
||||
$datum = '<datum><![CDATA[' . $date->format('d.m.Y') . ']]></datum>';
|
||||
$datum .= '<datumdetail><![CDATA[' . $date->format('Y-m-d H:i') . ']]></datumdetail>';
|
||||
$id = $edit ? '<news_id><![CDATA[' . $newsobj->news_id . ']]></news_id>' : '';
|
||||
$xml .= "<newswrapper>" . $newsobj->content . $datum . $id . "</newswrapper>";
|
||||
|
||||
$newsWrappers[] = $this->createNewsWrapper($newsobj, $edit);
|
||||
}
|
||||
|
||||
/* if ($studiengang_kz != 0) {
|
||||
$stg_obj = $this->ci->StudiengangModel->load($studiengang_kz);
|
||||
if (isError($stg_obj))
|
||||
return $stg_obj;
|
||||
$stg_obj = current(getData($stg_obj) ?: []);
|
||||
$content = $this->createNewsContent($newsWrappers, $infoscreen, $titel);
|
||||
if (isError($content))
|
||||
return $content;
|
||||
|
||||
if ($stg_obj) {
|
||||
if (!$edit && !$infoscreen) {
|
||||
$extras = $this->getNewsExtras($stg_obj, $semester);
|
||||
if (isError($extras))
|
||||
return $extras;
|
||||
$xml .= getData($extras);
|
||||
}
|
||||
$xml .= '<studiengang_bezeichnung><![CDATA[' . $stg_obj->bezeichnung . ']]></studiengang_bezeichnung>';
|
||||
}
|
||||
} */
|
||||
return success([
|
||||
"content" => getData($content),
|
||||
"row_count" => $news[0]->row_count ?? 0
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a newswrapper XML element from a news item.
|
||||
*
|
||||
* @param stdClass $newsobj
|
||||
* @param boolean $edit
|
||||
*
|
||||
* @return DOMElement
|
||||
*/
|
||||
public function createNewsWrapper($newsobj, $edit)
|
||||
{
|
||||
$id = $edit ? '<news_id><![CDATA[' . $newsobj->news_id . ']]></news_id>' : '';
|
||||
$isPublished = $edit ? '<is_published><![CDATA[' . ($newsobj->sichtbar ? 'true' : 'false') . ']]></is_published>' : '';
|
||||
|
||||
$date = new DateTime($newsobj->datum);
|
||||
$datum = '<datum><![CDATA[' . $date->format('d.m.Y') . ']]></datum>';
|
||||
$datum .= '<datumdetail><![CDATA[' . $date->format('Y-m-d H:i') . ']]></datumdetail>';
|
||||
|
||||
$hasVisibleToDate = isset($newsobj->datum_bis) && $newsobj->datum_bis !== '';
|
||||
$datumTo = $hasVisibleToDate
|
||||
? new DateTime($newsobj->datum_bis)
|
||||
: (clone $date)->add(new DateInterval('P' . MAXNEWSALTER . 'D'));
|
||||
if ($hasVisibleToDate)
|
||||
$datumTo->setTime(23, 59, 59);
|
||||
|
||||
$now = new DateTime();
|
||||
$isActive = '<is_active><![CDATA[' . ($datumTo >= $now ? 'true' : 'false') . ']]></is_active>';
|
||||
|
||||
$newsWrapper = new DOMDocument();
|
||||
$newsWrapper->loadXML('<newswrapper>' . $newsobj->content . $datum . $id . $isPublished . $isActive . '</newswrapper>');
|
||||
|
||||
return $newsWrapper->documentElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates rendered news content from one or more newswrapper XML elements.
|
||||
*
|
||||
* @param DOMElement|array $newsWrappers
|
||||
* @param boolean $infoscreen
|
||||
* @param string $titel
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public function createNewsContent($newsWrappers, $infoscreen, $titel)
|
||||
{
|
||||
if (!is_array($newsWrappers))
|
||||
$newsWrappers = [$newsWrappers];
|
||||
|
||||
$XML = new DOMDocument('1.0', 'UTF-8');
|
||||
$contentElement = $XML->createElement('content');
|
||||
$XML->appendChild($contentElement);
|
||||
|
||||
foreach ($newsWrappers as $newsWrapper)
|
||||
$contentElement->appendChild($XML->importNode($newsWrapper, true));
|
||||
|
||||
if ($titel != '') {
|
||||
$xml .= '<news_titel>' . $titel . '</news_titel>';
|
||||
$titleElement = $XML->createElement('news_titel');
|
||||
$titleElement->appendChild($XML->createTextNode($titel));
|
||||
$contentElement->appendChild($titleElement);
|
||||
}
|
||||
|
||||
$xml .= '</content>';
|
||||
|
||||
//XSLT Vorlage laden
|
||||
$template = $this->ci->TemplateModel->load($infoscreen ? 'news_infoscreen' : 'news');
|
||||
if (isError($template))
|
||||
return $template;
|
||||
$template = current(getData($template));
|
||||
|
||||
$XML = new DOMDocument();
|
||||
$XML->loadXML($xml);
|
||||
|
||||
$xsltemplate = new DOMDocument();
|
||||
$xsltemplate->loadXML($template->xslt_xhtml_c4);
|
||||
|
||||
|
||||
@@ -20,14 +20,16 @@ class News_model extends DB_Model
|
||||
public function getAll($limit = null)
|
||||
{
|
||||
$this->addJoin("campus.tbl_content","content_id");
|
||||
|
||||
return $this->execReadOnlyQuery('
|
||||
SELECT *, TO_CHAR(campus.tbl_news.datum, ?) as datumformatted
|
||||
FROM campus.tbl_news
|
||||
JOIN campus.tbl_content content ON content.content_id = campus.tbl_news.content_id
|
||||
WHERE
|
||||
WHERE
|
||||
--text IS NOT NULL AND
|
||||
datum <= NOW() AND (datum_bis IS NULL OR datum_bis >= now()::date)
|
||||
ORDER BY datum DESC
|
||||
datum <= NOW() AND (datum_bis >= NOW()::date OR datum_bis IS NULL) AND
|
||||
(NOW()-datum) < interval \'' . MAXNEWSALTER . ' days\'
|
||||
ORDER BY datum DESC ORDER BY campus.tbl_news.updateamum DESC
|
||||
LIMIT ' . $this->escape($limit)
|
||||
, ['DD/MM/YYYY']);
|
||||
}
|
||||
@@ -48,31 +50,28 @@ class News_model extends DB_Model
|
||||
* @param integer | null $semester
|
||||
* @param string $fachbereich_kurzbz
|
||||
* @param boolean $sichtbar
|
||||
* @param integer $maxalter
|
||||
* @param integer $page
|
||||
* @param integer $page_size
|
||||
* @param boolean $all
|
||||
* @param boolean $active
|
||||
* @param boolean $mischen
|
||||
*
|
||||
* TODO(chris): this is not a good function -> the params are all over the place
|
||||
*
|
||||
*/
|
||||
protected function prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $maxalter = 0, $page = 1, $page_size = 10, $all = false, $mischen = true)
|
||||
protected function prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $page = 1, $page_size = 10, $active = true, $mischen = true, $filterForDegreePrograms = false, $allowedDegreePrograms = [])
|
||||
{
|
||||
|
||||
$this->addSelect(["*", "COUNT(*) OVER() AS row_count"]);
|
||||
$this->addOrder('datum', 'DESC');
|
||||
$this->addOrder('tbl_news.updateamum', 'DESC');
|
||||
|
||||
$studiengang_kz = trim($studiengang_kz);
|
||||
$fachbereich_kurzbz = trim($fachbereich_kurzbz);
|
||||
|
||||
$where = [];
|
||||
if (trim($maxalter) != '0') {
|
||||
$where[] = "(now()-datum) < interval " . $this->db->escape($maxalter) . " days";
|
||||
|
||||
}
|
||||
if (!$all) {
|
||||
$where[] = "datum <= now()";
|
||||
$where[] = "(datum_bis >= now()::date OR datum_bis IS NULL)";
|
||||
|
||||
if ($active) {
|
||||
$where[] = "(datum <= NOW()::date AND (datum_bis >= NOW()::date OR (datum_bis IS NULL AND (NOW()-datum) < interval '" . MAXNEWSALTER . " days')))";
|
||||
}
|
||||
if ($fachbereich_kurzbz != '*') {
|
||||
if ($fachbereich_kurzbz == '') {
|
||||
@@ -82,27 +81,54 @@ class News_model extends DB_Model
|
||||
|
||||
}
|
||||
}
|
||||
if ($studiengang_kz == '0') {
|
||||
$where[] = "studiengang_kz = " . $this->db->escape($studiengang_kz);
|
||||
|
||||
if ($semester === NULL)
|
||||
$where[] = "semester IS NULL";
|
||||
elseif ($semester === 0)
|
||||
$where[] = "semester = 0";
|
||||
} elseif ($studiengang_kz != '') {
|
||||
$add = $mischen === true ? " OR (studiengang_kz = 0 AND semester IS NULL)" : "";
|
||||
$where[] = "((studiengang_kz = " . $this->db->escape($studiengang_kz) . " AND semester = " . $this->db->escape($semester) . ") OR (studiengang_kz = " . $this->db->escape($studiengang_kz) . " AND semester = 0) OR (studiengang_kz = 0 AND semester = " . $this->db->escape($semester) . ")" . $add . ")";
|
||||
$sgAndSemesterCondition = [];
|
||||
|
||||
if ($studiengang_kz !== '') {
|
||||
$sgAndSemesterCondition[] = 'studiengang_kz = ' . $this->db->escape($studiengang_kz);
|
||||
}
|
||||
if ($semester !== null && $semester !== 0) {
|
||||
$sgAndSemesterCondition[] = 'semester = ' . $this->db->escape($semester);
|
||||
}
|
||||
|
||||
$targetedSgAndSemesterCondition = [];
|
||||
|
||||
if (!empty($sgAndSemesterCondition)) {
|
||||
$targetedSgAndSemesterCondition[] = '(' . implode(' AND ', $sgAndSemesterCondition) . ')';
|
||||
}
|
||||
if ($mischen === true) {
|
||||
$targetedSgAndSemesterCondition[] = '(studiengang_kz = 0 AND (semester = 0 OR semester IS NULL))';
|
||||
|
||||
if ($semester !== null && $semester !== 0) {
|
||||
$targetedSgAndSemesterCondition[] = '(studiengang_kz = 0 AND semester = ' . $this->db->escape($semester) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$finalSgAndSemesterCondition = implode(' OR ', $targetedSgAndSemesterCondition);
|
||||
|
||||
if ($finalSgAndSemesterCondition !== '') {
|
||||
$where[] = "(" . $finalSgAndSemesterCondition . ")";
|
||||
}
|
||||
|
||||
$this->addJoin('campus.tbl_contentsprache cs', 'content_id');
|
||||
|
||||
$where[] = "cs.sichtbar = " . ($sichtbar ? "true" : "false");
|
||||
if (is_bool($sichtbar) || $sichtbar === 'true' || $sichtbar === 'false') {
|
||||
$isSichtbar = filter_var($sichtbar, FILTER_VALIDATE_BOOLEAN);
|
||||
$where[] = "cs.sichtbar = " . ($isSichtbar ? "true" : "false");
|
||||
}
|
||||
|
||||
$where[] = "cs.sprache = (CASE WHEN EXISTS(SELECT 1 FROM campus.tbl_contentsprache cs2 WHERE cs2.content_id=" . $this->dbTable . ".content_id AND sprache=" . $this->db->escape($sprache) . ") THEN " . $this->db->escape($sprache) . " ELSE " . $this->db->escape(DEFAULT_LANGUAGE) . " END)";
|
||||
|
||||
|
||||
$where[] = "cs.version = (SELECT MAX(version) FROM campus.tbl_contentsprache cs3 WHERE cs3.content_id=" . $this->dbTable . ".content_id AND cs3.sprache = (CASE WHEN EXISTS(SELECT 1 FROM campus.tbl_contentsprache cs2 WHERE cs2.content_id=" . $this->dbTable . ".content_id AND sprache=" . $this->db->escape($sprache) . ") THEN " . $this->db->escape($sprache) . " ELSE " . $this->db->escape(DEFAULT_LANGUAGE) . " END))";
|
||||
|
||||
if ($filterForDegreePrograms) {
|
||||
if (empty($allowedDegreePrograms)) {
|
||||
$where[] = "1=0";
|
||||
} else {
|
||||
$where[] = "studiengang_kz IN (" . implode(',', array_map([$this->db, 'escape'], $allowedDegreePrograms)) . ")";
|
||||
}
|
||||
}
|
||||
|
||||
$where = implode(" AND ", $where);
|
||||
|
||||
@@ -110,25 +136,16 @@ class News_model extends DB_Model
|
||||
|
||||
}
|
||||
|
||||
public function getNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $maxalter = 0, $page = 1, $page_size = 10, $all = false, $mischen = true)
|
||||
public function getNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $page = 1, $page_size = 10, $active = true, $mischen = true, $filterForDegreePrograms = false, $allowedDegreePrograms = [])
|
||||
{
|
||||
$this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen);
|
||||
|
||||
$this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $page, $page_size, $active, $mischen, $filterForDegreePrograms, $allowedDegreePrograms);
|
||||
// getting the number of rows of the query and adding pagination to the query result
|
||||
$num_rows = $this->getNumRows(true);
|
||||
$this->addPagination($page, $page_size, $num_rows);
|
||||
|
||||
// preparing the query again because every call to get_compiled_select or cour_all_results will add the from clause to the query
|
||||
$this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen);
|
||||
$this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $page, $page_size, $active, $mischen, $filterForDegreePrograms, $allowedDegreePrograms);
|
||||
|
||||
return $this->load();
|
||||
}
|
||||
|
||||
public function countNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $maxalter = 0, $page = 1, $page_size = 10, $all = false, $mischen = true)
|
||||
{
|
||||
$this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen);
|
||||
return $this->getNumRows();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -88,6 +88,30 @@ class Prestudent_model extends DB_Model
|
||||
return $this->execQuery($query, $parametersArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Studiengang and current Ausbildungssemester for every
|
||||
* Prestudent belonging to the same person as the student.
|
||||
*
|
||||
* @param string $student_uid
|
||||
* @return stdClass
|
||||
*/
|
||||
public function getStudiengaengAndAusbildungssemesterByStudentUid($student_uid)
|
||||
{
|
||||
$query = 'SELECT p.studiengang_kz,
|
||||
(
|
||||
SELECT ps.ausbildungssemester
|
||||
FROM public.tbl_prestudentstatus ps
|
||||
WHERE ps.prestudent_id = p.prestudent_id
|
||||
ORDER BY ps.datum DESC, ps.insertamum DESC, ps.ext_id DESC
|
||||
LIMIT 1
|
||||
) AS ausbildungssemester
|
||||
FROM public.tbl_student s
|
||||
JOIN public.tbl_prestudent p USING (prestudent_id)
|
||||
WHERE s.student_uid = ?';
|
||||
|
||||
return $this->execQuery($query, array($student_uid));
|
||||
}
|
||||
|
||||
/**
|
||||
* updateAufnahmegruppe
|
||||
*/
|
||||
|
||||
@@ -885,6 +885,14 @@ class Studiengang_model extends DB_Model
|
||||
return success($ass);
|
||||
}
|
||||
|
||||
public function getDegreePrograms() {
|
||||
$query = "SELECT *
|
||||
FROM public.tbl_studiengang
|
||||
ORDER BY public.tbl_studiengang.kurzbzlang";
|
||||
|
||||
return $this->execReadOnlyQuery($query);
|
||||
}
|
||||
|
||||
private function addEmailProperty(&$benutzerfunktionen) {
|
||||
if(count($benutzerfunktionen) && defined('DOMAIN'))
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ $includesArray = array(
|
||||
'primevue3' => true,
|
||||
'skipID' => '#fhccontent',
|
||||
'vuedatepicker11' => true,
|
||||
'tinymce5' => true,
|
||||
'customCSSs' => array(
|
||||
'public/css/components/verticalsplit.css',
|
||||
'public/css/components/searchbar/searchbar.css',
|
||||
@@ -23,7 +24,8 @@ $includesArray = array(
|
||||
'public/css/components/FormUnderline.css',
|
||||
'public/css/components/abgabetool/abgabe.css',
|
||||
'public/css/Cis4/Cms.css',
|
||||
'public/css/Cis4/Studium.css'
|
||||
'public/css/Cis4/Studium.css',
|
||||
'public/css/components/vue-datepicker.css',
|
||||
),
|
||||
'customJSs' => array(
|
||||
'vendor/npm-asset/primevue/accordion/accordion.min.js',
|
||||
@@ -49,6 +51,8 @@ $includesArray = array(
|
||||
$this->load->view('templates/CISVUE-Header', $includesArray);
|
||||
?>
|
||||
<div id="fhccontent" class="h-100" route=<?php echo $route ?>>
|
||||
<router-view></router-view>
|
||||
<router-view
|
||||
:permissions="<?= htmlspecialchars(json_encode($permissions ?? [])); ?>"
|
||||
></router-view>
|
||||
</div>
|
||||
<?php $this->load->view('templates/CISVUE-Footer', $includesArray); ?>
|
||||
|
||||
@@ -86,11 +86,11 @@ echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
<link rel="stylesheet" href="../skin/styles/jquery.css" type="text/css">
|
||||
<link rel="stylesheet" href="../vendor/components/jqueryui/themes/base/jquery-ui.min.css" type="text/css">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../skin/jquery-ui-1.9.2.custom.min.css">
|
||||
<script type="text/javascript" src="../vendor/jquery/jquery1/jquery-1.12.4.min.js"></script>
|
||||
<script type="text/javascript" src="../vendor/christianbach/tablesorter/jquery.tablesorter.min.js"></script>
|
||||
<script type="text/javascript" src="../vendor/components/jqueryui/jquery-ui.min.js"></script>
|
||||
<script type="text/javascript" src="../include/js/jquery.ui.datepicker.translation.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../skin/jquery-ui-1.9.2.custom.min.css">
|
||||
<script type="text/javascript" src="../vendor/jquery/jquery1/jquery-1.12.4.min.js"></script>
|
||||
<script type="text/javascript" src="../vendor/christianbach/tablesorter/jquery.tablesorter.min.js"></script>
|
||||
<script type="text/javascript" src="../vendor/components/jqueryui/jquery-ui.min.js"></script>
|
||||
<script type="text/javascript" src="../include/js/jquery.ui.datepicker.translation.js"></script>
|
||||
<script type="text/javascript" src="../vendor/jquery/sizzle/sizzle.js"></script>
|
||||
<script src="../vendor/components/jqueryui/jquery-ui.min.js" type="text/javascript"></script>
|
||||
<script type="text/javascript" src="../include/tiny_mce/tiny_mce.js"></script>
|
||||
|
||||
@@ -88,6 +88,29 @@ if (! $rechte->isberechtigt('basis/dms', null, 's', null))
|
||||
init: function(){
|
||||
},
|
||||
mySubmit : function (id) {
|
||||
var messageTarget = window.opener;
|
||||
|
||||
if (!messageTarget && window.parent !== window)
|
||||
messageTarget = window.parent;
|
||||
|
||||
if (messageTarget && typeof(messageTarget.postMessage) == "function")
|
||||
{
|
||||
messageTarget.postMessage(
|
||||
{
|
||||
type: "fhcomplete:dms:selected",
|
||||
dmsId: id
|
||||
},
|
||||
window.location.protocol + "//" + window.location.host
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof(tinyMCEPopup) == "undefined" ||
|
||||
!tinyMCEPopup.params ||
|
||||
typeof(tinyMCEPopup.getWindowArg) != "function"
|
||||
)
|
||||
return;
|
||||
|
||||
var URL = "dms.php?id="+id;
|
||||
var win = tinyMCEPopup.getWindowArg("window");
|
||||
|
||||
|
||||
+17
-38
@@ -1,45 +1,24 @@
|
||||
export default {
|
||||
content(content_id, version=null, sprache=null, sichtbar=null) {
|
||||
return this.$fhcApi.get(
|
||||
"/api/frontend/v1/Cms/content",
|
||||
{
|
||||
content_id: content_id,
|
||||
...(version?{version}:{}),
|
||||
...(sprache?{sprache}:{}),
|
||||
...(sichtbar?{sichtbar}:{}),
|
||||
}
|
||||
);
|
||||
},
|
||||
content(content_id, version = null, sprache = null, sichtbar = null) {
|
||||
return this.$fhcApi.get('/api/frontend/v1/Cms/content', {
|
||||
content_id: content_id,
|
||||
...(version ? { version } : {}),
|
||||
...(sprache ? { sprache } : {}),
|
||||
...(sichtbar ? { sichtbar } : {}),
|
||||
});
|
||||
},
|
||||
//api function used for the news View that renders the html
|
||||
getNews(page = 1, page_size = 10, sprache) {
|
||||
return this.$fhcApi.get(
|
||||
"/api/frontend/v1/Cms/getNews",
|
||||
{
|
||||
page,
|
||||
page_size,
|
||||
sprache,
|
||||
},
|
||||
);
|
||||
return this.$fhcApi.get('/api/frontend/v1/Cms/getNews', {
|
||||
page,
|
||||
page_size,
|
||||
sprache,
|
||||
});
|
||||
},
|
||||
//api function used for the widget component
|
||||
news(limit) {
|
||||
return this.$fhcApi.get(
|
||||
"/api/frontend/v1/Cms/news",
|
||||
{
|
||||
limit: limit
|
||||
}
|
||||
);
|
||||
return this.$fhcApi.get('/api/frontend/v1/Cms/news', {
|
||||
limit: limit,
|
||||
});
|
||||
},
|
||||
getNewsRowCount: function () {
|
||||
return this.$fhcApi.get(
|
||||
"/api/frontend/v1/Cms/getNewsRowCount",
|
||||
{}
|
||||
);
|
||||
},
|
||||
getNewsExtra: function(){
|
||||
return this.$fhcApi.get(
|
||||
"/api/frontend/v1/Cms/getStudiengangInfoForNews",
|
||||
{}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
content(content_id, version=null, sprache=null, sichtbar=null) {
|
||||
content(content_id, version = null, sprache = null, sichtbar = null) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Cms/content',
|
||||
@@ -24,41 +24,29 @@ export default {
|
||||
content_id,
|
||||
...(version ? { version } : {}),
|
||||
...(sprache ? { sprache } : {}),
|
||||
...(sichtbar ? { sichtbar } : {})
|
||||
}
|
||||
...(sichtbar ? { sichtbar } : {}),
|
||||
},
|
||||
};
|
||||
},
|
||||
//api function used for the news View that renders the html
|
||||
getNews(page = 1, page_size = 10, sprache) {
|
||||
getNews(page = 1, page_size = 10, sprache, filterPublished = true) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Cms/getNews',
|
||||
params: {
|
||||
page,
|
||||
page_size,
|
||||
sprache
|
||||
sprache,
|
||||
published: filterPublished,
|
||||
},
|
||||
};
|
||||
},
|
||||
//api function used for the widget component
|
||||
news(limit) {
|
||||
newsForWidget(limit) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Cms/news',
|
||||
params: { limit }
|
||||
url: '/api/frontend/v1/Cms/newsForWidget',
|
||||
params: { limit },
|
||||
};
|
||||
},
|
||||
getNewsRowCount() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Cms/getNewsRowCount'
|
||||
};
|
||||
},
|
||||
getNewsExtra() {
|
||||
// TODO(chris): seems to be called from nowhere?
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Cms/getStudiengangInfoForNews'
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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 {
|
||||
getNews(
|
||||
page = 1,
|
||||
page_size = 10,
|
||||
sprache,
|
||||
publishedFilter = true,
|
||||
isActive = true,
|
||||
degreeProgramShortCode = null,
|
||||
semester = null,
|
||||
) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/NewsAdministrationAPI/getNews',
|
||||
params: {
|
||||
page,
|
||||
page_size,
|
||||
sprache,
|
||||
published: publishedFilter,
|
||||
isActive,
|
||||
degreeProgramShortCode,
|
||||
semester,
|
||||
},
|
||||
};
|
||||
},
|
||||
getNewsItem(newsId) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/NewsAdministrationAPI/getNewsItem/${newsId}`,
|
||||
};
|
||||
},
|
||||
storeNewsItem(data) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/NewsAdministrationAPI/storeNewsItem',
|
||||
params: data,
|
||||
};
|
||||
},
|
||||
updateNewsItem(newsId, data) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: `/api/frontend/v1/NewsAdministrationAPI/updateNewsItem/${newsId}`,
|
||||
params: data,
|
||||
};
|
||||
},
|
||||
deleteNewsItem(newsId) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: `/api/frontend/v1/NewsAdministrationAPI/deleteNewsItem/${newsId}`,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -16,17 +16,23 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
getDegreePrograms() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Studgang/getDegreePrograms',
|
||||
};
|
||||
},
|
||||
studiengangInformation() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Studgang/getStudiengangInfo'
|
||||
url: '/api/frontend/v1/Studgang/getStudiengangInfo',
|
||||
};
|
||||
},
|
||||
getStudiengangByKz(studiengang_kz) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/organisation/StudiengangEP/getStudiengangByKz',
|
||||
params: { studiengang_kz }
|
||||
params: { studiengang_kz },
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
+135
-106
@@ -2,29 +2,35 @@ import FhcDashboard from '../../components/Dashboard/Dashboard.js';
|
||||
import PluginsPhrasen from '../../plugins/Phrasen.js';
|
||||
import Theme from '../../plugins/Theme.js';
|
||||
import contrast from '../../directives/contrast.js';
|
||||
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
|
||||
import LvPlan from "../../components/Cis/LvPlan/Lehrveranstaltung.js";
|
||||
import MyLvPlan from "../../components/Cis/LvPlan/MyLvPlan.js";
|
||||
import MylvStudent from "../../components/Cis/Mylv/Student.js";
|
||||
import Profil from "../../components/Cis/Profil/Profil.js";
|
||||
import Raumsuche from "../../components/Cis/Raumsuche/Raumsuche.js";
|
||||
import CmsNews from "../../components/Cis/Cms/News.js";
|
||||
import CmsContent from "../../components/Cis/Cms/Content.js";
|
||||
import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js";
|
||||
import RoomInformation, {DEFAULT_MODE_RAUMINFO_DESKTOP, DEFAULT_MODE_RAUMINFO_MOBILE} from "../../components/Cis/Mylv/RoomInformation.js";
|
||||
import AbgabetoolStudent from "../../components/Cis/Abgabetool/AbgabetoolStudent.js";
|
||||
import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js";
|
||||
import AbgabetoolAssistenz from "../../components/Cis/Abgabetool/AbgabetoolAssistenz.js";
|
||||
import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js";
|
||||
import Studium from "../../components/Cis/Studium/Studium.js";
|
||||
import StgOrgLvPlan from "../../components/Cis/LvPlan/StgOrg.js";
|
||||
import OtherLvPlan from "../../components/Cis/LvPlan/OtherLvPlan.js";
|
||||
import { setScrollbarWidth } from '../../helpers/CssVarCalcHelpers.js';
|
||||
import LvPlan from '../../components/Cis/LvPlan/Lehrveranstaltung.js';
|
||||
import MyLvPlan from '../../components/Cis/LvPlan/MyLvPlan.js';
|
||||
import MylvStudent from '../../components/Cis/Mylv/Student.js';
|
||||
import Profil from '../../components/Cis/Profil/Profil.js';
|
||||
import Raumsuche from '../../components/Cis/Raumsuche/Raumsuche.js';
|
||||
import CmsNews from '../../components/Cis/Cms/News.js';
|
||||
import NewsAdministration from '../../components/Cis/Cms/NewsAdministration/NewsAdministration.js';
|
||||
import CmsContent from '../../components/Cis/Cms/Content.js';
|
||||
import Info from '../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js';
|
||||
import RoomInformation, {
|
||||
DEFAULT_MODE_RAUMINFO_DESKTOP,
|
||||
DEFAULT_MODE_RAUMINFO_MOBILE,
|
||||
} from '../../components/Cis/Mylv/RoomInformation.js';
|
||||
import AbgabetoolStudent from '../../components/Cis/Abgabetool/AbgabetoolStudent.js';
|
||||
import AbgabetoolMitarbeiter from '../../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js';
|
||||
import AbgabetoolAssistenz from '../../components/Cis/Abgabetool/AbgabetoolAssistenz.js';
|
||||
import DeadlineOverview from '../../components/Cis/Abgabetool/DeadlineOverview.js';
|
||||
import Studium from '../../components/Cis/Studium/Studium.js';
|
||||
import StgOrgLvPlan from '../../components/Cis/LvPlan/StgOrg.js';
|
||||
import OtherLvPlan from '../../components/Cis/LvPlan/OtherLvPlan.js';
|
||||
|
||||
import ApiRouteInfo from '../../api/factory/routeinfo.js';
|
||||
import {capitalize} from "../../helpers/StringHelpers.js";
|
||||
import { capitalize } from '../../helpers/StringHelpers.js';
|
||||
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
const isMobile = window.matchMedia("(max-width: 767px)").matches;
|
||||
const ciPath =
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
const isMobile = window.matchMedia('(max-width: 767px)').matches;
|
||||
|
||||
const router = VueRouter.createRouter({
|
||||
history: VueRouter.createWebHistory(`/${ciPath}`),
|
||||
@@ -33,62 +39,66 @@ const router = VueRouter.createRouter({
|
||||
path: `/Cis/Studium`,
|
||||
name: 'Studium',
|
||||
component: Studium,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: `/Cis/Profil/View/:uid`,
|
||||
name: 'ProfilView',
|
||||
component: Profil,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: `/Cis/Profil`,
|
||||
name: 'Profil',
|
||||
component: Profil,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: `/Cis/Abgabetool/Student/:student_uid_prop?`,
|
||||
name: 'AbgabetoolStudent',
|
||||
component: AbgabetoolStudent,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: `/Cis/Abgabetool/Mitarbeiter`,
|
||||
name: 'AbgabetoolMitarbeiter',
|
||||
component: AbgabetoolMitarbeiter,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: `/Cis/Abgabetool/Assistenz/:stg_kz_prop?`,
|
||||
name: 'AbgabetoolAssistenz',
|
||||
component: AbgabetoolAssistenz,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: `/Cis/Abgabetool/Deadlines/:person_uid_prop?`,
|
||||
name: 'DeadlineOverview',
|
||||
component: DeadlineOverview,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: `/Cis/Raumsuche`,
|
||||
name: 'Raumsuche',
|
||||
component: Raumsuche,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
// Redirect old links to new format
|
||||
{
|
||||
path: "/CisVue/Cms/getRoomInformation/:ort_kurzbz",
|
||||
name: "RoomInformationOld",
|
||||
path: '/CisVue/Cms/getRoomInformation/:ort_kurzbz',
|
||||
name: 'RoomInformationOld',
|
||||
component: RoomInformation,
|
||||
redirect: (to) => {
|
||||
return { // redirect to longer Rauminfo url and map params
|
||||
name: "RoomInformation",
|
||||
params: { // in this case always populate other params since they are not optional
|
||||
return {
|
||||
// redirect to longer Rauminfo url and map params
|
||||
name: 'RoomInformation',
|
||||
params: {
|
||||
// in this case always populate other params since they are not optional
|
||||
ort_kurzbz: to.params.ort_kurzbz,
|
||||
mode: isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP,
|
||||
focus_date: new Date().toISOString().split("T")[0]
|
||||
mode: isMobile
|
||||
? DEFAULT_MODE_RAUMINFO_MOBILE
|
||||
: DEFAULT_MODE_RAUMINFO_DESKTOP,
|
||||
focus_date: new Date().toISOString().split('T')[0],
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -97,18 +107,28 @@ const router = VueRouter.createRouter({
|
||||
path: `/CisVue/Cms/getRoomInformation/:mode/:focus_date/:ort_kurzbz`,
|
||||
name: 'RoomInformation',
|
||||
component: RoomInformation,
|
||||
props: (route) => { // validate and set mode/focus date if for some reason missing
|
||||
const validModes = ["Month", "Week", "Day"];
|
||||
props: (route) => {
|
||||
// validate and set mode/focus date if for some reason missing
|
||||
const validModes = ['Month', 'Week', 'Day'];
|
||||
|
||||
// check mode string
|
||||
const mode = route.params.mode &&
|
||||
validModes.includes(route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase())
|
||||
? route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase()
|
||||
: (isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP);
|
||||
const mode =
|
||||
route.params.mode &&
|
||||
validModes.includes(
|
||||
route.params.mode.charAt(0).toUpperCase() +
|
||||
route.params.mode.slice(1).toLowerCase(),
|
||||
)
|
||||
? route.params.mode.charAt(0).toUpperCase() +
|
||||
route.params.mode.slice(1).toLowerCase()
|
||||
: isMobile
|
||||
? DEFAULT_MODE_RAUMINFO_MOBILE
|
||||
: DEFAULT_MODE_RAUMINFO_DESKTOP;
|
||||
|
||||
// default to today date if not provided
|
||||
const d = new Date(route.params.focus_date)
|
||||
const focus_date = !isNaN(d) ? route.params.focus_date : new Date().toISOString().split("T")[0];
|
||||
const d = new Date(route.params.focus_date);
|
||||
const focus_date = !isNaN(d)
|
||||
? route.params.focus_date
|
||||
: new Date().toISOString().split('T')[0];
|
||||
|
||||
// for consistency reasons format the props into one object but actually use a new name to we dont collide with
|
||||
// existing viewData declaration written from codeigniter 3 into routerview tag
|
||||
@@ -116,37 +136,44 @@ const router = VueRouter.createRouter({
|
||||
propsViewData: {
|
||||
mode,
|
||||
focus_date,
|
||||
ort_kurzbz: route.params.ort_kurzbz
|
||||
}
|
||||
ort_kurzbz: route.params.ort_kurzbz,
|
||||
},
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
// missing mode or focus_date -> set defaults
|
||||
if (!to.params.mode || !to.params.focus_date) {
|
||||
next({
|
||||
name: "RoomInformation",
|
||||
name: 'RoomInformation',
|
||||
params: {
|
||||
mode: to.params.mode || DEFAULT_MODE_RAUMINFO,
|
||||
focus_date: to.params.focus_date || new Date().toISOString().split("T")[0],
|
||||
ort_kurzbz: route.params.ort_kurzbz
|
||||
}
|
||||
focus_date:
|
||||
to.params.focus_date || new Date().toISOString().split('T')[0],
|
||||
ort_kurzbz: route.params.ort_kurzbz,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: `/CisVue/Cms/Content/:content_id`,
|
||||
name: 'Content',
|
||||
component: CmsContent,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: `/CisVue/Cms/News`,
|
||||
name: 'News',
|
||||
component: CmsNews,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: `/CisVue/Cms/newsAdministration`,
|
||||
name: 'NewsAdministration',
|
||||
component: NewsAdministration,
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: `/Cis/MyLv/:studiensemester?`,
|
||||
@@ -158,23 +185,24 @@ const router = VueRouter.createRouter({
|
||||
path: `/Cis/MyLv/Info/:studien_semester/:lehrveranstaltung_id`,
|
||||
name: 'LvInfo',
|
||||
component: Info,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
// Redirect old links to new format
|
||||
{
|
||||
// only trigger on first param being numeric to avoid paths like "LvPlan/Month" entering here
|
||||
path: "/Cis/LvPlan/:lv_id(\\d+)",
|
||||
name: "LvPlanOld",
|
||||
path: '/Cis/LvPlan/:lv_id(\\d+)',
|
||||
name: 'LvPlanOld',
|
||||
component: LvPlan,
|
||||
redirect(to) {
|
||||
const route = Vue.unref(router.currentRoute);
|
||||
const { mode, focus_date } = route.params; // keep mode and focus_date if available
|
||||
return { // redirect to longer LvPlan url and map params
|
||||
name: "LvPlan",
|
||||
return {
|
||||
// redirect to longer LvPlan url and map params
|
||||
name: 'LvPlan',
|
||||
params: {
|
||||
mode,
|
||||
focus_date,
|
||||
lv_id: to.params.lv_id
|
||||
lv_id: to.params.lv_id,
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -185,9 +213,9 @@ const router = VueRouter.createRouter({
|
||||
component: LvPlan,
|
||||
props(route) {
|
||||
return {
|
||||
propsViewData: route.params
|
||||
propsViewData: route.params,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: `/Cis/MyLvPlan/:mode?/:focus_date?`,
|
||||
@@ -195,9 +223,9 @@ const router = VueRouter.createRouter({
|
||||
component: MyLvPlan,
|
||||
props(route) {
|
||||
return {
|
||||
propsViewData: route.params
|
||||
propsViewData: route.params,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: `/Cis/StgOrgLvPlan/:mode?/:focus_date?/:stgkz?/:sem?/:verband?/:gruppe?`,
|
||||
@@ -205,48 +233,48 @@ const router = VueRouter.createRouter({
|
||||
component: StgOrgLvPlan,
|
||||
props(route) {
|
||||
return {
|
||||
propsViewData: route.params
|
||||
propsViewData: route.params,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: `/Cis/OtherLvPlan/:otherUid/:mode?/:focus_date?`,
|
||||
name: "OtherLvPlan",
|
||||
component: OtherLvPlan,
|
||||
props(route) {
|
||||
return {
|
||||
propsViewData: route.params
|
||||
};
|
||||
}
|
||||
},
|
||||
path: `/Cis/OtherLvPlan/:otherUid/:mode?/:focus_date?`,
|
||||
name: 'OtherLvPlan',
|
||||
component: OtherLvPlan,
|
||||
props(route) {
|
||||
return {
|
||||
propsViewData: route.params,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: `/Cis4`,
|
||||
name: 'Cis4',
|
||||
component: FhcDashboard,
|
||||
props: {dashboard: 'CIS'},
|
||||
props: { dashboard: 'CIS' },
|
||||
},
|
||||
{
|
||||
path: `/`,
|
||||
name: 'FhcDashboard',
|
||||
component: FhcDashboard,
|
||||
props: {dashboard: 'CIS'},
|
||||
props: { dashboard: 'CIS' },
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'Fallback',
|
||||
component: FhcDashboard,
|
||||
props: {dashboard: 'CIS'},
|
||||
props: { dashboard: 'CIS' },
|
||||
redirect: () => {
|
||||
return {
|
||||
name: "Cis4",
|
||||
name: 'Cis4',
|
||||
params: {
|
||||
dashboard: 'CIS'
|
||||
dashboard: 'CIS',
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
],
|
||||
});
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'CisApp',
|
||||
@@ -255,48 +283,49 @@ const app = Vue.createApp({
|
||||
windowWidth: 0,
|
||||
}),
|
||||
provide() {
|
||||
return { // provide injectable & watchable language property
|
||||
return {
|
||||
// provide injectable & watchable language property
|
||||
language: Vue.computed(() => this.$p.user_language),
|
||||
isMobile: Vue.computed(() => this.isMobile),
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isMobile: function() {
|
||||
return (this.windowWidth < 767);
|
||||
}
|
||||
isMobile: function () {
|
||||
return this.windowWidth < 767;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isInternalRoute(href) {
|
||||
const internalBase = window.location.origin
|
||||
const internalBase = window.location.origin;
|
||||
return href.startsWith(internalBase);
|
||||
},
|
||||
handleClick(event) {
|
||||
const target = event.target.closest('a');
|
||||
|
||||
if(target?.id == 'skiplink') return
|
||||
if (target?.id == 'skiplink') return;
|
||||
if (target && this.isInternalRoute(target.href)) {
|
||||
const url = new URL(target.href)
|
||||
|
||||
const path = url.pathname
|
||||
const base = this.$router.options.history.base
|
||||
const route = path.replace(base, '') || '/'
|
||||
const url = new URL(target.href);
|
||||
|
||||
const path = url.pathname;
|
||||
const base = this.$router.options.history.base;
|
||||
const route = path.replace(base, '') || '/';
|
||||
|
||||
// let click event propagate normally if we dont route internally
|
||||
const res = this.$router.resolve(route)
|
||||
if(!res?.matched?.length || res.name === 'Fallback') return
|
||||
|
||||
const res = this.$router.resolve(route);
|
||||
if (!res?.matched?.length || res.name === 'Fallback') return;
|
||||
|
||||
event.preventDefault(); // Prevent browser navigation
|
||||
|
||||
if(this.isMobile) { // toggle the menu
|
||||
|
||||
if (this.isMobile) {
|
||||
// toggle the menu
|
||||
const navMain = document.getElementById('nav-main');
|
||||
// fix unwanted toggle from off to on for some links on mobile
|
||||
if(navMain.classList.contains('show')){
|
||||
if (navMain.classList.contains('show')) {
|
||||
document.getElementById('nav-main-btn').click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.$router.push(route);
|
||||
|
||||
}
|
||||
},
|
||||
handleWindowResize() {
|
||||
@@ -308,11 +337,11 @@ const app = Vue.createApp({
|
||||
},
|
||||
async mounted() {
|
||||
document.addEventListener('click', this.handleClick);
|
||||
window.addEventListener("resize", this.handleWindowResize);
|
||||
window.addEventListener('resize', this.handleWindowResize);
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.handleClick);
|
||||
window.removeEventListener("resize", this.handleWindowResize);
|
||||
window.removeEventListener('resize', this.handleWindowResize);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -327,9 +356,9 @@ app.use(router);
|
||||
app.use(primevue.config.default, {
|
||||
zIndex: {
|
||||
overlay: 9000,
|
||||
tooltip: 8000
|
||||
}
|
||||
})
|
||||
tooltip: 8000,
|
||||
},
|
||||
});
|
||||
app.directive('tooltip', primevue.tooltip);
|
||||
app.use(PluginsPhrasen);
|
||||
app.use(Theme);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import FhcCalendar from "./Base.js";
|
||||
import FhcCalendar from './Base.js';
|
||||
|
||||
import ApiLvPlan from '../../api/factory/lvPlan.js';
|
||||
|
||||
@@ -11,50 +11,48 @@ import ModeMonth from './Mode/Month.js';
|
||||
import ModeList from './Mode/List.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarLvPlan",
|
||||
name: 'CalendarLvPlan',
|
||||
components: {
|
||||
FhcCalendar
|
||||
FhcCalendar,
|
||||
},
|
||||
inject: {
|
||||
isMobile: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
inject: ["isMobile"],
|
||||
props: {
|
||||
date: {
|
||||
type: [Date, String, Number, luxon.DateTime],
|
||||
default: luxon.DateTime.local()
|
||||
default: luxon.DateTime.local(),
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'Week'
|
||||
default: 'Week',
|
||||
},
|
||||
getPromiseFunc: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
shouldCompactEvents: Vue.computed(
|
||||
() => this.$props.mode === "Month" && this.isMobile,
|
||||
),
|
||||
compactibleEventTypes: Vue.computed(
|
||||
() => this.compactibleEventTypes,
|
||||
() => this.$props.mode === 'Month' && this.isMobile,
|
||||
),
|
||||
compactibleEventTypes: Vue.computed(() => this.compactibleEventTypes),
|
||||
};
|
||||
},
|
||||
emits: [
|
||||
"update:date",
|
||||
"update:mode",
|
||||
"update:range"
|
||||
],
|
||||
emits: ['update:date', 'update:mode', 'update:range'],
|
||||
data() {
|
||||
return {
|
||||
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
|
||||
modeOptions: {
|
||||
day: {
|
||||
emptyMessage: Vue.computed(() => this.$p.t('lehre/noLvFound')),
|
||||
emptyMessageDetails: Vue.computed(() => this.$p.t('lehre/noLvFound'))
|
||||
emptyMessageDetails: Vue.computed(() => this.$p.t('lehre/noLvFound')),
|
||||
},
|
||||
week: {
|
||||
collapseEmptyDays: false
|
||||
collapseEmptyDays: false,
|
||||
},
|
||||
list: {
|
||||
length: 7,
|
||||
@@ -72,16 +70,18 @@ export default {
|
||||
return [
|
||||
{
|
||||
class: 'background-past',
|
||||
end: now.startOf('day')
|
||||
}
|
||||
end: now.startOf('day'),
|
||||
},
|
||||
];
|
||||
|
||||
return [
|
||||
{
|
||||
class: 'background-past',
|
||||
end: now,
|
||||
label: now.startOf('minute').toISOTime({ suppressSeconds: true, includeOffset: false })
|
||||
}
|
||||
label: now
|
||||
.startOf('minute')
|
||||
.toISOTime({ suppressSeconds: true, includeOffset: false }),
|
||||
},
|
||||
];
|
||||
},
|
||||
modes() {
|
||||
@@ -100,8 +100,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
eventStyle(event) {
|
||||
if (!event.farbe)
|
||||
return undefined;
|
||||
if (!event.farbe) return undefined;
|
||||
return '--event-bg:#' + event.farbe;
|
||||
},
|
||||
updateRange(rangeInterval) {
|
||||
@@ -128,10 +127,13 @@ export default {
|
||||
},
|
||||
setup(props, context) {
|
||||
const rangeInterval = Vue.ref(null);
|
||||
|
||||
const { events, lv, reset } = useEventLoader(rangeInterval, props.getPromiseFunc);
|
||||
|
||||
Vue.watch(lv, newValue => {
|
||||
const { events, lv, reset } = useEventLoader(
|
||||
rangeInterval,
|
||||
props.getPromiseFunc,
|
||||
);
|
||||
|
||||
Vue.watch(lv, (newValue) => {
|
||||
context.emit('update:lv', newValue);
|
||||
});
|
||||
|
||||
@@ -142,14 +144,14 @@ export default {
|
||||
events,
|
||||
lv,
|
||||
reset,
|
||||
renderers
|
||||
renderers,
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
await this.getStunden();
|
||||
await this.getCompactibleEventTypes();
|
||||
},
|
||||
template: /* html */`
|
||||
template: /* html */ `
|
||||
<fhc-calendar
|
||||
ref="calendar"
|
||||
class="fhc-calendar-lvplan"
|
||||
@@ -203,5 +205,5 @@ export default {
|
||||
<template #actions>
|
||||
<slot />
|
||||
</template>
|
||||
</fhc-calendar>`
|
||||
}
|
||||
</fhc-calendar>`,
|
||||
};
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
import Pagination from "../../Pagination/Pagination.js";
|
||||
import StudiengangInformation from "./StudiengangInformation/StudiengangInformation.js";
|
||||
import BsConfirm from "../../Bootstrap/Confirm.js";
|
||||
import Pagination from '../../Pagination/Pagination.js';
|
||||
import StudiengangInformation from './StudiengangInformation/StudiengangInformation.js';
|
||||
import BsConfirm from '../../Bootstrap/Confirm.js';
|
||||
|
||||
import ApiCms from "../../../api/factory/cms.js";
|
||||
import ApiCms from '../../../api/factory/cms.js';
|
||||
|
||||
export default {
|
||||
name: "NewsComponent",
|
||||
name: 'NewsComponent',
|
||||
components: {
|
||||
Pagination,
|
||||
StudiengangInformation,
|
||||
},
|
||||
inject: ["isMobile"],
|
||||
inject: ['isMobile'],
|
||||
props: {
|
||||
permissions: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
content: null,
|
||||
maxPageCount: 0,
|
||||
page_size: 10,
|
||||
page_size: 100,
|
||||
page: 1,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
"$p.user_language.value": function (sprache) {
|
||||
'$p.user_language.value': function (sprache) {
|
||||
this.fetchNews();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasBasisNewsTypRPermission() {
|
||||
return this.permissions['basis/news_r'] || false;
|
||||
},
|
||||
sprache: function () {
|
||||
return this.$p.user_language.value;
|
||||
},
|
||||
@@ -35,9 +41,10 @@ export default {
|
||||
ApiCms.getNews(this.page, this.page_size, this.sprache),
|
||||
);
|
||||
this.content = newsResponse.data;
|
||||
this.maxPageCount = newsResponse.meta.row_count;
|
||||
|
||||
document.querySelectorAll("#cms [data-confirm]").forEach((el) => {
|
||||
el.addEventListener("click", (evt) => {
|
||||
document.querySelectorAll('#cms [data-confirm]').forEach((el) => {
|
||||
el.addEventListener('click', (evt) => {
|
||||
evt.preventDefault();
|
||||
BsConfirm.popup(el.dataset.confirm)
|
||||
.then(() => {
|
||||
@@ -46,12 +53,12 @@ export default {
|
||||
// TODO(chris): check for success then show message and/or reload
|
||||
location = location;
|
||||
})
|
||||
.catch((err) => console.error("ERROR:", err));
|
||||
.catch((err) => console.error('ERROR:', err));
|
||||
})
|
||||
.catch(() => {});
|
||||
});
|
||||
});
|
||||
document.querySelectorAll("#cms [data-href]").forEach((el) => {
|
||||
document.querySelectorAll('#cms [data-href]').forEach((el) => {
|
||||
el.href = el.dataset.href.replace(
|
||||
/^ROOT\//,
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root,
|
||||
@@ -73,48 +80,41 @@ export default {
|
||||
},
|
||||
formatExternalHtml() {
|
||||
document
|
||||
.querySelectorAll(".news-list-item .card-header")
|
||||
.querySelectorAll('.news-list-item .card-header')
|
||||
.forEach((el) => {
|
||||
el.classList.add("fhc-primary");
|
||||
el.classList.add('fhc-primary');
|
||||
});
|
||||
document.querySelectorAll(".news-list-item .row").forEach((el) => {
|
||||
el.classList.add("w-100");
|
||||
el.classList.add("align-items-center");
|
||||
document.querySelectorAll('.news-list-item .row').forEach((el) => {
|
||||
el.classList.add('w-100');
|
||||
el.classList.add('align-items-center');
|
||||
});
|
||||
document.querySelectorAll('.news-list-item .row h2').forEach((el) => {
|
||||
el.classList.add('mb-0');
|
||||
});
|
||||
document
|
||||
.querySelectorAll(".news-list-item .row h2")
|
||||
.forEach((el) => {
|
||||
el.classList.add("mb-0");
|
||||
});
|
||||
},
|
||||
afterPageUpdated(event) {
|
||||
this.page = event.page;
|
||||
this.page_size = event.rows;
|
||||
this.$refs.newsPageHeading.scrollIntoView({block: 'end'});
|
||||
this.$refs.newsPageHeading.scrollIntoView({ block: 'end' });
|
||||
this.loadNewPageContent(event);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.fetchNews();
|
||||
|
||||
this.$api
|
||||
.call(ApiCms.getNewsRowCount())
|
||||
.then((res) => res.data)
|
||||
.then((result) => {
|
||||
this.maxPageCount = result;
|
||||
});
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div :class="{'pb-3': isMobile}" class="overflow-x-hidden">
|
||||
<h2 ref="newsPageHeading" class="fhc-primary-color">News</h2>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h2 ref="newsPageHeading" class="fhc-primary-color">News</h2>
|
||||
<router-link
|
||||
v-if="hasBasisNewsTypRPermission"
|
||||
class="btn btn-primary mb-3"
|
||||
:to="{ name: 'NewsAdministration' }"
|
||||
>
|
||||
{{ $p.t('ui', 'newsAdministration') }} <i class="fas fa-cog m-0"></i>
|
||||
</router-link>
|
||||
</div>
|
||||
<hr/>
|
||||
<pagination
|
||||
v-if="content?true:false"
|
||||
:page="page"
|
||||
:page_size="page_size"
|
||||
@pageUpdated="afterPageUpdated($event)"
|
||||
:maxPageCount="maxPageCount"
|
||||
></pagination>
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row">
|
||||
<div class="col" v-html="content">
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import NewsItemForm from './NewsItemForm.js';
|
||||
import NewsItemPreview from './NewsItemPreview.js';
|
||||
import NewsList from './NewsList.js';
|
||||
import ApiNewsAdministration from '../../../../api/factory/newsAdministration.js';
|
||||
|
||||
export default {
|
||||
name: 'NewsAdministration',
|
||||
components: {
|
||||
NewsItemForm,
|
||||
NewsItemPreview,
|
||||
NewsList,
|
||||
},
|
||||
props: {
|
||||
permissions: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isNewsFormShown: false,
|
||||
isNewsPreviewShown: false,
|
||||
editableNewsItem: null,
|
||||
newsPreview: null,
|
||||
newsFormCollapse: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
isNewsFormShown: async function (isShown) {
|
||||
await this.$nextTick();
|
||||
this.newsFormCollapse?.[isShown ? 'show' : 'hide']();
|
||||
},
|
||||
'$route.query.newsId': {
|
||||
immediate: true,
|
||||
handler(newNewsId, oldNewsId) {
|
||||
if (!newNewsId) {
|
||||
this.isNewsFormShown = false;
|
||||
this.isNewsPreviewShown = false;
|
||||
this.editableNewsItem = null;
|
||||
this.newsPreview = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.editNewsItem({ newsId: this.$route.query.newsId });
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasBasisNewsTypRPermission() {
|
||||
return this.permissions['basis/news_r'] || false;
|
||||
},
|
||||
hasBasisNewsTypWPermission() {
|
||||
return this.permissions['basis/news_w'] || false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async scrollToNewsForm() {
|
||||
await this.$nextTick();
|
||||
this.$refs.newsItemForm?.$el?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
});
|
||||
},
|
||||
async reloadNews() {
|
||||
await this.$refs.newsList.fetchNews();
|
||||
},
|
||||
async showCreateNewsForm() {
|
||||
this.editableNewsItem = null;
|
||||
await this.$refs.newsItemForm?.fillFormData();
|
||||
|
||||
await this.$nextTick();
|
||||
|
||||
this.isNewsPreviewShown = false;
|
||||
this.isNewsFormShown = true;
|
||||
|
||||
this.scrollToNewsForm();
|
||||
},
|
||||
async hideNewsForm() {
|
||||
this.isNewsFormShown = false;
|
||||
this.isNewsPreviewShown = false;
|
||||
|
||||
this.editableNewsItem = null;
|
||||
await this.$refs.newsItemForm?.fillFormData();
|
||||
|
||||
this.newsPreview = null;
|
||||
},
|
||||
handleNewsItemSaved() {
|
||||
this.isNewsFormShown = false;
|
||||
this.isNewsPreviewShown = false;
|
||||
this.editableNewsItem = null;
|
||||
this.newsPreview = null;
|
||||
this.reloadNews();
|
||||
this.$refs.newsItemForm?.fillFormData();
|
||||
},
|
||||
async handleNewsItemUpdated() {
|
||||
this.isNewsFormShown = false;
|
||||
this.isNewsPreviewShown = false;
|
||||
this.editableNewsItem = null;
|
||||
this.newsPreview = null;
|
||||
this.reloadNews();
|
||||
this.$refs.newsItemForm?.fillFormData();
|
||||
},
|
||||
async loadNewsItem(newsId) {
|
||||
if (!newsId) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.$api.call(
|
||||
ApiNewsAdministration.getNewsItem(newsId),
|
||||
);
|
||||
const newsItem = response.data;
|
||||
|
||||
if (!newsItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return newsItem;
|
||||
} catch (error) {
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
},
|
||||
async editNewsItem(newsItem) {
|
||||
try {
|
||||
this.editableNewsItem = await this.loadNewsItem(newsItem.newsId);
|
||||
this.isNewsPreviewShown = false;
|
||||
|
||||
await this.$nextTick();
|
||||
|
||||
setTimeout(() => {
|
||||
this.isNewsFormShown = true;
|
||||
this.scrollToNewsForm();
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
},
|
||||
toggleNewsPreview() {
|
||||
this.isNewsPreviewShown = !this.isNewsPreviewShown;
|
||||
},
|
||||
handlePreviewChange(preview) {
|
||||
this.newsPreview = preview;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$p.loadCategory('ui').then(() => {
|
||||
this.phrasesLoaded = true;
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.newsFormCollapse = new bootstrap.Collapse(
|
||||
this.$refs.newsFormCollapse,
|
||||
{ toggle: false },
|
||||
);
|
||||
|
||||
if (this.isNewsFormShown) {
|
||||
this.newsFormCollapse.show();
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.newsFormCollapse?.dispose();
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="pb-3 overflow-x-hidden">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 ref="newsPageHeading" class="fhc-primary-color mb-0">{{ $capitalize($p.t('ui', 'newsAdministration')) }}</h2>
|
||||
<button
|
||||
v-if="!isNewsFormShown && hasBasisNewsTypWPermission"
|
||||
@click="showCreateNewsForm"
|
||||
type="button"
|
||||
class="btn btn-primary rounded-circle d-inline-flex align-items-center justify-content-center p-0"
|
||||
style="width: 2.5rem; height: 2.5rem"
|
||||
:title="$capitalize($p.t('ui', 'createNews'))"
|
||||
:aria-label="$capitalize($p.t('ui', 'createNews'))"
|
||||
aria-controls="news-item-form-collapse"
|
||||
>
|
||||
<i class="fa-solid fa-plus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div ref="newsFormCollapse" id="news-item-form-collapse" class="collapse">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div :class="isNewsPreviewShown ? 'col-12 col-xl-6' : 'col-12'">
|
||||
<news-item-form
|
||||
ref="newsItemForm"
|
||||
:key="editableNewsItem?.newsId || 'new-news-item'"
|
||||
id="news-item-form"
|
||||
:news-item="editableNewsItem"
|
||||
:is-preview-shown="isNewsPreviewShown"
|
||||
@created="handleNewsItemSaved"
|
||||
@update="handleNewsItemUpdated"
|
||||
@cancel="hideNewsForm"
|
||||
@preview-change="handlePreviewChange"
|
||||
@toggle-preview="toggleNewsPreview"
|
||||
></news-item-form>
|
||||
</div>
|
||||
<div v-if="isNewsPreviewShown" class="col-12 col-xl-6">
|
||||
<news-item-preview
|
||||
:form-data="newsPreview?.formData"
|
||||
:active-language="newsPreview?.activeLanguage"
|
||||
></news-item-preview>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<news-list
|
||||
v-if="hasBasisNewsTypRPermission"
|
||||
ref="newsList"
|
||||
@edit="editNewsItem"
|
||||
></news-list>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
export default {
|
||||
name: 'NewsItemAddLanguageTab',
|
||||
emits: ['tab-action'],
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$p.loadCategory('ui').then(() => {
|
||||
this.phrasesLoaded = true;
|
||||
});
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="py-4 text-center text-muted">
|
||||
<div v-if="config?.languages?.length" class="d-flex justify-content-center gap-2">
|
||||
<button
|
||||
v-for="language in config.languages"
|
||||
:key="language.value"
|
||||
type="button"
|
||||
class="btn btn-outline-primary text-start"
|
||||
@click="$emit('tab-action', { action: 'add-language', language: language.value })"
|
||||
>
|
||||
{{ language.label }}
|
||||
</button>
|
||||
</div>
|
||||
<p v-else class="mb-0 text-muted">{{ $capitalize($p.t('ui', 'allLanguagesAdded')) }}</p>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,239 @@
|
||||
import FormInput from '../../../Form/Input.js';
|
||||
|
||||
export default {
|
||||
name: 'NewsItemContentForm',
|
||||
components: {
|
||||
FormInput,
|
||||
},
|
||||
emits: ['update:modelValue', 'tab-action'],
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const translation = this.modelValue.find(
|
||||
(item) => item.language === this.config.language,
|
||||
);
|
||||
|
||||
return {
|
||||
editor: null,
|
||||
dmsFileBrowser: null,
|
||||
dmsFileBrowserClosePollId: null,
|
||||
dmsFileBrowserMessageHandler: null,
|
||||
contentData: {
|
||||
language: this.config.language,
|
||||
author: '',
|
||||
title: '',
|
||||
text: '',
|
||||
isPublished: false,
|
||||
...translation,
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
contentData: {
|
||||
deep: true,
|
||||
handler(contentData) {
|
||||
const translations = this.modelValue.map((item) => ({ ...item }));
|
||||
const index = translations.findIndex(
|
||||
(item) => item.language === this.config.language,
|
||||
);
|
||||
const translation = {
|
||||
...contentData,
|
||||
language: this.config.language,
|
||||
};
|
||||
|
||||
if (index === -1) {
|
||||
translations.push(translation);
|
||||
} else {
|
||||
translations[index] = translation;
|
||||
}
|
||||
|
||||
this.$emit('update:modelValue', translations);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
cleanupDmsFileBrowser(closePopup = false) {
|
||||
if (this.dmsFileBrowserClosePollId !== null) {
|
||||
window.clearInterval(this.dmsFileBrowserClosePollId);
|
||||
this.dmsFileBrowserClosePollId = null;
|
||||
}
|
||||
|
||||
if (this.dmsFileBrowserMessageHandler) {
|
||||
window.removeEventListener(
|
||||
'message',
|
||||
this.dmsFileBrowserMessageHandler,
|
||||
);
|
||||
this.dmsFileBrowserMessageHandler = null;
|
||||
}
|
||||
|
||||
if (closePopup && this.dmsFileBrowser && !this.dmsFileBrowser.closed) {
|
||||
this.dmsFileBrowser.close();
|
||||
}
|
||||
|
||||
this.dmsFileBrowser = null;
|
||||
},
|
||||
openDmsFileBrowser(callback, meta) {
|
||||
this.cleanupDmsFileBrowser(true);
|
||||
|
||||
const appRoot =
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/\/+$/, '') + '/';
|
||||
const fileType = meta && meta.filetype ? meta.filetype : 'file';
|
||||
const fileBrowserUrl =
|
||||
appRoot + 'cms/tinymce_dms.php?type=' + encodeURIComponent(fileType);
|
||||
const popupName = this.editor
|
||||
? 'FHCFileBrowser_' + this.editor.id
|
||||
: 'FHCFileBrowser';
|
||||
const popup = window.open(
|
||||
fileBrowserUrl,
|
||||
popupName,
|
||||
'width=750,height=550,resizable=yes,scrollbars=yes',
|
||||
);
|
||||
|
||||
if (!popup) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dmsFileBrowser = popup;
|
||||
|
||||
this.dmsFileBrowserMessageHandler = (event) => {
|
||||
const message = event.data;
|
||||
|
||||
if (
|
||||
event.origin !== window.location.origin ||
|
||||
event.source !== popup ||
|
||||
this.dmsFileBrowser !== popup ||
|
||||
!message ||
|
||||
message.type !== 'fhcomplete:dms:selected' ||
|
||||
!Number.isInteger(message.dmsId) ||
|
||||
message.dmsId <= 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
callback(
|
||||
appRoot + 'cms/dms.php?id=' + encodeURIComponent(message.dmsId),
|
||||
);
|
||||
} finally {
|
||||
this.cleanupDmsFileBrowser(true);
|
||||
}
|
||||
};
|
||||
window.addEventListener('message', this.dmsFileBrowserMessageHandler);
|
||||
|
||||
this.dmsFileBrowserClosePollId = window.setInterval(() => {
|
||||
if (this.dmsFileBrowser === popup && popup.closed) {
|
||||
this.cleanupDmsFileBrowser();
|
||||
}
|
||||
}, 250);
|
||||
popup.focus();
|
||||
},
|
||||
initTinyMCE() {
|
||||
const vm = this;
|
||||
tinymce.init({
|
||||
target: this.$refs.editor.$refs.input, // Important: enables multiple component instances
|
||||
height: 500,
|
||||
toolbar:
|
||||
'styleselect fontsizeselect | bold italic underline | alignleft aligncenter alignright alignjustify | link unlink image | bullist | pastetext',
|
||||
plugins: 'lists link image paste',
|
||||
paste_as_text: true,
|
||||
statusbar: true,
|
||||
resize: true,
|
||||
file_picker_types: 'file image',
|
||||
file_picker_callback: (callback, _value, meta) => {
|
||||
vm.openDmsFileBrowser(callback, meta);
|
||||
},
|
||||
|
||||
style_formats: [
|
||||
{ title: vm.$capitalize(vm.$p.t('ui', 'blocks')), block: 'div' },
|
||||
{ title: vm.$capitalize(vm.$p.t('ui', 'paragraph')), block: 'p' },
|
||||
{ title: vm.$capitalize(vm.$p.t('ui', 'heading1')), block: 'h1' },
|
||||
{ title: vm.$capitalize(vm.$p.t('ui', 'heading2')), block: 'h2' },
|
||||
{ title: vm.$capitalize(vm.$p.t('ui', 'heading3')), block: 'h3' },
|
||||
{ title: vm.$capitalize(vm.$p.t('ui', 'heading4')), block: 'h4' },
|
||||
{ title: vm.$capitalize(vm.$p.t('ui', 'heading5')), block: 'h5' },
|
||||
{ title: vm.$capitalize(vm.$p.t('ui', 'heading6')), block: 'h6' },
|
||||
],
|
||||
setup: (editor) => {
|
||||
vm.editor = editor;
|
||||
|
||||
editor.on('input change undo redo', () => {
|
||||
vm.contentData.text = editor.getContent();
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.$p.loadCategory(['global', 'ui', 'notiz']);
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.$refs.editor?.$refs.input) {
|
||||
this.initTinyMCE();
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.cleanupDmsFileBrowser(true);
|
||||
|
||||
if (this.editor) {
|
||||
this.editor.destroy();
|
||||
}
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="position-relative overflow-x-hidden">
|
||||
<button
|
||||
v-if="config.language !== 'German'"
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-danger position-absolute top-0 end-0"
|
||||
:title="$capitalize($p.t('ui', 'removeLanguage'))"
|
||||
:aria-label="$capitalize($p.t('ui', 'removeLanguage'))"
|
||||
@click="$emit('tab-action', { action: 'remove-language', language: config.language })"
|
||||
>
|
||||
<i class="fa-solid fa-trash-can" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-end gap-3">
|
||||
<form-input
|
||||
type="text"
|
||||
:label="$capitalize($p.t('notiz', 'verfasser'))"
|
||||
name="author"
|
||||
v-model="contentData.author"
|
||||
>
|
||||
</form-input>
|
||||
<form-input
|
||||
type="text"
|
||||
:label="$capitalize($p.t('global', 'betreff'))"
|
||||
name="title"
|
||||
v-model="contentData.title"
|
||||
>
|
||||
</form-input>
|
||||
<form-input
|
||||
type="checkbox"
|
||||
:label="$capitalize($p.t('ui', 'publish'))"
|
||||
name="isPublished"
|
||||
v-model="contentData.isPublished"
|
||||
>
|
||||
</form-input>
|
||||
</div>
|
||||
<form-input
|
||||
ref="editor"
|
||||
:label="$capitalize($p.t('global', 'text') + ' *')"
|
||||
type="textarea"
|
||||
name="text"
|
||||
v-model="contentData.text"
|
||||
rows="5"
|
||||
cols="75"
|
||||
style="min-height: 500px"
|
||||
>
|
||||
</form-input>
|
||||
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
import BootstrapModal from '../../../Bootstrap/Modal.js';
|
||||
|
||||
export default {
|
||||
name: 'NewsItemCopyTranslationModal',
|
||||
components: {
|
||||
BootstrapModal,
|
||||
},
|
||||
emits: ['copy', 'hidden'],
|
||||
props: {
|
||||
sourceLanguages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
targetLanguageLabel: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
targetHasContent: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedSourceLanguage: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
this.selectedSourceLanguage = null;
|
||||
this.$refs.modal.show();
|
||||
},
|
||||
hide() {
|
||||
this.$refs.modal.hide();
|
||||
},
|
||||
selectSourceLanguage(language) {
|
||||
if (this.targetHasContent) {
|
||||
this.selectedSourceLanguage = language;
|
||||
return;
|
||||
}
|
||||
|
||||
this.copyTranslation(language);
|
||||
},
|
||||
copyTranslation(language = this.selectedSourceLanguage) {
|
||||
const sourceLanguage = language?.value ?? language;
|
||||
|
||||
if (!sourceLanguage) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('copy', sourceLanguage);
|
||||
this.hide();
|
||||
},
|
||||
resetSelection() {
|
||||
this.selectedSourceLanguage = null;
|
||||
this.$emit('hidden');
|
||||
},
|
||||
getCopyingTranslationText() {
|
||||
return this.$capitalize(
|
||||
this.$p.t('ui', 'copyingTranslation', {
|
||||
source: this.selectedSourceLanguage?.label ?? '',
|
||||
target: this.targetLanguageLabel,
|
||||
}),
|
||||
);
|
||||
},
|
||||
getChooseLanguageText() {
|
||||
return this.$capitalize(
|
||||
this.$p.t('ui', 'chooseLanguageToCopyContent', {
|
||||
target: this.targetLanguageLabel,
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$p.loadCategory('ui').then(() => {
|
||||
this.phrasesLoaded = true;
|
||||
});
|
||||
},
|
||||
template: /*html*/ `
|
||||
<bootstrap-modal
|
||||
ref="modal"
|
||||
dialog-class="modal-dialog-centered"
|
||||
body-class="p-4"
|
||||
@hidden-bs-modal="resetSelection"
|
||||
>
|
||||
<template #title>
|
||||
{{ $capitalize(selectedSourceLanguage ? $p.t('ui', 'overwriteTranslationContent') : $p.t('ui', 'copyTranslationContent')) }}
|
||||
</template>
|
||||
<template v-if="selectedSourceLanguage">
|
||||
<p class="mb-3">
|
||||
{{ getCopyingTranslationText() }}
|
||||
</p>
|
||||
<p class="text-muted small">{{ $capitalize($p.t('ui', 'existingContentWillBeLost')) }}</p>
|
||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
||||
<button type="button" class="btn btn-outline-secondary" @click="selectedSourceLanguage = null">
|
||||
{{ $capitalize($p.t('ui', 'chooseAnotherLanguage')) }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" @click="copyTranslation()">
|
||||
{{ $capitalize($p.t('ui', 'overwriteAndCopy')) }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="text-muted">{{ getChooseLanguageText() }}</p>
|
||||
<div v-if="sourceLanguages.length" class="d-grid gap-2">
|
||||
<button
|
||||
v-for="language in sourceLanguages"
|
||||
:key="language.value"
|
||||
type="button"
|
||||
class="btn btn-outline-primary text-start"
|
||||
@click="selectSourceLanguage(language)"
|
||||
>
|
||||
{{ language.label }}
|
||||
</button>
|
||||
</div>
|
||||
<p v-else class="mb-0 text-muted">{{ $capitalize($p.t('ui', 'addLanguageBeforeCopying')) }}</p>
|
||||
</template>
|
||||
</bootstrap-modal>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,614 @@
|
||||
import CoreForm from '../../../Form/Form.js';
|
||||
import FormInput from '../../../Form/Input.js';
|
||||
import FhcTabs from '../../../Tabs.js';
|
||||
import NewsItemCopyTranslationModal from './NewsItemCopyTranslationModal.js';
|
||||
import ApiStudiengang from '../../../../api/factory/studiengang.js';
|
||||
import ApiNewsAdministration from '../../../../api/factory/newsAdministration.js';
|
||||
|
||||
const BASE_COMPONENT_URL =
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
'public/js/components/Cis/Cms/NewsAdministration/';
|
||||
|
||||
export default {
|
||||
name: 'NewsItemForm',
|
||||
components: {
|
||||
CoreForm,
|
||||
FormInput,
|
||||
FhcTabs,
|
||||
NewsItemCopyTranslationModal,
|
||||
},
|
||||
emits: [
|
||||
'created',
|
||||
'update',
|
||||
'cancel',
|
||||
'translation-copied',
|
||||
'preview-change',
|
||||
'toggle-preview',
|
||||
],
|
||||
props: {
|
||||
newsItem: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
isPreviewShown: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
degreePrograms: [],
|
||||
filteredDegreePrograms: [],
|
||||
isSaving: false,
|
||||
activeContentFormKey: 'germanContentForm',
|
||||
contentFormRenderKey: 0,
|
||||
formData: {
|
||||
visibleFrom: null,
|
||||
visibleTo: null,
|
||||
degreeProgram: null,
|
||||
semester: null,
|
||||
translations: [],
|
||||
},
|
||||
contentFormItems: {},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
'$p.user_language.value': function () {
|
||||
Object.values(this.contentFormItems).forEach((item) => {
|
||||
if (item.config?.language) {
|
||||
item.title = this.getContentFormTitle(item.config.language);
|
||||
}
|
||||
});
|
||||
},
|
||||
formData: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.emitPreviewChange();
|
||||
},
|
||||
},
|
||||
activeContentLanguage() {
|
||||
this.emitPreviewChange();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
sprache: function () {
|
||||
return this.$p.user_language.value;
|
||||
},
|
||||
availableLanguages() {
|
||||
this.sprache;
|
||||
|
||||
return FHC_JS_DATA_STORAGE_OBJECT?.server_languages?.map(
|
||||
(languageObject) => ({
|
||||
label: this.getLanguageLabel(languageObject.sprache),
|
||||
value: languageObject.sprache,
|
||||
}),
|
||||
);
|
||||
},
|
||||
semesters() {
|
||||
this.sprache;
|
||||
const semesterLabel = this.$capitalize(this.$p.t('lehre', 'semester'));
|
||||
|
||||
return [
|
||||
{
|
||||
label: this.$capitalize(this.$p.t('ui', 'all_semester')),
|
||||
value: null,
|
||||
},
|
||||
...Array.from({ length: 8 }, (_, index) => ({
|
||||
label: `${index + 1}. ${semesterLabel}`,
|
||||
value: String(index + 1),
|
||||
})),
|
||||
];
|
||||
},
|
||||
dropdownParsedDegreePrograms() {
|
||||
return this.degreePrograms
|
||||
.map((degreeProgram) => {
|
||||
let labelFragment = degreeProgram.typ + degreeProgram.kurzbz;
|
||||
labelFragment = labelFragment.trim().toUpperCase();
|
||||
|
||||
return {
|
||||
label: `${labelFragment} (${degreeProgram.bezeichnung})`,
|
||||
value: degreeProgram.studiengang_kz,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
},
|
||||
visibleContentLanguages() {
|
||||
return Object.values(this.contentFormItems)
|
||||
.map((item) => item.config?.language)
|
||||
.filter(Boolean);
|
||||
},
|
||||
languagesToAdd() {
|
||||
return this.availableLanguages.filter(
|
||||
(language) => !this.visibleContentLanguages.includes(language.value),
|
||||
);
|
||||
},
|
||||
activeContentLanguage() {
|
||||
return this.contentFormItems[this.activeContentFormKey]?.config?.language;
|
||||
},
|
||||
copySourceLanguages() {
|
||||
return this.visibleContentLanguages
|
||||
.filter((language) => language !== this.activeContentLanguage)
|
||||
.map((language) => ({
|
||||
label: this.getLanguageLabel(language),
|
||||
value: language,
|
||||
}));
|
||||
},
|
||||
activeTranslationHasContent() {
|
||||
return this.hasTranslationContent(
|
||||
this.getTranslation(this.activeContentLanguage),
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
emitPreviewChange() {
|
||||
this.$emit('preview-change', {
|
||||
formData: this.formData,
|
||||
activeLanguage: this.activeContentLanguage,
|
||||
});
|
||||
},
|
||||
getContentFormKey(language) {
|
||||
return `${language.toLowerCase()}ContentForm`;
|
||||
},
|
||||
createContentFormItem(language) {
|
||||
const key = this.getContentFormKey(language);
|
||||
|
||||
return {
|
||||
title: this.getContentFormTitle(language),
|
||||
component: BASE_COMPONENT_URL + 'NewsItemContentForm.js?124',
|
||||
config: {
|
||||
language,
|
||||
type: 'news',
|
||||
},
|
||||
key,
|
||||
};
|
||||
},
|
||||
createAddLanguageTab() {
|
||||
return {
|
||||
title: '+',
|
||||
component: BASE_COMPONENT_URL + 'NewsItemAddLanguageTab.js',
|
||||
config: { languages: [] },
|
||||
key: 'addLanguage',
|
||||
};
|
||||
},
|
||||
showCopyTranslationModal() {
|
||||
this.$refs.copyTranslationModal.show();
|
||||
},
|
||||
getLanguageLabel(language) {
|
||||
const languagePhrases = {
|
||||
German: ['global', 'deutsch'],
|
||||
English: ['global', 'englisch'],
|
||||
French: ['ui', 'franzoesisch'],
|
||||
Spanish: ['ui', 'spanisch'],
|
||||
};
|
||||
const phrase = languagePhrases[language];
|
||||
|
||||
return phrase
|
||||
? this.$capitalize(this.$p.t(phrase[0], phrase[1]))
|
||||
: language;
|
||||
},
|
||||
getContentFormTitle(language) {
|
||||
return this.$capitalize(
|
||||
this.$p.t('ui', 'contentFormTitle', {
|
||||
language: this.getLanguageLabel(language),
|
||||
}),
|
||||
);
|
||||
},
|
||||
getTranslation(language) {
|
||||
return this.formData.translations.find(
|
||||
(translation) => translation.language === language,
|
||||
);
|
||||
},
|
||||
hasTranslationContent(translation) {
|
||||
return ['author', 'title', 'text'].some(
|
||||
(field) => String(translation?.[field] ?? '').trim() !== '',
|
||||
);
|
||||
},
|
||||
copyTranslation(sourceLanguage) {
|
||||
const sourceTranslation = this.getTranslation(sourceLanguage);
|
||||
const targetTranslation = this.getTranslation(this.activeContentLanguage);
|
||||
|
||||
if (!sourceTranslation || !targetTranslation) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.assign(targetTranslation, {
|
||||
author: sourceTranslation.author,
|
||||
title: sourceTranslation.title,
|
||||
text: sourceTranslation.text,
|
||||
});
|
||||
|
||||
this.contentFormRenderKey += 1;
|
||||
this.$emit('translation-copied', {
|
||||
from: sourceLanguage,
|
||||
to: this.activeContentLanguage,
|
||||
});
|
||||
},
|
||||
handleContentTabChange(key) {
|
||||
this.activeContentFormKey = key;
|
||||
},
|
||||
handleContentTabAction({ key, payload }) {
|
||||
if (payload?.action === 'add-language') {
|
||||
this.addLanguage(payload.language);
|
||||
} else if (payload?.action === 'remove-language') {
|
||||
this.removeLanguage(payload.language);
|
||||
}
|
||||
},
|
||||
removeLanguage(language) {
|
||||
const key = this.getContentFormKey(language);
|
||||
|
||||
if (!this.contentFormItems[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentFormItems = { ...this.contentFormItems };
|
||||
const addLanguageTab = contentFormItems.addLanguage;
|
||||
delete contentFormItems[key];
|
||||
delete contentFormItems.addLanguage;
|
||||
|
||||
const remainingLanguages = Object.values(contentFormItems)
|
||||
.map((item) => item.config?.language)
|
||||
.filter(Boolean);
|
||||
contentFormItems.addLanguage = {
|
||||
...addLanguageTab,
|
||||
config: {
|
||||
hasAvailableLanguages: this.availableLanguages.some(
|
||||
(availableLanguage) =>
|
||||
!remainingLanguages.includes(availableLanguage.value),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
this.contentFormItems = contentFormItems;
|
||||
this.formData.translations = this.formData.translations.filter(
|
||||
(translation) => translation.language !== language,
|
||||
);
|
||||
|
||||
const nextContentFormKey = Object.keys(contentFormItems).find(
|
||||
(itemKey) => itemKey !== 'addLanguage',
|
||||
);
|
||||
this.activeContentFormKey = nextContentFormKey ?? 'addLanguage';
|
||||
|
||||
if (nextContentFormKey) {
|
||||
this.$nextTick(() => this.$refs.tabs.change(nextContentFormKey));
|
||||
}
|
||||
},
|
||||
addLanguage(language) {
|
||||
const key = this.getContentFormKey(language);
|
||||
|
||||
if (this.contentFormItems[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const addLanguageTab = this.contentFormItems.addLanguage;
|
||||
const contentFormItems = { ...this.contentFormItems };
|
||||
delete contentFormItems.addLanguage;
|
||||
contentFormItems[key] = this.createContentFormItem(language);
|
||||
contentFormItems.addLanguage = {
|
||||
...addLanguageTab,
|
||||
config: {
|
||||
hasAvailableLanguages: this.availableLanguages.some(
|
||||
(availableLanguage) =>
|
||||
availableLanguage.value !== language &&
|
||||
!this.visibleContentLanguages.includes(availableLanguage.value),
|
||||
),
|
||||
},
|
||||
};
|
||||
this.contentFormItems = contentFormItems;
|
||||
|
||||
if (
|
||||
!this.formData.translations.some((item) => item.language === language)
|
||||
) {
|
||||
this.formData.translations.push({
|
||||
language,
|
||||
author: '',
|
||||
title: '',
|
||||
text: '',
|
||||
isPublished: false,
|
||||
});
|
||||
}
|
||||
|
||||
this.activeContentFormKey = key;
|
||||
this.$nextTick(() => this.$refs.tabs.change(key));
|
||||
},
|
||||
restoreActiveContentTab() {
|
||||
this.$refs.tabs.change(this.activeContentFormKey);
|
||||
},
|
||||
fillFormData(newsItem) {
|
||||
this.formData.visibleFrom =
|
||||
newsItem?.visibleFrom ?? newsItem?.dateTime?.slice(0, 10) ?? null;
|
||||
this.formData.visibleTo = newsItem?.visibleTo ?? null;
|
||||
const degreeProgramShortCode =
|
||||
newsItem?.degreeProgramShortCode ??
|
||||
newsItem?.degreeProgram?.value ??
|
||||
null;
|
||||
this.formData.degreeProgram =
|
||||
this.dropdownParsedDegreePrograms.find(
|
||||
(degreeProgram) =>
|
||||
String(degreeProgram.value) === String(degreeProgramShortCode),
|
||||
) ??
|
||||
(degreeProgramShortCode === null
|
||||
? null
|
||||
: {
|
||||
label: String(degreeProgramShortCode),
|
||||
value: degreeProgramShortCode,
|
||||
});
|
||||
this.formData.semester = newsItem?.semester ?? null;
|
||||
|
||||
const translations = newsItem?.translations ?? [
|
||||
{
|
||||
language: newsItem?.language ?? 'German',
|
||||
author: newsItem?.author ?? '',
|
||||
title: newsItem?.title ?? '',
|
||||
text: newsItem?.content ?? '',
|
||||
isPublished: Boolean(newsItem?.isPublished),
|
||||
},
|
||||
];
|
||||
|
||||
this.formData.translations = translations.map((sourceTranslation) => ({
|
||||
language: sourceTranslation.language,
|
||||
author: sourceTranslation.author ?? '',
|
||||
title: sourceTranslation.title ?? '',
|
||||
text: sourceTranslation.text ?? '',
|
||||
isPublished: Boolean(sourceTranslation.isPublished),
|
||||
}));
|
||||
|
||||
const contentFormItems = {};
|
||||
|
||||
this.formData.translations.forEach((translation) => {
|
||||
if (translation.language) {
|
||||
contentFormItems[this.getContentFormKey(translation.language)] =
|
||||
this.createContentFormItem(translation.language);
|
||||
}
|
||||
});
|
||||
|
||||
contentFormItems.addLanguage = this.createAddLanguageTab();
|
||||
this.contentFormItems = contentFormItems;
|
||||
this.activeContentFormKey =
|
||||
Object.keys(contentFormItems).find((key) => key !== 'addLanguage') ??
|
||||
'addLanguage';
|
||||
},
|
||||
filterDegreePrograms(event) {
|
||||
const query = event.query.toLowerCase();
|
||||
|
||||
return (this.filteredDegreePrograms =
|
||||
this.dropdownParsedDegreePrograms.filter((unit) => {
|
||||
return unit.label.toLowerCase().includes(query);
|
||||
}));
|
||||
},
|
||||
async storeNewsItem() {
|
||||
if (this.isSaving) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parsedFormData = JSON.parse(JSON.stringify(this.formData));
|
||||
parsedFormData.degreeProgramShortCode =
|
||||
parsedFormData.degreeProgram?.value;
|
||||
delete parsedFormData.degreeProgram;
|
||||
|
||||
this.isSaving = true;
|
||||
|
||||
try {
|
||||
const response = await this.$refs.form.call(
|
||||
ApiNewsAdministration.storeNewsItem(parsedFormData),
|
||||
);
|
||||
|
||||
if (response.meta.status !== 'success') {
|
||||
this.$fhcAlert.alertError(
|
||||
this.$capitalize(this.$p.t('ui', 'fehlerBeimSpeichern')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$fhcAlert.alertSuccess(
|
||||
this.$capitalize(this.$p.t('ui', 'gespeichert')),
|
||||
);
|
||||
this.$emit('created');
|
||||
} catch (error) {
|
||||
if (!error?.handled) {
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
},
|
||||
async updateNewsItem() {
|
||||
if (!this.newsItem || this.isSaving) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parsedFormData = JSON.parse(JSON.stringify(this.formData));
|
||||
parsedFormData.degreeProgramShortCode =
|
||||
parsedFormData.degreeProgram?.value;
|
||||
delete parsedFormData.degreeProgram;
|
||||
|
||||
this.isSaving = true;
|
||||
|
||||
try {
|
||||
const response = await this.$refs.form.call(
|
||||
ApiNewsAdministration.updateNewsItem(
|
||||
this.newsItem.newsId,
|
||||
parsedFormData,
|
||||
),
|
||||
);
|
||||
|
||||
if (response.meta.status !== 'success') {
|
||||
this.$fhcAlert.alertError(
|
||||
this.$capitalize(this.$p.t('ui', 'fehlerBeimSpeichern')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$fhcAlert.alertSuccess(
|
||||
this.$capitalize(this.$p.t('ui', 'gespeichert')),
|
||||
);
|
||||
this.$emit('update');
|
||||
} catch (error) {
|
||||
if (!error?.handled) {
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
await this.$p.loadCategory(['global', 'ui', 'lehre']);
|
||||
|
||||
let getAllDegreePrograms = await this.$api.call(
|
||||
ApiStudiengang.getDegreePrograms(),
|
||||
);
|
||||
if (getAllDegreePrograms.meta.status === 'success') {
|
||||
this.degreePrograms = getAllDegreePrograms.data.sort((a, b) =>
|
||||
a.bezeichnung.localeCompare(b.bezeichnung),
|
||||
);
|
||||
} else {
|
||||
this.$fhcAlert.alertError(
|
||||
this.$capitalize(this.$p.t('ui', 'fehlerBeimLesen')),
|
||||
);
|
||||
}
|
||||
|
||||
this.fillFormData(this.newsItem);
|
||||
|
||||
if (!this.$props.newsItem) {
|
||||
this.formData.degreeProgram =
|
||||
this.dropdownParsedDegreePrograms.find(
|
||||
(degreeProgram) => degreeProgram.value === 0,
|
||||
) ?? null;
|
||||
}
|
||||
|
||||
const addLanguageTab = this.contentFormItems.addLanguage;
|
||||
if (addLanguageTab?.config) {
|
||||
addLanguageTab.config.languages = this.languagesToAdd;
|
||||
}
|
||||
},
|
||||
template: /*html*/ `
|
||||
<section
|
||||
class="card bg-white shadow-sm border-0 mb-4 overflow-x-hidden"
|
||||
aria-labelledby="news-item-form-heading"
|
||||
>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center gap-3 mb-3">
|
||||
<h4 id="news-item-form-heading" ref="newsPageHeading" class="card-title fhc-primary-color mb-0">{{ $capitalize($p.t('ui', 'newsForm')) }}</h4>
|
||||
<button
|
||||
@click="$emit('toggle-preview')"
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-primary d-inline-flex align-items-center justify-content-center"
|
||||
:title="$capitalize(isPreviewShown ? $p.t('ui', 'hidePreview') : $p.t('ui', 'showPreview'))"
|
||||
:aria-label="$capitalize(isPreviewShown ? $p.t('ui', 'hidePreview') : $p.t('ui', 'showPreview'))"
|
||||
:aria-expanded="isPreviewShown"
|
||||
aria-controls="news-item-preview"
|
||||
>
|
||||
<i class="fa-solid" :class="isPreviewShown ? 'fa-eye-slash' : 'fa-eye'" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<core-form ref="form">
|
||||
<div class="d-flex flex-row flex-md-row align-items-md-end gap-3">
|
||||
<div class="d-flex flex-row flex-md-row align-items-md-end gap-3">
|
||||
<form-input
|
||||
:label="$capitalize($p.t('ui', 'visible') + ' ' + $p.t('ui', 'von'))"
|
||||
:teleport="true"
|
||||
:enable-time-picker="false"
|
||||
type="datePicker"
|
||||
name="visibleFrom"
|
||||
v-model="formData.visibleFrom"
|
||||
model-type="yyyy-MM-dd"
|
||||
format="dd.MM.yyyy"
|
||||
auto-apply
|
||||
/>
|
||||
<form-input
|
||||
:label="$capitalize($p.t('ui', 'visible') + ' ' + $p.t('global', 'bis'))"
|
||||
:teleport="true"
|
||||
:enable-time-picker="false"
|
||||
type="datePicker"
|
||||
name="visibleTo"
|
||||
v-model="formData.visibleTo"
|
||||
model-type="yyyy-MM-dd"
|
||||
format="dd.MM.yyyy"
|
||||
auto-apply
|
||||
/>
|
||||
</div>
|
||||
<div class="d-flex flex-row flex-md-row align-items-md-end gap-3">
|
||||
<form-input
|
||||
:label="$capitalize($p.t('lehre/studiengang'))"
|
||||
:suggestions="filteredDegreePrograms"
|
||||
:optionValue="(option) => option.value"
|
||||
:optionLabel="(option) => option.label"
|
||||
@complete="filterDegreePrograms($event)"
|
||||
type="autocomplete"
|
||||
name="degreeProgramShortCode"
|
||||
v-model="formData.degreeProgram"
|
||||
dropdown
|
||||
forceSelection
|
||||
>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-model="formData.semester"
|
||||
:label="$capitalize($p.t('lehre/studiensemester'))"
|
||||
type="select"
|
||||
name="semester"
|
||||
>
|
||||
<option
|
||||
v-for="semester in semesters"
|
||||
:key="semester.value"
|
||||
:value="semester.value"
|
||||
>
|
||||
{{ semester.label }}
|
||||
</option>
|
||||
</form-input>
|
||||
</div>
|
||||
</div>
|
||||
<fhc-tabs
|
||||
:key="contentFormRenderKey"
|
||||
ref="tabs"
|
||||
:useprimevue="true"
|
||||
:config="contentFormItems"
|
||||
:default="activeContentFormKey"
|
||||
v-model="formData.translations"
|
||||
@change="handleContentTabChange"
|
||||
@tab-action="handleContentTabAction"
|
||||
style="flex: 1 1 0%; height: 0%"
|
||||
class="mt-3"
|
||||
>
|
||||
</fhc-tabs>
|
||||
<div class="d-flex justify-content-end mt-2">
|
||||
<button
|
||||
v-if="activeContentLanguage && copySourceLanguages.length"
|
||||
@click="showCopyTranslationModal"
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
:title="$capitalize($p.t('ui', 'copyContentFromAnotherLanguage'))"
|
||||
aria-haspopup="dialog"
|
||||
>
|
||||
<i class="fa-solid fa-copy me-1" aria-hidden="true"></i>
|
||||
{{ $capitalize($p.t('ui', 'copyContentFromAnotherLanguage')) }}
|
||||
</button>
|
||||
</div>
|
||||
<news-item-copy-translation-modal
|
||||
ref="copyTranslationModal"
|
||||
:source-languages="copySourceLanguages"
|
||||
:target-language-label="getLanguageLabel(activeContentLanguage)"
|
||||
:target-has-content="activeTranslationHasContent"
|
||||
@copy="copyTranslation"
|
||||
></news-item-copy-translation-modal>
|
||||
<div v-if="!isSaving" class="d-flex justify-content-end gap-2 mt-4 pt-3 border-top">
|
||||
<button
|
||||
@click="$emit('cancel')"
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
>
|
||||
{{ $capitalize($p.t('ui', 'abbrechen')) }}
|
||||
</button>
|
||||
<button
|
||||
@click="newsItem ? updateNewsItem() : storeNewsItem()"
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
{{ $capitalize($p.t('global', 'speichern')) }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="d-flex justify-content-end mt-4 pt-3 border-top" role="status">
|
||||
<span class="spinner-border" aria-hidden="true"></span>
|
||||
</div>
|
||||
</core-form>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,137 @@
|
||||
import FormInput from '../../../Form/Input.js';
|
||||
|
||||
export default {
|
||||
name: 'NewsItemPreview',
|
||||
components: {
|
||||
FormInput,
|
||||
},
|
||||
props: {
|
||||
formData: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
activeLanguage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedLanguage: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
sprache() {
|
||||
return this.$p.user_language.value;
|
||||
},
|
||||
availableLanguages() {
|
||||
return (this.formData?.translations ?? [])
|
||||
.map((translation) => translation.language)
|
||||
.filter(Boolean);
|
||||
},
|
||||
displayedLanguage() {
|
||||
return this.selectedLanguage || this.activeLanguage;
|
||||
},
|
||||
translation() {
|
||||
return this.formData?.translations?.find(
|
||||
(item) => item.language === this.displayedLanguage,
|
||||
);
|
||||
},
|
||||
parsedTranslationText() {
|
||||
const text = this.translation?.text ?? '';
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const appRoot = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/\/+$/, '');
|
||||
|
||||
return text.replace(
|
||||
/(?:https?:\/\/|\/|\.\.\/)?[^"'<>\s]*?dms\.php(\?[^"'<>\s]*)/gi,
|
||||
`${appRoot}/cms/dms.php$1`,
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
activeLanguage: {
|
||||
immediate: true,
|
||||
handler(language) {
|
||||
if (this.availableLanguages.includes(language)) {
|
||||
this.selectedLanguage = language;
|
||||
}
|
||||
},
|
||||
},
|
||||
availableLanguages(languages) {
|
||||
if (languages.includes(this.selectedLanguage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedLanguage = languages.includes(this.activeLanguage)
|
||||
? this.activeLanguage
|
||||
: (languages[0] ?? '');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$p.loadCategory(['global', 'ui']).then(() => {
|
||||
this.phrasesLoaded = true;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
getLanguageLabel(language) {
|
||||
this.sprache;
|
||||
const languagePhrases = {
|
||||
German: ['global', 'deutsch'],
|
||||
English: ['global', 'englisch'],
|
||||
French: ['ui', 'franzoesisch'],
|
||||
Spanish: ['ui', 'spanisch'],
|
||||
};
|
||||
const phrase = languagePhrases[language];
|
||||
|
||||
return phrase
|
||||
? this.$capitalize(this.$p.t(phrase[0], phrase[1]))
|
||||
: language;
|
||||
},
|
||||
},
|
||||
template: /*html*/ `
|
||||
<section
|
||||
id="news-item-preview"
|
||||
class="card bg-white shadow-sm border-0 mb-4 position-sticky"
|
||||
style="top: 1rem"
|
||||
aria-labelledby="news-item-preview-heading"
|
||||
>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center gap-3 mb-3">
|
||||
<h4 id="news-item-preview-heading" class="card-title fhc-primary-color mb-0">{{ $capitalize($p.t('ui', 'newsPreview')) }}</h4>
|
||||
<form-input
|
||||
v-if="availableLanguages.length > 1"
|
||||
v-model="selectedLanguage"
|
||||
type="select"
|
||||
name="previewLanguage"
|
||||
class="m-0"
|
||||
>
|
||||
<option
|
||||
v-for="language in availableLanguages"
|
||||
:key="language"
|
||||
:value="language"
|
||||
>
|
||||
{{ getLanguageLabel(language) }}
|
||||
</option>
|
||||
</form-input>
|
||||
</div>
|
||||
<article v-if="translation" class="card border">
|
||||
<header class="card-header" :class="translation.isPublished ? 'fhc-primary' : 'bg-secondary bg-opacity-25 text-secondary'">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||
<h5 class="mb-0">{{ translation.title || $capitalize($p.t('ui', 'untitledNews')) }}</h5>
|
||||
<span v-if="!translation.isPublished" class="badge text-bg-secondary">{{ $capitalize($p.t('ui', 'notPublishedYet')) }}</span>
|
||||
</div>
|
||||
<address v-if="translation.author" class="small mb-0 mt-2">{{ translation.author }}</address>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<div v-if="parsedTranslationText" class="card-text" v-html="parsedTranslationText"></div>
|
||||
<p v-else class="text-muted mb-0">{{ $capitalize($p.t('ui', 'newsContentPreviewHint')) }}</p>
|
||||
</div>
|
||||
</article>
|
||||
<p v-else class="text-muted mb-0">{{ $capitalize($p.t('ui', 'selectLanguageTabToPreview')) }}</p>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,171 @@
|
||||
import Pagination from '../../../Pagination/Pagination.js';
|
||||
import ApiNewsAdministration from '../../../../api/factory/newsAdministration.js';
|
||||
import NewsListItem from './NewsListItem.js';
|
||||
import NewsListFilter from './NewsListFilter.js';
|
||||
|
||||
export default {
|
||||
name: 'NewsList',
|
||||
emits: ['edit'],
|
||||
components: {
|
||||
NewsListItem,
|
||||
NewsListFilter,
|
||||
Pagination,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newsItems: [],
|
||||
isLoading: false,
|
||||
loadError: null,
|
||||
maxPageCount: 0,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
isPublishedFilter: 'all',
|
||||
isActiveFilter: true,
|
||||
degreeProgramShortCodeFilter: null,
|
||||
semesterFilter: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
'$p.user_language.value': function () {
|
||||
this.page = 1;
|
||||
this.fetchNews();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
sprache() {
|
||||
return this.$p.user_language.value;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async getNewsItem(newsId) {
|
||||
await this.fetchNews();
|
||||
|
||||
return this.newsItems.find(
|
||||
(news) => String(news.newsId) === String(newsId),
|
||||
);
|
||||
},
|
||||
async fetchNews() {
|
||||
this.isLoading = true;
|
||||
this.loadError = null;
|
||||
|
||||
try {
|
||||
const response = await this.$api.call(
|
||||
ApiNewsAdministration.getNews(
|
||||
this.page,
|
||||
this.pageSize,
|
||||
this.sprache,
|
||||
this.isPublishedFilter,
|
||||
this.isActiveFilter,
|
||||
this.degreeProgramShortCodeFilter,
|
||||
this.semesterFilter,
|
||||
),
|
||||
);
|
||||
this.newsItems = this.parseNewsHtml(response.data);
|
||||
this.maxPageCount = response.meta.row_count;
|
||||
} catch (error) {
|
||||
this.newsItems = [];
|
||||
this.loadError = this.$capitalize(this.$p.t('ui', 'fehlerBeimLesen'));
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
parseNewsHtml(html) {
|
||||
if (!html) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const document = new DOMParser().parseFromString(html, 'text/html');
|
||||
|
||||
return Array.from(document.querySelectorAll('.news-list-item')).map(
|
||||
(article) => this.parseNewsItem(article),
|
||||
);
|
||||
},
|
||||
parseNewsItem(article) {
|
||||
const newsId = article
|
||||
.querySelector('[news-id]')
|
||||
?.getAttribute('news-id');
|
||||
const title = article.querySelector('.card-header h2');
|
||||
const author = article.querySelector('address');
|
||||
const time = article.querySelector('time');
|
||||
const isPublished = article.querySelector(
|
||||
'.card-header span[is-published]',
|
||||
);
|
||||
|
||||
const isActive =
|
||||
article
|
||||
.querySelector('.card-header span[is-active]')
|
||||
?.getAttribute('is-active') === 'true';
|
||||
|
||||
const content = article.querySelector('.card-text');
|
||||
|
||||
return {
|
||||
newsId: newsId ? Number(newsId) : null,
|
||||
language: this.sprache,
|
||||
title: title ? title.textContent.trim() : '',
|
||||
author: author ? author.textContent.trim() : '',
|
||||
date: time ? time.textContent.trim() : '',
|
||||
dateTime: time ? time.getAttribute('datetime') || '' : '',
|
||||
content: content ? content.innerHTML : '',
|
||||
isPublished: isPublished
|
||||
? isPublished.getAttribute('is-published') === 'true'
|
||||
: null,
|
||||
isActive,
|
||||
};
|
||||
},
|
||||
afterPageUpdated(event) {
|
||||
this.page = event.page;
|
||||
this.pageSize = event.rows;
|
||||
this.fetchNews();
|
||||
this.$refs.newsListHeading.scrollIntoView({ block: 'end' });
|
||||
},
|
||||
afterFilterChanged(filters) {
|
||||
this.isPublishedFilter = filters.isPublished;
|
||||
this.isActiveFilter = filters.isActive;
|
||||
this.degreeProgramShortCodeFilter = filters.degreeProgramShortCode;
|
||||
this.semesterFilter = filters.semester;
|
||||
this.page = 1;
|
||||
this.fetchNews();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$p.loadCategory(['filter', 'global', 'lehre', 'ui']).then(() => {
|
||||
this.phrasesLoaded = true;
|
||||
});
|
||||
this.fetchNews();
|
||||
},
|
||||
template: /*html*/ `
|
||||
<section class="mt-4" aria-labelledby="news-administration-list-heading">
|
||||
<h3 id="news-administration-list-heading" ref="newsListHeading" class="fhc-primary-color">
|
||||
{{ $capitalize($p.t('ui', 'news')) }}
|
||||
</h3>
|
||||
<hr>
|
||||
<news-list-filter @filter-changed="afterFilterChanged"></news-list-filter>
|
||||
<div v-if="isLoading" class="d-flex justify-content-center py-4" role="status">
|
||||
<span class="spinner-border" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">{{ $capitalize($p.t('ui', 'loadingNews')) }}</span>
|
||||
</div>
|
||||
<div v-else-if="loadError" class="alert alert-danger" role="alert">
|
||||
{{ loadError }}
|
||||
</div>
|
||||
<div v-else-if="!newsItems.length" class="alert alert-info" role="status">
|
||||
{{ $capitalize($p.t('ui', 'noNewsAvailable')) }}
|
||||
</div>
|
||||
<div v-else class="pt-3">
|
||||
<news-list-item
|
||||
v-for="(news, index) in newsItems"
|
||||
:key="news.newsId || page + '-' + index"
|
||||
:news="news"
|
||||
@edit="$emit('edit', $event)"
|
||||
@deleted="fetchNews()"
|
||||
></news-list-item>
|
||||
<pagination
|
||||
v-if="maxPageCount > pageSize"
|
||||
:page="page"
|
||||
:page_size="pageSize"
|
||||
:maxPageCount="maxPageCount"
|
||||
@pageUpdated="afterPageUpdated"
|
||||
></pagination>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
import FormInput from '../../../Form/Input.js';
|
||||
import ApiStudiengang from '../../../../api/factory/studiengang.js';
|
||||
|
||||
export default {
|
||||
name: 'NewsListFilter',
|
||||
emits: ['filter-changed'],
|
||||
components: {
|
||||
FormInput,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isPublished: 'all',
|
||||
isActive: true,
|
||||
degreeProgram: null,
|
||||
semester: null,
|
||||
degreePrograms: [],
|
||||
filteredDegreePrograms: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
semesters() {
|
||||
this.$p.user_language.value;
|
||||
const semesterLabel = this.$capitalize(this.$p.t('lehre', 'semester'));
|
||||
|
||||
return [
|
||||
{
|
||||
label: this.$capitalize(this.$p.t('ui', 'all_semester')),
|
||||
value: null,
|
||||
},
|
||||
...Array.from({ length: 8 }, (_, index) => ({
|
||||
label: `${index + 1}. ${semesterLabel}`,
|
||||
value: String(index + 1),
|
||||
})),
|
||||
];
|
||||
},
|
||||
dropdownParsedDegreePrograms() {
|
||||
const degreePrograms = this.degreePrograms
|
||||
.map((degreeProgram) => {
|
||||
let labelFragment = degreeProgram.typ + degreeProgram.kurzbz;
|
||||
labelFragment = labelFragment.trim().toUpperCase();
|
||||
|
||||
return {
|
||||
label: `${labelFragment} (${degreeProgram.bezeichnung})`,
|
||||
value: degreeProgram.studiengang_kz,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return [
|
||||
{
|
||||
label: this.$capitalize(this.$p.t('ui', 'dropdownEmptyOption')),
|
||||
value: null,
|
||||
},
|
||||
...degreePrograms,
|
||||
];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isPublished() {
|
||||
this.emitFilterChanged();
|
||||
},
|
||||
isActive() {
|
||||
this.emitFilterChanged();
|
||||
},
|
||||
degreeProgram() {
|
||||
this.emitFilterChanged();
|
||||
},
|
||||
semester() {
|
||||
this.emitFilterChanged();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
emitFilterChanged() {
|
||||
this.$emit('filter-changed', {
|
||||
isPublished: this.isPublished,
|
||||
isActive: this.isActive,
|
||||
degreeProgramShortCode: this.degreeProgram?.value ?? null,
|
||||
semester: this.semester,
|
||||
});
|
||||
},
|
||||
filterDegreePrograms(event) {
|
||||
const query = event.query.toLowerCase();
|
||||
|
||||
this.filteredDegreePrograms = this.dropdownParsedDegreePrograms.filter(
|
||||
(degreeProgram) => degreeProgram.label.toLowerCase().includes(query),
|
||||
);
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
try {
|
||||
const response = await this.$api.call(ApiStudiengang.getDegreePrograms());
|
||||
if (response.meta.status === 'success') {
|
||||
this.degreePrograms = response.data.sort((a, b) =>
|
||||
a.bezeichnung.localeCompare(b.bezeichnung),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.degreePrograms = [];
|
||||
}
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="mb-3">
|
||||
<div class="border rounded p-3 mt-2 d-flex flex-wrap align-items-center gap-3">
|
||||
<form-input
|
||||
v-model="isPublished"
|
||||
type="select"
|
||||
name="is-published-filter"
|
||||
:label="$capitalize($p.t('ui', 'visible'))"
|
||||
container-class="mb-0"
|
||||
class="w-auto"
|
||||
>
|
||||
<option value="all">{{ $capitalize($p.t('global', 'alle')) }}</option>
|
||||
<option :value="true">{{ $capitalize($p.t('ui', 'visible')) }}</option>
|
||||
<option :value="false">{{ $capitalize($p.t('ui', 'notPublishedYet')) }}</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-model="degreeProgram"
|
||||
:label="$capitalize($p.t('lehre', 'studiengang'))"
|
||||
:suggestions="filteredDegreePrograms"
|
||||
:optionValue="(option) => option.value"
|
||||
:optionLabel="(option) => option.label"
|
||||
@complete="filterDegreePrograms($event)"
|
||||
type="autocomplete"
|
||||
name="degree-program-filter"
|
||||
dropdown
|
||||
clear
|
||||
></form-input>
|
||||
<form-input
|
||||
v-model="semester"
|
||||
:label="$capitalize($p.t('lehre', 'studiensemester'))"
|
||||
type="select"
|
||||
name="semester-filter"
|
||||
container-class="mb-0"
|
||||
>
|
||||
<option
|
||||
v-for="semesterOption in semesters"
|
||||
:key="semesterOption.value"
|
||||
:value="semesterOption.value"
|
||||
>
|
||||
{{ semesterOption.label }}
|
||||
</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-model="isActive"
|
||||
type="checkbox"
|
||||
name="is-active-filter"
|
||||
:label="$capitalize($p.t('global', 'aktiv'))"
|
||||
container-class="mb-0 align-self-end"
|
||||
></form-input>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,144 @@
|
||||
import ApiNewsAdministration from '../../../../api/factory/newsAdministration.js';
|
||||
|
||||
const createCollapseId = () => {
|
||||
const randomNumber = Math.floor(
|
||||
1000000000000000 + Math.random() * 8000000000000000,
|
||||
);
|
||||
return `news-administration-item-${randomNumber}`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'NewsListItem',
|
||||
emits: ['edit', 'deleted'],
|
||||
props: {
|
||||
news: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isExpanded: false,
|
||||
isDeleting: false,
|
||||
collapseId: createCollapseId(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleContent() {
|
||||
this.isExpanded = !this.isExpanded;
|
||||
},
|
||||
editNews(news) {
|
||||
this.$emit('edit', news);
|
||||
},
|
||||
async deleteNews(news) {
|
||||
if (this.isDeleting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(await this.$fhcAlert.confirm({
|
||||
message: this.$capitalize(this.$p.t('ui', 'frageSicherLoeschen')),
|
||||
acceptClass: 'btn btn-danger',
|
||||
})) === false
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDeleting = true;
|
||||
|
||||
try {
|
||||
const response = await this.$api.call(
|
||||
ApiNewsAdministration.deleteNewsItem(news.newsId),
|
||||
);
|
||||
|
||||
if (response.meta.status !== 'success') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('deleted', news);
|
||||
} catch (error) {
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
} finally {
|
||||
this.isDeleting = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$p.loadCategory(['global', 'ui']).then(() => {
|
||||
this.phrasesLoaded = true;
|
||||
});
|
||||
},
|
||||
template: /*html*/ `
|
||||
<article
|
||||
class="card mb-3"
|
||||
:class="{'border-secondary': !news.isPublished || !news.isActive}"
|
||||
>
|
||||
<header
|
||||
class="card-header d-flex justify-content-between align-items-center gap-3"
|
||||
:class="{
|
||||
'fhc-primary': news.isPublished && news.isActive,
|
||||
'bg-secondary bg-opacity-25 text-secondary': !news.isPublished || !news.isActive,
|
||||
}"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link text-reset text-decoration-none text-start d-flex align-items-center gap-2 flex-grow-1 p-0"
|
||||
:aria-expanded="isExpanded"
|
||||
:aria-controls="collapseId"
|
||||
@click="toggleContent"
|
||||
>
|
||||
<i
|
||||
class="fa-solid"
|
||||
:class="isExpanded ? 'fa-chevron-down' : 'fa-chevron-right'"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="h5 mb-0">{{ news.title }}</span>
|
||||
<span v-if="!news.isPublished" class="badge text-bg-secondary">
|
||||
{{ $capitalize($p.t('ui', 'notPublishedYet')) }}
|
||||
</span>
|
||||
<span v-if="!news.isActive" class="badge text-bg-secondary">
|
||||
{{ $capitalize($p.t('ui', 'noLongerActive')) }}
|
||||
</span>
|
||||
</button>
|
||||
<div class="d-flex align-items-center gap-2 flex-shrink-0">
|
||||
<div class="text-end">
|
||||
<address v-if="news.author" class="fw-bold small mb-0">
|
||||
{{ news.author }}
|
||||
</address>
|
||||
<time v-if="news.date" class="small" :datetime="news.dateTime">
|
||||
{{ news.date }}
|
||||
</time>
|
||||
</div>
|
||||
<div class="d-flex gap-1" :aria-label="$capitalize($p.t('global', 'actions'))">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm rounded-circle shadow-sm d-inline-flex align-items-center justify-content-center"
|
||||
:class="news.isPublished ? 'btn-light text-primary' : 'btn-outline-secondary bg-white'"
|
||||
style="width: 1.75rem; height: 1.75rem"
|
||||
:title="$capitalize($p.t('ui', 'bearbeiten'))"
|
||||
:aria-label="$capitalize($p.t('ui', 'bearbeiten'))"
|
||||
@click="editNews(news)"
|
||||
>
|
||||
<i class="fa-solid fa-pen" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm rounded-circle shadow-sm d-inline-flex align-items-center justify-content-center"
|
||||
:class="news.isPublished ? 'btn-light text-danger' : 'btn-outline-danger bg-white'"
|
||||
style="width: 1.75rem; height: 1.75rem"
|
||||
:title="$capitalize($p.t('ui', 'loeschen'))"
|
||||
:aria-label="$capitalize($p.t('ui', 'loeschen'))"
|
||||
:disabled="isDeleting"
|
||||
@click="deleteNews(news)"
|
||||
>
|
||||
<i class="fa-solid fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div v-show="isExpanded" :id="collapseId" class="card-body">
|
||||
<div class="card-text" v-html="news.content"></div>
|
||||
</div>
|
||||
</article>
|
||||
`,
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import FhcCalendar from "../../Calendar/LvPlan.js";
|
||||
import FhcCalendar from '../../Calendar/LvPlan.js';
|
||||
|
||||
import ApiLvPlan from '../../../api/factory/lvPlan.js';
|
||||
import ApiAuthinfo from '../../../api/factory/authinfo.js';
|
||||
@@ -9,10 +9,10 @@ export const DEFAULT_MODE_LVPLAN_DESKTOP = 'Week';
|
||||
export default {
|
||||
name: 'LvPlan',
|
||||
components: {
|
||||
FhcCalendar
|
||||
FhcCalendar,
|
||||
},
|
||||
props: {
|
||||
propsViewData: Object
|
||||
propsViewData: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -24,65 +24,102 @@ export default {
|
||||
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
|
||||
};
|
||||
},
|
||||
inject: ["isMobile"],
|
||||
computed:{
|
||||
inject: {
|
||||
isMobile: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
currentDay() {
|
||||
return this.propsViewData?.focus_date || luxon.DateTime.now().setZone(this.timezone).toISODate();
|
||||
return (
|
||||
this.propsViewData?.focus_date ||
|
||||
luxon.DateTime.now().setZone(this.timezone).toISODate()
|
||||
);
|
||||
},
|
||||
currentMode() {
|
||||
const defaultMode = this.isMobile ? DEFAULT_MODE_LVPLAN_MOBILE : DEFAULT_MODE_LVPLAN_DESKTOP;
|
||||
const defaultMode = this.isMobile
|
||||
? DEFAULT_MODE_LVPLAN_MOBILE
|
||||
: DEFAULT_MODE_LVPLAN_DESKTOP;
|
||||
return this.propsViewData?.mode || defaultMode;
|
||||
},
|
||||
downloadLinks() {
|
||||
if (!this.studiensemester_start || !this.studiensemester_ende || !this.uid)
|
||||
if (
|
||||
!this.studiensemester_start ||
|
||||
!this.studiensemester_ende ||
|
||||
!this.uid
|
||||
)
|
||||
return false;
|
||||
|
||||
const opts = { zone: this.timezone };
|
||||
const start = luxon.DateTime
|
||||
.fromISO(this.studiensemester_start, opts)
|
||||
.toUnixInteger();
|
||||
const ende = luxon.DateTime
|
||||
.fromISO(this.studiensemester_ende, opts)
|
||||
.toUnixInteger();
|
||||
|
||||
const download_link = FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'cis/private/lvplan/stpl_kalender.php'
|
||||
+ '?type=student'
|
||||
+ '&pers_uid=' + this.uid
|
||||
+ '&begin=' + start
|
||||
+ '&ende=' + ende;
|
||||
const opts = { zone: this.timezone };
|
||||
const start = luxon.DateTime.fromISO(
|
||||
this.studiensemester_start,
|
||||
opts,
|
||||
).toUnixInteger();
|
||||
const ende = luxon.DateTime.fromISO(
|
||||
this.studiensemester_ende,
|
||||
opts,
|
||||
).toUnixInteger();
|
||||
|
||||
const download_link =
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
'cis/private/lvplan/stpl_kalender.php' +
|
||||
'?type=student' +
|
||||
'&pers_uid=' +
|
||||
this.uid +
|
||||
'&begin=' +
|
||||
start +
|
||||
'&ende=' +
|
||||
ende;
|
||||
|
||||
return [
|
||||
{ title: "excel", icon: 'fa-solid fa-file-excel', link: download_link + '&format=excel' },
|
||||
{ title: "csv", icon: 'fa-solid fa-file-csv', link: download_link + '&format=csv' },
|
||||
{ title: "ical1", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=1&target=ical' },
|
||||
{ title: "ical2", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=2&target=ical' }
|
||||
{
|
||||
title: 'excel',
|
||||
icon: 'fa-solid fa-file-excel',
|
||||
link: download_link + '&format=excel',
|
||||
},
|
||||
{
|
||||
title: 'csv',
|
||||
icon: 'fa-solid fa-file-csv',
|
||||
link: download_link + '&format=csv',
|
||||
},
|
||||
{
|
||||
title: 'ical1',
|
||||
icon: 'fa-regular fa-calendar',
|
||||
link: download_link + '&format=ical&version=1&target=ical',
|
||||
},
|
||||
{
|
||||
title: 'ical2',
|
||||
icon: 'fa-regular fa-calendar',
|
||||
link: download_link + '&format=ical&version=2&target=ical',
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleChangeDate(day, newMode) {
|
||||
return this.handleChangeMode(newMode, day);
|
||||
},
|
||||
handleChangeMode(newMode, day) {
|
||||
const mode = newMode[0].toUpperCase() + newMode.slice(1)
|
||||
const mode = newMode[0].toUpperCase() + newMode.slice(1);
|
||||
const focus_date = day.toISODate();
|
||||
|
||||
|
||||
this.$router.push({
|
||||
name: "LvPlan",
|
||||
name: 'LvPlan',
|
||||
params: {
|
||||
mode,
|
||||
focus_date,
|
||||
lv_id: this.propsViewData?.lv_id ?? null
|
||||
}
|
||||
lv_id: this.propsViewData?.lv_id ?? null,
|
||||
},
|
||||
});
|
||||
},
|
||||
updateRange(rangeInterval) {
|
||||
this.$api
|
||||
.call(ApiLvPlan.studiensemesterDateInterval(
|
||||
rangeInterval.end.startOf('week').toISODate()
|
||||
))
|
||||
.then(res => {
|
||||
.call(
|
||||
ApiLvPlan.studiensemesterDateInterval(
|
||||
rangeInterval.end.startOf('week').toISODate(),
|
||||
),
|
||||
)
|
||||
.then((res) => {
|
||||
this.studiensemester_kurzbz = res.data.studiensemester_kurzbz;
|
||||
this.studiensemester_start = res.data.start;
|
||||
this.studiensemester_ende = res.data.ende;
|
||||
@@ -90,19 +127,25 @@ export default {
|
||||
},
|
||||
getPromiseFunc(start, end) {
|
||||
return [
|
||||
this.$api.call(ApiLvPlan.LvPlanEvents(start.toISODate(), end.toISODate(), this.propsViewData.lv_id)),
|
||||
this.$api.call(ApiLvPlan.getLvPlanReservierungen(start.toISODate(), end.toISODate()))
|
||||
this.$api.call(
|
||||
ApiLvPlan.LvPlanEvents(
|
||||
start.toISODate(),
|
||||
end.toISODate(),
|
||||
this.propsViewData.lv_id,
|
||||
),
|
||||
),
|
||||
this.$api.call(
|
||||
ApiLvPlan.getLvPlanReservierungen(start.toISODate(), end.toISODate()),
|
||||
),
|
||||
];
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$api
|
||||
.call(ApiAuthinfo.getAuthUID())
|
||||
.then(res => {
|
||||
this.uid = res.data.uid;
|
||||
});
|
||||
this.$api.call(ApiAuthinfo.getAuthUID()).then((res) => {
|
||||
this.uid = res.data.uid;
|
||||
});
|
||||
},
|
||||
template: /*html*/`
|
||||
template: /*html*/ `
|
||||
<div class="cis-lvplan-personal d-flex flex-column h-100">
|
||||
<h2>
|
||||
{{ $p.t('lehre/stundenplan') }}
|
||||
@@ -144,5 +187,5 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
</fhc-calendar>
|
||||
</div>`
|
||||
</div>`,
|
||||
};
|
||||
|
||||
@@ -1,72 +1,85 @@
|
||||
import CisMenuEntry from "./Menu/Entry.js";
|
||||
import FhcSearchbar from "../searchbar/searchbar.js";
|
||||
import CisSprachen from "./Sprachen.js"
|
||||
import ThemeSwitch from "./ThemeSwitch.js";
|
||||
import CisMenuEntry from './Menu/Entry.js';
|
||||
import FhcSearchbar from '../searchbar/searchbar.js';
|
||||
import CisSprachen from './Sprachen.js';
|
||||
import ThemeSwitch from './ThemeSwitch.js';
|
||||
import ApiCisMenu from '../../api/factory/cis/menu.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CisMenuEntry,
|
||||
FhcSearchbar,
|
||||
components: {
|
||||
CisMenuEntry,
|
||||
FhcSearchbar,
|
||||
CisSprachen,
|
||||
ThemeSwitch,
|
||||
},
|
||||
props: {
|
||||
},
|
||||
props: {
|
||||
rootUrl: String,
|
||||
logoUrl: String,
|
||||
avatarUrl: String,
|
||||
logoutUrl: String,
|
||||
logoUrl: String,
|
||||
avatarUrl: String,
|
||||
logoutUrl: String,
|
||||
selectedtypes: Array,
|
||||
searchbaroptions: Object,
|
||||
searchfunction: Function
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
entries: [],
|
||||
activeEntry:null,
|
||||
url:null,
|
||||
urlMatchRankings:[],
|
||||
navUserDropdown:null,
|
||||
menuOpen:true,
|
||||
};
|
||||
},
|
||||
inject: ["isNarrow", "isMobile"],
|
||||
provide(){
|
||||
return{
|
||||
searchbaroptions: Object,
|
||||
searchfunction: Function,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
entries: [],
|
||||
activeEntry: null,
|
||||
url: null,
|
||||
urlMatchRankings: [],
|
||||
navUserDropdown: null,
|
||||
menuOpen: true,
|
||||
};
|
||||
},
|
||||
inject: {
|
||||
isNarrow: {
|
||||
default: false,
|
||||
},
|
||||
isMobile: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
setActiveEntry: this.setActiveEntry,
|
||||
addUrlCount: this.addUrlCount,
|
||||
makeParentContentActive: this.makeParentContentActive,
|
||||
}
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
menuCollapseAriaLabel(){
|
||||
if(this.menuOpen){
|
||||
computed: {
|
||||
menuCollapseAriaLabel() {
|
||||
if (this.menuOpen) {
|
||||
return this.$p.t('global', 'collapseMenu');
|
||||
}else{
|
||||
} else {
|
||||
return this.$p.t('global', 'extendMenu');
|
||||
}
|
||||
},
|
||||
highestMatchingUrlCount(){
|
||||
highestMatchingUrlCount() {
|
||||
// gets the hightest ranking inside the array
|
||||
let highestMatch = Math.max(...this.urlMatchRankings);
|
||||
|
||||
if(this.urlMatchRankings.length > 0){
|
||||
if (this.urlMatchRankings.length > 0) {
|
||||
// if more than one entry has the same ranking, none should be active
|
||||
return this.urlMatchRankings.filter((value)=>value == highestMatch).length > 1 ? null : highestMatch;
|
||||
return this.urlMatchRankings.filter((value) => value == highestMatch)
|
||||
.length > 1
|
||||
? null
|
||||
: highestMatch;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
site_url(){
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
site_url() {
|
||||
return (
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchMenu() {
|
||||
return this.$api
|
||||
.call(ApiCisMenu.getMenu())
|
||||
.then(res => res.data)
|
||||
.then(menu => {
|
||||
.then((res) => res.data)
|
||||
.then((menu) => {
|
||||
this.entries = menu;
|
||||
});
|
||||
},
|
||||
@@ -76,43 +89,54 @@ export default {
|
||||
this.navUserDropdown.hide();
|
||||
}
|
||||
},
|
||||
handleShowNavUser(){
|
||||
document.addEventListener("click", this.checkSettingsVisibility);
|
||||
handleShowNavUser() {
|
||||
document.addEventListener('click', this.checkSettingsVisibility);
|
||||
},
|
||||
handleHideNavUser(){
|
||||
document.removeEventListener("click", this.checkSettingsVisibility);
|
||||
handleHideNavUser() {
|
||||
document.removeEventListener('click', this.checkSettingsVisibility);
|
||||
},
|
||||
makeParentContentActive(content_id, collection=this.entries, parent=null){
|
||||
if(!collection) return;
|
||||
if (typeof collection == 'object' && !Array.isArray(collection) && Object.entries(collection).length > 0) {
|
||||
makeParentContentActive(
|
||||
content_id,
|
||||
collection = this.entries,
|
||||
parent = null,
|
||||
) {
|
||||
if (!collection) return;
|
||||
if (
|
||||
typeof collection == 'object' &&
|
||||
!Array.isArray(collection) &&
|
||||
Object.entries(collection).length > 0
|
||||
) {
|
||||
collection = Object.values(collection);
|
||||
}
|
||||
for(let entry of collection){
|
||||
if(entry.content_id == content_id){
|
||||
for (let entry of collection) {
|
||||
if (entry.content_id == content_id) {
|
||||
this.activeEntry = parent;
|
||||
}
|
||||
this.makeParentContentActive(content_id, entry.childs, entry.content_id);
|
||||
this.makeParentContentActive(
|
||||
content_id,
|
||||
entry.childs,
|
||||
entry.content_id,
|
||||
);
|
||||
}
|
||||
|
||||
},
|
||||
addUrlCount(count){
|
||||
addUrlCount(count) {
|
||||
this.urlMatchRankings.push(count);
|
||||
},
|
||||
|
||||
setActiveEntry(content_id){
|
||||
setActiveEntry(content_id) {
|
||||
this.activeEntry = content_id;
|
||||
},
|
||||
},
|
||||
created(){
|
||||
created() {
|
||||
this.fetchMenu();
|
||||
},
|
||||
mounted(){
|
||||
this.$p.loadCategory(['ui', 'global', 'profilUpdate'])
|
||||
this.navUserDropdown = new bootstrap.Collapse(this.$refs.navUserDropdown,{
|
||||
toggle: false
|
||||
mounted() {
|
||||
this.$p.loadCategory(['ui', 'global', 'profilUpdate']);
|
||||
this.navUserDropdown = new bootstrap.Collapse(this.$refs.navUserDropdown, {
|
||||
toggle: false,
|
||||
});
|
||||
},
|
||||
template: /*html*/`
|
||||
template: /*html*/ `
|
||||
<div id="cis-header-bar" class="d-flex flex-row flex-grow-1">
|
||||
<div id="nav-logo" class="d-none d-lg-block">
|
||||
<div class="d-flex h-100 justify-content-between">
|
||||
@@ -212,5 +236,5 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>`
|
||||
</nav>`,
|
||||
};
|
||||
|
||||
@@ -6,18 +6,17 @@ import ApiCms from '../../api/factory/cms.js';
|
||||
const MAX_LOADED_NEWS = 30;
|
||||
|
||||
export default {
|
||||
name: "WidgetsNews",
|
||||
name: 'WidgetsNews',
|
||||
components: {
|
||||
BsModal
|
||||
BsModal,
|
||||
},
|
||||
data: () => ({
|
||||
allNewsList: [],
|
||||
singleNews: {},
|
||||
selected: null,
|
||||
size:0,
|
||||
size: 0,
|
||||
}),
|
||||
props: ['width', 'height']
|
||||
,
|
||||
props: ['width', 'height'],
|
||||
mixins: [AbstractWidget],
|
||||
computed: {
|
||||
sizeClass() {
|
||||
@@ -28,7 +27,6 @@ export default {
|
||||
// let quantity = this.width;
|
||||
let quantity = MAX_LOADED_NEWS;
|
||||
|
||||
|
||||
if (this.width === 1) {
|
||||
quantity = this.height === 1 ? 4 : MAX_LOADED_NEWS;
|
||||
}
|
||||
@@ -39,107 +37,129 @@ export default {
|
||||
},
|
||||
carouselItems() {
|
||||
return this.allNewsList.reduce((acc, cur) => {
|
||||
const el = document.getElementById('card-'+cur.news_id)
|
||||
const el = document.getElementById('card-' + cur.news_id);
|
||||
acc.push(el);
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateNewsContentClasses:function(){
|
||||
updateNewsContentClasses: function () {
|
||||
Vue.nextTick(() => {
|
||||
document.querySelectorAll(".fhc-news-card-item .card-body, .fhc-news-card-item .card, .fhc-news-card-item .card-header").forEach((el) => {
|
||||
if (!el.classList.contains("border-0")) {
|
||||
el.classList.add("border-0");
|
||||
}
|
||||
});
|
||||
document.querySelectorAll(".fhc-news-card-item .card-header").forEach((el) => {
|
||||
if (!el.classList.contains("px-5")) {
|
||||
el.classList.add("px-5");
|
||||
}
|
||||
if (!el.classList.contains("fhc-primary")) {
|
||||
el.classList.add("fhc-primary");
|
||||
}
|
||||
if (!el.classList.contains("position-sticky")) {
|
||||
el.classList.add("position-sticky");
|
||||
}
|
||||
if (!el.classList.contains("top-0")) {
|
||||
el.classList.add("top-0");
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
document.querySelectorAll(".fhc-news-card-item .card-header .row").forEach((el) => {
|
||||
if (!el.classList.contains("w-100")) {
|
||||
el.classList.add("w-100");
|
||||
}
|
||||
if (!el.classList.contains("align-items-center")) {
|
||||
el.classList.add("align-items-center");
|
||||
}
|
||||
});
|
||||
document.querySelectorAll(".fhc-news-card-item .card-header .row h2").forEach((el) => {
|
||||
if (!el.classList.contains("mb-0")) {
|
||||
el.classList.add("mb-0");
|
||||
}
|
||||
});
|
||||
})
|
||||
document
|
||||
.querySelectorAll(
|
||||
'.fhc-news-card-item .card-body, .fhc-news-card-item .card, .fhc-news-card-item .card-header',
|
||||
)
|
||||
.forEach((el) => {
|
||||
if (!el.classList.contains('border-0')) {
|
||||
el.classList.add('border-0');
|
||||
}
|
||||
});
|
||||
document
|
||||
.querySelectorAll('.fhc-news-card-item .card-header')
|
||||
.forEach((el) => {
|
||||
if (!el.classList.contains('px-5')) {
|
||||
el.classList.add('px-5');
|
||||
}
|
||||
if (!el.classList.contains('fhc-primary')) {
|
||||
el.classList.add('fhc-primary');
|
||||
}
|
||||
if (!el.classList.contains('position-sticky')) {
|
||||
el.classList.add('position-sticky');
|
||||
}
|
||||
if (!el.classList.contains('top-0')) {
|
||||
el.classList.add('top-0');
|
||||
}
|
||||
});
|
||||
document
|
||||
.querySelectorAll('.fhc-news-card-item .card-header .row')
|
||||
.forEach((el) => {
|
||||
if (!el.classList.contains('w-100')) {
|
||||
el.classList.add('w-100');
|
||||
}
|
||||
if (!el.classList.contains('align-items-center')) {
|
||||
el.classList.add('align-items-center');
|
||||
}
|
||||
});
|
||||
document
|
||||
.querySelectorAll('.fhc-news-card-item .card-header .row h2')
|
||||
.forEach((el) => {
|
||||
if (!el.classList.contains('mb-0')) {
|
||||
el.classList.add('mb-0');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
formatDate: function (dateTime) {
|
||||
const dt = new Date(dateTime);
|
||||
return numberPadding(dt.getDate()) + '.' + numberPadding((dt.getMonth() + 1)) + '.' + dt.getFullYear();
|
||||
return (
|
||||
numberPadding(dt.getDate()) +
|
||||
'.' +
|
||||
numberPadding(dt.getMonth() + 1) +
|
||||
'.' +
|
||||
dt.getFullYear()
|
||||
);
|
||||
},
|
||||
formatTime: function (dateTime) {
|
||||
const dt = new Date(dateTime);
|
||||
return numberPadding(dt.getHours()) + ':' + numberPadding(dt.getMinutes());
|
||||
return (
|
||||
numberPadding(dt.getHours()) + ':' + numberPadding(dt.getMinutes())
|
||||
);
|
||||
},
|
||||
isString(value){
|
||||
isString(value) {
|
||||
return Object.prototype.toString.call(value) === '[object String]';
|
||||
},
|
||||
setNext(){
|
||||
const thisIndex = this.allNewsList.findIndex(n=>n.news_id == this.selected.news_id)
|
||||
const nextIndex = thisIndex == (this.allNewsList.length - 1) ? 0 : thisIndex + 1
|
||||
setNext() {
|
||||
const thisIndex = this.allNewsList.findIndex(
|
||||
(n) => n.news_id == this.selected.news_id,
|
||||
);
|
||||
const nextIndex =
|
||||
thisIndex == this.allNewsList.length - 1 ? 0 : thisIndex + 1;
|
||||
|
||||
this.setSelected(this.allNewsList[nextIndex])
|
||||
this.setSelected(this.allNewsList[nextIndex]);
|
||||
this.updateNewsContentClasses();
|
||||
|
||||
},
|
||||
setPrev() {
|
||||
const thisIndex = this.allNewsList.findIndex(n=>n.news_id == this.selected.news_id)
|
||||
const prevIndex = thisIndex ? thisIndex - 1 : this.allNewsList.length - 1
|
||||
const thisIndex = this.allNewsList.findIndex(
|
||||
(n) => n.news_id == this.selected.news_id,
|
||||
);
|
||||
const prevIndex = thisIndex ? thisIndex - 1 : this.allNewsList.length - 1;
|
||||
|
||||
this.setSelected(this.allNewsList[prevIndex])
|
||||
this.setSelected(this.allNewsList[prevIndex]);
|
||||
this.updateNewsContentClasses();
|
||||
},
|
||||
getMenuItemClass(news) {
|
||||
let classString = ''
|
||||
if(this.selected && this.selected.news_id === news.news_id) {
|
||||
classString += 'selected'
|
||||
let classString = '';
|
||||
if (this.selected && this.selected.news_id === news.news_id) {
|
||||
classString += 'selected';
|
||||
}
|
||||
return classString
|
||||
return classString;
|
||||
},
|
||||
async setSelected(news) {
|
||||
const clickedElement = document.getElementById('card-'+news.news_id);
|
||||
const clickedElement = document.getElementById('card-' + news.news_id);
|
||||
const clickedElementIndex = this.allNewsList.indexOf(news);
|
||||
const oldElementIndex = this.allNewsList.indexOf(this.selected);
|
||||
|
||||
|
||||
//if the clicked element is already active, do nothing
|
||||
if(clickedElementIndex === oldElementIndex) return;
|
||||
if (clickedElementIndex === oldElementIndex) return;
|
||||
//add prev/next class to the clicked element
|
||||
if(clickedElementIndex > oldElementIndex) {
|
||||
if (clickedElementIndex > oldElementIndex) {
|
||||
clickedElement.classList.add('carousel-item-next');
|
||||
}else{
|
||||
} else {
|
||||
clickedElement.classList.add('carousel-item-prev');
|
||||
}
|
||||
|
||||
// move to clicked element
|
||||
await Vue.nextTick(() => { this.carouselInstance.to(clickedElementIndex); })
|
||||
await Vue.nextTick(() => {
|
||||
this.carouselInstance.to(clickedElementIndex);
|
||||
});
|
||||
this.selected = news;
|
||||
},
|
||||
contentURI: function (content_id) {
|
||||
return (
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
|
||||
"/CisVue/Cms/content/" +
|
||||
'/CisVue/Cms/content/' +
|
||||
content_id
|
||||
);
|
||||
},
|
||||
@@ -147,7 +167,7 @@ export default {
|
||||
return (
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
|
||||
"/CisVue/Cms/news"
|
||||
'/CisVue/Cms/news'
|
||||
);
|
||||
},
|
||||
setSingleNews(singleNews) {
|
||||
@@ -155,73 +175,72 @@ export default {
|
||||
this.$refs.newsModal.show();
|
||||
},
|
||||
initCarouselInstance() {
|
||||
Vue.nextTick(()=> {
|
||||
if(this.$refs.carousel) { // carousel ref might not exist in every widget width/height
|
||||
Vue.nextTick(() => {
|
||||
if (this.$refs.carousel) {
|
||||
// carousel ref might not exist in every widget width/height
|
||||
this.carouselInstance = new bootstrap.Carousel(this.$refs.carousel, {
|
||||
wrap: false, // keep this off even though it actually wraps
|
||||
interval: false
|
||||
interval: false,
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
initActiveItem() {
|
||||
Vue.nextTick(()=> {
|
||||
if (Array.isArray(this.$refs.carouselItems) && this.$refs.carouselItems.length >0) {
|
||||
this.$refs.carouselItems[0].classList.add("active")
|
||||
Vue.nextTick(() => {
|
||||
if (
|
||||
Array.isArray(this.$refs.carouselItems) &&
|
||||
this.$refs.carouselItems.length > 0
|
||||
) {
|
||||
this.$refs.carouselItems[0].classList.add('active');
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
width(newVal, oldVal) {
|
||||
if(oldVal == 1 && newVal > 1) { // carousel instance will have been disposed
|
||||
this.initCarouselInstance()
|
||||
this.initActiveItem()
|
||||
if (oldVal == 1 && newVal > 1) {
|
||||
// carousel instance will have been disposed
|
||||
this.initCarouselInstance();
|
||||
this.initActiveItem();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$emit("setConfig", false);
|
||||
this.$emit('setConfig', false);
|
||||
this.$api
|
||||
.call(ApiCms.news(MAX_LOADED_NEWS))
|
||||
.then(res => res.data)
|
||||
.call(ApiCms.newsForWidget(MAX_LOADED_NEWS))
|
||||
.then((res) => res.data)
|
||||
.then((news) => {
|
||||
this.allNewsList = Array.from(Object.values(news));
|
||||
this.allNewsList.sort((a, b) => {
|
||||
return new Date(b.datum) - new Date(a.datum);
|
||||
});
|
||||
this.selected = this.allNewsList.length ? this.allNewsList[0] : null
|
||||
this.initActiveItem()
|
||||
this.selected = this.allNewsList.length ? this.allNewsList[0] : null;
|
||||
this.initActiveItem();
|
||||
|
||||
this.updateNewsContentClasses();
|
||||
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("ERROR: ", err);
|
||||
console.error('ERROR: ', err);
|
||||
});
|
||||
|
||||
},
|
||||
mounted() {
|
||||
if (this.$refs.container) {
|
||||
new ResizeObserver(entries => {
|
||||
new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
let w = entry.contentBoxSize ? entry.contentBoxSize[0].inlineSize : entry.contentRect.width;
|
||||
let w = entry.contentBoxSize
|
||||
? entry.contentBoxSize[0].inlineSize
|
||||
: entry.contentRect.width;
|
||||
// TODO(chris): rework sizing
|
||||
if (w > 600)
|
||||
this.size = 3;
|
||||
else if (w > 350)
|
||||
this.size = 2;
|
||||
else if (w > 250)
|
||||
this.size = 1;
|
||||
else
|
||||
this.size = 0;
|
||||
}
|
||||
if (w > 600) this.size = 3;
|
||||
else if (w > 350) this.size = 2;
|
||||
else if (w > 250) this.size = 1;
|
||||
else this.size = 0;
|
||||
}
|
||||
}).observe(this.$refs.container);
|
||||
}
|
||||
|
||||
this.initCarouselInstance()
|
||||
|
||||
|
||||
this.initCarouselInstance();
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div ref="container" class="widgets-news h-100" :class="sizeClass" >
|
||||
|
||||
@@ -14,7 +14,8 @@ export default {
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'change',
|
||||
'changed'
|
||||
'changed',
|
||||
'tab-action'
|
||||
],
|
||||
props: {
|
||||
config: {
|
||||
@@ -234,6 +235,7 @@ export default {
|
||||
v-model="value"
|
||||
:config="tab.config"
|
||||
@update:suffix="updateSuffix($event)"
|
||||
@tab-action="$emit('tab-action', { key: tab.key, payload: $event })"
|
||||
></component>
|
||||
</keep-alive>
|
||||
</tabpanel>
|
||||
@@ -265,6 +267,7 @@ export default {
|
||||
v-model="value"
|
||||
:config="currentTab.config"
|
||||
@update:suffix="updateSuffix($event)"
|
||||
@tab-action="$emit('tab-action', { key: currentTab.key, payload: $event })"
|
||||
></component>
|
||||
</keep-alive>
|
||||
</div>
|
||||
@@ -272,4 +275,3 @@ export default {
|
||||
|
||||
</template>`
|
||||
};
|
||||
|
||||
|
||||
+823
-1
@@ -25,7 +25,6 @@
|
||||
//flag for at least one new phrase
|
||||
$new = false;
|
||||
|
||||
|
||||
$phrases = array(
|
||||
//******************* CORE/global
|
||||
array(
|
||||
@@ -60359,10 +60358,833 @@ I have been informed that I am under no obligation to consent to the transmissio
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'visible',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'sichtbar',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'visible',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'publish',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'veroeffentlichen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'publish',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
// NEWS ADMINISTRATION UI PHRASES ----------------------------------------------
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'newsAdministration',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'News-Verwaltung',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'News Administration',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'createNews',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'News erstellen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Create news',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'news',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'News',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'News',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'loadingNews',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'News werden geladen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Loading news',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'noNewsAvailable',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Keine News verfügbar.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'No news available.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'addTranslationLanguage',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Übersetzungssprache hinzufügen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Add translation language',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'allLanguagesAdded',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Alle verfügbaren Sprachen wurden hinzugefügt.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'All available languages have been added.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'selectLanguageForTranslation',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Wählen Sie eine Sprache aus, um eine Übersetzung hinzuzufügen.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Select a language to add a translation.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'removeLanguage',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Sprache entfernen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Remove language',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'copyTranslationContent',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Übersetzungsinhalt kopieren',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Copy translation content',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'overwriteTranslationContent',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Übersetzungsinhalt überschreiben?',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Overwrite translation content?',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'copyingTranslation',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Das Kopieren von {source} ersetzt Verfasser*in, Titel und Text in {target}.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Copying from {source} will replace the author, title, and text in {target}.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'existingContentWillBeLost',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Der bestehende Inhalt der aktuellen Sprache geht verloren.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'The existing content in the current language will be lost.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'chooseAnotherLanguage',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Andere Sprache auswählen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Choose another language',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'overwriteAndCopy',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Überschreiben und kopieren',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Overwrite and copy',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'chooseLanguageToCopyContent',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Wählen Sie die Sprache aus, deren Inhalt in {target} kopiert werden soll.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Choose the language whose content should be copied into {target}.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'addLanguageBeforeCopying',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Fügen Sie vor dem Kopieren eine weitere Sprache hinzu.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Add another language before copying content.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'newsPreview',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'News-Vorschau',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'News preview',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'untitledNews',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'News ohne Titel',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Untitled news',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'notPublishedYet',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Noch nicht veröffentlicht',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Not published yet',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'noLongerActive',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Nicht mehr aktiv',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'No longer active',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'newsContentPreviewHint',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Text eingeben, um die News-Inhaltsvorschau zu sehen.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Enter text to see the news content preview.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'selectLanguageTabToPreview',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Wählen Sie einen Sprachreiter aus, um dessen Inhalt in der Vorschau zu sehen.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Select a language tab to preview its content.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'newsForm',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'News-Formular',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'News form',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'contentFormTitle',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => '{language} Inhaltsformular',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => '{language} Content form',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'showPreview',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Vorschau anzeigen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Show preview',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'hidePreview',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Vorschau ausblenden',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Hide preview',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'copyContentFromAnotherLanguage',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Inhalt aus einer anderen Sprache kopieren',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Copy content from another language',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'franzoesisch',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Französisch',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'French',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'spanisch',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Spanisch',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Spanish',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'blocks',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Blöcke',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Blocks',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'paragraph',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Absatz',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Paragraph',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'heading1',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Überschrift 1',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Heading 1',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'heading2',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Überschrift 2',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Heading 2',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'heading3',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Überschrift 3',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Heading 3',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'heading4',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Überschrift 4',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Heading 4',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'heading5',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Überschrift 5',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Heading 5',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'heading6',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Überschrift 6',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Heading 6',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'dropdownEmptyOption',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => '--keine Auswahl--',
|
||||
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => '--no selection--',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
// generic tabulator phrases END
|
||||
);
|
||||
|
||||
|
||||
|
||||
//***** CHECK PHRASES & PHRASENTEXTE in German and English.
|
||||
//***** INSERT into phrase_tbl if new app + category + phrase found in phrasen-array.
|
||||
//***** INSERT into phrasentext_tbl if new text found in phrasen-phrases-array, conciders every language apart.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
/cypress/screenshots
|
||||
/cypress/videos
|
||||
/results
|
||||
Reference in New Issue
Block a user