Skip to content
Snippets Groups Projects
SemClass.class.php 23 KiB
Newer Older
<?php

/*
 *  Copyright (c) 2012  Rasmus Fuhse <fuhse@data-quest.de>
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of
 *  the License, or (at your option) any later version.
 */

/**
 * Class to define and manage attributes of seminar classes (or seminar categories).
 * Usually all sem-classes are stored in a global variable $SEM_CLASS which is
 * an array of SemClass objects.
 *
 * SemClass::getClasses() gets you all seminar classes in an array.
 *
 * You can access the attributes of a sem-class like an associative
 * array with $sem_class['default_read_level']. The uinderlying data is stored
 * in the database in the table sem_classes.
 *
 * If you want to have a name of a sem-class like "Lehre", please use
 * $sem_class['name'] and you will get a fully localized name and not the pure
 * database entry.
 *
 * This class manages also which modules are contained in which course-slots,
 * like "what module is used as a forum in my seminars". In the database stored
 * is the name of the module like "CoreForum" or a classname of a plugin or null
 * if the forum is completely disabled by root for this sem-class. Core-modules
 * can only be used within a standard slot. Plugins may also be used as optional
 * modules not contained in a slot.
 *
 * In the field 'modules' in the database is for each modules stored in a json-string
 * if the module is activatable by the teacher or not and if it is activated as
 * a default. Please use the methods SemClass::isSlotModule, SemClass::getSlotModule,
 * SemClass::isModuleAllowed, SemClass::isModuleMandatory, SemClass::isSlotMandatory
 * or even more simple SemClass::getNavigationForSlot (see documentation there).
 */
class SemClass implements ArrayAccess
{
    protected $data = [];

    static protected $studygroup_forbidden_modules = [
        'CoreAdmin',
        'CoreParticipants',
        'CoreSchedule'
    static protected $sem_classes = null;

    static public function getDefaultSemClass() {
        $data = [
            'name' => "Fehlerhafte Seminarklasse!",
            'modules' => '{"CoreOverview":{"activated":1,"sticky":1},"CoreAdmin":{"activated":1,"sticky":1}}',
            'visible' => 1,
            'is_group' => false
        ];
        return new SemClass($data);
    }

    /**
     * Generates a dummy SemClass for institutes of this type (as defined in config.inc.php).
     * @param integer $type   institute type
     * @return SemClass
     */
    static public function getDefaultInstituteClass($type)
    {
        global $INST_MODULES;

        // fall back to 'default' if modules are not defined
        $type = isset($INST_MODULES[$type]) ? $type : 'default';

        $data = [
            'name'                => 'Generierte Standardinstitutsklasse',
            'visible'             => 1,
            'overview'            => 'CoreOverview', // always available
            'admin'               => 'CoreAdmin'     // always available
        ];
        $slots = [
            'forum'               => 'CoreForum',
            'documents'           => 'CoreDocuments',
            'scm'                 => 'CoreScm',
            'wiki'                => 'CoreWiki',
            'calendar'            => 'CoreCalendar',
            'elearning_interface' => 'CoreElearningInterface',
            'personal'            => 'CorePersonal'
        ];
        $modules = [
            'CoreOverview'        => ['activated' => 1, 'sticky' => 1],
            'CoreAdmin'           => ['activated' => 1, 'sticky' => 1]
        ];

        foreach ($slots as $slot => $module) {
            $data[$slot] = $module;
Moritz Strohm's avatar
Moritz Strohm committed
            $modules[$module] = ['activated' => (int) ($INST_MODULES[$type][$slot] ?? 0), 'sticky' => 0];
        }
        $data['modules'] = json_encode($modules);

        return new SemClass($data);
    }

    /**
     * Constructor can be set with integer of sem_class_id or an array of
     * the old $SEM_CLASS style.
     * @param integer | array $data
     */
    public function __construct($data)
    {
        $db = DBManager::get();
        if (is_int($data)) {
            $statement = $db->prepare("SELECT * FROM sem_classes WHERE id = :id ");
            $statement->execute(['id' => $data]);
            $this->data = $statement->fetch(PDO::FETCH_ASSOC);
        } else {
            $this->data = $data;
        }
        if (!empty($this->data['modules'])) {
            $this->data['modules'] = self::object2array(json_decode($this->data['modules']));
        } else {
            $this->data['modules'] = [];
        }
        if (!empty($this->data['studygroup_mode'])) {
            if (!isset($this->data['modules']['CoreStudygroupAdmin'])) {
                $this->data['modules']['CoreStudygroupAdmin'] = ['activated' => 1, 'sticky' => 1];
            }
        } else {
            if (!isset($this->data['modules']['CoreAdmin'])) {
                $this->data['modules']['CoreAdmin'] = ['activated' => 1, 'sticky' => 1];
            }
        }
        foreach (array_keys($this->data['modules']) as $modulename) {
            if ($this->isModuleForbidden($modulename)) {
                unset($this->data['modules'][$modulename]);
            }
        }
    }


    /**
     * @param string $module
     * @return false|int
     */
    public function activateModuleInCourses($module)
    {
        $plugin = PluginManager::getInstance()->getPlugin($module);
        if ($plugin) {
            return Course::findEachBySQL(function ($course) use ($plugin) {
                return PluginManager::getInstance()->setPluginActivated($plugin->getPluginId(), $course->id, true);
            },
                "seminare.status IN (?)",
                [array_keys($this->getSemTypes())]);
        } else {
            return false;
        }
    }

    /**
     * @param string $module
     * @return false|int
     */
    public function deActivateModuleInCourses($module)
    {
        $plugin = PluginManager::getInstance()->getPlugin($module);
        if ($plugin) {
            return Course::findEachBySQL(function ($course) use ($plugin) {
                return PluginManager::getInstance()->setPluginActivated($plugin->getPluginId(), $course->id, false);
            },
                "seminare.status IN (?)",
                [array_keys($this->getSemTypes())]);
        } else {
            return false;
        }

    }

    /**
     * Returns the number of seminars of this sem_class in Stud.IP
     * @return integer
     */
    public function countSeminars()
    {
        $db = DBManager::get();
        $sum = 0;
        foreach ($GLOBALS['SEM_TYPE'] as $sem_type) {
            if ($sem_type['class'] == $this->data['id']) {
                $sum += $sem_type->countSeminars();
            }
        }
        return $sum;
    }


    /**
     * @param string $modulename
     * @return bool
     */
    public function isModuleForbidden($modulename)
    {
Moritz Strohm's avatar
Moritz Strohm committed
        if (!empty($this->data['studygroup_mode'])) {
            return in_array($modulename, self::$studygroup_forbidden_modules);
        } else {
            return strpos($modulename, 'Studygroup') !== false;
        }
    }

    /**
     * Returns the metadata of a module regarding this sem_class object.
     * @param string $modulename
     * @return array('sticky' => (bool), 'activated' => (bool))
     */
    public function getModuleMetadata($modulename)
    {
        return $this->data['modules'][$modulename];
    }

    /**
     * Sets the metadata for each module at once.
     * @param array $module_array: array($module_name => array('sticky' => (bool), 'activated' => (bool)), ...)
     */
    public function setModules($module_array)
    {
        $this->data['modules'] = $module_array;
    }

    /**
     * Returns all metadata of the modules at once.
     * @return array: array($module_name => array('sticky' => (bool), 'activated' => (bool)), ...)
     */
    public function getModules()
    {
        return $this->data['modules'];
    }

    /**
     * @return StudipModule[]
     */
    public function getModuleObjects()
    {
        $result = [];
        foreach (array_keys($this->getModules()) as $module) {
            $plugin = PluginManager::getInstance()->getPlugin($module);
            if ($plugin) {
                $result[$plugin->getPluginId()] = $plugin;
            }
        }
        return $result;
    }

    /**
     * @return string[]
     */
    public function getActivatedModules()
    {
        return array_keys(array_filter($this->data['modules'], function ($meta) {
            return $meta['activated'];
        }));
    }

    public function getActivatedModuleObjects()
    {
        $result = [];
        foreach ($this->getActivatedModules() as $module) {
            $plugin = PluginManager::getInstance()->getPlugin($module);
            if ($plugin) {
                $result[$plugin->getPluginId()] = $plugin;
            }
        }
        return $result;
    }

    public function getAdminModuleObject()
    {
André Noack's avatar
André Noack committed
        if ($this->data['studygroup_mode']) {
            $module = 'CoreStudygroupAdmin';
        } else {
            $module = 'CoreAdmin';
        }
        return PluginManager::getInstance()->getPlugin($module);
    }

    /**
     * Returns true if a module is activated on default for this sem_class.
     * @param string $modulename
     * @return boolean
     */
    public function isModuleActivated($modulename)
    {
        return isset($this->data['modules'][$modulename])
            && $this->data['modules'][$modulename]['activated'];
    }

    /**
     * Returns if a module is allowed to be displayed for this sem_class.
     * @param string $modulename
     * @return boolean
     */
    public function isModuleAllowed($modulename)
    {
        return !$this->isModuleForbidden($modulename)
            && (empty($this->data['modules'][$modulename])
            || !$this->data['modules'][$modulename]['sticky']
            || $this->data['modules'][$modulename]['activated']);
    }

    /**
     * Returns if a module is mandatory for this sem_class.
     * @param string $module
     * @return boolean
     */
    public function isModuleMandatory($module)
    {
        return isset($this->data['modules'][$module])
            && $this->data['modules'][$module]['sticky']
            && $this->data['modules'][$module]['activated'];
    }

    public function getSemTypes()
    {
        $types = [];
        foreach (SemType::getTypes() as $id => $type) {
            if ($type['class'] == $this->data['id']) {
                $types[$id] = $type;
            }
        }
        return $types;
    }

    /**
     * Checks if the current sem class is usable for course grouping.
     */
    public function isGroup()
    {
        return $this->data['is_group'];
    }

    /**
     * Checks if any SemClasses exist that provide grouping functionality.
     * @return SimpleCollection
     */
    public static function getGroupClasses()
    {
        return SimpleCollection::createFromArray(self::getClasses())->findBy('is_group', true);
    }

    /**
     * stores all data in the database
     * @return boolean success
     */
    public function store()
    {
        $db = DBManager::get();
        $statement = $db->prepare(
            "UPDATE sem_classes " .
                "SET name = :name, " .
                "description = :description, " .
                "create_description = :create_description, " .
                "studygroup_mode = :studygroup_mode, " .
                "only_inst_user = :only_inst_user, " .
                "default_read_level = :default_read_level, " .
                "default_write_level = :default_write_level, " .
                "bereiche = :bereiche, " .
                "module = :module, " .
                "show_browse = :show_browse, " .
                "write_access_nobody = :write_access_nobody, " .
                "topic_create_autor = :topic_create_autor, " .
                "visible = :visible, " .
                "course_creation_forbidden = :course_creation_forbidden, " .
                "modules = :modules, " .
                "title_dozent = :title_dozent, " .
                "title_dozent_plural = :title_dozent_plural, " .
                "title_tutor = :title_tutor, " .
                "title_tutor_plural = :title_tutor_plural, " .
                "title_autor = :title_autor, " .
                "title_autor_plural = :title_autor_plural, " .
                "admission_prelim_default = :admission_prelim_default, " .
                "admission_type_default = :admission_type_default, " .
                "show_raumzeit = :show_raumzeit, " .
                "is_group = :is_group, " .
                "chdate = UNIX_TIMESTAMP() " .
            "WHERE id = :id ".
        "");
        StudipCacheFactory::getCache()->expire('DB_SEM_CLASSES_ARRAY');
        return $statement->execute([
            'id' => $this->data['id'],
            'name' => $this->data['name'],
            'description' => $this->data['description'],
            'create_description' => $this->data['create_description'],
            'studygroup_mode' => (int) $this->data['studygroup_mode'],
            'only_inst_user' => (int) $this->data['only_inst_user'],
            'default_read_level' => (int) $this->data['default_read_level'],
            'default_write_level' => (int) $this->data['default_write_level'],
            'bereiche' => (int) $this->data['bereiche'],
            'module' => (int) $this->data['module'],
            'show_browse' => (int) $this->data['show_browse'],
            'write_access_nobody' => (int) $this->data['write_access_nobody'],
            'topic_create_autor' => (int) $this->data['topic_create_autor'],
            'visible' => (int) $this->data['visible'],
            'course_creation_forbidden' => (int) $this->data['course_creation_forbidden'],
            'modules' => json_encode((object) $this->data['modules']),
            'title_dozent' => $this->data['title_dozent']
                ? $this->data['title_dozent']
                : null,
            'title_dozent_plural' => $this->data['title_dozent_plural']
                ? $this->data['title_dozent_plural']
                : null,
            'title_tutor' => $this->data['title_tutor']
                ? $this->data['title_tutor']
                : null,
            'title_tutor_plural' => $this->data['title_tutor_plural']
                ? $this->data['title_tutor_plural']
                : null,
            'title_autor' => $this->data['title_autor']
                ? $this->data['title_autor']
                : null,
            'title_autor_plural' => $this->data['title_autor_plural']
                ? $this->data['title_autor_plural']
                : null,
            'admission_prelim_default' => (int)$this->data['admission_prelim_default'],
            'admission_type_default' => (int)$this->data['admission_type_default'],
            'show_raumzeit' => (int) $this->data['show_raumzeit'],
            'is_group' => (int) $this->data['is_group']
        ]);
    }

    /**
     * Deletes the sem_class-object and all its sem_types. Will only delete,
     * if there are no seminars in this sem_class.
     * Remember to refresh the global $SEM_CLASS and $SEM_TYPE array.
     * @return boolean : success of deletion
     */
    public function delete()
    {
        if ($this->countSeminars() === 0) {
            foreach ($GLOBALS['SEM_TYPE'] as $sem_type) {
                if ($sem_type['class'] == $this->data['id']) {
                    $sem_type->delete();
                }
            }
            $GLOBALS['SEM_TYPE'] = SemType::getTypes();
            $db = DBManager::get();
            $statement = $db->prepare("
                DELETE FROM sem_classes
                WHERE id = :id
            ");
            StudipCacheFactory::getCache()->expire('DB_SEM_CLASSES_ARRAY');
            return $statement->execute([
                'id' => $this->data['id']
            ]);
        } else {
            return false;
        }
    }

    /**
     * Sets an attribute of sem_class->data
     * @param string $offset
     * @param mixed $value
     */
    public function set($offset, $value)
    {
        $this->data[$offset] = $value;
    }

    /***************************************************************************
     *                          ArrayAccess methods                            *
     ***************************************************************************/

    /**
     * deprecated, does nothing, should not be used
     * @param string $offset
     * @param mixed $value
     *
     * @todo Add void return type when Stud.IP requires PHP8 minimal
    #[ReturnTypeWillChange]
    public function offsetSet($offset, $value)
    {
    }

    /**
     * Compatibility function with old $SEM_CLASS variable for plugins. Maps the
     * new array-structure to the old boolean values.
     * @param integer $offset: name of attribute
     * @return boolean|(localized)string
     *
     * @todo Add mixed return type when Stud.IP requires PHP8 minimal
    #[ReturnTypeWillChange]
    public function offsetGet($offset)
    {
        switch ($offset) {
            case "name":
                return gettext($this->data['name']);
            case "only_inst_user":
                return (bool) $this->data['only_inst_user'];
            case "bereiche":
                return (bool) $this->data['bereiche'];
            case "show_browse":
                return (bool) $this->data['show_browse'];
            case "write_access_nobody":
                return (bool) $this->data['write_access_nobody'];
            case "topic_create_autor":
                return (bool) $this->data['topic_create_autor'];
            case "visible":
                return (bool) $this->data['visible'];
            case "studygroup_mode":
                return (bool) $this->data['studygroup_mode'];
            case "admission_prelim_default":
               return (int) $this->data['admission_prelim_default'];
            case "admission_type_default":
               return (int) $this->data['admission_type_default'];
            case "is_group":
               return (bool) $this->data['is_group'];
        }
        //ansonsten
        return $this->data[$offset];
    }

    /**
     * ArrayAccess method to check if an attribute exists.
     * @param int $offset
     * @return bool
     *
     * @todo Add bool return type when Stud.IP requires PHP8 minimal
    #[ReturnTypeWillChange]
    public function offsetExists($offset)
    {
        return isset($this->data[$offset]);
    }

    /**
     * deprecated, does nothing, should not be used
     * @param string $offset
     *
     * @todo Add void return type when Stud.IP requires PHP8 minimal
    #[ReturnTypeWillChange]
    public function offsetUnset($offset)
    {
    }

    /***************************************************************************
     *                            static methods                               *
     ***************************************************************************/

    /**
     * Returns an array of all SemClasses in Stud.IP. Equivalent to global
     * $SEM_CLASS variable. This variable is statically stored in this class.
     * @return SemClass[] of SemClass
     */
    static public function getClasses()
    {
        if (!is_array(self::$sem_classes)) {
            $db = DBManager::get();
            self::$sem_classes = [];

            $cache = StudipCacheFactory::getCache();
            $class_array = unserialize($cache->read('DB_SEM_CLASSES_ARRAY'));
            if (!$class_array) {

                try {
                    $statement = $db->prepare(
                        "SELECT * FROM sem_classes ORDER BY id ASC "
                    );
                    $statement->execute();
                    $class_array = $statement->fetchAll(PDO::FETCH_ASSOC);

                    if ($class_array) {
                        $cache = StudipCacheFactory::getCache();
                        $cache->write('DB_SEM_CLASSES_ARRAY', serialize($class_array));
                    }
                } catch (PDOException $e) {
                    //for use without or before migration 92
                    $class_array = $GLOBALS['SEM_CLASS_OLD_VAR'];
                    if (is_array($class_array)) {
                        ksort($class_array);
                        foreach ($class_array as $id => $class) {
                            self::$sem_classes[$id] = new SemClass($class);
                        }
                    } else {
                        self::$sem_classes[1] = self::getDefaultSemClass();
                    }
                }
            }
            foreach ($class_array as $sem_class) {
                self::$sem_classes[$sem_class['id']] = new SemClass($sem_class);
            }
        }
        return self::$sem_classes;
    }

    /**
     * Refreshes the internal $sem_classes cache-variable.
     * @return array of SemClass
     */
    static public function refreshClasses()
    {
        StudipCacheFactory::getCache()->expire('DB_SEM_CLASSES_ARRAY');
        self::$sem_classes = null;
        return self::getClasses();
    }

    /**
     * Static method to recursively transform an object into an associative array.
     * @param mixed $obj: should be of class StdClass
     * @return array
     */
    static public function object2array($obj)
    {
        $arr_raw = is_object($obj) ? get_object_vars($obj) : $obj;
        foreach ($arr_raw as $key => $val) {
            $val = (is_array($val) || is_object($val)) ? self::object2array($val) : $val;
            $arr[$key] = $val;
        }
        return $arr;
    }


    /**
     * Static method only to keep the translationstrings of the values. It is
     * never used within the system.
     */
    static private function localization()
    {
        _("Lehre");
        _("Forschung");
        _("Organisation");
        _("Community");
        _("Arbeitsgruppen");
        _("importierte Kurse");
        _("Hauptveranstaltungen");

        _("Hier finden Sie alle in Stud.IP registrierten Lehrveranstaltungen");
        _("Verwenden Sie diese Kategorie, um normale Lehrveranstaltungen anzulegen");
        _("Hier finden Sie virtuelle Veranstaltungen zum Thema Forschung an der Universität");
        _("In dieser Kategorie können Sie virtuelle Veranstaltungen für Forschungsprojekte anlegen.");
        _("Hier finden Sie virtuelle Veranstaltungen zu verschiedenen Gremien an der Universität");
        _("Um virtuelle Veranstaltungen für Uni-Gremien anzulegen, verwenden Sie diese Kategorie");
        _("Hier finden Sie virtuelle Veranstaltungen zu unterschiedlichen Themen");
        _("Wenn Sie Veranstaltungen als Diskussiongruppen zu unterschiedlichen Themen anlegen möchten, verwenden Sie diese Kategorie.");
        _("Hier finden Sie verschiedene Arbeitsgruppen an der %s");
        _("Verwenden Sie diese Kategorie, um unterschiedliche Arbeitsgruppen anzulegen.");
        _("Veranstaltungen dieser Kategorie dienen als Gruppierungselement, um die Zusammengehörigkeit von Veranstaltungen anderer Kategorien abzubilden.");
    }

}