Skip to content
Snippets Groups Projects
Select Git revision
  • 3ee06e3c43393e4a8051fb42c2c41a170eede5fe
  • master default protected
2 results

HISinOneSync.php

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    HISinOneSync.php 22.87 KiB
    <?php
    
    require_once 'AuthenticatedSoapClient.php';
    
    class HISinOneSync
    {
        const UNIT_ID_DF = 'add9acb4890daadbcd4b7897e550cb17';
    
        private $unitService;
        private $accountService;
        private $keyvalueService;
    
        private $logs = '';
    
        public function sync_courses($lvgs, $semester, $update, $update_studip)
        {
            $this->unitService = new AuthenticatedSoapClient('UnitService');
            $this->accountService = new AuthenticatedSoapClient('AccountService');
            $this->keyvalueService = new AuthenticatedSoapClient('KeyvalueService');
    
            $personService = new AuthenticatedSoapClient('PersonService');
            $planelementService = new AuthenticatedSoapClient('PlanelementService');
            $courseInterfaceService = new AuthenticatedSoapClient('CourseInterfaceService');
            $curriculumDesignerService = new AuthenticatedSoapClient('CurriculumDesignerService');
            $curriculumDesignerRelationService = new AuthenticatedSoapClient('CurriculumDesignerRelationService');
    
            $his_gender = [1 => 'M', 2 => 'W', 3 => 'D'];
            $term_info = $this->get_term_info(date('Y-m-d', $semester->beginn));
            $unit_type = $this->get_unit_types();
    
            $courses = $this->match_courses($lvgs, $unit_type, $semester);
            $current = [];
            $updated = [];
            $deleted = [];
    
            foreach ($lvgs as $lvg) {
                foreach ($lvg->courses as $course) {
                    $unit_id = $courses[$course->id];
    
                    if ($unit_id && $this->course_in_semester($course, $semester)) {
                        $current[$unit_id] = true;
                    }
                }
    
                // drop relation to reduce memory usage
                $lvg->resetRelation('courses');
            }
    
            foreach ($lvgs as $lvg) {
                $this->log('lvg: %d, "%s"', $lvg->id, $lvg->name);
    
                try {
                    $lvg_unit = $curriculumDesignerService->readUnit(['unitId' => $lvg->id])->unitAttributes;
                    $children = $this->unit_get_children($lvg->id);
                    $orgunits = $this->unit_get_orgunits($lvg->id);
                    $orgunit_lid = $orgunits[0];
                } catch (SoapFault $e) {
                    $this->log('  error: SOAP fault for unit %s: %s (skipped)', $lvg->id, $e->getMessage());
                    continue;
                }
    
                if ($lvg_unit->kElementtype !== 'VG') {
                    $this->log('  error: unit %s has invalid type "%s" (skipped)', $lvg->id, $lvg_unit->kElementtype);
                    continue;
                }
    
                foreach ($lvg->courses as $course) {
                    if (!$this->course_in_semester($course, $semester)) {
                        continue;
                    }
    
                    $course_name = is_object($course->name) ? $course->name->localized('de_DE') : $course->name;
                    $course_name = trim(preg_replace('/ \[.*?\]|\[.*?\] ?/', '', $course_name));
    
                    $course_name_en = is_object($course->name) ? $course->name->localized('en_GB') : $course->name;
                    $course_name_en = trim(preg_replace('/ \[.*?\]|\[.*?\] ?/', '', $course_name_en));
    
                    $unit_id = $courses[$course->id];
    
                    if ($unit_id) {
                        $unit = $this->readUnit($unit_id);
    
                        if ($unit->elementnr != $course->veranstaltungsnummer || $unit->defaulttext != $course_name) {
                            $this->log('  updating unit %d, "%s: %s"', $unit_id, $unit->elementnr, $unit->defaulttext);
                            $unitAttributes = [
                                'elementnr' => $course->veranstaltungsnummer,
                                'defaulttext' => $course_name,
                                'longtext' => $course_name
                            ];
    
                            if ($update) {
                                $curriculumDesignerService->updateUnit(['unitId' => $unit_id, 'unitAttributes' => $unitAttributes]);
                                $unit = $this->readUnit($unit_id, $unitAttributes);
                            }
                        }
                    } else {
                        $this->log('  [new unit]');
    
                        if ($update) {
                            $unitAttributes = [
                                'kElementtype' => 'V',
                                'defaulttext' => $course_name,
                                'longtext' => $course_name,
                                'elementnr' => $course->veranstaltungsnummer,
                                'defaultlanguageValue' => 'de',
                                'validFrom' => '1900-01-01',
                                'validTo' => '2100-12-31',
                                'gradingType' => 'UB',
                                'extId' => '',
                                'orgunitLid' => $orgunit_lid
                            ];
                            $eventAttributes = [
                                'eventtype' => $lvg_unit->eventAttributes->eventtype,
                                'frequencyOfOffervalue' => $lvg_unit->eventAttributes->frequencyOfOffervalue
                            ];
    
                            $result = $curriculumDesignerService->createNewUnit(['unitAttributes' => $unitAttributes, 'eventAttributes' => $eventAttributes])->result;
    
                            if (is_numeric($result)) {
                                $unit_id = $result;
                                $unit = $this->readUnit($unit_id);
                                $courses[$course->id] = $unit_id;
                            } else {
                                $this->log('  error: %s', $result);
                            }
                        }
                    }
    
                    $this->log('  unit: %d, "%s: %s"', $unit_id, $course->veranstaltungsnummer, $course_name);
    
                    if ($unit_id) {
                        if ($update && $update_studip) {
                            $entry = DatafieldEntryModel::findByModel($course, self::UNIT_ID_DF)[0];
    
                            if ($entry) {
                                $entry->content = $unit_id;
                                $entry->store();
                            }
                        }
    
                        if (!isset($children[$unit_id])) {
                            $this->log('  adding relation %d -> %d ("%s: %s")', $lvg->id, $unit_id, $course->veranstaltungsnummer, $course_name);
    
                            if ($update) {
                                $curriculumDesignerService->connectParentAndChildUnit(['parentId' => $lvg->id, 'childId' => $unit_id]);
                                $children[$unit_id] = $unit;
                            }
                        }
                    }
    
                    if ($unit_id && !$updated[$unit_id]) {
                        if ($update) {
                            $unitAttributes = [
                                'defaulttext' => $course_name_en ?: $course_name,
                                'longtext' => $course_name_en ?: $course_name
                            ];
    
                            $curriculumDesignerService->translateUnit(['unitId' => $unit_id, 'translationLanguage' => 'en', 'unitAttributes' => $unitAttributes]);
                        }
    
                        $orgunits = $this->unit_get_orgunits($unit_id);
                        $parents = $this->unitService->readUnitWithParents(['unitId' => $unit_id])->unit;
                        $lvg_orgunits = [];
    
                        foreach ($parents->parents->parentRelation as $parent) {
                            $lvg_orgunits = array_unique(array_merge($lvg_orgunits, $this->unit_get_orgunits($parent->parentId)));
                        }
    
                        foreach (array_diff($lvg_orgunits, $orgunits) as $orgunit) {
                            $this->log('    adding orgunit relation with lid %d', $orgunit);
    
                            if ($update) {
                                $curriculumDesignerRelationService->createUnitOrgunitRelation(['unitId' => $unit_id, 'orgunitLid' => $orgunit, 'unitOrgunitRelation' => 'D']);
                            }
                        }
    
                        foreach (array_diff($orgunits, $lvg_orgunits) as $orgunit) {
                            $this->log('    removing orgunit relation with lid %d', $orgunit);
    
                            if ($update) {
                                $curriculumDesignerRelationService->deleteUnitOrgunitRelation(['unitId' => $unit_id, 'orgunitLid' => $orgunit, 'unitOrgunitRelation' => 'D']);
                            }
                        }
    
                        $elements = $courseInterfaceService->readPlanelementsForUnit(['unitId' => $unit_id, 'termyear' => $term_info[0], 'termTypeId' => $term_info[1]])->planelements;
    
                        if ($elements->planelement) {
                            $element = $elements->planelement[0];
                            $element_id = $element->id;
    
                            if ($element->defaulttext != $course_name) {
                                $this->log('    updating element %d, "%s"', $element_id, $element->defaulttext);
    
                                if ($update) {
                                    $planelement = [
                                        'planelementId' => $element_id,
                                        'defaulttext' => $course_name,
                                        'longtext' => $course_name
                                    ];
    
                                    $planelementService->updatePlanelementOfEvent(['planelement' => $planelement]);
                                }
                            }
                        } else {
                            $this->log('    [new element]');
                            $element_id = 0;
    
                            if ($update) {
                                $planelement = [
                                    'unitId' => $unit_id,
                                    'termType' => $term_info[2],
                                    'termYear' => $term_info[0],
                                    'defaulttext' => $course_name,
                                    'longtext' => $course_name
                                ];
    
                                $element_id = $planelementService->createNewPlanelementOfEvent($planelement)->planelementId;
                            }
                        }
    
                        if ($element_id) {
                            if ($update) {
                                $planelement = [
                                    'planelementId' => $element_id,
                                    'defaulttext' => $course_name_en ?: $course_name,
                                    'longtext' => $course_name_en ?: $course_name,
                                    'language' => 'en'
                                ];
    
                                $planelementService->updatePlanelementOfEvent(['planelement' => $planelement]);
                            }
    
                            $persons = $courseInterfaceService->getPersonResponsibleForPlanelement(['planelementId' => $element_id])->personPlanelements;
                            $persons_matched = [];
                            $person_ids = [];
    
                            foreach ($course->getMembersWithStatus('dozent') as $lecturer) {
                                if (stripos($lecturer->label, 'nicht prüfungsberechtigt') !== false) {
                                    continue;
                                }
    
                                $username = $lecturer->user->username;
                                $person_id = $this->find_account($username);
                                $found = false;
    
                                if (!$person_id) {
                                    // createPerson201912() fails if gender is not set
                                    if ($lecturer->user->auth_plugin === 'ldapos' && $lecturer->user->geschlecht) {
                                        $person = [
                                            'firstname' => $lecturer->user->vorname,
                                            'surname' => $lecturer->user->nachname,
                                            'gender' => $his_gender[$lecturer->user->geschlecht]
                                        ];
                                        $orgrole = [
                                            'orgunitLid' => $orgunit_lid,
                                            'role' => 'pruefer'
                                        ];
    
                                        $this->log('    [new account for username "%s", orgunit: %d]', $username, $orgunit_lid);
    
                                        if ($update) {
                                            $person_id = $personService->createPerson201912(['person' => $person, 'orgrole' => $orgrole])->id;
                                            $password = base64_encode(random_bytes(12));
                                            $this->accountService->createNewAccountForPerson(['personId' => $person_id, 'username' => $username, 'password' => $password]);
                                            $this->find_account($username, $person_id);
                                        }
                                    } else {
                                        $this->log('    warning: username "%s" not found', $username);
                                        continue;
                                    }
                                }
    
                                foreach ((array) $persons->personPlanelement as $person) {
                                    if ($person->abstractPersonId == $person_id) {
                                        $persons_matched[$person_id] = true;
                                        $found = true;
                                    }
                                }
    
                                if (!$found) {
                                    $this->log('    adding person %d ("%s") to element %d', $person_id, $username, $element_id);
    
                                    if ($update) {
                                        $person_ids[] = $person_id;
                                        $planelementService->assignResponsibleInstructor(['personId' => $person_id, 'planelementId' => $element_id]);
                                    }
                                }
                            }
    
                            foreach ((array) $persons->personPlanelement as $person) {
                                if ($persons_matched[$person->abstractPersonId]) {
                                    $person_ids[] = $person->abstractPersonId;
                                } else {
                                    $this->log('    removing person %d from element %d', $person->abstractPersonId, $element_id);
    
                                    if ($update) {
                                        $planelementService->revokeResponsibleInstructor(['personId' => $person->abstractPersonId, 'planelementId' => $element_id]);
                                    }
                                }
                            }
    
                            $this->log('    element: %d, "%s" (%s)', $element_id, $course_name, implode(', ', $person_ids));
                        }
    
                        $updated[$unit_id] = true;
                    }
                }
    
                foreach ($children as $child) {
                    if ($child->elementtypeId === $unit_type['V']) {
                        $delete = true;
    
                        foreach ($lvg->courses as $course) {
                            $entry = DatafieldEntryModel::findByModel($course, self::UNIT_ID_DF)[0];
                            $unit_id = $entry->content;
    
                            if ($child->id == $unit_id) {
                                if (!$current[$unit_id] || $this->course_is_recent($course, $semester)) {
                                    $delete = false;
                                }
                            }
                        }
    
                        if ($delete) {
                            $this->log('  removing relation %d -> %d ("%s: %s")', $lvg->id, $child->id, $child->elementnr, $child->defaulttext);
    
                            if ($update) {
                                $curriculumDesignerService->deleteUnitrelation(['parentUnitId' => $lvg->id, 'childUnitId' => $child->id]);
                                unset($children[$child->id]);
                            }
                        }
    
                        /*
                        if (array_search($child->id, $courses) === false && !$deleted[$child->id]) {
                            $elements = $courseInterfaceService->readPlanelementsForUnit(['unitId' => $child->id, 'termyear' => $term_info[0], 'termTypeId' => $term_info[1]])->planelements;
    
                            if ($elements->planelement) {
                                $element = $elements->planelement[0];
    
                                $this->log('  unit: %d, "%s: %s"', $child->id, $child->elementnr, $child->defaulttext);
                                $this->log('    deleting element %d, "%s"', $element->id, $element->defaulttext);
    
                                if ($update) {
                                    $planelementService->deletePlanelementOfEvent(['id' => $element->id]);
                                }
                            }
    
                            $deleted[$child->id] = true;
                        }
                        */
                    }
                }
    
                // drop relation to reduce memory usage
                $lvg->resetRelation('courses');
            }
        }
    
        private function match_courses($lvgs, $unit_type, $semester)
        {
            $units = [];
            $matched = [];
            $priority = [];
            $courses = [];
    
            foreach ($lvgs as $lvg) {
                try {
                    $children = $this->unit_get_children($lvg->id);
                } catch (SoapFault $e) {
                    // ignore this group
                    continue;
                }
    
                foreach ($children as $child) {
                    if ($child->elementtypeId === $unit_type['V']) {
                        $units[$child->id] = $child;
                    }
                }
            }
    
            foreach ($lvgs as $lvg) {
                foreach ($lvg->courses as $course) {
                    if (!$this->course_in_semester($course, $semester)) {
                        continue;
                    }
    
                    $course_name = is_object($course->name) ? $course->name->localized('de_DE') : $course->name;
                    $course_name = trim(preg_replace('/ \[.*?\]|\[.*?\] ?/', '', $course_name));
    
                    $entry = DatafieldEntryModel::findByModel($course, self::UNIT_ID_DF)[0];
                    $unit_id = $entry->content;
    
                    if ($unit_id) {
                        $matched[$unit_id] = $course->id;
                        $priority[$unit_id] = 0;
    
                        if (isset($units[$unit_id])) {
                            $priority[$unit_id] = 4;
                            continue;
                        }
                    }
    
                    foreach ($units as $unit) {
                        $prio = 0;
    
                        if ($unit->elementnr === $course->veranstaltungsnummer) {
                            if (strcasecmp(trim($unit->defaulttext), $course_name) === 0) {
                                $prio = 3;
                            } else if (stripos($course_name, $unit->defaulttext) !== false) {
                                $prio = 1;
                            } else if (stripos($unit->defaulttext, $course_name) !== false) {
                                $prio = 1;
                            }
                        } else if (strcasecmp(trim($unit->defaulttext), $course_name) === 0) {
                            $prio = 2;
                        }
    
                        if ($prio && $priority[$unit->id] < $prio) {
                            $matched[$unit->id] = $course->id;
                            $priority[$unit->id] = $prio;
                        }
                    }
                }
    
                $lvg->resetRelation('courses');
            }
    
            foreach ($matched as $unit_id => $course_id) {
                if (!$courses[$course_id] || $priority[$courses[$course_id]] < $priority[$unit_id]) {
                    $courses[$course_id] = $unit_id;
                }
            }
    
            return $courses;
        }
    
        private function get_term_info($date)
        {
            list($year, $month, $day) = explode('-', $date);
    
            if ($month < 4) {
                return [$year - 1, 2, 'WiSe'];
            } else if ($month < 10) {
                return [$year, 1, 'SoSe'];
            } else {
                return [$year, 2, 'WiSe'];
            }
        }
    
        private function course_in_semester($course, $semester)
        {
            return $course->visible == 1 &&
                   $course->getSemType()['class'] == 1 &&
                   $course->veranstaltungsnummer !== '' &&
                   $course->start_time <= $semester->beginn &&
                   ($semester->beginn <= $course->start_time + $course->duration_time || $course->duration_time == -1);
        }
    
        private function course_is_recent($course, $semester)
        {
            $semester = Semester::findByTimestamp($semester->beginn - 1);
    
            return $course->visible == 1 &&
                   $course->getSemType()['class'] == 1 &&
                   $course->veranstaltungsnummer !== '' &&
                   ($semester->beginn <= $course->start_time + $course->duration_time || $course->duration_time == -1);
        }
    
        private function get_unit_types()
        {
            $values = $this->keyvalueService->getAll(['valueClass' => 'ElementtypeValue', 'lang' => 'de'])->values;
            $result = [];
    
            foreach ($values->value as $value) {
                $result[$value->uniquename] = $value->id;
            }
    
            return $result;
        }
    
        private function readUnit($unit_id, $update = [])
        {
            static $cache = [];
    
            if (isset($cache[$unit_id])) {
                foreach ($update as $key => $value) {
                    $cache[$unit_id]->$key = $value;
                }
    
                return $cache[$unit_id];
            }
    
            $unit = $this->unitService->readUnit(['unitId' => $unit_id])->unit;
            return $cache[$unit_id] = $unit;
        }
    
        private function unit_get_orgunits($unit_id)
        {
            static $cache = [];
            $result = [];
    
            if (isset($cache[$unit_id])) {
                return $cache[$unit_id];
            }
    
            $orgunits = $this->unitService->findOrgunitsByUnit(['unitId' => $unit_id])->unitOrgunitList;
    
            foreach ((array) $orgunits->unitOrgunitInfo as $info) {
                $result[] = $info->orgunitLid;
            }
    
            return $cache[$unit_id] = $result;
        }
    
        private function unit_get_children($unit_id)
        {
            static $cache = [];
            $result = [];
    
            if (isset($cache[$unit_id])) {
                return $cache[$unit_id];
            }
    
            $unit = $this->unitService->readUnitWithChildren(['unitId' => $unit_id])->unit;
    
            foreach ((array) $unit->children->childRelation as $child) {
                $result[$child->childId] = $this->readUnit($child->childId);
            }
    
            return $cache[$unit_id] = $result;
        }
    
        private function find_account($username, $person_id = null)
        {
            static $cache = [];
            $result = false;
    
            if (isset($person_id)) {
                $cache[$username] = $person_id;
            }
    
            if (isset($cache[$username])) {
                return $cache[$username];
            }
    
            $account = $this->accountService->findAccount(['username' => $username])->findAccountResult;
    
            foreach ((array) $account->accountInfo as $account_info) {
                if ($account_info->username === $username) {
                    $result = $account_info->personId;
                }
            }
    
            return $cache[$username] = $result;
        }
    
        private function log($format, ...$args)
        {
            $this->logs .= vsprintf($format, $args) . "\n";
        }
    
        public function get_logs()
        {
            return $this->logs;
        }
    }