Skip to content
Snippets Groups Projects
Select Git revision
  • 5866bdf1497623253096c1e39fb8d853c1438634
  • main default protected
  • step-3263
  • feature/plugins-cli
  • feature/vite
  • step-2484-peerreview
  • biest/issue-5051
  • tests/simplify-jsonapi-tests
  • fix/typo-in-1a70031
  • feature/broadcasting
  • database-seeders-and-factories
  • feature/peer-review-2
  • feature-feedback-jsonapi
  • feature/peerreview
  • feature/balloon-plus
  • feature/stock-images-unsplash
  • tic-2588
  • 5.0
  • 5.2
  • biest/unlock-blocks
  • biest-1514
21 results

Modul.php

Blame
  • Forked from Stud.IP / Stud.IP
    Source project has a limited visibility.
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    Instance.php 17.16 KiB
    <?php
    
    namespace Courseware;
    
    /**
     * This class represents an instance of a courseware of a course or a user.
     *
     * @author  Marcus Eibrink-Lunzenauer <lunzenauer@elan-ev.de>
     * @author  Ron Lucke <lucke@elan-ev.de>
     * @license GPL2 or any later version
     *
     * @since   Stud.IP 5.0
     */
    class Instance
    {
        public static function deleteForRange(\Range $range): void
        {
            $root = null;
            switch ($range->getRangeType()) {
                case 'course':
                    $root = StructuralElement::getCoursewareCourse($range->getRangeId());
                    break;
                case 'user':
                    $root = StructuralElement::getCoursewareUser($range->getRangeId());
                    break;
                default:
                    throw new \InvalidArgumentException('Only ranges of type "user" and "course" are currently supported.');
            }
    
            // there is no courseware for this course
            if (!$root) {
                return;
            }
    
            $instance = new self($root);
    
            $last_element_configs = \ConfigValue::findBySQL('field = ? AND value LIKE ?', [
                'COURSEWARE_LAST_ELEMENT',
                '%' . $range->getRangeId() . '%',
            ]);
            foreach ($last_element_configs as $config) {
                $arr = json_decode($config->value, true);
                $arr = array_filter(
                    $arr,
                    function ($key) use ($range) {
                        return $key !== $range->id;
                    },
                    ARRAY_FILTER_USE_KEY
                );
                \UserConfig::get($config->range_id)->unsetValue('COURSEWARE_LAST_ELEMENT');
                \UserConfig::get($config->range_id)->store('COURSEWARE_LAST_ELEMENT', $arr);
            }
    
            $root->delete();
        }
    
        /**
         * @param \Range $range
         * @return ?static
         */
        public static function existsForRange(\Range $range): bool
        {
            switch ($range->getRangeType()) {
                case 'course':
                case 'user':
                    $result = \DBManager::get()->fetchOne(
                        'SELECT COUNT(*) as count FROM cw_structural_elements WHERE range_id = ? AND range_type = ? AND parent_id IS NULL',
                        [$range->getRangeId(), $range->getRangeType()]
                    );
    
                    return ((int) $result['count']) > 0;
    
                default:
                    throw new \InvalidArgumentException('Only ranges of type "user" and "course" are currently supported.');
            }
        }
    
    
            /**
         * @param \Range $range
         * @return ?static
         */
        public static function findForRange(\Range $range)
        {
            $root = null;
            switch ($range->getRangeType()) {
                case 'course':
                    $root = StructuralElement::getCoursewareCourse($range->getRangeId());
                    break;
                case 'user':
                    $root = StructuralElement::getCoursewareUser($range->getRangeId());
                    break;
            }
            if (!$root) {
                return null;
            }
    
            return new self($root);
        }
    
    
        /**
         * @var StructuralElement
         */
        private $root;
    
        /**
         * @var Unit
         */
        private $unit;
    
        /**
         * Create a new representation of a a courseware instance.
         *
         * This model class purely represents and does not create anything. Its purpose is to have all things related to a
         * single courseware instance in one place.
         *
         * @param StructuralElement $root the root of this courseware instance
         */
        public function __construct(StructuralElement $root)
        {
            $this->root = $root;
            $this->unit = $root->findUnit();
        }
    
        /**
         * Returns the root element of this courseware instance.
         *
         * @return StructuralElement the root element of this courseware instance
         */
        public function getRoot(): StructuralElement
        {
            return $this->root;
        }
    
        /**
         * Returns the unit belonging to this courseware instance.
         *
         * @return Unit the unit belonging this courseware instance
         */
        public function getUnit(): Unit
        {
            return $this->unit;
        }
    
        /**
         * Returns the range this courseware instance belongs to.
         *
         * @return \Range the range this courseware instance belongs to
         */
        public function getRange(): \Range
        {
            $rangeType = $this->root['range_type'];
    
            return $this->root->$rangeType;
        }
    
        /**
         * Returns the type of this courseware instance's range as coded in the root element.
         *
         * @return string the type of this courseware instance's range
         */
        public function getRangeType(): string
        {
            return $this->root['range_type'];
        }
    
        /**
         * Returns all associated block types registered to this courseware instance.
         *
         * @return array a list of all associated block types
         */
        public function getBlockTypes(): array
        {
            $types = BlockTypes\BlockType::getBlockTypes();
    
            return $types;
        }
    
        /**
         * Returns all associated container types registered to this courseware instance.
         *
         * @return array a list of all associated block types
         */
        public function getContainerTypes(): array
        {
            $types = ContainerTypes\ContainerType::getContainerTypes();
    
            return $types;
        }
    
        /**
         * Returns a user's favorite block types for this instance.
         *
         * @param \User $user the user for whom the favorite block types will be returned
         *
         * @return array a list of favorite block types
         */
        public function getFavoriteBlockTypes(\User $user): array
        {
            /** @var array $favoriteBlockTypes */
            $favoriteBlockTypes = \UserConfig::get($user->id)->getValue('COURSEWARE_FAVORITE_BLOCK_TYPES');
    
            return $favoriteBlockTypes;
        }
    
        /**
         * Sets a user's favorite block types for this courseware instance.
         *
         * @param \User $user      the user for whom the favorite block types will be set
         * @param array $favorites the list of favorite block types
         */
        public function setFavoriteBlockTypes(\User $user, array $favorites): void
        {
            \UserConfig::get($user->id)->store('COURSEWARE_FAVORITE_BLOCK_TYPES', $favorites);
        }
    
        /**
         * Returns whether this courseware instance uses a sequential progression through the structural elements.
         *
         * @return bool true if this courseware instance uses a sequential progression, false otherwise
         */
        public function getSequentialProgression(): bool
        {
            $sequentialProgression = $this->unit->config['sequential_progression'] ?? false;
    
            return (bool) $sequentialProgression;
        }
    
        /**
         * Sets whether this courseware instance uses a sequential progression through the structural elements.
         *
         * @param bool $isSequentialProgression true if this courseware instance uses a sequential progression
         */
        public function setSequentialProgression(bool $isSequentialProgression): void
        {
            $this->unit->config['sequential_progression'] = $isSequentialProgression ? 1 : 0;
        }
    
        const EDITING_PERMISSION_DOZENT = 'dozent';
        const EDITING_PERMISSION_TUTOR = 'tutor';
    
        /**
         * Returns the level needed to edit this courseware instance.
         *
         * @return string can be either `Instance::EDITING_PERMISSION_DOZENT` or  `Instance::EDITING_PERMISSION_TUTOR`
         */
        public function getEditingPermissionLevel(): string
        {
            /** @var string $editingPermissionLevel */
            $editingPermissionLevel = $this->unit->config['editing_permission'];
            if ($editingPermissionLevel) {
                $this->validateEditingPermissionLevel($editingPermissionLevel);
                return $editingPermissionLevel;
            }
    
            return self::EDITING_PERMISSION_TUTOR; // tutor is default
        }
    
        /**
         * Sets the level needed to edit this courseware instance.
         *
         * @param string $editingPermissionLevel can be either `Instance::EDITING_PERMISSION_DOZENT` or
         *                                       `Instance::EDITING_PERMISSION_TUTOR`
         */
        public function setEditingPermissionLevel(string $editingPermissionLevel): void
        {
            $this->validateEditingPermissionLevel($editingPermissionLevel);
            $this->unit->config['editing_permission'] = $editingPermissionLevel;
        }
    
        /**
         * Validates a editing permission level.
         *
         * @param string $editingPermissionLevel the editing permission level to validate
         *
         * @return bool true if this editing permission level is valid, false otherwise
         */
        public function isValidEditingPermissionLevel(string $editingPermissionLevel): bool
        {
            return in_array($editingPermissionLevel, [self::EDITING_PERMISSION_DOZENT, self::EDITING_PERMISSION_TUTOR]);
        }
    
        private function validateEditingPermissionLevel(string $editingPermissionLevel): void
        {
            if (!$this->isValidEditingPermissionLevel($editingPermissionLevel)) {
                throw new \InvalidArgumentException('Invalid editing permission of courseware.');
            }
        }
    
    
        /**
         * Returns the certificate creation settings.
         *
         * @return array
         */
        public function getCertificateSettings(): array
        {
            /** @var array $certificateSettings */
            $certificateSettings = isset($this->unit->config['certificate'])
                ? $this->unit->config['certificate']->getArrayCopy()
                : [];
            $this->validateCertificateSettings($certificateSettings);
    
            return $certificateSettings;
        }
    
        /**
         * Sets the certificate settings for this courseware instance.
         *
         * @param array $certificateSettings an array of parameters
         */
        public function setCertificateSettings(array $certificateSettings): void
        {
            if (count($certificateSettings) > 0) {
                $this->validateCertificateSettings($certificateSettings);
                $this->unit->config['certificate'] = $certificateSettings;
            } else {
                unset($this->unit->config['certificate']);
            }
        }
    
        /**
         * Validates certificate settings.
         *
         * @param \JSONArrayObject $certificateSettings settings for certificate creation
         *
         * @return bool true if all given values are valid, false otherwise
         */
        public function isValidCertificateSettings($certificateSettings): bool
        {
            return !isset($certificateSettings['threshold'])
                || (
                    $certificateSettings['threshold'] >= 0
                    && $certificateSettings['threshold'] <= 100
                );
        }
    
        private function validateCertificateSettings($certificateSettings): void
        {
            if (!$this->isValidCertificateSettings($certificateSettings)) {
                throw new \InvalidArgumentException('Invalid certificate settings given.');
            }
        }
    
        /**
         * Returns the reminder message sending settings.
         *
         * @return array
         */
        public function getReminderSettings(): array
        {
            /** @var array $reminderSettings */
            $reminderSettings = isset($this->unit->config['reminder'])
                ? $this->unit->config['reminder']->getArrayCopy()
                : [];
            $this->validateReminderSettings($reminderSettings);
    
            return $reminderSettings;
        }
    
        /**
         * Sets the reminder message settings this courseware instance.
         *
         * @param \JSONArrayObject $reminderSettings an array of parameters
         */
        public function setReminderSettings($reminderSettings): void
        {
            if (count($reminderSettings) > 0) {
                $this->validateReminderSettings($reminderSettings);
                $this->unit->config['reminder'] = $reminderSettings;
            } else {
                unset($this->unit->config['reminder']);
                unset($this->unit->config['last_reminder']);
            }
        }
    
        /**
         * Validates reminder message settings.
         *
         * @param \JSONArrayObject $reminderSettings settings for reminder mail sending
         *
         * @return bool true if all given values are valid, false otherwise
         */
        public function isValidReminderSettings($reminderSettings): bool
        {
            $valid = in_array($reminderSettings['interval'] ?? 0, [0, 7, 14, 30, 90, 180, 365]);
    
            return $valid;
        }
    
        private function validateReminderSettings($reminderSettings): void
        {
            if (!$this->isValidReminderSettings($reminderSettings)) {
                throw new \InvalidArgumentException('Invalid reminder settings given.');
            }
        }
    
        /**
         * Returns the progress resetting settings.
         *
         * @return array
         */
        public function getResetProgressSettings(): array
        {
            /** @var array $resetProgressSettings */
            $resetProgressSettings = isset($this->unit->config['reset_progress'])
                ? $this->unit->config['reset_progress']->getArrayCopy()
                : [];
            $this->validateResetProgressSettings($resetProgressSettings);
    
            return $resetProgressSettings;
        }
    
        /**
         * Sets the progress resetting settings this courseware instance.
         *
         * @param \JSONArrayObject $resetProgressSettings an array of parameters
         */
        public function setResetProgressSettings($resetProgressSettings): void
        {
            if (count($resetProgressSettings) > 0) {
                $this->validateResetProgressSettings($resetProgressSettings);
                $this->unit->config['reset_progress'] = $resetProgressSettings;
            } else {
                unset($this->unit->config['reset_progress']);
                unset($this->unit->config['last_progress_reset']);
            }
        }
    
        /**
         * Validates progress resetting settings.
         *
         * @param \JSONArrayObject $resetProgressSettings settings for progress resetting
         *
         * @return bool true if all given values are valid, false otherwise
         */
        public function isValidResetProgressSettings($resetProgressSettings): bool
        {
            $valid = in_array($resetProgressSettings['interval'] ?? 0, [0, 14, 30, 90, 180, 365]);
    
            return $valid;
        }
    
        private function validateResetProgressSettings($resetProgressSettings): void
        {
            if (!$this->isValidResetProgressSettings($resetProgressSettings)) {
                throw new \InvalidArgumentException('Invalid progress resetting settings given.');
            }
        }
    
        /**
         * Returns all bookmarks of a user associated to this courseware instance.
         *
         * @param \User $user the user for whom to find associated bookmarks for
         *
         * @return array a list of the given user's bookmarks associated to this instance
         */
        public function getUsersBookmarks(\User $user): array
        {
            return StructuralElement::findUsersBookmarksByRange($user, $this->getRange());
        }
    
        public function findAllStructuralElements(): array
        {
            $sql = 'SELECT se.*
                    FROM cw_structural_elements se
                    WHERE se.range_id = ? AND se.range_type = ?';
            $statement = \DBManager::get()->prepare($sql);
            $statement->execute([$this->root['range_id'], $this->root['range_type']]);
    
            $data = [];
            foreach ($statement as $key => $row) {
                $data[] = \Courseware\StructuralElement::build($row, false);
            }
    
            return $data;
        }
    
        public function findAllBlocks(): array
        {
            $sql = 'SELECT b.*
                    FROM cw_structural_elements se
                    JOIN cw_containers c ON se.id = c.structural_element_id
                    JOIN cw_blocks b ON c.id = b.container_id
                    WHERE se.range_id = ? AND se.range_type = ?';
            $statement = \DBManager::get()->prepare($sql);
            $statement->execute([$this->root['range_id'], $this->root['range_type']]);
    
            $data = [];
            foreach ($statement as $key => $row) {
                $data[] = \Courseware\Block::build($row, false);
            }
    
            return $data;
        }
    
        /**
         * Find all blocks of this instance and group them by their structural element's ID.
         * You may specify your own `$formatter` instead of the default one which stores the blocks as instances of \Courseware\Block.
         *
         * @param ?callable(array $row): mixed $formatter Provide your own callable if you need something else instead of
         *                                                full-blown instances of \Courseware\Block.
         * @return array all the (optionally formatted) blocks grouped by the IDs of the structural element containing
         *                  that block.
         */
        public function findAllBlocksGroupedByStructuralElementId(callable $formatter = null): array
        {
            if (!$formatter) {
                $formatter = function ($row) {
                    return \Courseware\Block::build($row, false);
                };
            }
    
            $sql = 'SELECT se.id AS structural_element_id, b.*
                    FROM cw_structural_elements se
                    JOIN cw_containers c ON se.id = c.structural_element_id
                    JOIN cw_blocks b ON c.id = b.container_id
                    WHERE se.range_id = ? AND se.range_type = ?';
    
            $statement = \DBManager::get()->prepare($sql);
            $statement->execute([$this->root['range_id'], $this->root['range_type']]);
    
            $data = [];
            foreach ($statement as $row) {
                $structuralElementId = $row['structural_element_id'];
                unset($row['structural_element_id']);
    
                if (!isset($data[$structuralElementId])) {
                    $data[$structuralElementId] = [];
                }
                $data[$structuralElementId][] = $formatter($row);
            }
    
            return $data;
        }
    
    }