Skip to content
Snippets Groups Projects
FileRef.php 22.8 KiB
Newer Older
<?php
/**
 * FileRef.php
 * model class for table file_refs
 *
 * 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.
 *
 * @author      André Noack <noack@data-quest.de>
 * @copyright   2016 Stud.IP Core-Group
 * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
 * @category    Stud.IP
 *
 *
 * @property string id database column
 * @property string file_id database column
 * @property string folder_id database column
 * @property string user_id database column
 * @property string name database column
 * @property string downloads database column
 * @property string description database column
 * @property string license database column
 * @property string content_terms_of_use_id database column
 * @property string mkdate database column
 * @property string chdate database column
 * @property SimpleORMap file belongs_to File
 * @property SimpleORMap folder belongs_to Folder
 * @property SimpleORMap owner belongs_to User
 * @property SimpleORMap terms_of_use belongs_to ContentTermsOfUse
 */
class FileRef extends SimpleORMap implements PrivacyObject, FeedbackRange
{
    protected static function configure($config = [])
    {
        $config['db_table'] = 'file_refs';
        $config['belongs_to']['file'] = [
            'foreign_key' => 'file_id',
        ];
        $config['belongs_to']['folder'] = [
            'foreign_key' => 'folder_id',
        ];
        $config['belongs_to']['owner'] = [
            'foreign_key' => 'user_id',
        ];

        $config['belongs_to']['terms_of_use'] = [
            'class_name' => ContentTermsOfUse::class,
            'foreign_key' => 'content_terms_of_use_id',
            'assoc_func' => 'findOrBuild'
        ];

        $config['additional_fields']['size'] = ['file', 'size'];
        $config['additional_fields']['mime_type'] = ['file', 'mime_type'];
        $config['additional_fields']['download_url']['set'] = 'setDownloadURL';
        $config['additional_fields']['download_url']['get'] = 'getDownloadURL';
        $config['additional_fields']['author_name']['get'] = 'getAuthorName';
        $config['additional_fields']['is_accessible']['get'] = 'getAccessibility';
        $config['additional_fields']['is_link']['get'] = 'isLink';
        $config['additional_fields']['foldertype']['set'] = 'setFolderType';
        $config['additional_fields']['foldertype']['get'] = 'getFolderType';

        $config['registered_callbacks']['after_delete'][] = 'cbRemoveFileIfOrphaned';
        $config['registered_callbacks']['after_delete'][] = 'cbRemoveFeedbackElements';
        $config['registered_callbacks']['before_store'][] = 'cbMakeUniqueFilename';

        parent::configure($config);
    }

    protected $folder_type;
    protected $download_url;
    public $path_to_blob;

    /**
     * This callback is called after deleting a FileRef.
     * It removes the File object that is associated with the FileRef,
     * if the File is not referenced by any other FileRef object.
     */
    public function cbRemoveFileIfOrphaned()
    {
        if (!self::countBySql('file_id = ?', [$this->file_id])) {
            File::deleteBySQL("id = ?", [$this->file_id]);
        }
    }

    /**
     * This callback is called after deleting a FileRef.
     * It removes feedback elements that are associated with the FileRef.
     */
    public function cbRemoveFeedbackElements()
    {
        FeedbackElement::deleteBySQL("range_id = ? AND range_type = 'FileRef'", [$this->id]);
    }

    /**
     * This callback is called before storing a FileRef object.
     * In case the name field is changed this callback assures that the
     * name of the FileRef is unique inside the folder where
     * the FileRef is placed.
     */
    public function cbMakeUniqueFilename()
    {
        if (isset($this->folder) && $this->isFieldDirty('name')) {
            $this->name = $this->folder->getUniqueName($this->name);
        }
        if ($this->isFieldDirty('folder_id')) {
            //We have moved the file ref. $this->folder may not work directly
            //so we have to load the folder manually:
            $folder = Folder::find($this->folder_id);
            if ($folder) {
                $this->name = $folder->getUniqueName($this->name);
            }
        }
    }

    /**
     * Overrides the usual download url that this file_ref would get by the system (sendfile.php...)
     * Use this method by cloud plugins.
     * If you set download URL to null, the normal sendfile.php will be set as default download URL.
     * @param $url : string as URL or null to set URL to sendfile.php-URL
     */
    public function setDownloadURL($field, $url) {
        $this->download_url = $url;
    }

    /**
     * Returns the download-URL for the FileRef.
     *
     * @param string $dltype The download type: 'normal', 'zip', 'force' or 'force_download'.
     *
     * @return string The URL for the FileRef.
     */
    public function getDownloadURL($dltype = 'normal')
    {
        if ($this->download_url) {
            return $this->download_url;
        }
        $mode = Config::get()->SENDFILE_LINK_MODE ?: 'normal';
        $link = [];
        $params = [];
        $type = '0';
        $file_name = $this->file->name ?: $this->name;
        $file_id = $this->id;

        switch($mode) {
            case 'rewrite':
                $link[] = 'download/';
                switch ($dltype) {
                    case 'zip':
                        $link[] = 'zip/';
                        break;
                    case 'force':
                    case 'force_download':
                        $link[] = 'force_download/';
                        break;
                    default:
                        $link[] = 'normal/';
                }
                $link[] = $type . '/' . $file_id . '/' . urlencode($file_name);
                break;
            default:
                $link[] = 'sendfile.php';
                if ($dltype == 'zip') {
                    $params['zip'] = 1;
                } elseif (in_array($dltype,  ['force_download', 'force'])) {
                    $params['force_download'] = 1;
                }
                $params['type'] = $type;
                $params['file_id'] = $file_id;
                $params['file_name'] = $file_name;
        }
        return URLHelper::getScriptURL(implode('', $link), $params);
    }

    /**
     * Returns the name of the FileRef's author.
     *
     * @return string The name of the FileRef's author.
     */
    public function getAuthorName()
    {
        if (isset($this->owner)) {
            return $this->owner->getFullName('no_title_rev');
        }
        return $this->file->author_name;
    }

    /**
     * Returns true if the file is accessible
     */
    public function getAccessibility() : bool
    {
        return (bool) $this->file->is_accessible;
    }

    /**
     * This method increments the download counter of the FileRef.
     *
     * @return int The number of rows of the file_refs table that have been altered.
     */
    public function incrementDownloadCounter()
    {
        $this->downloads += 1;
        if (!$this->isNew()) {
            $where_query = join(' AND ' , $this->getWhereQuery());
            $query = "UPDATE `{$this->db_table()}`
                      SET `downloads` = `downloads` + 1
                      WHERE {$where_query}";
            return DBManager::get()->exec($query);
        }
    }

    /**
     * Returns the license object for this file.
     *
     * @return Object (to be specified!)
     */
    public function getLicenseObject()
    {
        if (class_exists($this->license)) {
            return new $this->license();
        }
        throw new UnexpectedValueException("class: {$this->license} not found");
    }

    /**
     * Determines whether this FileRef is a link or not.
     * @deprecated
     * @return bool True, if the FileRef references a link, false otherwise.
     */
    public function isLink()
    {
        return false;
    }

    public function setFolderType($field, FolderType $folder_type)
    {
        $this->folder_type = $folder_type;
    }

    public function getFolderType()
    {
        if ($this->folder_type) {
            return $this->folder_type;
        } elseif($this->folder) {
            return $this->folder->getTypedFolder();
        }
    }

    /**
     * Determines if the FileRef references an image file.
     *
     * @return bool True, if the referenced file is an image file, false otherwise.
     */
    public function isImage()
    {
        return mb_strpos($this->mime_type, 'image/') === 0;
    }

    /**
     * Determines if the FileRef references an audio file.
     *
     * @return bool True, if the referenced file is an audio file, false otherwise.
     */
    public function isAudio()
    {
        return mb_strpos($this->mime_type, 'audio/') === 0;
    }


    /**
     * Determines if the FileRef references a video file.
     *
     * @return bool True, if the referenced file is a video file, false otherwise.
     */
    public function isVideo()
    {
        return mb_strpos($this->mime_type, 'video/') === 0;
    }

    /**
     * Export available data of a given user into a storage object
     * (an instance of the StoredUserData class) for that user.
     *
     * @param StoredUserData $storage object to store data into
     */
    public static function exportUserData(StoredUserData $storage)
    {
        $sorm = self::findBySQL("user_id = ?", [$storage->user_id]);
        if ($sorm) {
            $field_data = [];
            foreach ($sorm as $row) {
                $field_data[] = $row->toRawArray();
            }
            if ($field_data) {
                $storage->addTabularData(_('Dateien'), 'file_refs', $field_data);
            }
        }
    }

    /**
     * @returns FileType
     */
    public function getFileType()
    {
        $filetype = $this->file->filetype;
        if (class_exists($filetype) && is_subclass_of($filetype, 'FileType')) {
            return new $filetype($this);
        } else {
            return new UnknownFileType($this);
        }
    }

    public function getRangeName()
    {
        return $this->name;
    }

    public function getRangeIcon($role)
    {
        return FileManager::getIconForFileRef($this, $role);
    }

    public function getRangeUrl()
    {
        return 'course/files/index/' . $this->foldertype->getId();
    }

    public function getRangeCourseId()
    {
        return $this->foldertype->range_id;
    }

    public function isRangeAccessible(string $user_id = null): bool
    {
        $user_id = $user_id ?? $GLOBALS['user']->id;

        return $this->foldertype->isFileDownloadable($this->id, $user_id);
    }


    /**
     * This is a helper method to generate the SQL query for the
     * findAll and countAll methods. Relevant FileRef objects for these methods
     * can be limited by a time range and a specific course. The amount
     * and the offset of the FileRef objects to be retrieved can also be set.
     *
     * @param string $user_id The ID of the user that is used to count or
     *     retrieve FileRef objects.
     *
     * @param DateTime|null $begin The begin of the time range.
     *
     * @param DateTime|null $end The end of the time range.
     *
     * @param string $course_id The ID of the course from which FileRef objects
     *     shall be retrieved.
     *
     * @param int $file_limit The maximum amount of FileRef objects to be
     *     retrieved.
     *
     * @param int $file_offset The offset in the FileRef data from which to
     *     start retrieving objects.
     *
     * @returns mixed[] An array with two indexes:
     *     - sql: The SQL query string.
     *     - params: An array with SQL query parameters.
     */
    protected static function getAllSql($user_id, $begin = null, $end = null, $course_id = '', $file_limit = 0, $file_offset = 0)
    {
        $sql = '';
        $sql_params = [];
        if ($course_id) {
            //Limit the result for a specific course.
            $sql = 'INNER JOIN `folders`
                ON `file_refs`.`folder_id` = `folders`.`id`
                WHERE `folders`.`range_id` =  :course_id ';
            $sql_params['course_id'] = $course_id;
            if (($begin instanceof DateTime) && ($end instanceof DateTime)) {
                $sql .= 'AND `file_refs`.`chdate` BETWEEN :begin AND :end ';
                $sql_params['begin'] = $begin->getTimestamp();
                $sql_params['end'] = $end->getTimestamp();
            }
        } else {
            if ($GLOBALS['perm']->have_perm('root', $user_id)) {
                //This is easy: Get all recent files ordered by chdate.
                if (($begin instanceof DateTime) && ($end instanceof DateTime)) {
                    $sql = 'chdate BETWEEN :begin AND :end ';
                    $sql_params['begin'] = $begin->getTimestamp();
                    $sql_params['end'] = $end->getTimestamp();
                } else {
                    $sql = 'TRUE ';
                }
            } else {
                //Get the folders (all folders) of the user first and then
                //check for new files. This means getting all folders of courses
                //and institutes where the user has access to.

                $range_ids = FileManager::getRangeIdsForFolders($user_id, true);

                //Now get all file refs:
                $sql = 'INNER JOIN `folders`
                    ON `file_refs`.`folder_id` = `folders`.`id`
                    WHERE `folders`.`range_id` IN ( :range_ids ) ';
                $sql_params['range_ids'] = $range_ids;

                if (($begin instanceof DateTime) && ($end instanceof DateTime)) {
                    $sql .= 'AND `file_refs`.`chdate` BETWEEN :begin AND :end ';
                    $sql_params['begin'] = $begin->getTimestamp();
                    $sql_params['end'] = $end->getTimestamp();
                }
            }
        }
        $sql .= ' ORDER BY `file_refs`.`chdate` DESC, `file_refs`.`mkdate` DESC, `file_refs`.`name` ASC';
        if ($file_limit > 0) {
            $sql .= ' LIMIT :limit';
            $sql_params['limit'] = $file_limit;
            if ($file_offset > 0) {
                $sql .= ' OFFSET :offset';
                $sql_params['offset'] = $file_offset;
            }
        }
        return ['sql' => $sql, 'params' => $sql_params];
    }


    /**
     * This method retrieves the FileRef objects a user has access to.
     *
     * @see FileRef::getAllSql for the parameter description.
     *
     * @returns FileRef[] A list of FileRef objects.
     */
    public static function findAll($user_id, $begin = null, $end = null, $course_id = null, $file_limit = 0, $file_offset = 0)
    {
        $sql_data = self::getAllSql($user_id, $begin, $end, $course_id, $file_limit, $file_offset);
        return FileRef::findBySql($sql_data['sql'], $sql_data['params']);
    }


    /**
     * This method counts all the FileRef objects a user has access to.
     *
     * @see FileRef::getAllSql for the parameter description.
     *
     * @returns int The amount of found FileRef rows.
     */
    public static function countAll($user_id, $begin = null, $end = null, $course_id = null)
    {
        $sql_data = self::getAllSql($user_id, $begin, $end, $course_id);
        return FileRef::countBySql($sql_data['sql'], $sql_data['params']);
    }


    /**
     * This is a helper method to generate the SQL query for the
     * findPublicFiles and countPublicFiles methods. Relevant FileRef objects
     * for these methods can be limited by a time range. The amount
     * and the offset of the FileRef objects to be retrieved can also be set.
     *
     * @param string $user_id The ID of the user that is used to count or
     *     retrieve FileRef objects in public folders.
     *
     * @param DateTime|null $begin The begin of the time range.
     *
     * @param DateTime|null $end The end of the time range.
     *
     * @param int $file_limit The maximum amount of FileRef objects to be
     *     retrieved from public folders.
     *
     * @param int $file_offset The offset in the FileRef data from which to
     *     start retrieving objects from public folders.
     *
     * @returns mixed[] An array with two indexes:
     *     - sql: The SQL query string.
     *     - params: An array with SQL query parameters.
     */
    protected static function getPublicFilesSql($user_id, $begin = null, $end = null, $file_limit = 0, $file_offset = 0)
    {
        $sql = "INNER JOIN `folders`
            ON `file_refs`.`folder_id` = `folders`.`id`
            WHERE `folders`.`folder_type` = 'PublicFolder'
            AND `folders`.`range_type` = 'user' ";
        $sql_params = [];
        if ($user_id) {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            $sql .= " AND `folders`.`range_id` = :user_id";
            $sql_params['user_id'] = $user_id;
        }
        if (($begin instanceof DateTime) && ($end instanceof DateTime)) {
            $sql .= ' AND `file_refs`.`chdate` BETWEEN :begin AND :end';
            $sql_params['begin'] = $begin->getTimestamp();
            $sql_params['end'] = $end->getTimestamp();
        }
        $sql .= " ORDER BY `file_refs`.`chdate` DESC";
        if ($file_limit > 0) {
            $sql .= " LIMIT :file_limit";
            $sql_params['file_limit'] = $file_limit;
        }
        if ($file_offset > 0) {
            $sql .= " OFFSET :file_offset";
            $sql_params['file_offset'] = $file_offset;
        }
        return ['sql' => $sql, 'params' => $sql_params];
    }


    /**
     * This method retrieves the FileRef objects in public folders which
     * can be accessed by a user.
     *
     * @see FileRef::getPublicFilesSql for the parameter description.
     *
     * @returns FileRef[] A list of FileRef objects.
     */
    public static function findPublicFiles($user_id, $begin = null, $end = null, $file_limit = 0, $file_offset = 0)
    {
        $sql_data = self::getPublicFilesSql($user_id, $begin, $end, $file_limit, $file_offset);
        return self::findBySql($sql_data['sql'], $sql_data['params']);
    }


    /**
     * This method counts the FileRef objects in public folders which
     * can be accessed by a user.
     *
     * @see FileRef::getPublicFilesSql for the parameter description.
     *
     * @returns int The amount of found FileRef rows.
     */
    public static function countPublicFiles($user_id, $begin = null, $end = null)
    {
        $sql_data = self::getPublicFilesSql($user_id, $begin, $end);
        return self::countBySql($sql_data['sql'], $sql_data['params']);
    }


    /**
     * This is a helper method to generate the SQL query for the
     * findUploadedFiles and countUploadedFiles methods. Relevant FileRef
     * objects for these methods can be limited by a time range and a specified
     * course. Furthermore, it is possible to limit the search to FileRef
     * objects without a license set or with the "unknown" license set.
     * The amount and the offset of the FileRef objects to be retrieved can also
     * be set.
     *
     * @param string $user_id The ID of the user whose uploaded files shall be
     *     retrieved or counted.
     *
     * @param DateTime|null $begin The begin of the time range.
     *
     * @param DateTime|null $end The end of the time range.
     *
     * @param string $course_id The ID of the course from which FileRef objects
     *     shall be retrieved.
     *
     * @param bool $unknown_license_only Whether to only query for FileRef
     *     objects without a license or with the "unknown" license set
     *     (true) or to query all uploaded FileRef objects (false).
     *     Defaults to false.
     *
     * @param int $file_limit The maximum amount of FileRef objects to be
     *     retrieved from public folders.
     *
     * @param int $file_offset The offset in the FileRef data from which to
     *     start retrieving objects from public folders.
     *
     * @returns mixed[] An array with two indexes:
     *     - sql: The SQL query string.
     *     - params: An array with SQL query parameters.
     */
    protected static function getUploadedFilesSql($user_id, $begin = null, $end = null, $course_id = '', $unknown_license_only = false, $file_limit = 0, $file_offset = 0)
    {
        $sql = '';
        if ($course_id) {
            $sql = 'INNER JOIN `folders`
                ON `file_refs`.`folder_id` = `folders`.`id`
                WHERE ';
        }
        $sql .= "`file_refs`.`user_id` = :user_id";
        $sql_params = [
            'user_id' => $user_id
        ];
        if ($unknown_license_only) {
            $sql .= " AND (
                `file_refs`.`content_terms_of_use_id` IN ('', 'UNDEF_LICENSE')
                OR
                `file_refs`.`content_terms_of_use_id` IS NULL
            )";
        }

        if (($begin instanceof DateTime) && ($end instanceof DateTime)) {
            $active_sidebar_filters[] = 'course';
            $sql .= ' AND `file_refs`.`chdate` BETWEEN :begin AND :end ';
            $sql_params['begin'] = $begin->getTimestamp();
            $sql_params['end'] = $end->getTimestamp();
        }
        if ($course_id) {
            $sql .= ' AND `folders`.`range_id` = :course_id';
            $sql_params['course_id'] = $course_id;
        }
        $sql .= " ORDER BY `file_refs`.`chdate` DESC";
        if ($file_limit > 0) {
            $sql .= " LIMIT :file_limit";
            $sql_params['file_limit'] = $file_limit;
        }
        if ($file_offset > 0) {
            $sql .= " OFFSET :file_offset";
            $sql_params['file_offset'] = $file_offset;
        }
        return ['sql' => $sql, 'params' => $sql_params];
    }


    /**
     * Retrieves all uploaded files a user has uploaded.
     *
     * @see FileRef::getUploadedFilesSql for the parameter description.
     *
     * @returns FileRef[] A list of FileRef objects for files the user uploaded.
     */
    public static function findUploadedFiles($user_id, $begin = null, $end = null, $course_id = '', $unknown_license_only = false, $file_limit = 0, $file_offset = 0)
    {
        $sql_data = self::getUploadedFilesSql($user_id, $begin, $end, $course_id, $unknown_license_only, $file_limit, $file_offset);
        return self::findBySql($sql_data['sql'], $sql_data['params']);
    }


    /**
     * Counts all uploaded files a user has uploaded.
     *
     * @see FileRef::getUploadedFilesSql for the parameter description.
     *
     * @returns int The amount of files the user uploaded.
     */
    public static function countUploadedFiles($user_id, $begin = null, $end = null, $course_id = '', $unknown_license_only = false)
    {
        $sql_data = self::getUploadedFilesSql($user_id, $begin, $end, $course_id, $unknown_license_only);
        return self::countBySql($sql_data['sql'], $sql_data['params']);
    }
}