diff --git a/app/controllers/file.php b/app/controllers/file.php index b1ed35a0b8df3857d1c2c81d57524b9be15845ac..c53a2197b48fe7c0607643698a54915db921e8ba 100644 --- a/app/controllers/file.php +++ b/app/controllers/file.php @@ -461,11 +461,43 @@ class FileController extends AuthenticatedController $this->content_terms_of_use = $this->file->getTermsOfUse(); } + public function oer_post_upload_action($file_ref_id) + { + $this->file_ref_id = $file_ref_id; + PageLayout::setTitle(_('Datei für OER-Campus bereitstellen')); + + $this->semester_ende = date('d.m.Y', Semester::findCurrent()->ende); + + if (Request::isPost()) { + CSRFProtection::verifyUnsafeRequest(); + $oer_share = Request::int('oer_upload'); + $redirect = Request::get('redirect_to_files'); + + if ($oer_share === 1) { + // share now + return $this->share_oer_action($this->file_ref_id, $redirect); + } else if ($oer_share === 2) { + // save and send a reminder to share later + $oer_post_upload = new OERPostUpload(); + $oer_post_upload->file_ref_id = $this->file_ref_id; + $oer_post_upload->user_id = $GLOBALS['user']->id; + $oer_post_upload->reminder_date = Semester::findCurrent()->ende; + $oer_post_upload->store(); + $this->response->add_header('X-Dialog-Close', '1'); + $this->render_nothing(); + PageLayout::postSuccess(_('Erinnerung wurde gespeichert.')); + } else { + $this->response->add_header('X-Dialog-Close', '1'); + } + } + } + /** * The action for sharing a file on the oer campus */ - public function share_oer_action($file_ref_id) + public function share_oer_action($file_ref_id, $redirect = null) { + $this->redirect = $redirect; $this->file_ref = FileRef::find($file_ref_id); $this->file = $this->file_ref->getFileType(); @@ -486,9 +518,19 @@ class FileController extends AuthenticatedController 'image_tmp_name' => null ]; - $this->redirect("oer/mymaterial/edit"); + // only if you were in Dateibereich + if ($this->redirect === 'redirect_to_files') { + $_SESSION['NEW_OER']['redirect_url'] = 'files'; + $_SESSION['NEW_OER']['dir'] = $this->folder->getId(); + $_SESSION['NEW_OER']['cid'] = $this->folder->range_id; + } + + $this->redirect('oer/mymaterial/edit'); } + /** + * The action for suggesting a file for the oer campus to the file owner + */ public function suggest_oer_action($file_ref_id) { $this->file_ref_id = $file_ref_id; @@ -1611,6 +1653,20 @@ class FileController extends AuthenticatedController } } + if (count($file_ref_ids) === 1) { + if (Config::get()->OERCAMPUS_ENABLED + && Config::get()->OER_ENABLE_POST_UPLOAD + && $GLOBALS['perm']->have_perm('tutor') + ) { + if ($file_ref['content_terms_of_use_id'] === 'SELFMADE_NONPUB' + || $file_ref['content_terms_of_use_id'] === 'FREE_LICENSE' + ) { + $this->redirect('file/oer_post_upload/' . $file_ref['id']); + return; + } + } + } + if ($redirect) { $this->redirect($redirect); return; diff --git a/app/controllers/oer/mymaterial.php b/app/controllers/oer/mymaterial.php index 93b0cd40269d91eaf6d4f0cbc1e0d21ac6f1d94d..a240645c37c64fc7e05d6ba1b3d770e29711b1bc 100644 --- a/app/controllers/oer/mymaterial.php +++ b/app/controllers/oer/mymaterial.php @@ -145,17 +145,27 @@ class Oer_MymaterialController extends AuthenticatedController } unset($_SESSION['NEW_OER']); - PageLayout::postSuccess(_('Lernmaterial erfolgreich gespeichert.')); if (Request::get('redirect_url')) { - $this->redirect(URLHelper::getURL(Request::get('redirect_url'), [ - 'material_id' => $material->getId(), - 'url' => $this->url_for('oer/market/details/' . $material->id) - ])); + if (Request::get('redirect_url') === 'files') { + $this->redirect( + URLHelper::getURL( + 'dispatch.php/course/files/index/' . Request::get('dir'), + ['cid' => Request::get('cid')] + ) + ); + } else { + $this->redirect(URLHelper::getURL(Request::get('redirect_url'), [ + 'material_id' => $material->getId(), + 'url' => $this->url_for('oer/market/details/' . $material->id) + ])); + } } else { $this->redirect('oer/market/details/' . $material->id); } + + } if (isset($_SESSION['NEW_OER'])) { $this->template = $_SESSION['NEW_OER']; diff --git a/app/views/file/oer_post_upload.php b/app/views/file/oer_post_upload.php new file mode 100644 index 0000000000000000000000000000000000000000..8e5ca552d4eec46741afb9e1999c22960fb4ae3a --- /dev/null +++ b/app/views/file/oer_post_upload.php @@ -0,0 +1,78 @@ +<?php +if (!isset($selected_oer_upload)) { + $selected_oer_upload = 0; +} +?> +<form action="<?= $controller->link_for('file/oer_post_upload/', $file_ref_id)?>" + method="post" class="default" data-dialog="reload-on-close"> + <?= CSRFProtection::tokenTag() ?> + + <div id="select_oer_upload_info"> + <span><?= _('Wenn Sie möchten, können Sie die hochgeladene Datei für den OER-Campus bereitstellen.') ?></span> + <span><?= sprintf(_('Falls Sie die Datei zu einem späteren Zeitpunkt bereitstellen möchten, + wird Ihnen am Ende des Semesters (%s) eine Nachricht zugeschickt.'), $semester_ende) ?></span> + </div> + <fieldset class="select_oer_upload"> + <input type="radio" name="oer_upload" id="oer-upload-no" value="0" + <? if (0 == $selected_oer_upload) echo 'checked'; ?>> + <label for="oer-upload-no"> + <div class="icon"> + <?= Icon::create('decline')->asImg(32) ?> + </div> + <div class="text"> + <?= _('Nicht für den OER-Campus bereitstellen.') ?> + </div> + <?= Icon::create('arr_1down')->asImg(24, ['class' => 'arrow']) ?> + <?= Icon::create('check-circle')->asImg(32, ['class' => 'check']) ?> + </label> + <div class="oer_upload_description"> + <div class="description"> + <?= _('Ich möchte die hochgeladene Datei jetzt nicht im OER-Campus bereitstellen. + Ich habe jedoch später jederzeit die Möglichkeit dazu.') ?> + </div> + </div> + + <input type="radio" name="oer_upload" id="oer-upload-yes" value="1" + <? if (1 == $selected_oer_upload) echo 'checked'; ?>> + <label for="oer-upload-yes"> + <div class="icon"> + <?= Icon::create('accept')->asImg(32) ?> + </div> + <div class="text"> + <?= _('Jetzt für den OER-Campus bereitstellen.') ?> + </div> + <?= Icon::create('arr_1down')->asImg(24, ['class' => 'arrow']) ?> + <?= Icon::create('check-circle')->asImg(32, ['class' => 'check']) ?> + </label> + <div class="oer_upload_description"> + <div class="description"> + <?= _('Die Datei wird direkt im OER-Campus bereitgestellt. Sie ist dann neben vielen weiteren freien Lernmaterialien an allen Stud.IP-Standorten mit aktiviertem OER-Campus sichtbar.') ?> + </div> + </div> + + <input type="radio" name="oer_upload" id="oer-upload-later" value="2" + <? if (2 == $selected_oer_upload) echo 'checked'; ?>> + <label for="oer-upload-later"> + <div class="icon"> + <?= Icon::create('date')->asImg(32) ?> + </div> + <div class="text"> + <?= _('Zu einem späteren Zeitpunkt für den OER-Campus bereitstellen.') ?> + </div> + <?= Icon::create('arr_1down')->asImg(24, ['class' => 'arrow']) ?> + <?= Icon::create('check-circle')->asImg(32, ['class' => 'check']) ?> + </label> + <div class="oer_upload_description"> + <div class="description"> + <?= sprintf(_('Ich möchte am Semesterende (%s) daran erinnert werden, diese Datei gegebenenfalls im OER-Campus bereitzustellen.'), $semester_ende) ?> + </div> + </div> + </fieldset> + + <input type="hidden" name="redirect_to_files" value="redirect_to_files"> + <footer data-dialog-button> + <?= Studip\Button::createAccept(_('Speichern'))?> + <?= Studip\Button::createCancel(_('Abbrechen'))?> + + </footer> +</form> diff --git a/db/migrations/5.3.12_add_oer_post_upload_table.php b/db/migrations/5.3.12_add_oer_post_upload_table.php new file mode 100644 index 0000000000000000000000000000000000000000..1a2c88e7a82127cc1b4189d9e2a9ae5a403fc198 --- /dev/null +++ b/db/migrations/5.3.12_add_oer_post_upload_table.php @@ -0,0 +1,101 @@ +<?php +class AddOerPostUploadTable extends Migration +{ + public function description() + { + return "Adds table to create oer upload reminders and entry to cronjob schedule and task and config option"; + } + + public function up() + { + $db = DBmanager::Get(); + + $db->exec("CREATE TABLE IF NOT EXISTS `oer_post_upload` ( + `file_ref_id` char(32) CHARACTER SET latin1 COLLATE latin1_bin, + `user_id` char(32) CHARACTER SET latin1 COLLATE latin1_bin, + `reminder_date` int unsigned, + `mkdate` int(11) NOT NULL, + `chdate` int(11) NOT NULL, + PRIMARY KEY (`user_id`, `file_ref_id`) + )"); + + // Add default cron tasks and schedules + $new_job = [ + 'filename' => 'lib/cronjobs/remind_oer_upload.class.php', + 'class' => RemindOerUpload::class, + 'priority' => 'normal', + 'minute' => '0', + 'hour' => '1', + 'active' => '1' + ]; + + $query = "INSERT IGNORE INTO `cronjobs_tasks` + (`task_id`, `filename`, `class`, `active`) + VALUES (:task_id, :filename, :class, 1)"; + $task_statement = DBManager::get()->prepare($query); + + $query = "INSERT IGNORE INTO `cronjobs_schedules` + (`schedule_id`, `task_id`, `parameters`, `priority`, + `type`, `minute`, `hour`, `mkdate`, `chdate`, + `last_result`, `active`) + VALUES (:schedule_id, :task_id, '[]', :priority, 'periodic', + :minute, :hour, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), + NULL, :active)"; + $schedule_statement = DBManager::get()->prepare($query); + + + $task_id = md5(uniqid('task', true)); + + $task_statement->execute([ + ':task_id' => $task_id, + ':filename' => $new_job['filename'], + ':class' => $new_job['class'], + ]); + + $schedule_id = md5(uniqid('schedule', true)); + $schedule_statement->execute([ + ':schedule_id' => $schedule_id, + ':task_id' => $task_id, + ':priority' => $new_job['priority'], + ':hour' => $new_job['hour'], + ':minute' => $new_job['minute'], + ':active' => $new_job['active'] + ]); + + $query = "INSERT IGNORE INTO `config` + SET `field` = :field, + `value` = :value, + `type` = :type, + `range` = :range, + `section` = :section, + `mkdate` = UNIX_TIMESTAMP(), + `chdate` = UNIX_TIMESTAMP(), + `description` = :description"; + $config_statement = DBManager::get()->prepare($query); + + $config_statement->execute([ + ':field' => 'OER_ENABLE_POST_UPLOAD', + ':value' => '1', + ':type' => 'boolean', + ':range' => 'global', + ':section' => 'OERCampus', + ':description' => 'Post-Upload-Dialog nach Hochladen einer Datei erlauben?', + ]); + + } + + public function down() + { + CronjobTask::deleteBySQL('class = ?', [RemindOerUpload::class]); + + $query = "DROP TABLE `oer_post_upload`"; + DBManager::get()->exec($query); + + $query = "DELETE `config`, `config_values` + FROM `config` + LEFT JOIN `config_values` USING (`field`) + WHERE `field` = 'OER_ENABLE_POST_UPLOAD'"; + DBManager::get()->exec($query); + } + +} diff --git a/lib/cronjobs/remind_oer_upload.class.php b/lib/cronjobs/remind_oer_upload.class.php new file mode 100644 index 0000000000000000000000000000000000000000..9ac20e799d610717cbc4c2ab07a12d6194c26533 --- /dev/null +++ b/lib/cronjobs/remind_oer_upload.class.php @@ -0,0 +1,76 @@ +<?php +/** + * remind_oer_upload.class.php - Sends reminder emails for uploading files to OER Campus. + * + * @author Michaela Brückner <brueckner@data-quest.de>, Suchi & Berg GmbH <info@data-quest.de> + * @access public + * @since 5.2 + */ + +require_once 'lib/classes/CronJob.class.php'; + +class RemindOerUpload extends CronJob +{ + + public static function getName() + { + return _('An OER-Campus Upload erinnern'); + } + + public static function getDescription() + { + return _('Erinnert den Autor am Ende des Semesters an eine Datei, die in den OER-Campus hochgeladen werden soll.'); + } + + public function execute($last_result, $parameters = []) + { + // check the reminder date, which now is in past + $query = "SELECT `file_ref_id` FROM `oer_post_upload` + WHERE `reminder_date` < UNIX_TIMESTAMP()"; + $results = DBManager::get()->fetchAll($query); + + // get file information from file_ref_id + foreach ($results as $result) { + $file_ref = FileRef::find($result['file_ref_id']); + + if (!FileRef::countBySql('id = ?', [$result['file_ref_id']])) { + // file might be deleted meanwhile, so do not try to send a reminder for it + } else { + $filetype = $file_ref->getFileType(); + $file_to_suggest = $filetype->convertToStandardFile(); + + $this->author = $file_ref->owner->username; + $this->link_to_share = URLHelper::getURL('dispatch.php/file/share_oer/' . $result['file_ref_id']); + $this->linktext = _('Klicken Sie hier, um das Material im OER-Campus zu veröffentlichen.'); + $this->formatted_link = '['. $this->linktext .']' . $this->link_to_share; + + $oer_reminder_message = sprintf(_("Sie wollten daran erinnert werden, die folgende Datei im OER-Campus zu veröffentlichen:\n\n" + . "Dateiname: %s \n" + . "Beschreibung: %s \n" + . "%s \n\n"), + $file_to_suggest->getFilename(), + $file_to_suggest->getDescription(), + $this->formatted_link + ); + + $messaging = new messaging(); + + $messaging->insert_message( + $oer_reminder_message, + $this->author, + '____%system%____', + '', + Request::option('message_id'), + '', + null, + _('Erinnerung zur Veröffentlichung einer Datei im OER-Campus') + ); + + OERPostUpload::deleteBySQL("file_ref_id = ?", [$result['file_ref_id']]); + + } + + } + + } +} diff --git a/lib/models/OERPostUpload.php b/lib/models/OERPostUpload.php new file mode 100644 index 0000000000000000000000000000000000000000..8bf73863f6f2ec220a9146cb33bdfe86156fae50 --- /dev/null +++ b/lib/models/OERPostUpload.php @@ -0,0 +1,35 @@ +<?php + +/** + * Model class to handle reminder for possible OER upload files + * + * 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 Michaela Brückner <brueckner@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 5.3 + * @property string file_ref_id database column + * @property string user_id database column + * @property int reminder_date database column + * @property string mkdate database column + * @property string chdate database column + */ +class OERPostUpload extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'oer_post_upload'; + $config['belongs_to']['file'] = [ + 'class_name' => File::class, + 'foreign_key' => 'id', + 'on_delete' => 'delete' + ]; + + parent::configure($config); + } + +} diff --git a/resources/assets/stylesheets/less/files.less b/resources/assets/stylesheets/less/files.less index 9f9ae16898c439f29f77ccbeb54f730786bb1834..0b40c0b4e024de953afdbc76cea8384f3998e2b4 100644 --- a/resources/assets/stylesheets/less/files.less +++ b/resources/assets/stylesheets/less/files.less @@ -193,11 +193,13 @@ div.file_select_possibilities, .folder_type_select_possibilities { /* for file/edit view only: */ - input[name=content_terms_of_use_id] { + input[name=content_terms_of_use_id], + input[name=oer_upload] { display: none; } - input[name=content_terms_of_use_id]:checked + label { + input[name=content_terms_of_use_id]:checked + label, + input[name=oer_upload]:checked + label { background-color: @brand-color-darker; color: @contrast-content-white; @@ -400,72 +402,81 @@ table.documents { } -form.default fieldset.select_terms_of_use { - > legend { - margin: 0px; - width: 100%; +form.default { + #select_oer_upload_info { + padding-top: 15px; + padding-bottom: 15px; } - border: none; - padding: 0px; - margin-left: 0px; - margin-right: 0px; - > input[type=radio] { - opacity: 0; - position: absolute; - &:focus + label { - outline: auto; - } - } - > label { - cursor: pointer; - border: 1px solid @content-color-40; - transition: background-color 200ms; - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px; - padding-bottom: 2px; - margin-bottom: 0; - border-top: none; - > .text { + fieldset.select_terms_of_use, + fieldset.select_oer_upload { + > legend { + margin: 0px; width: 100%; - margin-left: 10px; } - > .arrow { - margin-right: 5px; + border: none; + padding: 0px; + margin-left: 0px; + margin-right: 0px; + + > input[type=radio] { + opacity: 0; + position: absolute; + &:focus + label { + outline: auto; + } } - > .check { - display: none; + > label { + cursor: pointer; + border: 1px solid @content-color-40; + transition: background-color 200ms; + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px; + padding-bottom: 2px; + margin-bottom: 0; + border-top: none; + > .text { + width: 100%; + margin-left: 10px; + } + > .arrow { + margin-right: 5px; + } + > .check { + display: none; + } } - } - > label:first-of-type { - border-top: 1px solid @content-color-40; - } - > div { - border: 1px solid @content-color-40; - border-top: none; - display: none; - padding: 10px; - - } - > input[type=radio]:checked + label { - background-color: @content-color-20; - transition: background-color 200ms; - > .arrow { + > label:first-of-type { + border-top: 1px solid @content-color-40; + } + > div { + border: 1px solid @content-color-40; + border-top: none; display: none; + padding: 10px; + } - > .check { - display: inline-block; + > input[type=radio]:checked + label { + background-color: @content-color-20; + transition: background-color 200ms; + > .arrow { + display: none; + } + > .check { + display: inline-block; + } } - } - > input[type=radio]:checked + label + div { - display: block; - .description { - animation-duration: 400ms; - animation-name: terms_of_use_fadein; + > input[type=radio]:checked + label + div { + display: block; + .description { + animation-duration: 400ms; + animation-name: terms_of_use_fadein; + } } } + } @keyframes terms_of_use_fadein {