. */ if (!defined('BASEPATH')) exit('No direct script access allowed'); class NewsAdministrationAPI extends FHCAPI_Controller { const GENERAL_DEGREE_PROGRAM_ID = 0; /** * 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->loadPhrases([ 'global' ]); $this->load->library('CmsLib'); } //------------------------------------------------------------------------------------------------------------------ // Public methods public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = true, $sichtbar = true, $maxAlter = 0) { $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; $passedInMaxAlter = $this->input->get('maxAlter', true); if($passedInMaxAlter !== null) { $maxAlter = intval($passedInMaxAlter); } $passedSichtbar = $this->input->get('published', true); if($passedSichtbar !== null) { $sichtbar = $passedSichtbar; } $allowedDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news"); array_push($allowedDegreePrograms, self::GENERAL_DEGREE_PROGRAM_ID); $news = $this->cmslib->getNews($infoscreen, $studiengang_kz, $semester, $mischen, $titel, $edit, $sichtbar, $page, $page_size, $sprache, $maxAlter, true, $allowedDegreePrograms); $news = $this->getDataOrTerminateWithError($news); $this->addMeta('phrases', json_decode($this->p->getJson())); $this->addMeta('row_count', $news["full_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)); $allowedDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news"); array_push($allowedDegreePrograms, self::GENERAL_DEGREE_PROGRAM_ID); if (!in_array($news->studiengang_kz, $allowedDegreePrograms)) { $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 ? $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) { $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); } 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 creates new versions of 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); } $latestVersions = []; foreach ((array)getData($existingTranslationsResult) as $existingTranslation) { $language = $existingTranslation->sprache; $version = (int)$existingTranslation->version; $latestVersions[$language] = max($latestVersions[$language] ?? 0, $version); } foreach ($data['translations'] as $translation) { $translationResult = $this->ContentspracheModel->insert([ 'sprache' => $translation['sprache'], 'content_id' => $existingNews->content_id, 'version' => ($latestVersions[$translation['sprache']] ?? 0) + 1, 'sichtbar' => $translation['sichtbar'], 'content' => $translation['content'], 'titel' => $translation['betreff'], 'insertamum' => $now, 'insertvon' => $uid, 'updateamum' => $now, 'updatevon' => $uid ]); $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); } }