diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php index c7a6c898a7f1f79f1dfb695f00b46bc4c8ac1834..a7048bdd3f750a0b6cebb6f7e99c2b74e708c3db 100644 --- a/lib/classes/JsonApi/RouteMap.php +++ b/lib/classes/JsonApi/RouteMap.php @@ -127,6 +127,7 @@ class RouteMap } $this->addAuthenticatedAvatarRoutes($group); + $this->addAuthenticatedMvvRoutes($group); $this->addAuthenticatedEventsRoutes($group); $this->addAuthenticatedFeedbackRoutes($group); $this->addAuthenticatedFilesRoutes($group); @@ -393,6 +394,8 @@ class RouteMap $group->get('/sem-classes/{id}/sem-types', Routes\Courses\SemTypesBySemClassIndex::class); $group->get('/sem-types', Routes\Courses\SemTypesIndex::class); $group->get('/sem-types/{id}', Routes\Courses\SemTypesShow::class); + + $group->get('/module-components/{id}/courses', Routes\Courses\CoursesByModuleComponentsIndex::class); } private function addAuthenticatedCoursewareRoutes(RouteCollectorProxy $group): void @@ -697,6 +700,28 @@ class RouteMap $group->get('/user-filter-fields/{id}', Routes\UserFilters\UserFilterFieldsShow::class); } + private function addAuthenticatedMvvRoutes(RouteCollectorProxy $group): void + { + $group->get('/courses-of-study', Routes\Mvv\CoursesOfStudyIndex::class); + $group->get('/courses-of-study/{id}', Routes\Mvv\CoursesOfStudyShow::class); + $group->get('/courses-of-study/{id}/components', Routes\Mvv\ComponentsByCoursesOfStudyIndex::class); + $group->get('/course-of-study-components/{id}/versions', Routes\Mvv\VersionsByCourseOfStudyComponentsIndex::class); + $group->get('/course-of-study-components/{id}/subject', Routes\Mvv\SubjectsByCourseOfStudyComponentsShow::class); + $group->get('/courses-of-study/{id}/degree', Routes\Mvv\DegreesByCoursesOfStudyShow::class); + $group->get('/degrees', Routes\Mvv\DegreesIndex::class); + $group->get('/degrees/{id}', Routes\Mvv\DegreesShow::class); + $group->get('/subjects',Routes\Mvv\SubjectsIndex::class); + $group->get('/subjects/{id}',Routes\Mvv\SubjectsShow::class); + $group->get('/component-versions/{id}', Routes\Mvv\ComponentVersionsShow::class); + $group->get('/modules', Routes\Mvv\ModulesIndex::class); + $group->get('/modules/{id}', Routes\Mvv\ModulesShow::class); + $group->get('/modules/{id}/module-components', Routes\Mvv\ModuleComponentsByModuleIndex::class); + $group->get('/module-components/{id}', Routes\Mvv\ModuleComponentsShow::class); + // not a JSON:API route + $group->get('/component-version-deep/{id}', Routes\Mvv\ComponentVersionsDeep::class); + + } + private function addRelationship(RouteCollectorProxy $group, string $url, string $handler): void { $group->map(['GET', 'PATCH', 'POST', 'DELETE'], $url, $handler); diff --git a/lib/classes/JsonApi/Routes/Courses/CoursesByModuleComponentsIndex.php b/lib/classes/JsonApi/Routes/Courses/CoursesByModuleComponentsIndex.php new file mode 100644 index 0000000000000000000000000000000000000000..e7eaee9e610331015e7aa1abdba65c859215639c --- /dev/null +++ b/lib/classes/JsonApi/Routes/Courses/CoursesByModuleComponentsIndex.php @@ -0,0 +1,158 @@ +<?php + +namespace JsonApi\Routes\Courses; + +use Modulteil; +use Course; +use JsonApi\Errors\BadRequestException; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Semester; + +class CoursesByModuleComponentsIndex extends JsonApiController +{ + protected $allowedIncludePaths = [ + 'blubber-threads', + 'end-semester', + 'events', + 'feedback-elements', + 'file-refs', + 'folders', + 'forum-categories', + 'institute', + 'memberships', + 'news', + 'participating-institutes', + 'sem-class', + 'sem-type', + 'start-semester', + 'status-groups', + 'wiki-pages', + ]; + + protected $allowedPagingParameters = ['offset', 'limit']; + + protected $allowedFilteringParameters = ['semester', 'df']; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke(Request $request, Response $response, ?array $args): Response + { + $component = Modulteil::find($args['id']); + if (!$component) { + throw new RecordNotFoundException(); + } + + $filtering = $this->getQueryParameters()->getFilteringParameters() ?: []; + + $error = $this->validateFilters($filtering); + if ($error) { + throw new BadRequestException($error); + } + + $courses = $this->findCoursesByComponent( + $component, + $filtering + ); + [$offset, $limit] = $this->getOffsetAndLimit(); + + return $this->getPaginatedContentResponse( + array_slice($courses, $offset, $limit), + count($courses) + ); + } + + private function validateFilters(array $filtering): ?string + { + // semester + if ( + isset($filtering['semester']) + && !Semester::exists($filtering['semester']) + ) { + return 'Invalid "semester".'; + } + + // data fields + if (isset($filtering['df']) && is_array($filtering['df'])) { + $accepted_dfs = $this->getAcceptedDataFields(); + foreach (array_keys($filtering['df']) as $df) { + if (!in_array($df, $accepted_dfs)) { + return 'Invalid data field as filtering parameter.'; + } + } + } + return null; + } + + /** + * Get ids of accepted datafields for current user. + * Only simple types of bool, textline, selectbox and radio with global + * visibility for all users are accepted. + * + * @return array Accepted datafields + */ + private function getAcceptedDataFields(): array + { + $data_fields = \DataField::findAndMapBySQL( + fn(\DataField $data_field) => $data_field->id, + "`object_type` = 'sem' AND `view_perms` = 'user' + AND `type` IN('bool', 'textline', 'selectbox', 'radio')" + ); + return $data_fields; + } + + private function getSemesterFilter(array $filtering): ?Semester + { + if (!isset($filtering['semester'])) { + return null; + } + return Semester::find($filtering['semester']); + } + + + /** + * Finds visible courses by given module component. + * + * @param Modulteil $component + * @param Semester|null $semester + * + * @return Course[] Visible courses assigned to module component + */ + private function findCoursesByComponent(Modulteil $component, array $filtering): array + { + $course_ids = []; + foreach ($component->lvgruppen as $lvgruppe) { + $course_ids += $lvgruppe->courses->findBy('visible', '1')->pluck('id'); + } + if (count($course_ids) === 0) { + return []; + } + + if (isset($filtering['df']) && is_array($filtering['df'])) { + $df_course_ids = $course_ids; + foreach ($filtering['df'] as $id => $value) { + $df_course_ids = array_intersect($df_course_ids, \DatafieldEntryModel::findAndMapBySQL( + fn($df) => $df->range_id, + '`datafield_id` = ? AND `range_id` IN (?) AND `content` = ?', + [$id, $course_ids, $value] + )); + } + + $course_ids = array_merge_recursive($df_course_ids); + } + $courses = Course::findMany( + $course_ids, + 'ORDER BY name' + ); + + $semester = $this->getSemesterFilter($filtering); + if ($semester) { + $courses = array_filter($courses, fn(\Course $course) => $course->isInSemester($semester)); + } + + return $courses; + } +} diff --git a/lib/classes/JsonApi/Routes/Courses/CoursesIndex.php b/lib/classes/JsonApi/Routes/Courses/CoursesIndex.php index d97cdc0b3c3a4314592126d800a6ba4d783b7b64..5c3e46ee6acfbc6334f0a06a247a2fb14cb2b758 100644 --- a/lib/classes/JsonApi/Routes/Courses/CoursesIndex.php +++ b/lib/classes/JsonApi/Routes/Courses/CoursesIndex.php @@ -10,7 +10,7 @@ use JsonApi\JsonApiController; class CoursesIndex extends JsonApiController { - protected $allowedFilteringParameters = ['q', 'fields', 'semester', 'category', 'scope_choose', 'range_choose']; + protected $allowedFilteringParameters = ['q', 'fields', 'semester', 'category', 'scope_choose', 'range_choose', 'df']; protected $allowedIncludePaths = [ 'blubber-threads', @@ -51,7 +51,7 @@ class CoursesIndex extends JsonApiController list($offset, $limit) = $this->getOffsetAndLimit(); return $this->getPaginatedContentResponse( - \Course::findMany(array_slice($courseIds, $offset, $limit)), + $this->getCourses(array_slice($courseIds, $offset, $limit)), count($courseIds) ); } @@ -80,6 +80,16 @@ class CoursesIndex extends JsonApiController return 'Invalid "semester".'; } } + + // data fields + if (isset($filtering['df']) && is_array($filtering['df'])) { + $accepted_dfs = $this->getAcceptedDataFields(); + foreach (array_keys($filtering['df']) as $df) { + if (!in_array($df, $accepted_dfs)) { + return 'Invalid data field as filtering parameter.'; + } + } + } } private function getContextFilters() @@ -99,6 +109,47 @@ class CoursesIndex extends JsonApiController return array_merge($defaults, $filtering); } + private function getCourses(array $course_ids): array + { + $filtering = $this->getQueryParameters()->getFilteringParameters() ?: []; + if (isset($filtering['df']) && is_array($filtering['df'])) { + $df_where = []; + $params = [ + $course_ids + ]; + foreach ($filtering['df'] as $id => $value) { + $df_where[] = ' (`datafields_entries`.`datafield_id` = ? AND `datafields_entries`.`content` = ?) '; + $params[] = $id; + $params[] = $value; + } + return \Course::findBySQL("JOIN `datafields_entries` + ON `seminare`.`seminar_id` = `datafields_entries`.`range_id` + WHERE `seminare`.`seminar_id` IN (?) + AND " . + implode('AND', $df_where), + $params); + } else { + return \Course::findMany($course_ids); + } + } + + /** + * Get ids of accepted datafields for current user. + * Only simple types of bool, textline, selectbox and radio with global + * visibility for all users are accepted. + * + * @return array + */ + private function getAcceptedDataFields(): array + { + $data_fields = \DataField::findAndMapBySQL( + fn(\DataField $data_field) => $data_field->id, + "`object_type` = 'sem' AND `view_perms` = 'user' + AND `type` IN('bool', 'textline', 'selectbox', 'radio')" + ); + return $data_fields; + } + /** * @SuppressWarnings(PHPMD.Superglobals) */ diff --git a/lib/classes/JsonApi/Routes/Institutes/InstitutesIndex.php b/lib/classes/JsonApi/Routes/Institutes/InstitutesIndex.php index 6eef1b63ccdceca3ffddb7ea7bc0ea381b84855d..d31cbe8866cb961a450f2dcaebf8d6c862172e60 100644 --- a/lib/classes/JsonApi/Routes/Institutes/InstitutesIndex.php +++ b/lib/classes/JsonApi/Routes/Institutes/InstitutesIndex.php @@ -13,6 +13,7 @@ class InstitutesIndex extends JsonApiController InstituteSchema::REL_FACULTY, InstituteSchema::REL_STATUS_GROUPS, InstituteSchema::REL_SUB_INSTITUTES, + InstituteSchema::REL_COURSES_OF_STUDY, ]; protected $allowedFilteringParameters = ['is-faculty']; diff --git a/lib/classes/JsonApi/Routes/Institutes/InstitutesShow.php b/lib/classes/JsonApi/Routes/Institutes/InstitutesShow.php index 5ef3b5a8d02ea0c5d7d6bef03af05c526fea7a63..6a3e0a981848d1a5076fc422725bed99126e5b40 100644 --- a/lib/classes/JsonApi/Routes/Institutes/InstitutesShow.php +++ b/lib/classes/JsonApi/Routes/Institutes/InstitutesShow.php @@ -18,6 +18,7 @@ class InstitutesShow extends JsonApiController InstituteSchema::REL_FACULTY, InstituteSchema::REL_STATUS_GROUPS, InstituteSchema::REL_SUB_INSTITUTES, + InstituteSchema::REL_COURSES_OF_STUDY, ]; /** diff --git a/lib/classes/JsonApi/Routes/Mvv/Authority.php b/lib/classes/JsonApi/Routes/Mvv/Authority.php new file mode 100644 index 0000000000000000000000000000000000000000..8c9dcf97669d76408e878689833a9868852746cd --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/Authority.php @@ -0,0 +1,68 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use Studiengang; +use Modul; +use User; + +class Authority +{ + public static function canIndexCoursesOfStudy(User $user): bool + { + return true; + } + + /** + * @SuppressWarnings(PHPMD.Superglobals) + */ + public static function canShowCourseOfStudy(User $user, Studiengang $resource): bool + { + return $GLOBALS['perm']->have_perm('user') && self::isReadableStudyCourse($user, $resource); + } + + private static function isReadableStudyCourse(User $user, Studiengang $resource) + { + $public_status = \ModuleManagementModel::getPublicStatus(Studiengang::class); + return in_array($resource->stat, $public_status) + || \RolePersistence::isAssignedRole($user->id, 'MVVAdmin'); + + } + + public static function canIndexModules(User $user): bool + { + return true; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public static function canShowModule(User $user, Modul $resource): bool + { + return $GLOBALS['perm']->have_perm('user') && self::isReadableModule($user, $resource); + } + + private static function isReadableModule(User $user, Modul $resource): bool + { + $public_status = \ModuleManagementModel::getPublicStatus(Modul::class); + return in_array($resource->stat, $public_status) + || \RolePersistence::isAssignedRole($user->id, 'MVVAdmin'); + + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public static function canShowComponentVersion(User $user, \StgteilVersion $resource): bool + { + return $GLOBALS['perm']->have_perm('user') && self::isReadableComponentVersion($user, $resource); + } + + private static function isReadableComponentVersion(User $user, \StgteilVersion $resource): bool + { + $public_status = \ModuleManagementModel::getPublicStatus(\StgteilVersion::class); + return in_array($resource->stat, $public_status) + || \RolePersistence::isAssignedRole($user->id, 'MVVAdmin'); + + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/ComponentVersionsDeep.php b/lib/classes/JsonApi/Routes/Mvv/ComponentVersionsDeep.php new file mode 100644 index 0000000000000000000000000000000000000000..7b81ccf8f986f68e5fc8bfde768deac579300013 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/ComponentVersionsDeep.php @@ -0,0 +1,258 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use JsonApi\Errors\BadRequestException; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\NonJsonApiController; +use Psr\Container\ContainerInterface; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\StreamFactoryInterface; + +class ComponentVersionsDeep extends NonJsonApiController +{ + protected $allowedFilteringParameters = ['q', 'institute', 'semester', 'section']; + + public function __construct( + ContainerInterface $container, + private StreamFactoryInterface $streamFactory + ) { + parent::__construct($container); + } + + public function __invoke(Request $request, Response $response, array $args) + { + $component_version = \StgteilVersion::find($args['id']); + if (!$component_version) { + throw new RecordNotFoundException(); + } + + $parameters = $request->getQueryParams(); + + $this->validateParameters($parameters); + + $data = $this->getVersionData($component_version, $parameters); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withBody($this->streamFactory->createStream(json_encode($data))); + } + + private function validateParameters(array $parameters): void + { + if (!isset($parameters['semester'])) { + throw new BadRequestException('Parameter semester is missing'); + } + + if (!\Semester::exists($parameters['semester'])) { + throw new BadRequestException('Semester not found'); + } + } + + private function getVersionData(\StgteilVersion $version, array $parameters): array + { + $data = [ + 'id' => $version->id, + 'display-name' => $version->getDisplayName(), + 'start-semester' => $version->start_semester ? $this->getSemesterData($version->start_semester) : '', + 'end-semester' => $version->end_semester ? $this->getSemesterData($version->end_semester) : '', + 'code' => $version->code, + 'description' => $version->beschreibung, + 'date-of-decision' => $version->beschlussdatum, + 'edition-number' => $version->fassung_nr, + 'edition-type' => \Config::get()->MVV_STGTEILVERSION['FASSUNG_TYP'][$version->fassung_typ] ?? '', + 'status' => \Config::get()->MVV_STGTEILVERSION['STATUS']['values'][$version->stat], + 'sections' => $this->getSectionsData($version, $parameters), + ]; + return $data; + } + + private function getSectionsData(\StgteilVersion $version, array $parameters): array + { + $data = []; + foreach ($version->abschnitte as $section) { + $data[] = [ + 'id' => $section->id, + 'display-name' => $section->getDisplayName(), + 'comment' => $section->kommentar, + 'position' => $section->position, + 'cp' => $section->kp, + 'caption' => $section->ueberschrift, + 'modules' => $this->getModulesData($section, $parameters), + ]; + } + return $data; + } + + private function getModulesData(\StgteilAbschnitt $section, array $parameters): array + { + $status = \Config::get()->MVV_MODUL['STATUS']['values']; + $semester = \Semester::find($parameters['semester']); + $modules_filtered = $section->module->filter( + fn(\Modul $module) => + ((empty($module->start_semester) || $module->start_semester->beginn <= $semester->ende) + && (empty($module->end_semester) || $module->end_semester->ende >= $semester->beginn) + && $status[$module->stat]['public'] === 1) + ); + $data = []; + foreach ($modules_filtered as $module) { + $data[] = [ + 'id' => $module->id, + 'display-name' => (string) $module->getDisplayName(), + 'code' => (string) $module->code, + 'date-of-decision' => $module->beschlussdatum ? date('c', $module->beschlussdatum) : '', + 'edition-number' => (string) $module->fassung_nr, + 'edition-type' => \Config::get()->MVV_MODUL['FASSUNG_TYP'][$module->fassung_typ] ?? '', + 'version-number' => (string) $module->version, + 'semester-duration' => (string) $module->dauer, + 'capacity' => (string) $module->kapazitaet, + 'cp' => $module->kp, + 'workload-self' => (string) $module->wl_selbst, + 'workload-exam' => (string) $module->wl_pruef, + 'examination-period' => \Config::get()->MVV_MODUL['PRUEF_EBENE']['values'][$module->pruef_ebene] ?? '', + 'grade-factor' => (string) $module->faktor_note, + 'foreign-key' => (string) $module->flexnow_modul, + 'name' => (string) $module->deskriptoren->bezeichnung, + 'responsible' => (string) $module->deskriptoren->verantwortlich, + 'prerequisite' => (string) $module->deskriptoren->voraussetzung, + 'objectives' => (string) $module->deskriptoren->kompetenzziele, + 'content' => (string) $module->deskriptoren->inhalte, + 'literature' => (string) $module->deskriptoren->literatur, + 'links' => (string) $module->deskriptoren->links, + 'comment' => (string) $module->deskriptoren->kommentar, + 'cycle' => (string) $module->deskriptoren->turnus, + 'comment-capacity' => (string) $module->deskriptoren->kommentar_kapazitaet, + 'comment_sws' => (string) $module->deskriptoren->kommentar_sws, + 'status' => \Config::get()->MVV_MODUL['STATUS']['values'][$module->stat], + 'module-languages' => $this->getModuleLanguagesData($module), + 'module-section-data' => $this->getModuleSectionData( + $module->abschnitte_modul->findOneBy('abschnitt_id', $section->id)), + 'module-components' => $this->getModuleComponentsData($module, $parameters), + 'start-semester' => $module->start_semester ? $this->getSemesterData($module->start_semester) : '', + 'end-semester' => $module->end_semester ? $this->getSemesterData($module->end_semester) : '', + ]; + } + return $data; + } + + private function getSemesterData(\Semester $semester): array + { + return [ + 'id' => $semester->id, + 'name' => $semester->name, + 'short-name' => $semester->semester_token, + 'semester-start' => $semester->beginn, + 'semester-end' => $semester->ende, + 'foreign-key' => $semester->external_id, + 'teaching-start' => $semester->vorles_beginn, + 'teaching-end' => $semester->vorles_ende, + 'semester-switch-time' => $semester->sem_wechsel, + ]; + } + + private function getModuleComponentsData(\Modul $module, array $parameters): array + { + foreach ($module->modulteile as $component) { + $data[] = [ + 'id' => $component->id, + 'name' => (string) $component->deskriptoren->bezeichnung, + 'position' => $component->position, + 'foreign-key' => $component->flexnow_modul, + 'number' => $component->nummer, + 'number-label' => \Config::get()->MVV_MODULTEIL['NUM_BEZEICHNUNG']['values'][$component->num_bezeichnung] ?? '', + 'teaching-method' => \Config::get()->MVV_MODULTEIL['LERNLEHRFORM']['values'][$component->lernlehrform] ?? '', + 'semester' => $component->semester, + 'number-of-participants' => $component->kapazitaet, + 'cp' => $component->kp, + 'sws' => $component->sws, + 'workload-compulsory' => $component->wl_praesenz, + 'workload-preparation' => $component->wl_bereitung, + 'workload-self' => $component->wl_selbst, + 'workload-exam' => $component->wl_pruef, + 'share-of-grade' => $component->anteil_note, + 'compensable' => $component->ausgleichbar, + 'compulsory-attendance' => $component->pflicht, + 'prerequisites' => $component->deskriptoren->voraussetzung, + 'comment' => $component->deskriptoren->kommentar, + 'comment-capacity' => $component->deskriptoren->kommentar_kapazitaet, + 'comment-wl-compulsory' => $component->deskriptoren->kommentar_wl_praesenz, + 'comment-wl-preparation' => $component->deskriptoren->kommentar_wl_bereitung, + 'comment-wl-self' => $component->deskriptoren->kommentar_wl_selbst, + 'comment-wl-exam' => $component->deskriptoren->kommentar_wl_pruef, + 'exam-prerequisites' => $component->deskriptoren->pruef_vorleistung, + 'exam-requirements' => $component->deskriptoren->pruef_leistung, + 'comment-compulsory-attendance' => $component->deskriptoren->kommentar_pflicht, + 'courses' => $this->getCoursesData($component, $parameters), + 'course-semesters' => $this->getModuleComponentSectionData($component), + ]; + } + return $data; + } + + private function getCoursesData(\Modulteil $component, array $parameters): array + { + $course_ids = []; + foreach ($component->lvgruppen as $lvgruppe) { + $course_ids += $lvgruppe->courses->pluck('id'); + } + + if (count($course_ids) === 0) { + return []; + } + + $courses = \Course::findBySQL( + '`seminar_id` IN (?) AND `visible` = 1 ORDER BY start_time, name', + [$course_ids] + ); + $semester = \Semester::find($parameters['semester']); + $courses = array_filter($courses, fn (\Course $course) => $course->isInSemester($semester)); + + $data = []; + foreach ($courses as $course) { + $data[] = [ + 'id' => $course->id, + 'name' => $course->name, + 'number' => $course->veranstaltungsnummer + ]; + } + return $data; + } + + private function getModuleComponentSectionData(\Modulteil $component): array + { + $data = []; + foreach ($component->abschnitt_assignments as $assignment) { + $data = [ + 'course-semester' => $assignment->fachsemester, + 'differentiation' => $assignment->differenzierung, + ]; + } + return $data; + } + + private function getModuleSectionData(\StgteilabschnittModul $module_section): array + { + return [ + 'id' => $module_section->abschnitt_modul_id, + 'name' => $module_section->bezeichnung, + 'code' => $module_section->modulcode, + 'position' => $module_section->position, + 'foreign-key' => $module_section->flexnow_modul, + ]; + } + + private function getModuleLanguagesData(\Modul $module): array + { + $languages = \Config::get()->MVV_MODUL['SPRACHE']['values']; + $data = []; + foreach ($module->languages as $language) { + $data[] = [ + 'language' => $language->lang, + 'name' => $languages[$language->lang]['name'], + 'position' => $language->position, + ]; + } + return $data; + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/ComponentVersionsShow.php b/lib/classes/JsonApi/Routes/Mvv/ComponentVersionsShow.php new file mode 100644 index 0000000000000000000000000000000000000000..248cc696c5529883033360b7d3dbe7b1004b33b7 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/ComponentVersionsShow.php @@ -0,0 +1,29 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class ComponentVersionsShow extends JsonApiController +{ + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + */ + public function __invoke(Request $request, Response $response, $args) + { + $version = \StgteilVersion::find($args['id']); + if (!$version) { + throw new RecordNotFoundException(); + } + + if (!Authority::canShowComponentVersion($this->getUser($request), $version)) { + throw new AuthorizationFailedException(); + } + + return $this->getContentResponse($version); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/ComponentsByCoursesOfStudyIndex.php b/lib/classes/JsonApi/Routes/Mvv/ComponentsByCoursesOfStudyIndex.php new file mode 100644 index 0000000000000000000000000000000000000000..17fbdcfc0c53f04398f439ba0a69800db50fca2c --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/ComponentsByCoursesOfStudyIndex.php @@ -0,0 +1,39 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use JsonApi\Schemas\CourseOfStudyComponent; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class ComponentsByCoursesOfStudyIndex extends JsonApiController +{ + protected $allowedPagingParameters = [ + 'offset', + 'limit' + ]; + + protected $allowedIncludePaths = [ + CourseOfStudyComponent::REL_SUBJECT, + CourseOfStudyComponent::REL_VERSIONS, + ]; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + */ + public function __invoke(Request $request, Response $response, $args) + { + $course_of_study = \Studiengang::find($args['id']); + if (!$course_of_study) { + throw new RecordNotFoundException(); + } + [$offset, $limit] = $this->getOffsetAndLimit(); + + return $this->getPaginatedContentResponse( + $course_of_study->studiengangteile->limit($offset, $limit), + count($course_of_study->studiengangteile) + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/CourseOfStudyComponentsShow.php b/lib/classes/JsonApi/Routes/Mvv/CourseOfStudyComponentsShow.php new file mode 100644 index 0000000000000000000000000000000000000000..496959177b7ba77e913f36e21e045e421af2636d --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/CourseOfStudyComponentsShow.php @@ -0,0 +1,31 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use JsonApi\Schemas\CourseOfStudyComponent; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class CourseOfStudyComponentsShow extends JsonApiController +{ + + protected $allowedIncludePaths = [ + CourseOfStudyComponent::REL_SUBJECT, + CourseOfStudyComponent::REL_VERSIONS, + ]; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + */ + public function __invoke(Request $request, Response $response, $args) + { + $component = \StudiengangTeil::find($args['id']); + if (!$component) { + throw new RecordNotFoundException(); + } + + return $this->getContentResponse($component); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/CoursesOfStudyIndex.php b/lib/classes/JsonApi/Routes/Mvv/CoursesOfStudyIndex.php new file mode 100644 index 0000000000000000000000000000000000000000..2040153ec01a85d6c600d3ce0e1f01611bc1af9b --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/CoursesOfStudyIndex.php @@ -0,0 +1,121 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use JsonApi\Schemas\CourseOfStudy; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\BadRequestException; +use JsonApi\JsonApiController; + +class CoursesOfStudyIndex extends JsonApiController +{ + protected $allowedFilteringParameters = ['q', 'institute', 'semester', 'degree', 'category', 'type']; + + protected $allowedIncludePaths = [ + CourseOfStudy::REL_SECTIONS, + CourseOfStudy::REL_INSTITUTE, + CourseOfStudy::REL_COMPONENTS, + CourseOfStudy::REL_DEGREE, + CourseOfStudy::REL_END_SEMESTER, + CourseOfStudy::REL_START_SEMESTER, + ]; + + protected $allowedPagingParameters = ['offset', 'limit']; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + */ + public function __invoke(Request $request, Response $response, $args) + { + if (!Authority::canIndexCoursesOfStudy($user = $this->getUser($request))) { + throw new AuthorizationFailedException(); + } + + $filtering = $this->getQueryParameters()->getFilteringParameters() ?: []; + $error = $this->validateFilters($filtering); + if ($error) { + throw new BadRequestException($error); + } + + [$offset, $limit] = $this->getOffsetAndLimit(); + $courses_of_study = $this->getCoursesOfStudy($filtering, $offset, $limit); + + return $this->getPaginatedContentResponse( + $courses_of_study, + count($courses_of_study) + ); + } + + private function validateFilters($filtering) + { + // keyword aka q + if (isset($filtering['q']) && mb_strlen($filtering['q']) < 3) { + return 'Search term too short.'; + } + + // institute + if (isset($filtering['institute']) && !\Institute::exists($filtering['institute'])) { + return 'Filter `institute` must be a valid id.'; + } + + // degree + if (isset($filtering['degree']) && !\Abschluss::exists($filtering['degree'])) { + return 'Filter `degree` must be a valid id.'; + } + + // degree category + if (isset($filtering['category']) && !\AbschlussKategorie::find($filtering['category'])) { + return 'Filter `category` must be a valid id'; + } + + // semester + if (isset($filtering['semester']) && !\Semester::exists($filtering['semester'])) { + return 'Filter `semester` must be a valid id.'; + } + } + + private function getCoursesOfStudy($filtering, $offset, $limit): array + { + $join = ''; + $where = ' 1 '; + $filtering['offset'] = $offset; + $filtering['limit'] = $limit; + if (isset($filtering['institute'])) { + $where .= ' AND `institut_id` = :institute '; + } + if (isset($filtering['type'])) { + $where .= ' AND `typ` = :type '; + } + if (isset($filtering['degree'])) { + $where .= ' AND `mvv_studiengang`.`abschluss_id` = :degree '; + } + if (isset($filtering['category'])) { + $join .= 'LEFT JOIN `mvv_abschluss_zuord` USING(`abschluss_id`) '; + $where .= ' AND `mvv_abschluss_zuord`.`kategorie_id` = :category'; + } + if (isset($filtering['semester'])) { + $semester = \Semester::find($filtering['semester']); + unset($filtering['semester']); + $filtering['semester_start'] = $semester->beginn; + $filtering['semester_end'] = $semester->ende; + $join .= 'LEFT JOIN `semester_data` AS `start_sem` + ON (`mvv_studiengang`.`start` = `start_sem`.`semester_id`) + LEFT JOIN `semester_data` AS `end_sem` + ON (`mvv_studiengang`.`end` = `end_sem`.`semester_id`) '; + $where .= ' AND (`start_sem`.`beginn` <= :semester_end OR ISNULL(`start_sem`.`beginn`)) + AND (`end_sem`.`ende` >= :semester_start OR ISNULL(`end_sem`.`ende`))'; + } + if (isset($filtering['q'])) { + $where .= " AND (`mvv_studiengang`.`name` LIKE CONCAT('%', :q, '%') OR `mvv_studiengang`.`name_kurz` LIKE CONCAT('%', :q, '%')) "; + } + $where .= ' ORDER BY `mvv_studiengang`.`name` ASC + LIMIT :limit OFFSET :offset'; + return \Studiengang::findBySQL( + ($join ? $join . ' WHERE ' : '') . $where, + $filtering + ); + } + +} diff --git a/lib/classes/JsonApi/Routes/Mvv/CoursesOfStudyShow.php b/lib/classes/JsonApi/Routes/Mvv/CoursesOfStudyShow.php new file mode 100644 index 0000000000000000000000000000000000000000..8e8504a2dfd50d06582e588ef616d6e048b3317b --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/CoursesOfStudyShow.php @@ -0,0 +1,31 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class CoursesOfStudyShow extends JsonApiController +{ + + protected $allowedIncludePaths = null; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + */ + public function __invoke(Request $request, Response $response, $args) + { + $course_of_study = \Studiengang::find($args['id']); + if (!$course_of_study) { + throw new RecordNotFoundException(); + } + if (!Authority::canShowCourseOfStudy($user = $this->getUser($request), $course_of_study)) { + throw new AuthorizationFailedException(); + } + + return $this->getContentResponse($course_of_study); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/DegreesByCoursesOfStudyShow.php b/lib/classes/JsonApi/Routes/Mvv/DegreesByCoursesOfStudyShow.php new file mode 100644 index 0000000000000000000000000000000000000000..006b9e7b200b1374c935fe5ae2241fcff875bd3d --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/DegreesByCoursesOfStudyShow.php @@ -0,0 +1,26 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class DegreesByCoursesOfStudyShow extends JsonApiController +{ + protected $allowedIncludePaths = []; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke(Request $request, Response $response, $args) + { + $course_of_study = \Studiengang::find($args['id']); + if (empty($course_of_study->abschluss)) { + throw new RecordNotFoundException('Could not find degree.'); + } + + return $this->getContentResponse($course_of_study->abschluss); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/DegreesIndex.php b/lib/classes/JsonApi/Routes/Mvv/DegreesIndex.php new file mode 100644 index 0000000000000000000000000000000000000000..bff4c14d16957bbb5fbcf551f0475926cd638a3c --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/DegreesIndex.php @@ -0,0 +1,26 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use Abschluss; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\JsonApiController; + +class DegreesIndex extends JsonApiController +{ + protected $allowedPagingParameters = ['offset', 'limit']; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke(Request $request, Response $response, $args) + { + [$offset, $limit] = $this->getOffsetAndLimit(); + + return $this->getPaginatedContentResponse( + Abschluss::findBySQL("1 ORDER BY name LIMIT {$offset}, {$limit}"), + Abschluss::countBySql('1') + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/DegreesShow.php b/lib/classes/JsonApi/Routes/Mvv/DegreesShow.php new file mode 100644 index 0000000000000000000000000000000000000000..ce521e8d995406f1d41c6459cf89f12f4f4c1fec --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/DegreesShow.php @@ -0,0 +1,26 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class DegreesShow extends JsonApiController +{ + protected $allowedIncludePaths = []; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke(Request $request, Response $response, $args) + { + $degree = \Abschluss::find($args['id']); + if (!$degree) { + throw new RecordNotFoundException('Could not find degree.'); + } + + return $this->getContentResponse($degree); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/ModuleComponentsByModuleIndex.php b/lib/classes/JsonApi/Routes/Mvv/ModuleComponentsByModuleIndex.php new file mode 100644 index 0000000000000000000000000000000000000000..9fb2b0bc32ad5e042e2695e29e996c5fe9e71894 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/ModuleComponentsByModuleIndex.php @@ -0,0 +1,35 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use JsonApi\Schemas\ModuleComponent; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class ModuleComponentsByModuleIndex extends JsonApiController +{ + protected $allowedPagingParameters = ['offset', 'limit']; + + protected $allowedIncludePaths = [ + ModuleComponent::REL_COURSES, + ]; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + */ + public function __invoke(Request $request, Response $response, $args) + { + $module = \Modul::find($args['id']); + if (!$module) { + throw new RecordNotFoundException(); + } + [$offset, $limit] = $this->getOffsetAndLimit(); + + return $this->getPaginatedContentResponse( + $module->modulteile->limit($offset, $limit), + count($module->modulteile) + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/ModuleComponentsShow.php b/lib/classes/JsonApi/Routes/Mvv/ModuleComponentsShow.php new file mode 100644 index 0000000000000000000000000000000000000000..a3c4d440bb7e4538de981933df0702edf20666bf --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/ModuleComponentsShow.php @@ -0,0 +1,29 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use JsonApi\Schemas\ModuleComponent; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class ModuleComponentsShow extends JsonApiController +{ + protected $allowedIncludePaths = [ + ModuleComponent::REL_COURSES, + ]; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke(Request $request, Response $response, $args) + { + $component = \Modulteil::find($args['id']); + if (!$component) { + throw new RecordNotFoundException('Could not find module component.'); + } + + return $this->getContentResponse($component); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/ModulesIndex.php b/lib/classes/JsonApi/Routes/Mvv/ModulesIndex.php new file mode 100644 index 0000000000000000000000000000000000000000..3018fd404e1c14f9f4de0efad605e352d526abfc --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/ModulesIndex.php @@ -0,0 +1,111 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\BadRequestException; +use JsonApi\Schemas\Module; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\JsonApiController; + +class ModulesIndex extends JsonApiController +{ + protected $allowedFilteringParameters = ['q', 'institute', 'semester', 'section']; + + protected $allowedPagingParameters = ['offset', 'limit']; + + protected $allowedIncludePaths = [ + Module::REL_MODULE_COMPONENTS, + Module::REL_END_SEMESTER, + Module::REL_START_SEMESTER, + Module::REL_RESPONSIBLE_DEPARTMENT, + Module::REL_DEPARTMENTS, + Module::REL_SOURCE_MODULE, + Module::REL_VARIANT_MODULE, + ]; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + */ + public function __invoke(Request $request, Response $response, $args) + { + if (!Authority::canIndexModules($user = $this->getUser($request))) { + throw new AuthorizationFailedException(); + } + + $filtering = $this->getQueryParameters()->getFilteringParameters() ?: []; + $error = $this->validateFilters($filtering); + if ($error) { + throw new BadRequestException($error); + } + + [$offset, $limit] = $this->getOffsetAndLimit(); + $modules = $this->getModules($filtering, $offset, $limit); + + return $this->getPaginatedContentResponse( + $modules, + count($modules) + ); + } + + private function validateFilters($filtering) + { + // keyword aka q + if (isset($filtering['q']) && mb_strlen($filtering['q']) < 3) { + return 'Search term too short.'; + } + + // institute + if (isset($filtering['institute']) && !\Institute::exists($filtering['institute'])) { + return 'Filter `institute` must be a valid id.'; + } + + // section + if (isset($filtering['section']) && !\StgteilAbschnitt::exists($filtering['section'])) { + return 'Filter `section` must be a valid id'; + } + + // semester + if (isset($filtering['semester']) && !\Semester::exists($filtering['semester'])) { + return 'Filter `semester` must be a valid id.'; + } + } + + private function getModules($filtering, $offset, $limit): array + { + $join = ''; + $where = ' 1 '; + $filtering['offset'] = $offset; + $filtering['limit'] = $limit; + if (isset($filtering['institute'])) { + $where .= ' AND `institut_id` = :institute '; + } + if (isset($filtering['section'])) { + $join .= 'LEFT JOIN `mvv_stgteilabschnitt_modul` USING(`modul_id`) '; + $where .= ' AND `mvv_stgteilabschnitt_modul`.`abschnitt_id` = :section'; + } + if (isset($filtering['semester'])) { + $semester = \Semester::find($filtering['semester']); + unset($filtering['semester']); + $filtering['semester_start'] = $semester->beginn; + $filtering['semester_end'] = $semester->ende; + $join .= 'LEFT JOIN `semester_data` AS `start_sem` + ON (`mvv_modul`.`start` = `start_sem`.`semester_id`) + LEFT JOIN `semester_data` AS `end_sem` + ON (`mvv_modul`.`end` = `end_sem`.`semester_id`) '; + $where .= ' AND (`start_sem`.`beginn` <= :semester_end OR ISNULL(`start_sem`.`beginn`)) + AND (`end_sem`.`ende` >= :semester_start OR ISNULL(`end_sem`.`ende`))'; + } + $join .= 'LEFT JOIN `mvv_modul_deskriptor` USING(`modul_id`) '; + if (isset($filtering['q'])) { + $where .= " AND (`mvv_modul_deskriptor`.`bezeichnung` LIKE CONCAT('%', :q, '%') OR `mvv_modul`.`code` LIKE CONCAT('%', :q, '%')) "; + } + $where .= ' ORDER BY `mvv_modul`.`code` ASC, `mvv_modul_deskriptor`.`bezeichnung` ASC + LIMIT :limit OFFSET :offset'; + return \Modul::findBySQL( + ($join ? $join . ' WHERE ' : '') . $where, + $filtering + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/ModulesShow.php b/lib/classes/JsonApi/Routes/Mvv/ModulesShow.php new file mode 100644 index 0000000000000000000000000000000000000000..8db7773901044afc14ffb8af7074347b1f92c773 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/ModulesShow.php @@ -0,0 +1,32 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class ModulesShow extends JsonApiController +{ + + protected $allowedIncludePaths = null; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + */ + public function __invoke(Request $request, Response $response, $args) + { + $module = \Modul::find($args['id']); + if (!$module) { + throw new RecordNotFoundException(); + } + + if (!Authority::canShowModule($this->getUser($request), $module)) { + throw new AuthorizationFailedException(); + } + + return $this->getContentResponse($module); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/SubjectsByCourseOfStudyComponentsShow.php b/lib/classes/JsonApi/Routes/Mvv/SubjectsByCourseOfStudyComponentsShow.php new file mode 100644 index 0000000000000000000000000000000000000000..cc19ee277ab5c1dcd72206682a97ca67d8656a4c --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/SubjectsByCourseOfStudyComponentsShow.php @@ -0,0 +1,29 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use JsonApi\Schemas\Subject; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class SubjectsByCourseOfStudyComponentsShow extends JsonApiController +{ + protected $allowedIncludePaths = [ + Subject::REL_DEPARTMENTS, + ]; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke(Request $request, Response $response, $args) + { + $component = \StudiengangTeil::find($args['id']); + if (empty($component->fach)) { + throw new RecordNotFoundException('Could not find subject.'); + } + + return $this->getContentResponse($component->fach); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/SubjectsIndex.php b/lib/classes/JsonApi/Routes/Mvv/SubjectsIndex.php new file mode 100644 index 0000000000000000000000000000000000000000..338f71ab1012b224c5935135f1cd403ef40f13da --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/SubjectsIndex.php @@ -0,0 +1,26 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use Fach; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\JsonApiController; + +class SubjectsIndex extends JsonApiController +{ + protected $allowedPagingParameters = ['offset', 'limit']; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke(Request $request, Response $response, $args) + { + [$offset, $limit] = $this->getOffsetAndLimit(); + + return $this->getPaginatedContentResponse( + Fach::findBySQL("1 ORDER BY name LIMIT {$offset}, {$limit}"), + Fach::countBySql('1') + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/SubjectsShow.php b/lib/classes/JsonApi/Routes/Mvv/SubjectsShow.php new file mode 100644 index 0000000000000000000000000000000000000000..63428bc0c98b545478b240528992a25892078a5c --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/SubjectsShow.php @@ -0,0 +1,29 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use JsonApi\Schemas\Subject; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class SubjectsShow extends JsonApiController +{ + protected $allowedIncludePaths = [ + Subject::REL_DEPARTMENTS, + ]; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke(Request $request, Response $response, $args) + { + $subject = \Fach::find($args['id']); + if (!$subject) { + throw new RecordNotFoundException('Could not find subject.'); + } + + return $this->getContentResponse($subject); + } +} diff --git a/lib/classes/JsonApi/Routes/Mvv/VersionsByCourseOfStudyComponentsIndex.php b/lib/classes/JsonApi/Routes/Mvv/VersionsByCourseOfStudyComponentsIndex.php new file mode 100644 index 0000000000000000000000000000000000000000..9d321b9789e3677a286b3d8a80a3344d168f098d --- /dev/null +++ b/lib/classes/JsonApi/Routes/Mvv/VersionsByCourseOfStudyComponentsIndex.php @@ -0,0 +1,37 @@ +<?php + +namespace JsonApi\Routes\Mvv; + +use JsonApi\Schemas\ComponentVersion; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +class VersionsByCourseOfStudyComponentsIndex extends JsonApiController +{ + protected $allowedPagingParameters = ['offset', 'limit']; + + protected $allowedIncludePaths = [ + ComponentVersion::REL_SECTIONS, + ComponentVersion::REL_START_SEMESTER, + ComponentVersion::REL_END_SEMESTER, + ]; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + */ + public function __invoke(Request $request, Response $response, $args) + { + $component = \StudiengangTeil::find($args['id']); + if (!$component) { + throw new RecordNotFoundException(); + } + [$offset, $limit] = $this->getOffsetAndLimit(); + + return $this->getPaginatedContentResponse( + $component->versionen->limit($offset, $limit), + count($component->versionen) + ); + } +} diff --git a/lib/classes/JsonApi/SchemaMap.php b/lib/classes/JsonApi/SchemaMap.php index c651b8774dce590d4f422f08b34b3be7a04aee99..801bf293831782ec2d2ed33c04e9daec26968bfd 100644 --- a/lib/classes/JsonApi/SchemaMap.php +++ b/lib/classes/JsonApi/SchemaMap.php @@ -66,6 +66,15 @@ class SchemaMap \FolderType::class => Schemas\Folder::class, \UserFilter::class => Schemas\UserFilter::class, \UserFilterField::class => Schemas\UserFilterField::class, + \Studiengang::class => Schemas\CourseOfStudy::class, + \StudiengangTeil::class => Schemas\CourseOfStudyComponent::class, + \StgteilVersion::class => Schemas\ComponentVersion::class, + \Fach::class => Schemas\Subject::class, + \Abschluss::class => Schemas\Degree::class, + \Modul::class => Schemas\Module::class, + \Modulteil::class => Schemas\ModuleComponent::class, + \StgteilAbschnitt::class => Schemas\ComponentSection::class, + \Courseware\Block::class => Schemas\Courseware\Block::class, \Courseware\BlockComment::class => Schemas\Courseware\BlockComment::class, \Courseware\BlockFeedback::class => Schemas\Courseware\BlockFeedback::class, diff --git a/lib/classes/JsonApi/Schemas/ComponentSection.php b/lib/classes/JsonApi/Schemas/ComponentSection.php new file mode 100644 index 0000000000000000000000000000000000000000..459d6e0791ab824087130e0f701b2a6081213a84 --- /dev/null +++ b/lib/classes/JsonApi/Schemas/ComponentSection.php @@ -0,0 +1,52 @@ +<?php +namespace JsonApi\Schemas; + +use Neomerx\JsonApi\Contracts\Schema\ContextInterface; +use Neomerx\JsonApi\Schema\Link; + +class ComponentSection extends SchemaProvider +{ + const REL_MODULES = 'modules'; + const TYPE = 'component-sections'; + + public function getId($resource): ?string + { + return $resource->id; + } + + public function getAttributes($resource, ContextInterface $context): iterable + { + return [ + 'display-name' => (string) $resource->getDisplayName(), + 'comment' => $resource->kommentar, + 'position' => $resource->position, + 'cp' => $resource->kp, + 'caption' => $resource->ueberschrift, + 'type' => get_class($resource) + ]; + } + + public function getRelationships($resource, ContextInterface $context): iterable + { + $relationships = []; + + $relationships = $this->addModulesRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_MODULES)); + + return $relationships; + } + + private function addModulesRelationship(array $relationships, $resource, $includeData) + { + $relationships[self::REL_MODULES] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_MODULES), + ], + ]; + + if ($includeData) { + $relationships[self::REL_MODULES][self::RELATIONSHIP_DATA] = $resource->module; + } + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/ComponentVersion.php b/lib/classes/JsonApi/Schemas/ComponentVersion.php new file mode 100644 index 0000000000000000000000000000000000000000..ea89716142d1d055eddd70503f5ad2436f3583bc --- /dev/null +++ b/lib/classes/JsonApi/Schemas/ComponentVersion.php @@ -0,0 +1,94 @@ +<?php +namespace JsonApi\Schemas; + +use Neomerx\JsonApi\Contracts\Schema\ContextInterface; +use Neomerx\JsonApi\Schema\Link; + +class ComponentVersion extends SchemaProvider +{ + const REL_SECTIONS = 'component-sections'; + const REL_START_SEMESTER = 'start-semester'; + const REL_END_SEMESTER = 'end-semester'; + const TYPE = 'component-versions'; + + public function getId($resource): ?string + { + return $resource->id; + } + + public function getAttributes($resource, ContextInterface $context): iterable + { + return [ + 'display-name' => (string) $resource->getDisplayName(), + 'code' => (string) $resource->code, + 'date' => (string) $resource->beschlussdatum, + 'version-number' => (string) $resource->fassung_nr, + 'version-type' => (string) $resource->fassung_typ, + 'description' => (string) $resource->beschreibung, + 'status' => (string) $resource->stat, + 'status-name' => \Config::get()->MVV_STGTEILVERSION['STATUS']['values'][$resource->stat]['name'], + 'type' => get_class($resource) + ]; + } + + public function getRelationships($resource, ContextInterface $context): iterable + { + $relationships = []; + + if ($semester = $this->getStartSemester($resource)) { + $relationships[self::REL_START_SEMESTER] = $semester; + } + if ($semester = $this->getEndSemester($resource)) { + $relationships[self::REL_END_SEMESTER] = $semester; + } + + $relationships = $this->addSectionsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_SECTIONS)); + + return $relationships; + } + + private function getStartSemester(\StgteilVersion $version) + { + $semester = \Semester::find($version->start_sem); + if (!$semester) { + return null; + } + + return [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($semester), + ], + self::RELATIONSHIP_DATA => $semester, + ]; + } + + private function getEndSemester(\StgteilVersion $version) + { + $semester = \Semester::find($version->end_sem); + if (!$semester) { + return null; + } + + return [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($semester), + ], + self::RELATIONSHIP_DATA => $semester, + ]; + } + + private function addSectionsRelationship(array $relationships, $resource, $includeData) + { + $relationships[self::REL_SECTIONS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_SECTIONS), + ], + ]; + + if ($includeData) { + $relationships[self::REL_SECTIONS][self::RELATIONSHIP_DATA] = $resource->abschnitte; + } + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/CourseOfStudy.php b/lib/classes/JsonApi/Schemas/CourseOfStudy.php new file mode 100644 index 0000000000000000000000000000000000000000..0e6a0a34258bcc5c73457e93b1ecdd1a0920580e --- /dev/null +++ b/lib/classes/JsonApi/Schemas/CourseOfStudy.php @@ -0,0 +1,151 @@ +<?php +namespace JsonApi\Schemas; + +use Neomerx\JsonApi\Contracts\Schema\ContextInterface; +use Neomerx\JsonApi\Schema\Link; + +class CourseOfStudy extends SchemaProvider +{ + const REL_SECTIONS = 'sections'; + const REL_INSTITUTE = 'institute'; + const REL_COMPONENTS = 'components'; + const REL_DEGREE = 'degree'; + const REL_END_SEMESTER = 'end-semester'; + const REL_START_SEMESTER = 'start-semester'; + const TYPE = 'courses-of-study'; + + public function getId($resource): ?string + { + return $resource->id; + } + + public function getAttributes($resource, ContextInterface $context): iterable + { + return [ + 'display-name' => (string) $resource->getDisplayName(), + 'name' => (string) $resource->name, + 'short-name' => (string) $resource->name_kurz, + 'type' => (string) $resource->typ, + 'status' => (string) $resource->stat, + 'status-name' => \Config::get()->MVV_STUDIENGANG['STATUS']['values'][$resource->stat]['name'] ?? '', + 'classname' => get_class($resource) + ]; + } + + public function getRelationships($resource, ContextInterface $context): iterable + { + $relationships = []; + + $institute = \Institute::find($resource->institut_id); + if ($institute) { + $relationships[self::REL_INSTITUTE] = $this->getInstitute($resource, $this->shouldInclude($context, self::REL_INSTITUTE)); + } + + if ($semester = $this->getStartSemester($resource)) { + $relationships[self::REL_START_SEMESTER] = $semester; + } + if ($semester = $this->getEndSemester($resource)) { + $relationships[self::REL_END_SEMESTER] = $semester; + } + + $relationships = $this->addSectionsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_SECTIONS)); + $relationships = $this->addComponentsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_COMPONENTS)); + $relationships = $this->addDegreeRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_DEGREE)); + + return $relationships; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + private function getInstitute(\Studiengang $course_of_study, $shouldInclude) + { + $institute = \Institute::find($course_of_study->institut_id); + return $institute + ? [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($institute), + ], + self::RELATIONSHIP_DATA => $institute, + ] + : [ + self::RELATIONSHIP_DATA => null, + ]; + } + + private function getStartSemester(\Studiengang $course_of_study) + { + $semester = \Semester::find($course_of_study->start); + if (!$semester) { + return null; + } + + return [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($semester), + ], + self::RELATIONSHIP_DATA => $semester, + ]; + } + + private function getEndSemester(\Studiengang $course_of_study) + { + $semester = \Semester::find($course_of_study->end); + if (!$semester) { + return null; + } + + return [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($semester), + ], + self::RELATIONSHIP_DATA => $semester, + ]; + } + + private function addSectionsRelationship(array $relationships, $resource, $includeData) + { + $relationships[self::REL_SECTIONS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_SECTIONS), + ], + ]; + + if ($includeData) { + $relationships[self::REL_SECTIONS][self::RELATIONSHIP_DATA] = $resource->stgteil_bezeichnungen; + } + + return $relationships; + } + + private function addComponentsRelationship(array $relationships, $resource, $includeData) + { + $relationships[self::REL_COMPONENTS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_COMPONENTS), + ], + ]; + + if ($includeData) { + $relationships[self::REL_COMPONENTS][self::RELATIONSHIP_DATA] = $resource->studiengangteile; + } + + return $relationships; + } + + private function addDegreeRelationship(array $relationships, $resource, $includeData) + { + $relationships[self::REL_DEGREE] = [ + self::RELATIONSHIP_LINKS_SELF => $this->createLinkToResource($resource->abschluss), + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_DEGREE), + ], + ]; + + if ($includeData) { + $relationships[self::REL_DEGREE][self::RELATIONSHIP_DATA] = $resource->abschluss; + } + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/CourseOfStudyComponent.php b/lib/classes/JsonApi/Schemas/CourseOfStudyComponent.php new file mode 100644 index 0000000000000000000000000000000000000000..c351f121b20b4500630cbc21ba9ebf4d311a2ee8 --- /dev/null +++ b/lib/classes/JsonApi/Schemas/CourseOfStudyComponent.php @@ -0,0 +1,73 @@ +<?php +namespace JsonApi\Schemas; + +use Neomerx\JsonApi\Contracts\Schema\ContextInterface; +use Neomerx\JsonApi\Schema\Link; + +class CourseOfStudyComponent extends SchemaProvider +{ + const REL_SUBJECT = 'subject'; + const REL_VERSIONS = 'versions'; + const TYPE = 'courses-of-study-components'; + + public function getId($resource): ?string + { + return $resource->id; + } + + public function getAttributes($resource, ContextInterface $context): iterable + { + return [ + 'display-name' => (string) $resource->getDisplayName(), + 'title-supplement' => (string) $resource->zusatz, + 'cp' => (string) $resource->kp, + 'semesters' => (string) $resource->semester, + 'classname' => get_class($resource) + ]; + } + + public function getRelationships($resource, ContextInterface $context): iterable + { + $relationships = []; + + if ($resource->fach) { + $relationships[self::REL_SUBJECT] = $this->getSubject($resource, $this->shouldInclude($context, self::REL_SUBJECT)); + } + + $relationships = $this->addVersionsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_VERSIONS)); + + return $relationships; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + private function getSubject(\StudiengangTeil $component, $shouldInclude) + { + return $component->fach + ? [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($component->fach), + ], + self::RELATIONSHIP_DATA => $component->fach, + ] + : [ + self::RELATIONSHIP_DATA => null, + ]; + } + + private function addVersionsRelationship(array $relationships, $resource, $includeData) + { + $relationships[self::REL_VERSIONS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_VERSIONS), + ], + ]; + + if ($includeData) { + $relationships[self::REL_VERSIONS][self::RELATIONSHIP_DATA] = $resource->versionen; + } + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/Institute.php b/lib/classes/JsonApi/Schemas/Institute.php index 0084ca6baa0915f6798e340862a59693787932d1..61058f7457c5a449fd668aa23757ebaa38f0cc5c 100644 --- a/lib/classes/JsonApi/Schemas/Institute.php +++ b/lib/classes/JsonApi/Schemas/Institute.php @@ -16,6 +16,7 @@ class Institute extends SchemaProvider const REL_MEMBERSHIPS = 'memberships'; const REL_STATUS_GROUPS = 'status-groups'; const REL_SUB_INSTITUTES = 'sub-institutes'; + const REL_COURSES_OF_STUDY = 'courses-of-study'; /** * @param \Institute $institute @@ -95,6 +96,12 @@ class Institute extends SchemaProvider $this->shouldInclude($context, self::REL_SUB_INSTITUTES) ); + $relationships = $this->getCoursesOfStudyRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_COURSES_OF_STUDY) + ); + return $relationships; } @@ -156,6 +163,30 @@ class Institute extends SchemaProvider return array_merge($relationships, [self::REL_STATUS_GROUPS => $relation]); } + private function getCoursesOfStudyRelationship( + array $relationships, + $resource, + $includeData + ): array { + $relation = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_COURSES_OF_STUDY), + ], + ]; + + if ($includeData) { + $relation[self::RELATIONSHIP_DATA] = $resource->courses_of_study; + } else { + $relation[self::RELATIONSHIP_DATA] = $resource->courses_of_study->map(function (\Studiengang $cos): \Studiengang { + return \Studiengang::build(['id' => $cos->id]); + }); + } + + $relationships[self::REL_COURSES_OF_STUDY] = $relation; + + return $relationships; + } + public function hasResourceMeta($resource): bool { return true; @@ -168,6 +199,7 @@ class Institute extends SchemaProvider { return [ 'sub-institutes-count' => count($resource->sub_institutes), + 'courses-of-study-count' => count($resource->courses_of_study), ]; } } diff --git a/lib/classes/JsonApi/Schemas/Module.php b/lib/classes/JsonApi/Schemas/Module.php new file mode 100644 index 0000000000000000000000000000000000000000..55fe085992c96187d099c22bca9d3f37572968d8 --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Module.php @@ -0,0 +1,171 @@ +<?php +namespace JsonApi\Schemas; + +use Neomerx\JsonApi\Contracts\Schema\ContextInterface; +use Neomerx\JsonApi\Schema\Link; + +class Module extends SchemaProvider +{ + const REL_DEPARTMENTS = 'departments'; + const REL_RESPONSIBLE_DEPARTMENT = 'responsible-department'; + const REL_SOURCE_MODULE = 'source-module'; + const REL_VARIANT_MODULE = 'variant-module'; + const REL_START_SEMESTER = 'start-semester'; + const REL_END_SEMESTER = 'end-semester'; + const REL_MODULE_COMPONENTS = 'module-components'; + const REL_LANGUAGES = 'languages'; + + const TYPE = 'modules'; + + public function getId($resource): ?string + { + return $resource->id; + } + + public function getAttributes($resource, ContextInterface $context): iterable + { + return [ + 'display-name' => (string) $resource->getDisplayName(), + 'code' => (string) $resource->code, + 'date-of-decision' => $resource->beschlussdatum ? date('c', $resource->beschlussdatum) : '', + 'edition-number' => (string) $resource->fassung_nr, + 'edition-type' => \Config::get()->MVV_STGTEILVERSION['FASSUNG_TYP'][$resource->fassung_typ] ?? '', + 'version-number' => (string) $resource->version, + 'semester-duration' => (string) $resource->dauer, + 'capacity' => (string) $resource->kapazitaet, + 'cp' => $resource->kp, + 'workload-self' => (string) $resource->wl_selbst, + 'workload-exam' => (string) $resource->wl_pruef, + 'examination-period' => \Config::get()->MVV_MODUL['PRUEF_EBENE']['values'][$resource->pruef_ebene] ?? '', + 'grade-factor' => (string) $resource->faktor_note, + // 'module-responsible' => (string) $resource->verantwortlich, + 'foreign-key' => (string) $resource->flexnow_modul, + 'name' => (string) $resource->deskriptoren->bezeichnung, + 'responsible' => (string) $resource->deskriptoren->verantwortlich, + 'prerequisite' => (string) $resource->deskriptoren->voraussetzung, + 'objectives' => (string) $resource->deskriptoren->kompetenzziele, + 'content' => (string) $resource->deskriptoren->inhalte, + 'literature' => (string) $resource->deskriptoren->literatur, + 'links' => (string) $resource->deskriptoren->links, + 'comment' => (string) $resource->deskriptoren->kommentar, + 'cycle' => (string) $resource->deskriptoren->turnus, + 'comment-capacity' => (string) $resource->deskriptoren->kommentar_kapazitaet, + 'comment_sws' => (string) $resource->deskriptoren->kommentar_sws, + 'type' => get_class($resource), + 'status' => \Config::get()->MVV_MODUL['STATUS']['values'][$resource->stat], + ]; + } + + public function getRelationships($resource, ContextInterface $context): iterable + { + $relationships = []; + + if ($semester = $this->getStartSemester($resource)) { + $relationships[self::REL_START_SEMESTER] = $semester; + } + if ($semester = $this->getEndSemester($resource)) { + $relationships[self::REL_END_SEMESTER] = $semester; + } + if ($responsible_department = $this->getResponsibleDepartment($resource)) { + $relationships[self::REL_RESPONSIBLE_DEPARTMENT] = $responsible_department; + } + /* + if (!empty($resource->responsible_institute)) { + $relationships[self::REL_RESPONSIBLE_DEPARTMENT] = + [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($resource->responsible_institute->institute), + ], + self::RELATIONSHIP_DATA => $resource->responsible_institute, + ]; + } + */ +/* + $relationships = $this->addResponsibleDepartmentRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_RESPONSIBLE_DEPARTMENT) + ); +*/ + $relationships = $this->addDepartments( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_DEPARTMENTS) + ); + $relationships = $this->addModuleComponentsRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_MODULE_COMPONENTS) + ); + + return $relationships; + } + + private function getStartSemester(\Modul $modul) + { + if (!$semester = \Semester::find($modul->start)) { + return null; + } + + return [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($semester), + ], + self::RELATIONSHIP_DATA => $semester, + ]; + } + + private function getEndSemester(\Modul $modul) + { + $semester = \Semester::find($modul->end); + if (!$semester) { + return null; + } + + return [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($semester), + ], + self::RELATIONSHIP_DATA => $semester, + ]; + } + + private function getResponsibleDepartment(\Modul $modul) + { + $responsible_department = \Institute::build(['id' => $modul->responsible_institute->institut_id]); + if (!$responsible_department) { + return null; + } + + return [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($responsible_department), + ], + self::RELATIONSHIP_DATA => $responsible_department, + ]; + } + + private function addDepartments(array $relationships, $resource, $includeData) + { + $departments = $resource->assigned_institutes->orderBy('position')->map(function (\ModulInst $module_inst) { + return \Institute::build(['id' => $module_inst->institut_id]); + }); + + $relationships[self::REL_DEPARTMENTS][self::RELATIONSHIP_DATA] = $departments; + + return $relationships; + } + + private function addModuleComponentsRelationship(array $relationships, $resource, $includeData) + { + $relationships[self::REL_MODULE_COMPONENTS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_MODULE_COMPONENTS), + ], + ]; + + $relationships[self::REL_MODULE_COMPONENTS][self::RELATIONSHIP_DATA] = $resource->modulteile; + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/ModuleComponent.php b/lib/classes/JsonApi/Schemas/ModuleComponent.php new file mode 100644 index 0000000000000000000000000000000000000000..7ff24e9046de8de9c700b72da4bb1d4a68dd6b30 --- /dev/null +++ b/lib/classes/JsonApi/Schemas/ModuleComponent.php @@ -0,0 +1,70 @@ +<?php +namespace JsonApi\Schemas; + +use Neomerx\JsonApi\Contracts\Schema\ContextInterface; +use Neomerx\JsonApi\Schema\Link; + +class ModuleComponent extends SchemaProvider +{ + const REL_COURSES = 'courses'; + const TYPE = 'module-components'; + + public function getId($resource): ?string + { + return $resource->id; + } + + public function getAttributes($resource, ContextInterface $context): iterable + { + return [ + 'name' => (string) $resource->deskriptoren->bezeichnung, + 'position' => $resource->position, + 'foreign_key' => $resource->flexnow_modul, + 'number' => $resource->nummer, + 'number_label' => \Config::get()->MVV_MODULTEIL['NUM_BEZEICHNUNG']['values'][$resource->num_bezeichnung] ?? '', + 'teaching_method' => \Config::get()->MVV_MODULTEIL['LERNLEHRFORM']['values'][$resource->lernlehrform] ?? '', + 'semester' => $resource->semester, + 'number_of_participants' => $resource->kapazitaet, + 'cp' => $resource->kp, + 'sws' => $resource->sws, + 'workload_compulsory' => $resource->wl_praesenz, + 'workload_preparation' => $resource->wl_bereitung, + 'workload_self' => $resource->wl_selbst, + 'workload_exam' => $resource->wl_pruef, + 'share_of_grade' => $resource->anteil_note, + 'compensable' => $resource->ausgleichbar, + 'compulsory_attendance' => $resource->pflicht, + 'prerequisites' => $resource->deskriptoren->voraussetzung, + 'comment' => $resource->deskriptoren->kommentar, + 'comment_capacity' => $resource->deskriptoren->kommentar_kapazitaet, + 'comment_wl_compulsory' => $resource->deskriptoren->kommentar_wl_praesenz, + 'comment_wl_preparation' => $resource->deskriptoren->kommentar_wl_bereitung, + 'comment_wl_self' => $resource->deskriptoren->kommentar_wl_selbst, + 'comment_wl_exam' => $resource->deskriptoren->kommentar_wl_pruef, + 'exam_prerequisites' => $resource->deskriptoren->pruef_vorleistung, + 'exam_requirements' => $resource->deskriptoren->pruef_leistung, + 'comment_compulsory_attendance' => $resource->deskriptoren->kommentar_pflicht, + 'type' => get_class($resource) + ]; + } + + public function getRelationships($resource, ContextInterface $context): iterable + { + $relationships = []; + + $relationships = $this->addCoursesRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_COURSES)); + + return $relationships; + } + + private function addCoursesRelationship(array $relationships, \Modulteil $resource, $includeData) + { + $relationships[self::REL_COURSES] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_COURSES), + ], + ]; + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/ModuleInstitute.php b/lib/classes/JsonApi/Schemas/ModuleInstitute.php new file mode 100644 index 0000000000000000000000000000000000000000..d5b8e722c771dbeb5341bed961b60bbbf32343ef --- /dev/null +++ b/lib/classes/JsonApi/Schemas/ModuleInstitute.php @@ -0,0 +1,67 @@ +<?php +namespace JsonApi\Schemas; + +use Neomerx\JsonApi\Contracts\Schema\ContextInterface; +use Neomerx\JsonApi\Schema\Link; + +class ModuleInstitute extends SchemaProvider +{ + const REL_MODULE = 'modules'; + const REL_INSTITUTE = 'institutes'; + const TYPE = 'module-institutes'; + + public function getId($resource): ?string + { + return $resource->id; + } + + public function getAttributes($resource, ContextInterface $context): iterable + { + return [ + 'name' => (string) $resource->name, + 'short-name' => (string) $resource->name_kurz, + 'description' => (string) $resource->beschreibung, + 'type' => get_class($resource) + ]; + } + + public function getRelationships($resource, ContextInterface $context): iterable + { + $relationships = []; + + $relationships = $this->addModuleRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_MODULE)); + $relationships = $this->addInstituteRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_INSTITUTE)); + + return $relationships; + } + + private function addModuleRelationship(array $relationships, $resource, $includeData) + { + $relationships[self::REL_MODULE] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_MODULE), + ], + ]; + + if ($includeData) { + $relationships[self::REL_MODULE][self::RELATIONSHIP_DATA] = $resource->module; + } + + return $relationships; + } + + private function addInstituteRelationship(array $relationships, $resource, $includeData) + { + $relationships[self::REL_INSTITUTE] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_INSTITUTE), + ], + ]; + + if ($includeData) { + $relationships[self::REL_INSTITUTE][self::RELATIONSHIP_DATA] = $resource->institute; + } + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/Subject.php b/lib/classes/JsonApi/Schemas/Subject.php new file mode 100644 index 0000000000000000000000000000000000000000..d8fdd61031e78dad1a7f932029a6e1bf8d34e57e --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Subject.php @@ -0,0 +1,61 @@ +<?php +namespace JsonApi\Schemas; + +use Fach; +use Neomerx\JsonApi\Contracts\Schema\ContextInterface; +use Neomerx\JsonApi\Schema\Link; + +class Subject extends SchemaProvider +{ + const REL_DEPARTMENTS = 'departments'; + const TYPE = 'subjects'; + + /** + * @param Fach $resource + */ + public function getId($resource): ?string + { + return $resource->id; + } + + /** + * @param Fach $resource + */ + public function getAttributes($resource, ContextInterface $context): iterable + { + return [ + 'name' => (string) $resource->name, + 'short-name' => (string) $resource->name_kurz, + 'description' => (string) $resource->beschreibung, + 'type' => get_class($resource) + ]; + } + + public function getRelationships($resource, ContextInterface $context): iterable + { + $relationships = []; + + $relationships = $this->addDepartmentsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_DEPARTMENTS)); + + return $relationships; + } + + private function addDepartmentsRelationship(array $relationships, $resource, $includeData) + { + $relationships[self::REL_DEPARTMENTS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_DEPARTMENTS), + ], + ]; + + if ($includeData) { + // use institute schema + if (!empty($resource->departments)) { + $institutes = \Institute::findMany($resource->departments->pluck('id')); + $relationships[self::REL_DEPARTMENTS][self::RELATIONSHIP_DATA] = $institutes; + } + } + + return $relationships; + } +} diff --git a/lib/models/Institute.php b/lib/models/Institute.php index 5ecdcefc6c2de578549a00e1f9734774064adba8..e87a40879bca19b05c7742bafbfe439325bf8f70 100644 --- a/lib/models/Institute.php +++ b/lib/models/Institute.php @@ -129,6 +129,11 @@ class Institute extends SimpleORMap implements Range 'order_by' => 'ORDER BY position', 'on_delete' => 'delete', ]; + $config['has_many']['courses_of_study'] = [ + 'class_name' => Studiengang::class, + 'assoc_foreign_key' => 'institut_id', + 'order_by' => 'ORDER BY name ASC', + ]; $config['additional_fields']['all_status_groups']['get'] = function ($institute) { return Statusgruppen::findAllByRangeId($institute->id, true); }; diff --git a/lib/models/Modulteil.php b/lib/models/Modulteil.php index 397fc18de55063d93e3119d6088cac72e4bcefea..3b8f146de6fc2138bd2a38754e8f480976f4e080 100644 --- a/lib/models/Modulteil.php +++ b/lib/models/Modulteil.php @@ -379,11 +379,11 @@ class Modulteil extends ModuleManagementModelTreeItem /** * Retrieves all courses this Modulteil is assigned by its LV-Gruppen. * Filtered by a given semester considering the global visibility or the - * the visibility for a given user. + * visibility for a given user. * * @param string $semester_id The id of a semester. * @param mixed $only_visible Boolean true retrieves only visible courses, false - * retrieves all courses. If $only_visible is an user id it depends on the users + * retrieves all courses. If $only_visible is a user id it depends on the users * status which courses will be retrieved. * @return array An array of course data. */ diff --git a/lib/models/StgteilVersion.php b/lib/models/StgteilVersion.php index 5add60b5ca9a12eabb4157a6b662dddc94180332..113875c260533e83af0367942a571a5efd6ed15b 100644 --- a/lib/models/StgteilVersion.php +++ b/lib/models/StgteilVersion.php @@ -67,6 +67,14 @@ class StgteilVersion extends ModuleManagementModelTreeItem 'on_delete' => 'delete', 'on_store' => 'store' ]; + $config['belongs_to']['start_semester'] = [ + 'class_name' => Semester::class, + 'foreign_key' => 'start_sem', + ]; + $config['belongs_to']['end_semester'] = [ + 'class_name' => Semester::class, + 'foreign_key' => 'end_sem', + ]; $config['additional_fields']['count_abschnitte']['get'] = function($version) { return $version->count_abschnitte; }; diff --git a/lib/models/Studiengang.php b/lib/models/Studiengang.php index ee89abde0e1cad9f884049569f61ada08c92d87c..3c8a10f6e5e96b00180dee9427f7601297722bbd 100644 --- a/lib/models/Studiengang.php +++ b/lib/models/Studiengang.php @@ -171,7 +171,7 @@ class Studiengang extends ModuleManagementModelTreeItem $config['i18n_fields']['name_kurz'] = true; $config['i18n_fields']['beschreibung'] = true; - $config['default_values']['enroll'] = $GLOBALS['MVV_STUDIENGANG']['ENROLL']['default']; + $config['default_values']['enroll'] = Config::get()->MVV_STUDIENGANG['ENROLL']['default']; parent::configure($config); } @@ -648,7 +648,7 @@ class Studiengang extends ModuleManagementModelTreeItem $result = []; foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $status) { $result[$status['stat']] = [ - 'name' => $GLOBALS['MVV_STUDIENGANG']['STATUS']['values'][$status['stat']]['name'] ?? _('Undefinierter Status'), + 'name' => Config::get()->MVV_STUDIENGANG['STATUS']['values'][$status['stat']]['name'] ?? _('Undefinierter Status'), 'count_objects' => $status['count_objects'] ]; } @@ -845,7 +845,7 @@ class Studiengang extends ModuleManagementModelTreeItem { $assigned_languages = array(); $languages_flipped = array_flip($languages); - foreach ($GLOBALS['MVV_STUDIENGANG']['SPRACHE']['values'] as $key => $language) { + foreach (Config::get()->MVV_STUDIENGANG['SPRACHE']['values'] as $key => $language) { if (isset($languages_flipped[$key])) { $language = StudycourseLanguage::find([$this->id, $key]); if (!$language) { diff --git a/tests/jsonapi/_bootstrap.php b/tests/jsonapi/_bootstrap.php index 01538aae7d794295173f553f3ca8615776d544a8..2b30aa91ce6dbeec7057a141dc0c93476bfb31c5 100644 --- a/tests/jsonapi/_bootstrap.php +++ b/tests/jsonapi/_bootstrap.php @@ -28,6 +28,7 @@ $CACHING_ENABLE = false; date_default_timezone_set('Europe/Berlin'); require 'config.inc.php'; +require 'mvv_config.php'; require_once __DIR__ . '/../../lib/bootstrap-autoload.php';