diff --git a/.htaccess b/.htaccess index 08513b37af2f..a2a32b63e1f8 100644 --- a/.htaccess +++ b/.htaccess @@ -12,7 +12,8 @@ RewriteCond %{HTTP_USER_AGENT} ^(DavClnt)$ RewriteCond %{REQUEST_METHOD} ^(OPTIONS)$ RewriteRule .* "-" [R=401,L] - + + RedirectMatch 404 /\.git diff --git a/Modules/Blog/classes/class.ilBlogPostingGUI.php b/Modules/Blog/classes/class.ilBlogPostingGUI.php index 9e2c0646e392..b4f32858ffdc 100644 --- a/Modules/Blog/classes/class.ilBlogPostingGUI.php +++ b/Modules/Blog/classes/class.ilBlogPostingGUI.php @@ -801,6 +801,8 @@ public function saveKeywordsForm() if ($this->checkAccess("write") || $this->checkAccess("contribute")) { if (is_array($keywords)) { $this->getBlogPosting()->updateKeywords($keywords); + } else { + $this->getBlogPosting()->updateKeywords([]); } } diff --git a/Modules/BookingManager/classes/class.ilBookingObjectsTableGUI.php b/Modules/BookingManager/classes/class.ilBookingObjectsTableGUI.php index 84e9e1aef22a..6ca5540c5459 100644 --- a/Modules/BookingManager/classes/class.ilBookingObjectsTableGUI.php +++ b/Modules/BookingManager/classes/class.ilBookingObjectsTableGUI.php @@ -376,17 +376,23 @@ protected function fillRow($a_set) // note: this call is currently super expensive // see #26388, it has been performed even for users without edit permissions before // now the call has been moved here, but still this needs improvement - if (!empty(ilBookingParticipant::getAssignableParticipants($a_set["booking_object_id"]))) { + // EDIT: deactivated for now due to performance reasons + // if (!empty(ilBookingParticipant::getAssignableParticipants($a_set["booking_object_id"]))) { if (is_object($this->filter['period']['from'])) { - $ilCtrl->setParameter($this->parent_obj, 'sseed', - $this->filter['period']['from']->get(IL_CAL_DATE)); + $ilCtrl->setParameter( + $this->parent_obj, + 'sseed', + $this->filter['period']['from']->get(IL_CAL_DATE) + ); } - $items[] = $this->ui_factory->button()->shy($lng->txt('book_assign_participant'), - $ilCtrl->getLinkTarget($this->parent_obj, 'assignParticipants')); + $items[] = $this->ui_factory->button()->shy( + $lng->txt('book_assign_participant'), + $ilCtrl->getLinkTarget($this->parent_obj, 'assignParticipants') + ); $ilCtrl->setParameter($this->parent_obj, 'sseed', ''); - } + //} } if ($a_set['info_file']) { diff --git a/Modules/BookingManager/classes/class.ilBookingReservationsTableGUI.php b/Modules/BookingManager/classes/class.ilBookingReservationsTableGUI.php index 477652cef76a..7196457a66f6 100644 --- a/Modules/BookingManager/classes/class.ilBookingReservationsTableGUI.php +++ b/Modules/BookingManager/classes/class.ilBookingReservationsTableGUI.php @@ -500,20 +500,65 @@ public function getItems(array $filter) return $d['user_id']; }, $data)); + $user_columns = []; + $odf_ids = []; + foreach ($this->getSelectedUserColumns() as $field) { + if (substr($field, 0, 3) == 'odf') { + $odf_ids[] = substr($field, 4); + } else { + $user_columns[] = $field; + } + } + // user data fields $query = new ilUserQuery(); $query->setLimit(9999); - $query->setAdditionalFields($this->getSelectedUserColumns()); + $query->setAdditionalFields($user_columns); $query->setUserFilter($user_ids); $ud = $query->query(); + $usr_data = []; foreach ($ud["set"] as $v) { - foreach ($this->getSelectedUserColumns() as $c) { + foreach ($user_columns as $c) { $usr_data[$v["usr_id"]][$c] = $v[$c]; } } foreach ($data as $key => $v) { - $data[$key] = array_merge($v, $usr_data[$v["user_id"]]); + if (isset($usr_data[$v["user_id"]])) { + $data[$key] = array_merge($v, $usr_data[$v["user_id"]]); + } + } + + // object specific user data fields of parent course or group + if ($odf_ids) { + $parent = $this->getParentGroupCourse(); + $parent_obj_id = ilObject::_lookupObjectId($parent['ref_id']); + $parent_obj_type = ilObject::_lookupType($parent_obj_id); + + $confirmation_required = ($parent_obj_type == 'crs') + ? ilPrivacySettings::_getInstance()->courseConfirmationRequired() + : ilPrivacySettings::_getInstance()->groupConfirmationRequired(); + if ($confirmation_required) { + $user_ids = array_diff($user_ids, ilMemberAgreement::lookupAcceptedAgreements($parent_obj_id)); + } + $odf_data = ilCourseUserData::_getValuesByObjId($parent_obj_id); + + $usr_data = []; + foreach ($odf_data as $usr_id => $fields) { + if (in_array($usr_id, $user_ids)) { + foreach ($fields as $field_id => $value) { + if (in_array($field_id, $odf_ids)) { + $usr_data[$usr_id]['odf_' . $field_id] = $value; + } + } + } + } + + foreach ($data as $key => $v) { + if (isset($usr_data[$v["user_id"]])) { + $data[$key] = array_merge($v, $usr_data[$v["user_id"]]); + } + } } } @@ -628,6 +673,17 @@ protected function fillRow($a_set) $this->tpl->setVariable("URL_ACTION", $ilCtrl->getLinkTarget($this->parent_obj, 'rsvConfirmCancel')); $ilCtrl->setParameter($this->parent_obj, 'reservation_id', ""); $this->tpl->setVariable("TXT_ACTION", $lng->txt('book_set_cancel')); + $this->tpl->setCurrentBlock("action"); + $this->tpl->parseCurrentBlock(); + } + + if ($ilAccess->checkAccess('write', '', $this->ref_id)) { + $ilCtrl->setParameter($this->parent_obj, 'reservation_id', $a_set['booking_reservation_id']); + $this->tpl->setVariable("URL_ACTION", $ilCtrl->getLinkTarget($this->parent_obj, 'rsvConfirmDelete')); + $ilCtrl->setParameter($this->parent_obj, 'reservation_id', ""); + $this->tpl->setVariable("TXT_ACTION", $lng->txt('delete')); + $this->tpl->setCurrentBlock("action"); + $this->tpl->parseCurrentBlock(); } /* advsellist version diff --git a/Modules/BookingManager/classes/class.ilObjBookingPoolGUI.php b/Modules/BookingManager/classes/class.ilObjBookingPoolGUI.php index 7b4548ffe7af..2c398bd28598 100644 --- a/Modules/BookingManager/classes/class.ilObjBookingPoolGUI.php +++ b/Modules/BookingManager/classes/class.ilObjBookingPoolGUI.php @@ -1648,6 +1648,73 @@ public function rsvCancelObject() $this->logObject(); } + public function rsvConfirmDeleteObject() + { + global $DIC; + if (!$this->checkPermissionBool("write")) { + ilUtil::sendFailure($this->lng->txt('permission_denied'), true); + $this->ctrl->redirect($this, 'log'); + } + + $this->tabs_gui->clearTargets(); + $this->tabs_gui->setBackTarget( + $this->lng->txt("back"), + $this->ctrl->getLinkTarget($this, "log") + ); + + include_once 'Services/Utilities/classes/class.ilConfirmationGUI.php'; + $conf = new ilConfirmationGUI(); + $conf->setFormAction($this->ctrl->getFormAction($this, 'rsvDelete')); + $conf->setHeaderText($this->lng->txt('book_confirm_delete')); + $conf->setConfirm($this->lng->txt('book_set_delete'), 'rsvDelete'); + $conf->setCancel($this->lng->txt('cancel'), 'log'); + + list($obj_id, $user_id, $from, $to) = explode("_", $DIC->http()->request()->getQueryParams()['reservation_id']); + $ids = ilBookingReservation::getCancelDetails($obj_id, $user_id, $from, $to); + $rsv = new ilBookingReservation($ids[0]); + $obj = new ilBookingObject($rsv->getObjectId()); + + $details = sprintf($this->lng->txt('X_reservations_of'), count($ids)) . ' ' . $obj->getTitle(); + if ($this->object->getScheduleType() != ilObjBookingPool::TYPE_NO_SCHEDULE) { + $details .= ", " . ilDatePresentation::formatPeriod( + new ilDateTime($rsv->getFrom(), IL_CAL_UNIX), + new ilDateTime($rsv->getTo() + 1, IL_CAL_UNIX) + ); + } + + $conf->addItem('rsv_ids', implode(',', $ids), $details); + $this->tpl->setContent($conf->getHTML()); + } + + public function rsvDeleteObject() + { + global $DIC; + $get = $DIC->http()->request()->getParsedBody()['rsv_ids']; + if ($get) { + include_once 'Modules/BookingManager/classes/class.ilBookingReservation.php'; + foreach (explode(',', $get) as $id) { + $res = new ilBookingReservation($id); + $obj = new ilBookingObject($res->getObjectId()); + if ($obj->getPoolId() != $this->object->getId() || !$this->checkPermissionBool("write")) { + ilUtil::sendFailure($this->lng->txt('permission_denied'), true); + $this->ctrl->redirect($this, 'log'); + } + if ($this->object->getScheduleType() != ilObjBookingPool::TYPE_NO_SCHEDULE) { + $cal_entry_id = $res->getCalendarEntry(); + if ($cal_entry_id) { + include_once 'Services/Calendar/classes/class.ilCalendarEntry.php'; + $entry = new ilCalendarEntry($cal_entry_id); + $entry->delete(); + } + } + $res->delete(); + } + } + + ilUtil::sendSuccess($this->lng->txt('reservation_deleted')); + $this->logObject(); + } + public function rsvInUseObject() { $this->checkPermission("write"); diff --git a/Modules/BookingManager/classes/class.ilScheduleInputGUI.php b/Modules/BookingManager/classes/class.ilScheduleInputGUI.php index d5b4f5ac3583..a5bfac4f08e9 100755 --- a/Modules/BookingManager/classes/class.ilScheduleInputGUI.php +++ b/Modules/BookingManager/classes/class.ilScheduleInputGUI.php @@ -134,7 +134,7 @@ public function checkInput() public static function getPostData($a_post_var, $a_remove_invalid = true) { $res = array(); - for ($loop = 0; $loop < 24; $loop++) { + for ($loop = 0; $loop < 240; $loop++) { $days = $_POST[$a_post_var . "_days~" . $loop]; $from = self::parseTime( $_POST[$a_post_var . "_from_hh~" . $loop], diff --git a/Modules/BookingManager/templates/default/tpl.booking_reservation_row.html b/Modules/BookingManager/templates/default/tpl.booking_reservation_row.html index 8dd9246303ff..17554ad6a8c5 100644 --- a/Modules/BookingManager/templates/default/tpl.booking_reservation_row.html +++ b/Modules/BookingManager/templates/default/tpl.booking_reservation_row.html @@ -48,6 +48,8 @@ + {TXT_ACTION} + diff --git a/Modules/Category/classes/class.ilCategoryXmlWriter.php b/Modules/Category/classes/class.ilCategoryXmlWriter.php index 7b018365e827..eedc1d757412 100644 --- a/Modules/Category/classes/class.ilCategoryXmlWriter.php +++ b/Modules/Category/classes/class.ilCategoryXmlWriter.php @@ -137,17 +137,15 @@ protected function buildTranslations() $this->xmlStartTag('Translations'); $translations = $this->getCategory()->getObjectTranslation()->getLanguages(); - - foreach ((array) $translations as $translation) { $this->xmlStartTag( 'Translation', array( 'default' => (int) $translation['lang_default'], - 'language' => $translation['lang']) + 'language' => $translation['lang_code']) ); $this->xmlElement('Title', array(), $translation['title']); - $this->xmlElement('Description', array(), $translation['desc']); + $this->xmlElement('Description', array(), $translation['description']); $this->xmlEndTag('Translation'); } $this->xmlEndTag('Translations'); diff --git a/Modules/Category/classes/class.ilObjCategoryListGUI.php b/Modules/Category/classes/class.ilObjCategoryListGUI.php index 97266466a5df..ba810acba8fa 100644 --- a/Modules/Category/classes/class.ilObjCategoryListGUI.php +++ b/Modules/Category/classes/class.ilObjCategoryListGUI.php @@ -145,4 +145,12 @@ public function getCommandLink($a_cmd) return $cmd_link; } + + /** + * @inheritDoc + */ + public function checkInfoPageOnAsynchronousRendering() : bool + { + return true; + } } // END class.ilObjCategoryGUI diff --git a/Modules/Chatroom/chat/Bootstrap/SetupEnvironment.js b/Modules/Chatroom/chat/Bootstrap/SetupEnvironment.js index d3d8828ce50e..82c8858e993d 100644 --- a/Modules/Chatroom/chat/Bootstrap/SetupEnvironment.js +++ b/Modules/Chatroom/chat/Bootstrap/SetupEnvironment.js @@ -2,6 +2,7 @@ var CONST = require('../Constants'); var Container = require('../AppContainer'); var Winston = require('winston'); var Util = require('util'); +var DateHelper = require('../Helper/Date'); /** @@ -37,9 +38,9 @@ module.exports = function SetupEnvironment(result, callback) { filename: logFile, level: logLevel, json: false, - timestamp: function(){ - var date = new Date(); - return date.toDateString() + ' ' + date.toTimeString(); + timestamp: function() { + const t = new Date(); + return DateHelper.iso8601DatetimeFormat(t) + DateHelper.iso8601TimezoneFormat(t); }, formatter: function(options) { return Util.format( @@ -61,8 +62,8 @@ module.exports = function SetupEnvironment(result, callback) { humanReadableUnhandledException: true, json: false, timestamp: function(){ - var date = new Date(); - return date.toDateString() + ' ' + date.toTimeString(); + const t = new Date(); + return DateHelper.iso8601DatetimeFormat(t) + DateHelper.iso8601TimezoneFormat(t); }, formatter: function(options) { return Util.format( diff --git a/Modules/Chatroom/chat/Handler/AccessHandler.js b/Modules/Chatroom/chat/Handler/AccessHandler.js index 73221be49269..c691536196ab 100644 --- a/Modules/Chatroom/chat/Handler/AccessHandler.js +++ b/Modules/Chatroom/chat/Handler/AccessHandler.js @@ -43,7 +43,7 @@ AccessHandler.prototype.canAccessRoom = function(socket, subscriberId, roomId) { * @param {Socket} socket */ AccessHandler.prototype.disconnect = function(socket) { - Container.getLogger().info('Disconnected socket %s', socket.id); + Container.getLogger().debug('Disconnected socket %s', socket.id); socket.disconnect(); }; diff --git a/Modules/Chatroom/chat/Handler/AuthenticationHandler.js b/Modules/Chatroom/chat/Handler/AuthenticationHandler.js index 9f2ce4bcc0ce..b96a38c85fcc 100644 --- a/Modules/Chatroom/chat/Handler/AuthenticationHandler.js +++ b/Modules/Chatroom/chat/Handler/AuthenticationHandler.js @@ -9,7 +9,7 @@ module.exports = function(req, res, next) { Container.getLogger().warn('Access denied cause of no permission for %s', req.params.namespace); _accessDenied(res); } else { - Container.getLogger().info('Access granted for %s', req.params.namespace); + Container.getLogger().debug('Access granted for %s', req.params.namespace); next(); } }; diff --git a/Modules/Chatroom/chat/Handler/IMSocketHandler.js b/Modules/Chatroom/chat/Handler/IMSocketHandler.js index 8c7f2f82795e..1987e01550ac 100644 --- a/Modules/Chatroom/chat/Handler/IMSocketHandler.js +++ b/Modules/Chatroom/chat/Handler/IMSocketHandler.js @@ -2,7 +2,7 @@ var Container = require('../AppContainer'); module.exports = function(socket) { - Container.getLogger().info('New IM Connection with SocketId: %s', socket.id); + Container.getLogger().debug('New IM Connection with SocketId: %s', socket.id); socket.on('login', _getTask('ConversationLogin')); socket.on('conversations', _getTask('ListConversations')); diff --git a/Modules/Chatroom/chat/Handler/SocketHandler.js b/Modules/Chatroom/chat/Handler/SocketHandler.js index 6ea550f42514..b6a03ef8d57a 100644 --- a/Modules/Chatroom/chat/Handler/SocketHandler.js +++ b/Modules/Chatroom/chat/Handler/SocketHandler.js @@ -2,7 +2,7 @@ var Container = require('../AppContainer'); module.exports = function(socket) { - Container.getLogger().info('New Connection with SocketId: %s', socket.id); + Container.getLogger().debug('New Connection with SocketId: %s', socket.id); socket.on('login', _getTask('Login')); socket.on('enterRoom', _getTask('EnterRoom')); diff --git a/Modules/Chatroom/chat/Helper/Date.js b/Modules/Chatroom/chat/Helper/Date.js index 88edd73ae5d3..18f685a22d14 100644 --- a/Modules/Chatroom/chat/Helper/Date.js +++ b/Modules/Chatroom/chat/Helper/Date.js @@ -16,6 +16,51 @@ DateHelper.prototype.getTimestamp = function() { return date.getTime(); }; +DateHelper.prototype.iso8601TimezoneFormat = function(date) { + let timezone_offset_i = date.getTimezoneOffset(), + offset_H = parseInt(Math.abs(timezone_offset_i / 60)), + offset_i = Math.abs(timezone_offset_i % 60), + timezone_standard; + + if (offset_H < 10) { + offset_H = '0' + offset_H; + } + + if (offset_i < 10) { + offset_i = '0' + offset_i; + } + + if (timezone_offset_i < 0) { + timezone_standard = '+' + offset_H + ':' + offset_i; + } else if (timezone_offset_i > 0) { + timezone_standard = '-' + offset_H + ':' + offset_i; + } else if (timezone_offset_i === 0) { + timezone_standard = 'Z'; + } + + return timezone_standard +}; + +DateHelper.prototype.iso8601DatetimeFormat = function(date) { + let Y = date.getFullYear(), + m = date.getMonth() + 1, + d = date.getDate(), + H = date.getHours(), + i = date.getMinutes(), + s = date.getSeconds(); + + d = d < 10 ? '0' + d : d; + m = m < 10 ? '0' + m : m; + H = H < 10 ? '0' + H : H; + i = i < 10 ? '0' + i : i; + s = s < 10 ? '0' + s : s; + + return [ + Y + '-' + m + '-' + d + + 'T' + H + ':' + i + ':' + s + ].join(""); +}; + /** * @type {DateHelper} */ diff --git a/Modules/Chatroom/chat/Model/Conversation.js b/Modules/Chatroom/chat/Model/Conversation.js index 403966387930..922a3337a6c4 100644 --- a/Modules/Chatroom/chat/Model/Conversation.js +++ b/Modules/Chatroom/chat/Model/Conversation.js @@ -175,15 +175,29 @@ var Conversation = function Conversation(id, participants) } let id = val.id; + if (typeof val.getId === 'function') { + id = val.getId(); + } + if (typeof id === "undefined") { return false; } - if (typeof val.getId === 'function') { - id = val.getId(); + if (typeof participant.getId !== 'function') { + Container.getLogger().warn( + "Invalid participant object: Type = %s / Variable = %s", + typeof participant, + JSON.stringify(participant) + ); + return false; + } + + let participantId = participant.getId(); + if (typeof participantId === "undefined") { + return false; } - if (id.toString() === participant.getId().toString()) { + if (id.toString() === participantId.toString()) { return true; } diff --git a/Modules/Chatroom/chat/SocketTasks/ConversationActivity.js b/Modules/Chatroom/chat/SocketTasks/ConversationActivity.js index 1b0da51722d3..255e38dd7e18 100644 --- a/Modules/Chatroom/chat/SocketTasks/ConversationActivity.js +++ b/Modules/Chatroom/chat/SocketTasks/ConversationActivity.js @@ -13,7 +13,7 @@ module.exports = function(conversationId, userId, timestamp) { if (conversation !== null && conversation.isParticipant(this.participant)) { namespace.getDatabase().trackActivity(conversationId, userId, timestamp); - Container.getLogger().info('Track Activity for user %s in %s: %s', userId, conversationId, timestamp); + Container.getLogger().debug('Track Activity for user %s in %s: %s', userId, conversationId, timestamp); } } }; \ No newline at end of file diff --git a/Modules/Chatroom/chat/SocketTasks/ConversationHistory.js b/Modules/Chatroom/chat/SocketTasks/ConversationHistory.js index a2ef529c7526..40fed9490036 100644 --- a/Modules/Chatroom/chat/SocketTasks/ConversationHistory.js +++ b/Modules/Chatroom/chat/SocketTasks/ConversationHistory.js @@ -31,7 +31,7 @@ module.exports = function(conversationId, oldestMessageTimestamp) { socket.participant.emit('history', json); - Container.getLogger().info('Requested History for %s since %s', conversationId, oldestMessageTimestamp); + Container.getLogger().debug('Requested History for %s since %s', conversationId, oldestMessageTimestamp); } namespace.getDatabase().loadConversationHistory( diff --git a/Modules/Chatroom/chat/SocketTasks/ConversationLogin.js b/Modules/Chatroom/chat/SocketTasks/ConversationLogin.js index 7474acbbc7c5..e5c9b8d38165 100644 --- a/Modules/Chatroom/chat/SocketTasks/ConversationLogin.js +++ b/Modules/Chatroom/chat/SocketTasks/ConversationLogin.js @@ -6,7 +6,7 @@ module.exports = function(id, name) { var namespace = Container.getNamespace(this.nsp.name); - Container.getLogger().info('Participant %s connected for namespace %s', name, namespace.getName()); + Container.getLogger().debug('Participant %s connected for namespace %s', name, namespace.getName()); var participant = namespace.getSubscriber(id); diff --git a/Modules/Chatroom/chat/SocketTasks/Disconnect.js b/Modules/Chatroom/chat/SocketTasks/Disconnect.js index 9a8acad7e98c..0a405eaa2696 100644 --- a/Modules/Chatroom/chat/SocketTasks/Disconnect.js +++ b/Modules/Chatroom/chat/SocketTasks/Disconnect.js @@ -70,7 +70,7 @@ module.exports = function() Container.removeTimeout(subscriberId); } - Container.setTimeout(subscriberId, subscriberLeftNamespaceHandler, 5000); + Container.setTimeout(subscriberId, subscriberLeftNamespaceHandler, 15000); this.subscriber.removeSocketId(this.id); }; diff --git a/Modules/Chatroom/chat/SocketTasks/ListConversations.js b/Modules/Chatroom/chat/SocketTasks/ListConversations.js index 155e55f8e248..25be74e1c9f9 100644 --- a/Modules/Chatroom/chat/SocketTasks/ListConversations.js +++ b/Modules/Chatroom/chat/SocketTasks/ListConversations.js @@ -2,7 +2,7 @@ var Container = require('../AppContainer'); var async = require('async'); module.exports = function() { - Container.getLogger().info('Requested Conversations list'); + Container.getLogger().debug('Requested Conversations list'); var namespace = Container.getNamespace(this.nsp.name); var conversations = this.participant.getConversations(); diff --git a/Modules/Chatroom/classes/class.ilChatroomUser.php b/Modules/Chatroom/classes/class.ilChatroomUser.php index e5be7f83e034..93db5b41b25b 100644 --- a/Modules/Chatroom/classes/class.ilChatroomUser.php +++ b/Modules/Chatroom/classes/class.ilChatroomUser.php @@ -143,7 +143,7 @@ public function buildShortname() { $firstname = $this->user->getFirstname(); - return $firstname{0} . '. ' . $this->user->getLastname(); + return $firstname[0] . '. ' . $this->user->getLastname(); } /** diff --git a/Modules/Cloud/js/ilCloudFileList.js b/Modules/Cloud/js/ilCloudFileList.js index 627de1f1d59c..d5cfc28c2d04 100644 --- a/Modules/Cloud/js/ilCloudFileList.js +++ b/Modules/Cloud/js/ilCloudFileList.js @@ -54,6 +54,11 @@ function ilCloudFileList(url_get_block, url_create_folder, url_upload_file, url_ $('#xcld_block_' + id).hide(); } + // is currently loading + this.isLoading = function () { + return $("#loading_div_background").is(':visible'); + } + //show Block with Id this.removeBlock = function (id) { $('#xcld_block_' + id).remove(); @@ -370,10 +375,10 @@ function ilCloudFileList(url_get_block, url_create_folder, url_upload_file, url_ }); $.address.change(function (event) { - if (event.pathNames[0] == "delete_item") { + if (event.pathNames[0] === "delete_item") { self.deleteItem(event.parameters.id); } - else if (uploading == false) { + else if (uploading === false && !self.isLoading()) { self.hideBlock(current_id); event.parameters.current_id ? current_id = event.parameters.current_id : current_id = current_id; event.parameters.current_path ? current_path = decodeURIComponent((event.parameters.current_path + '').replace(/\+/g, '%20')) : current_path = current_path; diff --git a/Modules/Cloud/js/ilCloudFileList.min.js b/Modules/Cloud/js/ilCloudFileList.min.js index 1f306a4664dd..638bc1f084a4 100644 --- a/Modules/Cloud/js/ilCloudFileList.min.js +++ b/Modules/Cloud/js/ilCloudFileList.min.js @@ -1 +1 @@ -function ilCloudFileList(a,b,c,d,e,f,g,h,i){var a=a,b=b,c=c,d=d,j=e,e=e,g=g,f=f,h=h,i=i,k=this,l=!1;this.showDebugMessage=function(a){console.log("cld: "+a)},this.getCurrentId=function(){return g},this.setRootPath=function(a){f=a},this.showBlock=function(a){$("#xcld_block_"+a).show(),$("#xcld_locator_"+a).show()},this.hideBlock=function(a){$("#xcld_block_"+a).hide()},this.removeBlock=function(a){$("#xcld_block_"+a).remove(),$("#xcld_locator_"+a).remove()},this.showItemCreationList=function(){$("#xcld_toolbar").show(),$("#il_add_new_cld_item_v").show()},this.hideItemCreationList=function(){$("#xcld_toolbar").hide(),$("#il_add_new_cld_item_v").hide()},this.toggleTabs=function(){g==e?($("#tab_settings").show(),$("#tab_id_permissions").show(),$("#tab_id_info").show()):($("#tab_settings").hide(),$("#tab_id_permissions").hide(),$("#tab_id_info").hide())},this.hideMessage=function(){k.showDebugMessage("hideMessage"),$("#xcld_message").hide()},this.showMessage=function(a){k.showDebugMessage("showMessage"),$("#xcld_message").html(a),$("#xcld_message").show(),display_message=!0},this.showCurrent=function(b,c,d){b||this.hideMessage(),perm_link=$("#current_perma_link").val(),escaped_current_path=encodeURIComponent(encodeURIComponent(h)),perm_link=perm_link.replace(/path_.*_endPath/,"path_"+escaped_current_path+"_endPath"),$("#current_perma_link").val(perm_link),this.toggleTabs(),0<$("#xcld_block_"+g).length?(this.showDebugMessage("showCurrent: Block already exists, not going Ajax. id="+g),$(".xcld_locator").hide(),this.showBlock(g),this.hideProgressAnimation(),c instanceof Function&&c(this,d)):(this.showProgressAnimation(),$.ajax({type:"POST",url:a.replace(/&/g,"&"),data:{id:g,path:h}}).done(function(a){a.success?(k.showDebugMessage("showCurrent: Block did not exist, successfull ajax request. id="+g+" path= "+h),$("#xcld_blocks").append(a.content),$(".xcld_locator").hide(),$(".ilToolbarSeparator").before(a.locator)):(k.showDebugMessage("showCurrent: Block did not exist, not successfull ajax request. id="+g+" path= "+h),a.message?(k.showDebugMessage("showCurrent: Block did not exist, not successfull ajax request. message="+a.message),k.showMessage(a.message)):(k.showDebugMessage("showCurrent: Block did not exist, not successfull ajax request. data="+a),k.showMessage(a)),k.hideItemCreationList()),c instanceof Function&&c(k,d),k.hideProgressAnimation()}))},this.deleteItem=function(a){this.clicked_delete_item||(this.clicked_delete_item=!0,this.hideMessage(),$.ajax({type:"POST",url:d.replace(/&/g,"&"),data:{id:a}}).done(function(a){k.clicked_delete_item=!1,a.success?(k.showDebugMessage("deleteItem: Form successfully created per ajax. id="+g+" path= "+h),k.hideBlock(g),k.hideItemCreationList(),$("#xcld_blocks").append(a.content),$("input[name='cmd[deleteItem]']").click(function(){k.showProgressAnimation()})):a.message?(k.showDebugMessage("deleteItem: Form not successfully created per ajax. message="+a.message),k.showMessage(a.message)):(k.showDebugMessage("deleteItem: Form not successfully created per ajax. data="+a),k.showMessage(a))}))},this.afterDeleteItem=function(a){if(this.clicked_delete_item=!1,a.success||"cancel"==a.status){var b=function(a,b){$("#cld_delete_item").remove(),a.showItemCreationList(),"cancel"==b.status?a.showDebugMessage("afterDeleteItem: Deletion cancelled."):b.success&&(a.showDebugMessage("afterDeleteItem: Item successfully deleted."),a.showMessage(b.message))};a.success?(k.removeBlock(g),this.showCurrent(!0,b,a)):this.showCurrent(!1,b,a)}else a.message?(k.showDebugMessage("afterDeleteItem: Deletion of item failed. message="+a.message),k.showMessage(a.message)):(k.showDebugMessage("afterDeleteItem: Deletion of Item failed. data="+a),k.showMessage(a)),display_message=!1,k.hideProgressAnimation()},this.createFolder=function(){this.clicked_create_folder||(this.clicked_create_folder=!0,this.hideMessage(),$.ajax({type:"POST",url:b.replace(/&/g,"&"),data:{id:g}}).done(function(a){a.success?(k.clicked_create_folder=!0,k.showDebugMessage("createFolder: Form successfully created per ajax. id="+g+" path= "+h),k.hideBlock(g),k.hideItemCreationList(),$("#xcld_blocks").append(a.content),$("input[name='cmd[createFolder]']").click(function(){k.showProgressAnimation()})):a.message?(k.showDebugMessage("createFolder: Form not successfully created per ajax. message="+a.message),k.showMessage(a.message)):(k.showDebugMessage("createFolder: Form not successfully created per ajax. data="+a),k.showMessage(a))}))},this.afterCreateFolder=function(a){if(k.clicked_create_folder=!1,a.success||"cancel"==a.status){var b=function(a,b){$("#form_cld_create_folder").remove(),a.showItemCreationList(),"cancel"==b.status?a.showDebugMessage("afterCreateFolder: Creation cancelled."):b.success&&(a.showDebugMessage("afterCreateFolder: Folder successfully created."),window.location.hash=$("#xcld_folder_"+b.folder_id).find("a:first").attr("href"),a.showMessage(b.message))};a.success?(k.removeBlock(g),this.showCurrent(!0,b,a)):this.showCurrent(!1,b,a)}else a.message?(k.showDebugMessage("afterCreateFolder: Creation of folder failed. message="+a.message),k.showMessage(a.message)):(k.showDebugMessage("afterCreateFolder: Creation of folder failed. data="+a),k.showMessage(a)),display_message=!1,k.hideProgressAnimation()},this.uploadFile=function(){this.clicked_upload_item||(this.clicked_upload_item=!0,l=!0,this.hideMessage(),this.showProgressAnimation(),$.ajax({type:"POST",url:c.replace(/&/g,"&"),data:{folder_id:g}}).done(function(a){k.hideProgressAnimation(),k.removeBlock(g),k.hideItemCreationList(),$("#xcld_blocks").append(a),$(".ilFileUploadToggleOptions").click(function(){$(".ilFileUploadEntryDescription").remove()}),$(".ilFileUploadContainer").children(".ilFormInfo").html(i)}))},this.afterUpload=function(a){k.clicked_upload_item=!1,k.showDebugMessage("afterUpload: "+a),this.showCurrent(!1,function(a){$("#form_upload").remove(),a.showItemCreationList(),l=!1})},this.showProgressAnimation=function(){$("#loading_div_background").show()},this.hideProgressAnimation=function(){$("#loading_div_background").hide()},$("#xcld_message").insertBefore("#xcld_toolbar"),$("iframe#cld_blank_target").load(function(){var a=this.contentDocument.body;""!=$(a).html()&&(k.showDebugMessage("an unknown occured. message: "+$(a).html()),k.showMessage($(a).html()),k.hideProgressAnimation())}),$.address.change(function(a){"delete_item"==a.pathNames[0]?k.deleteItem(a.parameters.id):!1==l&&(k.hideBlock(g),g=a.parameters.current_id?a.parameters.current_id:g,h=a.parameters.current_path?decodeURIComponent((a.parameters.current_path+"").replace(/\+/g,"%20")):h,j=a.parameters.parent_id?a.parameters.parent_id:e,k.hideBlock(j),k.showCurrent(a.parameters.show_message)),k.showDebugMessage("address.change: Change of address notified. event: "+a.pathNames[0]+" id: "+g)})} \ No newline at end of file +function ilCloudFileList(a,b,c,d,e,f,g,h,i){var a=a,b=b,c=c,d=d,j=e,e=e,g=g,f=f,h=h,i=i,k=this,l=!1;this.showDebugMessage=function(a){console.log("cld: "+a)},this.getCurrentId=function(){return g},this.setRootPath=function(a){f=a},this.showBlock=function(a){$("#xcld_block_"+a).show(),$("#xcld_locator_"+a).show()},this.hideBlock=function(a){$("#xcld_block_"+a).hide()},this.isLoading=function(){return $("#loading_div_background").is(":visible")},this.removeBlock=function(a){$("#xcld_block_"+a).remove(),$("#xcld_locator_"+a).remove()},this.showItemCreationList=function(){$("#xcld_toolbar").show(),$("#il_add_new_cld_item_v").show()},this.hideItemCreationList=function(){$("#xcld_toolbar").hide(),$("#il_add_new_cld_item_v").hide()},this.toggleTabs=function(){g==e?($("#tab_settings").show(),$("#tab_id_permissions").show(),$("#tab_id_info").show()):($("#tab_settings").hide(),$("#tab_id_permissions").hide(),$("#tab_id_info").hide())},this.hideMessage=function(){k.showDebugMessage("hideMessage"),$("#xcld_message").hide()},this.showMessage=function(a){k.showDebugMessage("showMessage"),$("#xcld_message").html(a),$("#xcld_message").show(),display_message=!0},this.showCurrent=function(b,c,d){b||this.hideMessage(),perm_link=$("#current_perma_link").val(),escaped_current_path=encodeURIComponent(encodeURIComponent(h)),perm_link=perm_link.replace(/path_.*_endPath/,"path_"+escaped_current_path+"_endPath"),$("#current_perma_link").val(perm_link),this.toggleTabs(),0<$("#xcld_block_"+g).length?(this.showDebugMessage("showCurrent: Block already exists, not going Ajax. id="+g),$(".xcld_locator").hide(),this.showBlock(g),this.hideProgressAnimation(),c instanceof Function&&c(this,d)):(this.showProgressAnimation(),$.ajax({type:"POST",url:a.replace(/&/g,"&"),data:{id:g,path:h}}).done(function(a){a.success?(k.showDebugMessage("showCurrent: Block did not exist, successfull ajax request. id="+g+" path= "+h),$("#xcld_blocks").append(a.content),$(".xcld_locator").hide(),$(".ilToolbarSeparator").before(a.locator)):(k.showDebugMessage("showCurrent: Block did not exist, not successfull ajax request. id="+g+" path= "+h),a.message?(k.showDebugMessage("showCurrent: Block did not exist, not successfull ajax request. message="+a.message),k.showMessage(a.message)):(k.showDebugMessage("showCurrent: Block did not exist, not successfull ajax request. data="+a),k.showMessage(a)),k.hideItemCreationList()),c instanceof Function&&c(k,d),k.hideProgressAnimation()}))},this.deleteItem=function(a){this.clicked_delete_item||(this.clicked_delete_item=!0,this.hideMessage(),$.ajax({type:"POST",url:d.replace(/&/g,"&"),data:{id:a}}).done(function(a){k.clicked_delete_item=!1,a.success?(k.showDebugMessage("deleteItem: Form successfully created per ajax. id="+g+" path= "+h),k.hideBlock(g),k.hideItemCreationList(),$("#xcld_blocks").append(a.content),$("input[name='cmd[deleteItem]']").click(function(){k.showProgressAnimation()})):a.message?(k.showDebugMessage("deleteItem: Form not successfully created per ajax. message="+a.message),k.showMessage(a.message)):(k.showDebugMessage("deleteItem: Form not successfully created per ajax. data="+a),k.showMessage(a))}))},this.afterDeleteItem=function(a){if(this.clicked_delete_item=!1,a.success||"cancel"==a.status){var b=function(a,b){$("#cld_delete_item").remove(),a.showItemCreationList(),"cancel"==b.status?a.showDebugMessage("afterDeleteItem: Deletion cancelled."):b.success&&(a.showDebugMessage("afterDeleteItem: Item successfully deleted."),a.showMessage(b.message))};a.success?(k.removeBlock(g),this.showCurrent(!0,b,a)):this.showCurrent(!1,b,a)}else a.message?(k.showDebugMessage("afterDeleteItem: Deletion of item failed. message="+a.message),k.showMessage(a.message)):(k.showDebugMessage("afterDeleteItem: Deletion of Item failed. data="+a),k.showMessage(a)),display_message=!1,k.hideProgressAnimation()},this.createFolder=function(){this.clicked_create_folder||(this.clicked_create_folder=!0,this.hideMessage(),$.ajax({type:"POST",url:b.replace(/&/g,"&"),data:{id:g}}).done(function(a){a.success?(k.clicked_create_folder=!0,k.showDebugMessage("createFolder: Form successfully created per ajax. id="+g+" path= "+h),k.hideBlock(g),k.hideItemCreationList(),$("#xcld_blocks").append(a.content),$("input[name='cmd[createFolder]']").click(function(){k.showProgressAnimation()})):a.message?(k.showDebugMessage("createFolder: Form not successfully created per ajax. message="+a.message),k.showMessage(a.message)):(k.showDebugMessage("createFolder: Form not successfully created per ajax. data="+a),k.showMessage(a))}))},this.afterCreateFolder=function(a){if(k.clicked_create_folder=!1,a.success||"cancel"==a.status){var b=function(a,b){$("#form_cld_create_folder").remove(),a.showItemCreationList(),"cancel"==b.status?a.showDebugMessage("afterCreateFolder: Creation cancelled."):b.success&&(a.showDebugMessage("afterCreateFolder: Folder successfully created."),window.location.hash=$("#xcld_folder_"+b.folder_id).find("a:first").attr("href"),a.showMessage(b.message))};a.success?(k.removeBlock(g),this.showCurrent(!0,b,a)):this.showCurrent(!1,b,a)}else a.message?(k.showDebugMessage("afterCreateFolder: Creation of folder failed. message="+a.message),k.showMessage(a.message)):(k.showDebugMessage("afterCreateFolder: Creation of folder failed. data="+a),k.showMessage(a)),display_message=!1,k.hideProgressAnimation()},this.uploadFile=function(){this.clicked_upload_item||(this.clicked_upload_item=!0,l=!0,this.hideMessage(),this.showProgressAnimation(),$.ajax({type:"POST",url:c.replace(/&/g,"&"),data:{folder_id:g}}).done(function(a){k.hideProgressAnimation(),k.removeBlock(g),k.hideItemCreationList(),$("#xcld_blocks").append(a),$(".ilFileUploadToggleOptions").click(function(){$(".ilFileUploadEntryDescription").remove()}),$(".ilFileUploadContainer").children(".ilFormInfo").html(i)}))},this.afterUpload=function(a){k.clicked_upload_item=!1,k.showDebugMessage("afterUpload: "+a),this.showCurrent(!1,function(a){$("#form_upload").remove(),a.showItemCreationList(),l=!1})},this.showProgressAnimation=function(){$("#loading_div_background").show()},this.hideProgressAnimation=function(){$("#loading_div_background").hide()},$("#xcld_message").insertBefore("#xcld_toolbar"),$("iframe#cld_blank_target").load(function(){var a=this.contentDocument.body;""!=$(a).html()&&(k.showDebugMessage("an unknown occured. message: "+$(a).html()),k.showMessage($(a).html()),k.hideProgressAnimation())}),$.address.change(function(a){"delete_item"===a.pathNames[0]?k.deleteItem(a.parameters.id):!1===l&&!k.isLoading()&&(k.hideBlock(g),g=a.parameters.current_id?a.parameters.current_id:g,h=a.parameters.current_path?decodeURIComponent((a.parameters.current_path+"").replace(/\+/g,"%20")):h,j=a.parameters.parent_id?a.parameters.parent_id:e,k.hideBlock(j),k.showCurrent(a.parameters.show_message)),k.showDebugMessage("address.change: Change of address notified. event: "+a.pathNames[0]+" id: "+g)})} \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilContentPageKioskModeView.php b/Modules/ContentPage/classes/class.ilContentPageKioskModeView.php index 05596949c7ca..137678cbbe5c 100644 --- a/Modules/ContentPage/classes/class.ilContentPageKioskModeView.php +++ b/Modules/ContentPage/classes/class.ilContentPageKioskModeView.php @@ -15,7 +15,8 @@ */ class ilContentPageKioskModeView extends ilKioskModeView { - const CMD_TOGGLE_LEARNING_PROGRESS = 'toggleManualLearningProgress'; + const CMD_LP_TO_COMPLETED = 'lp_completed'; + const CMD_LP_TO_INCOMPLETE = 'lp_incomplete'; /** @var \ilObjContentPage */ protected $contentPageObject; @@ -105,13 +106,15 @@ protected function builtLearningProgressToggleControl(ControlBuilder $builder) $this->lng->loadLanguageModule('copa'); $learningProgressToggleCtrlLabel = $this->lng->txt('copa_btn_lp_toggle_state_completed'); + $cmd = self::CMD_LP_TO_INCOMPLETE; if (!$isCompleted) { $learningProgressToggleCtrlLabel = $this->lng->txt('copa_btn_lp_toggle_state_not_completed'); + $cmd = self::CMD_LP_TO_COMPLETED; } $builder->generic( $learningProgressToggleCtrlLabel, - self::CMD_TOGGLE_LEARNING_PROGRESS, + $cmd, 1 ); } @@ -132,20 +135,26 @@ public function updateGet(State $state, string $command, int $param = null) : St */ protected function toggleLearningProgress(string $command) { - if (self::CMD_TOGGLE_LEARNING_PROGRESS === $command) { - $learningProgress = \ilObjectLP::getInstance($this->contentPageObject->getId()); - if ($learningProgress->getCurrentMode() == \ilLPObjSettings::LP_MODE_MANUAL) { - $marks = new \ilLPMarks($this->contentPageObject->getId(), $this->user->getId()); - $marks->setCompleted(!$marks->getCompleted()); + if (in_array($command, [ + self::CMD_LP_TO_COMPLETED, + self::CMD_LP_TO_INCOMPLETE + ])) { + $learningProgress = ilObjectLP::getInstance($this->contentPageObject->getId()); + if ($learningProgress->getCurrentMode() == ilLPObjSettings::LP_MODE_MANUAL) { + $marks = new ilLPMarks($this->contentPageObject->getId(), $this->user->getId()); + + $old_state = $marks->getCompleted(); + $new_state = ($command === self::CMD_LP_TO_COMPLETED); + $marks->setCompleted($new_state); $marks->update(); - - \ilLPStatusWrapper::_updateStatus($this->contentPageObject->getId(), $this->user->getId()); - - $this->lng->loadLanguageModule('trac'); - - $this->messages[] = $this->uiFactory->messageBox()->success( - $this->lng->txt('trac_updated_status') - ); + ilLPStatusWrapper::_updateStatus($this->contentPageObject->getId(), $this->user->getId()); + + if ($old_state != $new_state) { + $this->lng->loadLanguageModule('trac'); + $this->messages[] = $this->uiFactory->messageBox()->success( + $this->lng->txt('trac_updated_status') + ); + } } } } diff --git a/Modules/ContentPage/classes/class.ilObjContentPageListGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageListGUI.php index b2e0dfe5ccce..e449698d4264 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPageListGUI.php +++ b/Modules/ContentPage/classes/class.ilObjContentPageListGUI.php @@ -39,4 +39,12 @@ public function getInfoScreenStatus() return false; } + + /** + * @inheritDoc + */ + public function checkInfoPageOnAsynchronousRendering() : bool + { + return true; + } } diff --git a/Modules/Course/classes/class.ilCourseLMHistory.php b/Modules/Course/classes/class.ilCourseLMHistory.php index dc8cd5c3e15c..d796fbb62262 100644 --- a/Modules/Course/classes/class.ilCourseLMHistory.php +++ b/Modules/Course/classes/class.ilCourseLMHistory.php @@ -75,19 +75,16 @@ public static function _updateLastAccess($a_user_id, $a_lm_ref_id, $a_page_id) return true; } - // Delete old entries - $query = "DELETE FROM crs_lm_history " . - "WHERE lm_ref_id = " . $ilDB->quote($a_lm_ref_id, 'integer') . " " . - "AND usr_id = " . $ilDB->quote($a_user_id, 'integer') . ""; - $res = $ilDB->manipulate($query); + $ilDB->replace("crs_lm_history", [ + "crs_ref_id" => ["integer", $crs_ref_id], + "lm_ref_id" => ["integer", $a_lm_ref_id], + "usr_id" => ["integer", $a_user_id] + ], [ + "lm_page_id" => ["integer", $a_page_id], + "last_access" => ["integer", time()] + ] + ); - // Add new entry - $fields = array("usr_id" => array("integer", $a_user_id), - "crs_ref_id" => array("integer", $crs_ref_id), - "lm_ref_id" => array("integer", $a_lm_ref_id), - "lm_page_id" => array("integer", $a_page_id), - "last_access" => array("integer", time())); - $ilDB->insert("crs_lm_history", $fields); return true; } diff --git a/Modules/Course/classes/class.ilCourseXMLParser.php b/Modules/Course/classes/class.ilCourseXMLParser.php index 7f7f93cb30e1..a91fe4ebc342 100644 --- a/Modules/Course/classes/class.ilCourseXMLParser.php +++ b/Modules/Course/classes/class.ilCourseXMLParser.php @@ -351,6 +351,10 @@ private function handleAdmin($a_attribs, $id_data) if ($a_attribs['passed'] == 'Yes') { $this->course_members->updatePassed($id_data['usr_id'], true); } + if (isset($a_attribs['contact']) && $a_attribs['contact'] == 'Yes') { + // default for new course admin/tutors is "no contact" + $this->course_members->updateContact($id_data['usr_id'], true); + } $this->course_members_array[$id_data['usr_id']] = "added"; } else { // update @@ -360,6 +364,11 @@ private function handleAdmin($a_attribs, $id_data) if ($a_attribs['passed'] == 'Yes') { $this->course_members->updatePassed($id_data['usr_id'], true); } + if (isset($a_attribs['contact']) && $a_attribs['contact'] == 'Yes') { + $this->course_members->updateContact($id_data['usr_id'], true); + } elseif (isset($a_attribs['contact']) && $a_attribs['contact'] == 'No') { + $this->course_members->updateContact($id_data['usr_id'], false); + } $this->course_members->updateBlocked($id_data['usr_id'], false); } } elseif (isset($a_attribs['action']) && $a_attribs['action'] == 'Detach' && $this->course_members->isAdmin($id_data['usr_id'])) { @@ -389,6 +398,10 @@ private function handleTutor($a_attribs, $id_data) if ($a_attribs['passed'] == 'Yes') { $this->course_members->updatePassed($id_data['usr_id'], true); } + if (isset($a_attribs['contact']) && $a_attribs['contact'] == 'Yes') { + // default for new course admin/tutors is "no contact" + $this->course_members->updateContact($id_data['usr_id'], true); + } $this->course_members_array[$id_data['usr_id']] = "added"; } else { if ($a_attribs['notification'] == 'Yes') { @@ -397,6 +410,11 @@ private function handleTutor($a_attribs, $id_data) if ($a_attribs['passed'] == 'Yes') { $this->course_members->updatePassed($id_data['usr_id'], true); } + if (isset($a_attribs['contact']) && $a_attribs['contact'] == 'Yes') { + $this->course_members->updateContact($id_data['usr_id'], true); + } elseif (isset($a_attribs['contact']) && $a_attribs['contact'] == 'No') { + $this->course_members->updateContact($id_data['usr_id'], false); + } $this->course_members->updateBlocked($id_data['usr_id'], false); } } elseif (isset($a_attribs['action']) && $a_attribs['action'] == 'Detach' && $this->course_members->isTutor($id_data['usr_id'])) { diff --git a/Modules/Course/classes/class.ilCourseXMLWriter.php b/Modules/Course/classes/class.ilCourseXMLWriter.php index 1d42e00c9d39..6bd46159c8fb 100755 --- a/Modules/Course/classes/class.ilCourseXMLWriter.php +++ b/Modules/Course/classes/class.ilCourseXMLWriter.php @@ -186,6 +186,7 @@ public function __buildAdmin() $attr['id'] = 'il_' . $this->ilias->getSetting('inst_id') . '_usr_' . $id; $attr['notification'] = ($this->course_obj->getMembersObject()->isNotificationEnabled($id)) ? 'Yes' : 'No'; $attr['passed'] = $this->course_obj->getMembersObject()->hasPassed($id) ? 'Yes' : 'No'; + $attr['contact'] = $this->course_obj->getMembersObject()->isContact($id) ? 'Yes' : 'No'; $this->xmlStartTag('Admin', $attr); $this->xmlEndTag('Admin'); @@ -206,6 +207,7 @@ public function __buildTutor() $attr['id'] = 'il_' . $this->ilias->getSetting('inst_id') . '_usr_' . $id; $attr['notification'] = ($this->course_obj->getMembersObject()->isNotificationEnabled($id)) ? 'Yes' : 'No'; $attr['passed'] = $this->course_obj->getMembersObject()->hasPassed($id) ? 'Yes' : 'No'; + $attr['contact'] = $this->course_obj->getMembersObject()->isContact($id) ? 'Yes' : 'No'; $this->xmlStartTag('Tutor', $attr); $this->xmlEndTag('Tutor'); diff --git a/Modules/DataCollection/classes/Content/class.ilDclRecordListGUI.php b/Modules/DataCollection/classes/Content/class.ilDclRecordListGUI.php index c3752fc38606..62c538190926 100644 --- a/Modules/DataCollection/classes/Content/class.ilDclRecordListGUI.php +++ b/Modules/DataCollection/classes/Content/class.ilDclRecordListGUI.php @@ -546,6 +546,14 @@ protected function getRecordListTableGUI($use_tableview_filter) $limit = $list->getLimit(); $offset = $list->getOffset(); + $num_records = count($table_obj->getPartialRecords($list->getOrderField(), $list->getOrderDirection(), $limit, $offset, $list->getFilter())); + + // Fix no data found on new filter application when on a site other than the first + if ($num_records === 0) { + $list->resetOffset(); + $offset = 0; + } + $data = $table_obj->getPartialRecords($list->getOrderField(), $list->getOrderDirection(), $limit, $offset, $list->getFilter()); $records = $data['records']; $total = $data['total']; diff --git a/Modules/Exercise/PeerReview/class.ExcPeerReviewDistribution.php b/Modules/Exercise/PeerReview/class.ExcPeerReviewDistribution.php new file mode 100644 index 000000000000..0c23d19963d9 --- /dev/null +++ b/Modules/Exercise/PeerReview/class.ExcPeerReviewDistribution.php @@ -0,0 +1,121 @@ + + */ +class ExcPeerReviewDistribution +{ + /** + * @var array + */ + protected $user_ids = []; + + /** + * @var array + */ + protected $user_order = []; + + /** + * @var int + */ + protected $num_assignments; + + /** + * ExcPeerReviewDistribution constructor. + * @param int[] $user_ids + * @param int $num_assignments + */ + public function __construct(array $user_ids, $num_assignments) + { + $this->user_ids = array_values($user_ids); // ensure numerical indexing + + // we cannot assign more users to a single user than count($user_ids) - 1 + $num_assignments = min($num_assignments, count($user_ids) - 1); + + // we cannot create a negative number of assignments + $num_assignments = max($num_assignments, 0); + + $this->num_assignments = $num_assignments; + $this->initDistribution(); + } + + /** + * Init distribution + */ + protected function initDistribution() + { + $this->user_order = $this->randomUserOrder($this->user_ids); + } + + /** + * Random user order + * @param array + * @return array + */ + protected function randomUserOrder($user_ids) : array + { + $order = []; + while (count($user_ids) > 0) { + $next = rand(0, count($user_ids) - 1); + $order[] = $user_ids[$next]; + unset($user_ids[$next]); + $user_ids = array_values($user_ids); // re-index + } + return $order; + } + + /** + * Get user order + * @return array + */ + public function getUserOrder() : array + { + return $this->user_order; + } + + /** + * Get peers of rater + * + * @param int $user_id + * @return int[] + */ + public function getPeersOfRater($user_id) + { + $peers = []; + $key = array_search($user_id, $this->user_order); + if ($key === false) { + return []; + } + for ($j = 1; $j <= $this->num_assignments; $j++) { + $peer_key = ($key + $j) % (count($this->user_order)); + $peers[] = $this->user_order[$peer_key]; + } + return $peers; + } +} diff --git a/Modules/Exercise/classes/class.ilExGradesTableGUI.php b/Modules/Exercise/classes/class.ilExGradesTableGUI.php index 3fc676bac649..123354d6f13d 100644 --- a/Modules/Exercise/classes/class.ilExGradesTableGUI.php +++ b/Modules/Exercise/classes/class.ilExGradesTableGUI.php @@ -48,10 +48,10 @@ public function __construct($a_parent_obj, $a_parent_cmd, $a_exc, $a_mem_obj) foreach ($mems as $d) { $data[$d] = ilObjUser::_lookupName($d); $data[$d]["user_id"] = $d; + $data[$d]["name"] = $data[$d]["lastname"] . ", " .$data[$d]["firstname"]; } parent::__construct($a_parent_obj, $a_parent_cmd); - $this->setData($data); $this->ass_data = ilExAssignment::getInstancesByExercise($this->exc_id); @@ -61,7 +61,7 @@ public function __construct($a_parent_obj, $a_parent_cmd, $a_exc, $a_mem_obj) //$this->setLimit(9999); // $this->addColumn("", "", "1", true); - $this->addColumn($this->lng->txt("name"), "lastname"); + $this->addColumn($this->lng->txt("name"), "name"); $cnt = 1; foreach ($this->ass_data as $ass) { $ilCtrl->setParameter($this->parent_obj, "ass_id", $ass->getId()); @@ -83,7 +83,7 @@ public function __construct($a_parent_obj, $a_parent_cmd, $a_exc, $a_mem_obj) // $this->addColumn($this->lng->txt("exc_grading"), "solved_time"); // $this->addColumn($this->lng->txt("mail"), "feedback_time"); - $this->setDefaultOrderField("lastname"); + $this->setDefaultOrderField("name"); $this->setDefaultOrderDirection("asc"); $this->setEnableHeader(true); diff --git a/Modules/Exercise/classes/class.ilExPeerReview.php b/Modules/Exercise/classes/class.ilExPeerReview.php index d408d389ca3e..8d75f9394081 100644 --- a/Modules/Exercise/classes/class.ilExPeerReview.php +++ b/Modules/Exercise/classes/class.ilExPeerReview.php @@ -72,63 +72,12 @@ protected function initPeerReviews() if (!$this->hasPeerReviewGroups()) { $user_ids = $this->getValidPeerReviewUsers(); - - // forever alone - if (sizeof($user_ids) < 2) { - return false; - } - - $rater_ids = $user_ids; - $matrix = array(); - $max = min(sizeof($user_ids) - 1, $this->assignment->getPeerReviewMin()); - for ($loop = 0; $loop < $max; $loop++) { - $run_ids = array_combine($user_ids, $user_ids); - - foreach ($rater_ids as $rater_id) { - $possible_peer_ids = $run_ids; - - // may not rate himself - unset($possible_peer_ids[$rater_id]); - - // already has linked peers - if (array_key_exists($rater_id, $matrix)) { - $possible_peer_ids = array_diff($possible_peer_ids, $matrix[$rater_id]); - } - - // #15665 / #15883 - if (!sizeof($possible_peer_ids)) { - // no more possible peers left? start over with all valid users - $run_ids = array_combine($user_ids, $user_ids); - - // see above - $possible_peer_ids = $run_ids; - - // may not rate himself - unset($possible_peer_ids[$rater_id]); + include_once("./Modules/Exercise/PeerReview/class.ExcPeerReviewDistribution.php"); + $distribution = new \ILIAS\Exercise\PeerReview\ExcPeerReviewDistribution($user_ids, $this->assignment->getPeerReviewMin()); - // already has linked peers - if (array_key_exists($rater_id, $matrix)) { - $possible_peer_ids = array_diff($possible_peer_ids, $matrix[$rater_id]); - } - } - - // #14947 - if (sizeof($possible_peer_ids)) { - $peer_id = array_rand($possible_peer_ids); - if (!array_key_exists($rater_id, $matrix)) { - $matrix[$rater_id] = array(); - } - $matrix[$rater_id][] = $peer_id; - } - - // remove peer_id from possible ids in this run - unset($run_ids[$peer_id]); - } - } - - foreach ($matrix as $rater_id => $peer_ids) { - foreach ($peer_ids as $peer_id) { + foreach ($user_ids as $rater_id) { + foreach ($distribution->getPeersOfRater($rater_id) as $peer_id) { $ilDB->manipulate("INSERT INTO exc_assignment_peer" . " (ass_id, giver_id, peer_id)" . " VALUES (" . $ilDB->quote($this->assignment_id, "integer") . diff --git a/Modules/Exercise/classes/class.ilExSubmissionFileGUI.php b/Modules/Exercise/classes/class.ilExSubmissionFileGUI.php index e88e2b67f29d..59c443e3810f 100644 --- a/Modules/Exercise/classes/class.ilExSubmissionFileGUI.php +++ b/Modules/Exercise/classes/class.ilExSubmissionFileGUI.php @@ -139,8 +139,10 @@ public function submissionScreenObject() $ilToolbar->addText($dl); } - $b = $this->ui->factory()->button()->standard($this->lng->txt("file_add"), - $this->ctrl->getLinkTarget($this, "uploadForm")); + $b = $this->ui->factory()->button()->standard( + $this->lng->txt("file_add"), + $this->ctrl->getLinkTarget($this, "uploadForm") + ); $ilToolbar->addStickyItem($b); if (!$max_files || diff --git a/Modules/Exercise/classes/class.ilExcAssMemberState.php b/Modules/Exercise/classes/class.ilExcAssMemberState.php index a15ea4e577a5..f59fe0a00de6 100644 --- a/Modules/Exercise/classes/class.ilExcAssMemberState.php +++ b/Modules/Exercise/classes/class.ilExcAssMemberState.php @@ -576,5 +576,4 @@ public function isGlobalFeedbackFileAccessible(ilExSubmission $submission) return $access; } - } diff --git a/Modules/Exercise/classes/class.ilExerciseMemberTableGUI.php b/Modules/Exercise/classes/class.ilExerciseMemberTableGUI.php index 06421b47753f..4496bbbc1e0d 100644 --- a/Modules/Exercise/classes/class.ilExerciseMemberTableGUI.php +++ b/Modules/Exercise/classes/class.ilExerciseMemberTableGUI.php @@ -55,7 +55,7 @@ protected function parseData() // filter user access $usr_ids = array_keys($tmp_data); $filtered_usr_ids = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser( - 'etit_submissions_grades', + 'edit_submissions_grades', 'edit_submissions_grades', $this->exc->getRefId(), $usr_ids diff --git a/Modules/Exercise/classes/class.ilExerciseSubmissionTableGUI.php b/Modules/Exercise/classes/class.ilExerciseSubmissionTableGUI.php index 27e503b79b17..0d7d3031a283 100644 --- a/Modules/Exercise/classes/class.ilExerciseSubmissionTableGUI.php +++ b/Modules/Exercise/classes/class.ilExerciseSubmissionTableGUI.php @@ -31,7 +31,7 @@ abstract class ilExerciseSubmissionTableGUI extends ilTable2GUI // needs PH P5.6 for array support protected $cols_mandatory = array("name", "status"); - protected $cols_default = array("login", "submission_date", "idl", "calc_deadline"); + protected $cols_default = array("login", "submission", "idl", "calc_deadline"); protected $cols_order = array("image", "name", "login", "team_members", "sent_time", "submission", "calc_deadline", "idl", "status", "mark", "status_time", "feedback_time", "comment", "notice"); @@ -60,7 +60,7 @@ public function __construct($a_parent_obj, $a_parent_cmd, ilObjExercise $a_exc, $this->exc = $a_exc; $this->initMode($a_item_id); - + parent::__construct($a_parent_obj, $a_parent_cmd); $this->setShowTemplates(true); @@ -187,7 +187,6 @@ public function getSelectableColumns() ); } } - return $cols; } @@ -213,7 +212,7 @@ protected function parseColumns() $cols["comment"] = array($this->lng->txt("exc_tbl_comment"), "comment"); } - $cols["notice"] = array($this->lng->txt("exc_tbl_notice"), "note"); + $cols["notice"] = array($this->lng->txt("exc_tbl_notice"), "notice"); return $cols; } @@ -222,7 +221,7 @@ protected function parseRow($a_user_id, ilExAssignment $a_ass, array $a_row) { $ilCtrl = $this->ctrl; $ilAccess = $this->access; - + $has_no_team_yet = ($a_ass->hasTeam() && !ilExAssignmentTeam::getTeamId($a_ass->getId(), $a_user_id)); @@ -334,6 +333,7 @@ protected function parseRow($a_user_id, ilExAssignment $a_ass, array $a_row) // selectable columns foreach ($this->getSelectedColumns() as $col) { + $include_seconds = false; switch ($col) { case "image": if (!$a_ass->hasTeam()) { @@ -402,6 +402,7 @@ protected function parseRow($a_user_id, ilExAssignment $a_ass, array $a_row) break; case "submission": + $include_seconds = true; if ($a_row["submission_obj"]) { foreach ($a_row["submission_obj"]->getFiles() as $file) { if ($file["late"]) { @@ -419,7 +420,12 @@ protected function parseRow($a_user_id, ilExAssignment $a_ass, array $a_row) $this->tpl->setVariable( "VAL_" . strtoupper($col), $a_row[$col] - ? ilDatePresentation::formatDate(new ilDateTime($a_row[$col], IL_CAL_DATETIME)) + ? ilDatePresentation::formatDate( + new ilDateTime($a_row[$col], IL_CAL_DATETIME), + false, + false, + $include_seconds + ) : " " ); break; diff --git a/Modules/Exercise/classes/class.ilObjExercise.php b/Modules/Exercise/classes/class.ilObjExercise.php index 2e063700a527..cc74cb36547e 100755 --- a/Modules/Exercise/classes/class.ilObjExercise.php +++ b/Modules/Exercise/classes/class.ilObjExercise.php @@ -593,7 +593,8 @@ public function exportGradesExcel() $row = $cnt = 1; $excel->setCell($row, 0, $this->lng->txt("name")); foreach ($ass_data as $ass) { - $excel->setCell($row, $cnt++, $cnt - 1); + $excel->setCell($row, $cnt++, ($cnt / 2) . " - ". $this->lng->txt("exc_tbl_status")); + $excel->setCell($row, $cnt++, (($cnt - 1) / 2) . " - ". $this->lng->txt("exc_tbl_mark")); } $excel->setCell($row, $cnt++, $this->lng->txt("exc_total_exc")); $excel->setCell($row, $cnt++, $this->lng->txt("exc_mark")); @@ -604,7 +605,7 @@ public function exportGradesExcel() $mem_obj = new ilExerciseMembers($this); $filtered_members = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser( - 'etit_submissions_grades', + 'edit_submissions_grades', 'edit_submissions_grades', $this->getRefId(), (array) $mem_obj->getMembers() @@ -625,7 +626,9 @@ public function exportGradesExcel() reset($ass_data); foreach ($ass_data as $ass) { $status = $ass->getMemberStatus($user_id)->getStatus(); + $mark = $ass->getMemberStatus($user_id)->getMark(); $excel->setCell($row, $col++, $this->lng->txt("exc_" . $status)); + $excel->setCell($row, $col++, $mark); } // total status diff --git a/Modules/Exercise/templates/default/tpl.assignment_head.html b/Modules/Exercise/templates/default/tpl.assignment_head.html index ee21c6bc8683..cf3734aa4417 100644 --- a/Modules/Exercise/templates/default/tpl.assignment_head.html +++ b/Modules/Exercise/templates/default/tpl.assignment_head.html @@ -1,7 +1,5 @@ -
+ {ALT_STATUS} -

{TITLE}

-
- {PROP}: {PROP_VAL}   -
-
+ {TITLE} + {PROP}: {PROP_VAL}   + diff --git a/Modules/Exercise/templates/default/tpl.delivered_file_row.html b/Modules/Exercise/templates/default/tpl.delivered_file_row.html index 20718b1e53c2..41f7c95f61e1 100644 --- a/Modules/Exercise/templates/default/tpl.delivered_file_row.html +++ b/Modules/Exercise/templates/default/tpl.delivered_file_row.html @@ -4,9 +4,9 @@ {DELIVERED_OWNER} -{DELIVERED_DATE} +{DELIVERED_DATE} -{DELIVERED_LATE} +{DELIVERED_LATE} -{ACTION_TXT} +{ACTION_TXT} diff --git a/Modules/Exercise/test/PeerReview/ExcPeerReviewTest.php b/Modules/Exercise/test/PeerReview/ExcPeerReviewTest.php new file mode 100644 index 000000000000..f2df1e66a5e9 --- /dev/null +++ b/Modules/Exercise/test/PeerReview/ExcPeerReviewTest.php @@ -0,0 +1,84 @@ + + */ +class ExcPeerReviewTest extends TestCase +{ + //protected $backupGlobals = false; + + protected function setUp() : void + { + parent::setUp(); + } + + protected function tearDown() : void + { + } + + protected function getDistribution($user_ids, $num_assignments) + { + include_once("./Modules/Exercise/PeerReview/class.ExcPeerReviewDistribution.php"); + return new \ILIAS\Exercise\PeerReview\ExcPeerReviewDistribution($user_ids, $num_assignments); + } + + /** + * Test if each rater has $num_assignments peers + */ + public function testDistributionNumberOfPeers() + { + $user_ids = [100,200,300,400,500]; + $num_assignments = 3; + + $distribution = $this->getDistribution($user_ids, $num_assignments); + + foreach ($user_ids as $user_id) { + $this->assertEquals(count($distribution->getPeersOfRater($user_id)), $num_assignments); + } + } + + /** + * Test if each peer is assigned to $num_assignments raters + */ + public function testDistributionNumberOfRaters() + { + $user_ids = [10,20,30,40,50]; + $num_assignments = 4; + + $distribution = $this->getDistribution($user_ids, $num_assignments); + + $peer_raters = []; + foreach ($user_ids as $user_id) { + foreach ($distribution->getPeersOfRater($user_id) as $peer) { + $peer_raters[$peer][$user_id] = $user_id; + } + } + + $this->assertEquals(count($peer_raters), count($user_ids)); + + foreach ($peer_raters as $peer => $raters) { + $this->assertEquals(count($raters), $num_assignments); + } + } + + /** + * Test if raters are not assigned as peers to themselves + */ + public function testDistributionNoSelfAssignment() + { + $user_ids = [10,20,30,40,50]; + $num_assignments = 4; + + $distribution = $this->getDistribution($user_ids, $num_assignments); + + foreach ($user_ids as $user_id) { + foreach ($distribution->getPeersOfRater($user_id) as $peer) { + $this->assertNotEquals($user_id, $peer); + } + } + } +} diff --git a/Modules/Exercise/test/bootstrap.php b/Modules/Exercise/test/bootstrap.php new file mode 100644 index 000000000000..958a143d478a --- /dev/null +++ b/Modules/Exercise/test/bootstrap.php @@ -0,0 +1,5 @@ + + */ +class ilModulesExerciseSuite extends TestSuite +{ + public static function suite() + { + $suite = new ilModulesExerciseSuite(); + + require_once("./Modules/Exercise/test/PeerReview/ExcPeerReviewTest.php"); + $suite->addTestSuite("ExcPeerReviewTest"); + + return $suite; + } +} diff --git a/Modules/File/classes/Versions/class.ilFileVersionFormGUI.php b/Modules/File/classes/Versions/class.ilFileVersionFormGUI.php index 1d17e79b8881..4fd6a18f75e7 100644 --- a/Modules/File/classes/Versions/class.ilFileVersionFormGUI.php +++ b/Modules/File/classes/Versions/class.ilFileVersionFormGUI.php @@ -93,7 +93,11 @@ private function initForm() // File if ($this->dnd) { // File (D&D) - $file = new ilFileStandardDropzoneInputGUI($this->lng->txt(self::F_FILE), self::F_FILE); + $file = new ilFileStandardDropzoneInputGUI( + ilFileVersionsGUI::CMD_DEFAULT, + $this->lng->txt(self::F_FILE), + self::F_FILE + ); $file->setRequired(true); // $file->setUploadUrl($this->ctrl->getLinkTarget($this->calling_gui, ilFileVersionsGUI::C, "", true, true)); $file->setMaxFiles(1); diff --git a/Modules/File/classes/class.ilFileXMLParser.php b/Modules/File/classes/class.ilFileXMLParser.php index fa0711c616a4..d26ace102059 100644 --- a/Modules/File/classes/class.ilFileXMLParser.php +++ b/Modules/File/classes/class.ilFileXMLParser.php @@ -13,6 +13,9 @@ * @extends ilSaxParser */ +use ILIAS\Data\DataSize; +use ILIAS\Filesystem\Util\LegacyPathHelper; + include_once './Services/Xml/classes/class.ilSaxParser.php'; include_once 'Modules/File/classes/class.ilFileException.php'; include_once 'Services/Utilities/classes/class.ilFileUtils.php'; @@ -334,12 +337,19 @@ public function handlerEndTag($a_xml_parser, $a_name) } } - //$this->content = $content; - // see #17211 - if ($this->version == $this->file->getVersion()) { - if (is_file($this->tmpFilename)) { - $this->file->setFileSize(filesize($this->tmpFilename)); // strlen($this->content)); + global $DIC; + $rel = LegacyPathHelper::createRelativePath($this->tmpFilename); + if ($DIC->filesystem()->temp()->has($rel)) { + $this->file->setFileSize($DIC->filesystem()->temp()->getSize($rel, DataSize::Byte)->getSize()); + } else { + $array = $DIC->filesystem()->temp()->listContents(dirname($rel)); + $first_file = reset($array); + + if ($first_file instanceof \ILIAS\Filesystem\DTO\Metadata) { + $this->file->setFileSize($DIC->filesystem()->temp()->getSize($first_file->getPath(), + DataSize::Byte)->getSize()); + } } // if no file type is given => lookup mime type @@ -410,9 +420,15 @@ public function setFileContents() foreach ($this->versions as $version) { if (!file_exists($version["tmpFilename"])) { - ilLoggerFactory::getLogger('file')->error(__METHOD__ . ' "' . $version["tmpFilename"] . '" file not found.'); - - continue; + // try to get first file of dir + $files = scandir(dirname($version["tmpFilename"])); + $version["tmpFilename"] = rtrim(dirname($version["tmpFilename"]), + "/") . "/" . $files[2];// because [0] = "." [1] = ".." + if (!file_exists($version["tmpFilename"])) { + ilLoggerFactory::getLogger('file')->error(__METHOD__ . ' "' . ($version["tmpFilename"]) . '" file not found.'); + + continue; + } } if (filesize($version["tmpFilename"]) == 0) { diff --git a/Modules/File/classes/class.ilObjFile.php b/Modules/File/classes/class.ilObjFile.php index ae57fa2f215d..a7f9963f7a5b 100755 --- a/Modules/File/classes/class.ilObjFile.php +++ b/Modules/File/classes/class.ilObjFile.php @@ -889,7 +889,7 @@ public function sendFile($a_hist_entry_id = null) return true; } - throw new FileNotFoundException("This file cannot be found in ILIAS or has been blocked due to security reasons."); + throw new FileNotFoundException($this->lng->txt('file_not_found_sec')); } diff --git a/Modules/File/classes/class.ilObjFileGUI.php b/Modules/File/classes/class.ilObjFileGUI.php index 08740d381a9a..7328a2a03419 100755 --- a/Modules/File/classes/class.ilObjFileGUI.php +++ b/Modules/File/classes/class.ilObjFileGUI.php @@ -582,7 +582,11 @@ protected function initPropertiesForm($mode = "create") } if ($upload_possible) { - $file = new ilFileStandardDropzoneInputGUI($this->lng->txt('obj_file'), 'file'); + $file = new ilFileStandardDropzoneInputGUI( + 'cancel', + $this->lng->txt('obj_file'), + 'file' + ); $file->setRequired(false); $form->addItem($file); @@ -1026,7 +1030,7 @@ public function uploadFiles() foreach ($DIC->upload()->getResults() as $result) { if (!ilFileUtils::hasValidExtension($result->getName())) { ilUtil::sendInfo( - $this->lng->txt('file_upload_info_file_with_critical_unknown_extension_later_renamed_when_downloading'), + $this->lng->txt('file_upload_info_file_with_critical_extension'), true ); } diff --git a/Modules/Forum/classes/class.ilForumExporter.php b/Modules/Forum/classes/class.ilForumExporter.php index a98743d62357..26d604586e15 100644 --- a/Modules/Forum/classes/class.ilForumExporter.php +++ b/Modules/Forum/classes/class.ilForumExporter.php @@ -57,6 +57,13 @@ public function getXmlExportTailDependencies($a_entity, $a_target_release, $a_id ); } + // news settings + $res[] = [ + "component" => "Services/News", + "entity" => "news_settings", + "ids" => $a_ids + ]; + return $res; } diff --git a/Modules/Forum/classes/class.ilForumPostDraft.php b/Modules/Forum/classes/class.ilForumPostDraft.php index dec32ca4a8e1..512c2dbfd157 100644 --- a/Modules/Forum/classes/class.ilForumPostDraft.php +++ b/Modules/Forum/classes/class.ilForumPostDraft.php @@ -493,12 +493,7 @@ public function updateDraft() array( 'post_subject' => array('text', $this->getPostSubject()), 'post_message' => array('clob', $this->getPostMessage()), - 'notify' => array('integer', $this->getNotify()), - 'post_notify' => array('integer', $this->getPostNotify()), - 'post_update' => array('timestamp', date("Y-m-d H:i:s")), - 'update_user_id' => array('integer', $this->getUpdateUserId()), - 'post_user_alias' => array('text', $this->getPostUserAlias()), - 'pos_display_usr_id' => array('integer', $this->getPostDisplayUserId()) + 'post_user_alias' => array('text', $this->getPostUserAlias()) ), array('draft_id' => array('integer', $this->getDraftId())) ); diff --git a/Modules/Forum/classes/class.ilObjForumAccess.php b/Modules/Forum/classes/class.ilObjForumAccess.php index 02b0ff504404..88bddc73f9a5 100644 --- a/Modules/Forum/classes/class.ilObjForumAccess.php +++ b/Modules/Forum/classes/class.ilObjForumAccess.php @@ -132,6 +132,7 @@ public static function _lookupDiskUsage($a_obj_id) */ public static function prepareMessageForLists($text) { + $text = str_replace('
', ' ', $text); $text = strip_tags($text); $text = preg_replace('/\[(\/)?quote\]/', '', $text); if (ilStr::strLen($text) > 40) { diff --git a/Modules/Forum/classes/class.ilObjForumGUI.php b/Modules/Forum/classes/class.ilObjForumGUI.php index dd2d6121906f..e0810466d928 100755 --- a/Modules/Forum/classes/class.ilObjForumGUI.php +++ b/Modules/Forum/classes/class.ilObjForumGUI.php @@ -211,7 +211,7 @@ private function isTopLevelReplyCommand() : bool { return in_array( strtolower($this->ctrl->getCmd()), - array_map('strtolower', array('createTopLevelPost', 'quoteTopLevelPost', 'saveTopLevelPost')) + array_map('strtolower', array('createTopLevelPost', 'saveTopLevelPost')) ); } @@ -225,7 +225,7 @@ public function executeCommand() 'performPostActivation', 'askForPostActivation', 'askForPostDeactivation', 'toggleThreadNotification', 'toggleThreadNotificationTab', - 'toggleStickiness', 'cancelPost', 'savePost', 'saveTopLevelPost', 'createTopLevelPost', 'quoteTopLevelPost', 'quotePost', 'getQuotationHTMLAsynch', + 'toggleStickiness', 'cancelPost', 'savePost', 'saveTopLevelPost', 'createTopLevelPost', 'quotePost', 'getQuotationHTMLAsynch', 'autosaveDraftAsync', 'autosaveThreadDraftAsync', 'saveAsDraft', 'editDraft', 'updateDraft', 'deliverDraftZipFile', 'deliverZipFile', 'cancelDraft', 'deleteThreadDrafts', @@ -1672,6 +1672,8 @@ private function initReplyEditForm() /** * @var $oFDForum ilFileDataForum */ + $isReply = in_array($this->requestAction, ['showreply', 'ready_showreply']); + $isDraft = in_array($this->requestAction, ['publishDraft', 'editdraft']); // init objects $oForumObjects = $this->getForumObjects(); @@ -1704,9 +1706,9 @@ private function initReplyEditForm() } $this->ctrl->clearParameters($this); - if (in_array($this->requestAction, ['showreply', 'ready_showreply'])) { + if ($isReply) { $this->replyEditForm->setTitle($this->lng->txt('forums_your_reply')); - } elseif (in_array($this->requestAction, ['showdraft', 'editdraft'])) { + } elseif ($isDraft) { $this->replyEditForm->setTitle($this->lng->txt('forums_edit_draft')); } else { $this->replyEditForm->setTitle($this->lng->txt('forums_edit_post')); @@ -1714,7 +1716,7 @@ private function initReplyEditForm() if ( $this->isWritingWithPseudonymAllowed() && - in_array($this->requestAction, array('showreply', 'ready_showreply')) + in_array($this->requestAction, array('showreply', 'ready_showreply', 'editdraft')) ) { $oAnonymousNameGUI = new ilTextInputGUI($this->lng->txt('forums_your_name'), 'alias'); $oAnonymousNameGUI->setMaxLength(64); @@ -1734,9 +1736,7 @@ private function initReplyEditForm() $this->replyEditForm->addItem($oSubjectGUI); $oPostGUI = new ilTextAreaInputGUI( - $this->requestAction == 'showreply' || $this->requestAction == 'ready_showreply' ? - $this->lng->txt('forums_your_reply') : - $this->lng->txt('forums_edit_post'), + $isReply ? $this->lng->txt('forums_your_reply') : $this->lng->txt('forums_edit_post'), 'message' ); $oPostGUI->setRequired(true); @@ -1745,11 +1745,20 @@ private function initReplyEditForm() $oPostGUI->addPlugin('latex'); $oPostGUI->addButton('latex'); $oPostGUI->addButton('pastelatex'); - $oPostGUI->addPlugin('ilfrmquote'); - if (in_array($this->requestAction, ['showreply', 'showdraft'])) { + $quotingAllowed = ( + !$this->isTopLevelReplyCommand() && ( + ($isReply && $this->objCurrentPost->getDepth() >= 2) || + (!$isDraft && !$isReply && $this->objCurrentPost->getDepth() > 2) || + ($isDraft && $this->objCurrentPost->getDepth() >= 2) + ) + ); + + if ($quotingAllowed) { $oPostGUI->addButton('ilFrmQuoteAjaxCall'); + $oPostGUI->addPlugin('ilfrmquote'); } + $oPostGUI->removePlugin('advlink'); $oPostGUI->setRTERootBlockElement(''); $oPostGUI->usePurifier(true); @@ -1867,9 +1876,7 @@ private function initReplyEditForm() } if (strtolower($rtestring) != 'iltinymce' || !ilObjAdvancedEditing::_getRichTextEditorUserState()) { - if ($this->isTopLevelReplyCommand()) { - $this->replyEditForm->addCommandButton('quoteTopLevelPost', $this->lng->txt('forum_add_quote')); - } else { + if ($quotingAllowed) { $this->replyEditForm->addCommandButton('quotePost', $this->lng->txt('forum_add_quote')); } } @@ -1885,6 +1892,8 @@ private function initReplyEditForm() if ($this->requestAction == 'editdraft') { $this->replyEditForm->addCommandButton('updateDraft', $this->lng->txt('save_message')); + } elseif ($this->isTopLevelReplyCommand()) { + $this->replyEditForm->addCommandButton('saveTopLevelDraft', $this->lng->txt('save_message')); } else { $this->replyEditForm->addCommandButton('saveAsDraft', $this->lng->txt('save_message')); } @@ -1940,15 +1949,6 @@ public function saveTopLevelPostObject() return; } - /** - * - */ - public function quoteTopLevelPostObject() - { - $this->quotePostObject(); - return; - } - public function publishSelectedDraftObject() { if (isset($_GET['draft_id']) && (int) $_GET['draft_id'] > 0) { @@ -1989,7 +1989,8 @@ public function publishDraftObject($use_replyform = true) if ($use_replyform) { $oReplyEditForm = $this->getReplyEditForm(); - if (!$oReplyEditForm->checkInput() && !$draft_obj instanceof ilForumPostDraft) { + // @Nadia: Why do we need this additional check here (with this check mandatory fields are NOT checked, so I suggest to remove it): && !$draft_obj instanceof ilForumPostDraft + if (!$oReplyEditForm->checkInput()) { $oReplyEditForm->setValuesByPost(); return $this->viewThreadObject(); } @@ -2766,7 +2767,7 @@ public function viewThreadObject() if ( $firstNodeInThread instanceof ilForumPost && - in_array($this->ctrl->getCmd(), array('createTopLevelPost', 'saveTopLevelPost', 'quoteTopLevelPost')) && + in_array($this->ctrl->getCmd(), array('createTopLevelPost', 'saveTopLevelPost')) && !$this->objCurrentTopic->isClosed() && $this->access->checkAccess('add_reply', '', (int) $_GET['ref_id'])) { // Important: Don't separate the following two lines (very fragile code ...) @@ -2775,28 +2776,7 @@ public function viewThreadObject() if ($this->ctrl->getCmd() == 'saveTopLevelPost') { $form->setValuesByPost(); - } elseif ($this->ctrl->getCmd() == 'quoteTopLevelPost') { - $authorinfo = new ilForumAuthorInformation( - $firstNodeInThread->getPosAuthorId(), - $firstNodeInThread->getDisplayUserId(), - $firstNodeInThread->getUserAlias(), - $firstNodeInThread->getImportName() - ); - - $form->setValuesByPost(); - $form->getItemByPostVar('message')->setValue( - ilRTE::_replaceMediaObjectImageSrc( - $frm->prepareText($firstNodeInThread->getMessage(), 1, $authorinfo->getAuthorName()) . "\n" . $form->getInput('message'), - 1 - ) - ); } - $this->ctrl->setParameter($this, 'pos_pk', $firstNodeInThread->getId()); - $this->ctrl->setParameter($this, 'thr_pk', $firstNodeInThread->getThreadId()); - $jsTpl = new ilTemplate('tpl.forum_post_quoation_ajax_handler.html', true, true, 'Modules/Forum'); - $jsTpl->setVariable('IL_FRM_QUOTE_CALLBACK_SRC', $this->ctrl->getLinkTarget($this, 'getQuotationHTMLAsynch', '', true)); - $this->ctrl->clearParameters($this); - $this->tpl->setVariable('BOTTOM_FORM_ADDITIONAL_JS', $jsTpl->get()); $this->tpl->setVariable('BOTTOM_FORM', $form->getHTML()); } } else { @@ -4948,6 +4928,16 @@ private function renderPostingForm(ilForum $frm, ilForumPost $node, string $acti 'del_file' => array() )); } + + $this->ctrl->setParameter($this, 'pos_pk', $this->objCurrentPost->getParentId()); + $this->ctrl->setParameter($this, 'thr_pk', $this->objCurrentPost->getThreadId()); + $jsTpl = new ilTemplate('tpl.forum_post_quoation_ajax_handler.html', true, true, 'Modules/Forum'); + $jsTpl->setVariable( + 'IL_FRM_QUOTE_CALLBACK_SRC', + $this->ctrl->getLinkTarget($this, 'getQuotationHTMLAsynch', '', true) + ); + $this->ctrl->clearParameters($this); + $this->tpl->setVariable('FORM_ADDITIONAL_JS', $jsTpl->get()); break; case 'editdraft': @@ -4976,6 +4966,17 @@ private function renderPostingForm(ilForum $frm, ilForumPost $node, string $acti )); } } + + $this->ctrl->setParameter($this, 'pos_pk', $this->objCurrentPost->getId()); + $this->ctrl->setParameter($this, 'thr_pk', $this->objCurrentPost->getThreadId()); + + $jsTpl = new ilTemplate('tpl.forum_post_quoation_ajax_handler.html', true, true, 'Modules/Forum'); + $jsTpl->setVariable( + 'IL_FRM_QUOTE_CALLBACK_SRC', + $this->ctrl->getLinkTarget($this, 'getQuotationHTMLAsynch', '', true) + ); + $this->ctrl->clearParameters($this); + $this->tpl->setVariable('FORM_ADDITIONAL_JS', $jsTpl->get()); break; } $this->ctrl->setParameter($this, 'pos_pk', $this->objCurrentPost->getId()); diff --git a/Modules/Glossary/classes/class.ilGlossaryDataSet.php b/Modules/Glossary/classes/class.ilGlossaryDataSet.php index 16387df5a730..438179462a9f 100644 --- a/Modules/Glossary/classes/class.ilGlossaryDataSet.php +++ b/Modules/Glossary/classes/class.ilGlossaryDataSet.php @@ -11,6 +11,7 @@ * - glo_term: data from glossary_term * - glo_definition: data from glossary_definition * - glo_advmd_col_order: ordering md fields + * - glo_auto_glossaries: automatically linked glossaries * * @author Alex Killing * @version $Id$ @@ -42,7 +43,7 @@ public function __construct() */ public function getSupportedVersions() { - return array("5.1.0"); + return array("5.1.0", "5.4.0"); } /** @@ -68,6 +69,7 @@ protected function getTypes($a_entity, $a_version) if ($a_entity == "glo") { switch ($a_version) { case "5.1.0": + case "5.4.0": return array( "Id" => "integer", "Title" => "text", @@ -84,6 +86,7 @@ protected function getTypes($a_entity, $a_version) if ($a_entity == "glo_term") { switch ($a_version) { case "5.1.0": + case "5.4.0": return array( "Id" => "integer", "GloId" => "integer", @@ -97,6 +100,7 @@ protected function getTypes($a_entity, $a_version) if ($a_entity == "glo_definition") { switch ($a_version) { case "5.1.0": + case "5.4.0": return array( "Id" => "integer", "TermId" => "integer", @@ -110,6 +114,7 @@ protected function getTypes($a_entity, $a_version) if ($a_entity == "glo_advmd_col_order") { switch ($a_version) { case "5.1.0": + case "5.4.0": return array( "GloId" => "integer", "FieldId" => "text", @@ -117,6 +122,16 @@ protected function getTypes($a_entity, $a_version) ); } } + + if ($a_entity == "glo_auto_glossaries") { + switch ($a_version) { + case "5.4.0": + return array( + "GloId" => "integer", + "AutoGloId" => "text" + ); + } + } } /** @@ -136,6 +151,7 @@ public function readData($a_entity, $a_version, $a_ids, $a_field = "") if ($a_entity == "glo") { switch ($a_version) { case "5.1.0": + case "5.4.0": $this->getDirectDataFromQuery("SELECT o.title, o.description, g.id, g.virtual, pres_mode, snippet_length, show_tax, glo_menu_active" . " FROM glossary g JOIN object_data o " . " ON (g.id = o.obj_id) " . @@ -147,17 +163,35 @@ public function readData($a_entity, $a_version, $a_ids, $a_field = "") if ($a_entity == "glo_term") { switch ($a_version) { case "5.1.0": - // todo: how does import id needs to be set? $this->getDirectDataFromQuery("SELECT id, glo_id, term, language" . " FROM glossary_term " . " WHERE " . $ilDB->in("glo_id", $a_ids, false, "integer")); break; + + case "5.4.0": + $this->getDirectDataFromQuery("SELECT id, glo_id, term, language" . + " FROM glossary_term " . + " WHERE " . $ilDB->in("glo_id", $a_ids, false, "integer")); + + $set = $ilDB->query("SELECT r.term_id, r.glo_id, t.term, t.language " . + "FROM glo_term_reference r JOIN glossary_term t ON (r.term_id = t.id) " . + " WHERE " . $ilDB->in("r.glo_id", $a_ids, false, "integer")); + while ($rec = $ilDB->fetchAssoc($set)) { + $this->data[] = [ + "Id" => $rec["term_id"], + "GloId" => $rec["glo_id"], + "Term" => $rec["term"], + "Language" => $rec["language"], + ]; + } + break; } } if ($a_entity == "glo_definition") { switch ($a_version) { case "5.1.0": + case "5.4.0": $this->getDirectDataFromQuery("SELECT id, term_id, short_text, nr, short_text_dirty" . " FROM glossary_definition " . " WHERE " . $ilDB->in("term_id", $a_ids, false, "integer")); @@ -168,12 +202,29 @@ public function readData($a_entity, $a_version, $a_ids, $a_field = "") if ($a_entity == "glo_advmd_col_order") { switch ($a_version) { case "5.1.0": + case "5.4.0": $this->getDirectDataFromQuery("SELECT glo_id, field_id, order_nr" . " FROM glo_advmd_col_order " . " WHERE " . $ilDB->in("glo_id", $a_ids, false, "integer")); break; } } + + if ($a_entity == "glo_auto_glossaries") { + switch ($a_version) { + case "5.4.0": + $set = $ilDB->query("SELECT * FROM glo_glossaries " . + " WHERE " . $ilDB->in("id", $a_ids, false, "integer")); + $this->data = []; + while ($rec = $ilDB->fetchAssoc($set)) { + $this->data[] = [ + "GloId" => $rec["id"], + "AutoGloId" => "il_" . IL_INST_ID . "_glo_" . $rec["glo_id"] + ]; + } + break; + } + } } /** @@ -185,7 +236,8 @@ protected function getDependencies($a_entity, $a_version, $a_rec, $a_ids) case "glo": return array( "glo_term" => array("ids" => $a_rec["Id"]), - "glo_advmd_col_order" => array("ids" => $a_rec["Id"]) + "glo_advmd_col_order" => array("ids" => $a_rec["Id"]), + "glo_auto_glossaries" => array("ids" => $a_rec["Id"]) ); case "glo_term": @@ -224,6 +276,9 @@ public function importRecord($a_entity, $a_types, $a_rec, $a_mapping, $a_schema_ $newObj->setSnippetLength($a_rec["SnippetLength"]); $newObj->setActiveGlossaryMenu($a_rec["GloMenuActive"]); $newObj->setShowTaxonomy($a_rec["ShowTax"]); + if ($this->getCurrentInstallationId() > 0) { + $newObj->setImportId("il_" . $this->getCurrentInstallationId() . "_glo_" . $a_rec["Id"]); + } $newObj->update(true); $this->current_obj = $newObj; @@ -324,6 +379,16 @@ public function importRecord($a_entity, $a_types, $a_rec, $a_mapping, $a_schema_ // processing $a_mapping->addMapping("Modules/Glossary", "advmd_col_order", $a_rec["GloId"] . ":" . $a_rec["FieldId"], $a_rec["OrderNr"]); break; + + case "glo_auto_glossaries": + $auto_glo_id = ilObject::_lookupObjIdByImportId($a_rec["AutoGloId"]); + $glo_id = (int) $a_mapping->getMapping("Modules/Glossary", "glo", $a_rec["GloId"]); + if ($glo_id > 0 && $auto_glo_id > 0 && ilObject::_lookupType($auto_glo_id) == "glo") { + $glo = new ilObjGlossary($glo_id, false); + $glo->addAutoGlossary($auto_glo_id); + $glo->updateAutoGlossaries(); + } + break; } } } diff --git a/Modules/Glossary/classes/class.ilGlossaryExporter.php b/Modules/Glossary/classes/class.ilGlossaryExporter.php index 4e4cf1f02765..1be0c96b26b8 100644 --- a/Modules/Glossary/classes/class.ilGlossaryExporter.php +++ b/Modules/Glossary/classes/class.ilGlossaryExporter.php @@ -52,7 +52,19 @@ public function getXmlExportTailDependencies($a_entity, $a_target_release, $a_id // workaround for #0023923 $all_refs = ilObject::_getAllReferences($id); $ref_id = current($all_refs); - $terms = ilGlossaryTerm::getTermList($ref_id); + + // see #29014, we include referenced terms in the export as well + $terms = ilGlossaryTerm::getTermList( + $ref_id, + "", + "", + "", + 0, + false, + null, + true + ); + foreach ($terms as $t) { $defs = ilGlossaryDefinition::getDefinitionList($t["id"]); foreach ($defs as $d) { @@ -176,8 +188,14 @@ public function getXmlRepresentation($a_entity, $a_schema_version, $a_id) public function getValidSchemaVersions($a_entity) { return array( + "5.4.0" => array( + "namespace" => "http://www.ilias.de/Modules/Glossary/htlm/5_4", + "xsd_file" => "ilias_glo_5_4.xsd", + "uses_dataset" => true, + "min" => "5.4.0", + "max" => ""), "5.1.0" => array( - "namespace" => "http://www.ilias.de/Modules/Glossary/htlm/4_1", + "namespace" => "http://www.ilias.de/Modules/Glossary/htlm/5_1", "xsd_file" => "ilias_glo_5_1.xsd", "uses_dataset" => true, "min" => "5.1.0", diff --git a/Modules/Glossary/classes/class.ilObjGlossary.php b/Modules/Glossary/classes/class.ilObjGlossary.php index 83127ba87137..241b5cf14889 100755 --- a/Modules/Glossary/classes/class.ilObjGlossary.php +++ b/Modules/Glossary/classes/class.ilObjGlossary.php @@ -368,15 +368,24 @@ public function setAutoGlossaries($a_val) $this->auto_glossaries = array(); if (is_array($a_val)) { foreach ($a_val as $v) { - $v = (int) $v; - if ($v > 0 && ilObject::_lookupType($v) == "glo" && - !in_array($v, $this->auto_glossaries)) { - $this->auto_glossaries[] = $v; - } + $this->addAutoGlossary($v); } } } + /** + * Add auto glossary + * @param int $glo_id + */ + public function addAutoGlossary($glo_id) + { + $glo_id = (int) $glo_id; + if ($glo_id > 0 && ilObject::_lookupType($glo_id) == "glo" && + !in_array($glo_id, $this->auto_glossaries)) { + $this->auto_glossaries[] = $glo_id; + } + } + /** * Get auto glossaries * diff --git a/Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMGUI.php b/Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMGUI.php index 653ac1c68145..c27c96c6491f 100755 --- a/Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMGUI.php +++ b/Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMGUI.php @@ -343,7 +343,7 @@ public function getSettingsFormValues() $values["startfile"] = $startfile; $values["title"] = $this->object->getTitle(); - $values["desc"] = $this->object->getDescription(); + $values["desc"] = $this->object->getLongDescription(); //$values["lic"] = $this->object->getShowLicense(); $this->form->setValuesByArray($values); diff --git a/Modules/LearningModule/classes/class.ilLMPresentationGUI.php b/Modules/LearningModule/classes/class.ilLMPresentationGUI.php index 7eec66237631..2ceec1e8e8b4 100755 --- a/Modules/LearningModule/classes/class.ilLMPresentationGUI.php +++ b/Modules/LearningModule/classes/class.ilLMPresentationGUI.php @@ -4,6 +4,8 @@ require_once("./Modules/LearningModule/classes/class.ilObjLearningModule.php"); require_once("./Services/MainMenu/classes/class.ilMainMenuGUI.php"); require_once("./Services/Style/Content/classes/class.ilObjStyleSheet.php"); +require_once 'Modules/LearningModule/classes/class.ilLMPageObject.php'; + /** * Class ilLMPresentationGUI @@ -170,6 +172,7 @@ public function executeCommand() $lng = $this->lng; $ilCtrl = $this->ctrl; $ilUser = $this->user; + $ilErr = $this->error; // check read permission and parent conditions // todo: replace all this by ilAccess call @@ -807,7 +810,6 @@ public function ilTOC($a_get_explorer = false) $exp->setOfflineMode(true); } if (!$exp->handleCommand()) { - if ($a_get_explorer) { return $exp; } else { diff --git a/Modules/LearningModule/classes/class.ilLMTracker.php b/Modules/LearningModule/classes/class.ilLMTracker.php index 84cceef44024..52ec6ed14194 100644 --- a/Modules/LearningModule/classes/class.ilLMTracker.php +++ b/Modules/LearningModule/classes/class.ilLMTracker.php @@ -182,25 +182,20 @@ public function trackAccess($a_page_id, $user_id) */ public function trackLastPageAccess($usr_id, $lm_id, $obj_id) { - $ilDB = $this->db; - - // first check if an entry for this user and this lm already exist, when so, delete - $q = "DELETE FROM lo_access " . - "WHERE usr_id = " . $ilDB->quote((int) $usr_id, "integer") . " " . - "AND lm_id = " . $ilDB->quote((int) $lm_id, "integer"); - $ilDB->manipulate($q); - $title = ""; - - $q = "INSERT INTO lo_access " . - "(timestamp,usr_id,lm_id,obj_id,lm_title) " . - "VALUES " . - "(" . $ilDB->now() . "," . - $ilDB->quote((int) $usr_id, "integer") . "," . - $ilDB->quote((int) $lm_id, "integer") . "," . - $ilDB->quote((int) $obj_id, "integer") . "," . - $ilDB->quote($title, "text") . ")"; - $ilDB->manipulate($q); + $db = $this->db; + $db->replace( + "lo_access", + [ + "usr_id" => ["integer", $usr_id], + "lm_id" => ["integer", $lm_id] + ], + [ + "timestamp" => ["timestamp", ilUtil::now()], + "obj_id" => ["integer", $obj_id], + "lm_title" => ["text", $title] + ] + ); } diff --git a/Modules/LearningModule/classes/class.ilObjContentObjectGUI.php b/Modules/LearningModule/classes/class.ilObjContentObjectGUI.php index f0875e192b29..9f0e83f349df 100755 --- a/Modules/LearningModule/classes/class.ilObjContentObjectGUI.php +++ b/Modules/LearningModule/classes/class.ilObjContentObjectGUI.php @@ -577,7 +577,7 @@ public function getPropertiesFormValues() $values = array(); $title = $this->object->getTitle(); - $description = $this->object->getDescription(); + $description = $this->object->getLongDescription(); include_once("./Services/Object/classes/class.ilObjectTranslation.php"); $ot = ilObjectTranslation::getInstance($this->object->getId()); if ($ot->getContentActivated()) { diff --git a/Modules/LearningSequence/classes/Xml/class.ilLearningSequenceXMLParser.php b/Modules/LearningSequence/classes/Xml/class.ilLearningSequenceXMLParser.php index 64b5f7cbb0e7..7006049a2612 100644 --- a/Modules/LearningSequence/classes/Xml/class.ilLearningSequenceXMLParser.php +++ b/Modules/LearningSequence/classes/Xml/class.ilLearningSequenceXMLParser.php @@ -89,15 +89,15 @@ public function handleEndTag($parser, string $name) $this->counter++; break; case "abstract": - $this->settings["abstract"] = trim($this->cdata); + $this->settings["abstract"] = base64_decode(trim($this->cdata)); break; case "extro": - $this->settings["extro"] = trim($this->cdata); + $this->settings["extro"] = base64_decode(trim($this->cdata)); break; case "abstract_img": $this->settings["abstract_img"] = trim($this->cdata); break; - case "extor_img": + case "extro_img": $this->settings["extro_img"] = trim($this->cdata); break; case "abstract_img_data": diff --git a/Modules/LearningSequence/classes/Xml/class.ilLearningSequenceXMLWriter.php b/Modules/LearningSequence/classes/Xml/class.ilLearningSequenceXMLWriter.php index f49f75081f0e..9b3c59b62ef2 100644 --- a/Modules/LearningSequence/classes/Xml/class.ilLearningSequenceXMLWriter.php +++ b/Modules/LearningSequence/classes/Xml/class.ilLearningSequenceXMLWriter.php @@ -109,8 +109,8 @@ protected function writeSettings() $abstract_img = $this->settings->getAbstractImage(); $extro_img = $this->settings->getExtroImage(); - $this->xmlElement("abstract", null, $this->settings->getAbstract()); - $this->xmlElement("extro", null, $this->settings->getExtro()); + $this->xmlElement("abstract", null, base64_encode($this->settings->getAbstract())); + $this->xmlElement("extro", null, base64_encode($this->settings->getExtro())); $this->xmlElement("members_gallery", null, $this->settings->getMembersGallery()); $this->xmlElement("abstract_img", null, $abstract_img); $this->xmlElement("extro_img", null, $extro_img); diff --git a/Modules/MediaPool/classes/class.ilMediaPoolPageUsagesTableGUI.php b/Modules/MediaPool/classes/class.ilMediaPoolPageUsagesTableGUI.php index 840d745a4471..f67fe5fbf90a 100644 --- a/Modules/MediaPool/classes/class.ilMediaPoolPageUsagesTableGUI.php +++ b/Modules/MediaPool/classes/class.ilMediaPoolPageUsagesTableGUI.php @@ -67,7 +67,7 @@ public function getItems() $page_obj = ilPageObjectFactory::getInstance($us_arr[0], $usage["id"]); $usage["page"] = $page_obj; $repo_tree = $this->repo_tree; - $ref_ids = array_filter(ilObject::_getAllReferences($page_obj->getRepoObjId()), function($ref_id) use ($repo_tree) { + $ref_ids = array_filter(ilObject::_getAllReferences($page_obj->getRepoObjId()), function ($ref_id) use ($repo_tree) { return $repo_tree->isInTree($ref_id); }); $usage["ref_ids"] = $ref_ids; @@ -182,7 +182,7 @@ protected function fillRow($a_set) } if ($usage["trash"]) { - $item["obj_title"].= " (".$lng->txt("trash").")"; + $item["obj_title"] .= " (" . $lng->txt("trash") . ")"; } break; @@ -220,7 +220,7 @@ protected function fillRow($a_set) } $ver .= $sep . $version["hist_nr"]; if ($version["lang"] != "") { - $ver.= "/".$version["lang"]; + $ver .= "/" . $version["lang"]; } $sep = ", "; } diff --git a/Modules/MediaPool/classes/class.ilObjMediaPoolGUI.php b/Modules/MediaPool/classes/class.ilObjMediaPoolGUI.php index a6eaf7f2c8a2..157f39029e2e 100755 --- a/Modules/MediaPool/classes/class.ilObjMediaPoolGUI.php +++ b/Modules/MediaPool/classes/class.ilObjMediaPoolGUI.php @@ -2011,7 +2011,11 @@ public function initBulkUploadForm() $form->setFormAction($ctrl->getFormAction($this)); $form->setPreventDoubleSubmission(false); - $item = new ilFileStandardDropzoneInputGUI($lng->txt("mep_media_files"), 'media_files'); + $item = new ilFileStandardDropzoneInputGUI( + 'cancel', + $lng->txt("mep_media_files"), + 'media_files' + ); $item->setUploadUrl($ctrl->getLinkTarget($this, "performBulkUpload", "", true, true)); $item->setMaxFiles(20); $form->addItem($item); diff --git a/Modules/OrgUnit/classes/Positions/class.ilOrgUnitPositionGUI.php b/Modules/OrgUnit/classes/Positions/class.ilOrgUnitPositionGUI.php index eebe727e1f8e..5efde267fe82 100644 --- a/Modules/OrgUnit/classes/Positions/class.ilOrgUnitPositionGUI.php +++ b/Modules/OrgUnit/classes/Positions/class.ilOrgUnitPositionGUI.php @@ -244,7 +244,7 @@ public function addSubTabs() $this->ctrl()->saveParameter($this, 'arid'); $this->ctrl()->saveParameterByClass(ilOrgUnitDefaultPermissionGUI::class, 'arid'); $this->pushSubTab(self::SUBTAB_SETTINGS, $this->ctrl() - ->getLinkTarget($this, self::CMD_INDEX)); + ->getLinkTarget($this, self::CMD_EDIT)); $this->pushSubTab(self::SUBTAB_PERMISSIONS, $this->ctrl() ->getLinkTargetByClass(ilOrgUnitDefaultPermissionGUI::class, self::CMD_INDEX)); } diff --git a/Modules/OrgUnit/classes/class.ilObjOrgUnit.php b/Modules/OrgUnit/classes/class.ilObjOrgUnit.php index 81826be39326..cae5449f074a 100644 --- a/Modules/OrgUnit/classes/class.ilObjOrgUnit.php +++ b/Modules/OrgUnit/classes/class.ilObjOrgUnit.php @@ -493,15 +493,6 @@ public function delete() $ilDB = $DIC['ilDB']; $ilAppEventHandler = $DIC['ilAppEventHandler']; - //Delete all Childs - foreach (ilObjOrgUnitTree::_getInstance()->getAllChildren($this->ref_id) as $child_ref_id) { - if (!ilObjOrgUnit::_exists($child_ref_id, true) || $this->ref_id == $child_ref_id) { - continue; //already deleted || the current org_unit - } - $org_unit = new ilObjOrgUnit($child_ref_id); - $org_unit->delete(); - } - // always call parent delete function first!! if (!parent::delete()) { return false; diff --git a/Modules/OrgUnit/classes/class.ilObjOrgUnitGUI.php b/Modules/OrgUnit/classes/class.ilObjOrgUnitGUI.php index 22dc5d172faa..265cefe7c552 100644 --- a/Modules/OrgUnit/classes/class.ilObjOrgUnitGUI.php +++ b/Modules/OrgUnit/classes/class.ilObjOrgUnitGUI.php @@ -102,7 +102,11 @@ public function __construct() $this->tpl->addCss('./Modules/OrgUnit/templates/default/orgu.css'); } - + /** + * @throws ilCtrlException + * @throws ilException + * @throws ilRepositoryException + */ public function executeCommand() { $cmd = $this->ctrl->getCmd(); @@ -741,17 +745,18 @@ public function &__initTableGUI() return parent::__initTableGUI(); } - /** * confirmed deletion of org units -> org units are deleted immediately, without putting them to the trash + * @throws ilRepositoryException */ public function confirmedDeleteObject() { - if (count($_POST['id']) > 0) { - foreach ($_POST['id'] as $ref_id) { - $il_obj_orgunit = new ilObjOrgUnit($ref_id); - $il_obj_orgunit->delete(); - } + global $DIC; + + $ids = filter_input(INPUT_POST, 'id', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY); + if (count($ids) > 0) { + ilRepUtil::removeObjectsFromSystem($ids); + ilUtil::sendSuccess($DIC->language()->txt("info_deleted"), true); } $this->ctrl->returnToParent($this); } @@ -771,7 +776,7 @@ public function deleteObject($a_error = false) $arr_ref_ids = []; //Delete via Manage (more than one) - if (count($_POST['id']) > 0) { + if (is_array($_POST['id']) && count($_POST['id']) > 0) { $arr_ref_ids = $_POST['id']; } elseif ($_GET['item_ref_id'] > 0) { $arr_ref_ids = [$_GET['item_ref_id']]; diff --git a/Modules/Poll/classes/class.ilObjPoll.php b/Modules/Poll/classes/class.ilObjPoll.php index 7bf1419e5a78..112614c79010 100644 --- a/Modules/Poll/classes/class.ilObjPoll.php +++ b/Modules/Poll/classes/class.ilObjPoll.php @@ -479,7 +479,6 @@ public function uploadImage(array $a_upload, $a_clone = false) } else { $success = copy($a_upload["tmp_name"], $path . $original); } - if ($success) { chmod($path . $original, 0770); @@ -488,9 +487,19 @@ public function uploadImage(array $a_upload, $a_clone = false) $original_file = ilUtil::escapeShellArg($path . $original); $thumb_file = ilUtil::escapeShellArg($path . $thumb); $processed_file = ilUtil::escapeShellArg($path . $processed); - ilUtil::execConvert($original_file . "[0] -geometry \"100x100>\" -quality 100 PNG:" . $thumb_file); - ilUtil::execConvert($original_file . "[0] -geometry \"" . self::getImageSize() . ">\" -quality 100 PNG:" . $processed_file); - + + // -geometry "100x100>" is escaped by -geometry "100x100\>" + // re-replace "\>" with ">" + $convert_100 = $original_file . "[0] -geometry \"100x100>\" -quality 100 PNG:" . $thumb_file; + $escaped_convert_100 = ilUtil::escapeShellCmd($convert_100); + $escaped_convert_100 = str_replace('-geometry "100x100\>', '-geometry "100x100>', $escaped_convert_100); + ilUtil::execQuoted(PATH_TO_CONVERT, $escaped_convert_100); + + $convert_300 = $original_file . "[0] -geometry \"" . self::getImageSize() . ">\" -quality 100 PNG:" . $processed_file; + $escaped_convert_300 = ilUtil::escapeShellCmd($convert_300); + $escaped_convert_300 = str_replace('-geometry "' . self::getImageSize() . '\>"', '-geometry "' . self::getImageSize() .'>"', $escaped_convert_300); + ilUtil::execQuoted(PATH_TO_CONVERT, $escaped_convert_300); + $this->setImage($processed); return true; } diff --git a/Modules/Poll/classes/class.ilObjPollAccess.php b/Modules/Poll/classes/class.ilObjPollAccess.php index 17f6cb6c195a..abaebc05c4f4 100644 --- a/Modules/Poll/classes/class.ilObjPollAccess.php +++ b/Modules/Poll/classes/class.ilObjPollAccess.php @@ -70,6 +70,13 @@ public function _checkAccess($a_cmd, $a_permission, $a_ref_id, $a_obj_id, $a_use if ($a_user_id == "") { $a_user_id = $ilUser->getId(); } + + if ( + $a_cmd == 'preview' && + $a_permission == 'read' + ) { + return false; + } // check "global" online switch if (!self::_lookupOnline($a_obj_id) && diff --git a/Modules/Portfolio/classes/class.ilObjPortfolioBaseGUI.php b/Modules/Portfolio/classes/class.ilObjPortfolioBaseGUI.php index c23496375169..ec9369b8edaf 100644 --- a/Modules/Portfolio/classes/class.ilObjPortfolioBaseGUI.php +++ b/Modules/Portfolio/classes/class.ilObjPortfolioBaseGUI.php @@ -30,6 +30,21 @@ abstract class ilObjPortfolioBaseGUI extends ilObject2GUI protected $perma_link; // [string] protected $page_id; // [int] protected $page_mode; // [string] preview|edit + + /** + * @var int + */ + protected $requested_ppage; + + /** + * @var int + */ + protected $requested_user_page; + + /** + * @var string + */ + protected $requested_back_url = ""; public function __construct($a_id = 0, $a_id_type = self::REPOSITORY_NODE_ID, $a_parent_node_id = 0) { @@ -52,6 +67,20 @@ public function __construct($a_id = 0, $a_id_type = self::REPOSITORY_NODE_ID, $a $this->lng->loadLanguageModule("prtf"); $this->lng->loadLanguageModule("user"); $this->lng->loadLanguageModule("obj"); + + $this->requested_ppage = (int) $_REQUEST["ppage"]; + $this->requested_user_page = (int) $_REQUEST["user_page"]; + + // temp sanitization, should be done smarter in the future + $back = str_replace("&", ":::", $_REQUEST["back_url"]); + $back = preg_replace( + "/[^a-zA-Z0-9_\.\?=:\s]/", + "", + $back + ); + $this->requested_back_url = str_replace(":::", "&", $back); + + $this->ctrl->setParameterbyClass("ilobjportfoliogui", "back_url", rawurlencode($this->requested_back_url)); } protected function addLocatorItems() @@ -82,19 +111,19 @@ protected function addLocatorItems() protected function determinePageCall() { // edit - if (isset($_REQUEST["ppage"])) { + if ($this->requested_ppage > 0) { if (!$this->checkPermissionBool("write")) { $this->ctrl->redirect($this, "view"); } - $this->page_id = $_REQUEST["ppage"]; + $this->page_id = $this->requested_ppage; $this->page_mode = "edit"; $this->ctrl->setParameter($this, "ppage", $this->page_id); return true; } // preview else { - $this->page_id = $_REQUEST["user_page"]; + $this->page_id = $this->requested_user_page; $this->page_mode = "preview"; $this->ctrl->setParameter($this, "user_page", $this->page_id); return false; @@ -576,7 +605,7 @@ public function preview($a_return = false, $a_content = false, $a_show_notes = t $this->tabs_gui->clearTargets(); $pages = ilPortfolioPage::getAllPortfolioPages($portfolio_id); - $current_page = (int) $_GET["user_page"]; + $current_page = $this->requested_user_page; // validate current page if ($pages && $current_page) { @@ -608,8 +637,8 @@ public function preview($a_return = false, $a_content = false, $a_show_notes = t $back_caption = ""; // public profile - if ($_REQUEST["back_url"]) { - $back = $_REQUEST["back_url"]; + if ($this->requested_back_url != "") { + $back = $this->requested_back_url; } elseif ($_GET["baseClass"] != "ilPublicUserProfileGUI" && $this->user_id && $this->user_id != ANONYMOUS_USER_ID) { if (!$this->checkPermissionBool("write")) { diff --git a/Modules/Portfolio/classes/class.ilObjPortfolioGUI.php b/Modules/Portfolio/classes/class.ilObjPortfolioGUI.php index acb57eedb060..e5ec10a40150 100644 --- a/Modules/Portfolio/classes/class.ilObjPortfolioGUI.php +++ b/Modules/Portfolio/classes/class.ilObjPortfolioGUI.php @@ -74,11 +74,6 @@ public function executeCommand() $this->checkPermission("read"); - // goto link to portfolio page - if ($_GET["gtp"]) { - $_GET["user_page"] = $_GET["gtp"]; - } - $this->setTitleAndDescription(); $next_class = $this->ctrl->getNextClass($this); @@ -1042,16 +1037,19 @@ protected function createFromTemplateDirect($title = "") public static function _goto($a_target) { + /** @var \ILIAS\DI\Container $DIC */ + global $DIC; + + $ctrl = $DIC->ctrl(); + $id = explode("_", $a_target); $_GET["baseClass"] = "ilsharedresourceGUI"; - $_GET["prt_id"] = $id[0]; + $ctrl->setParameterByClass("ilobjportfoliogui", "prt_id", $id[0]); if (sizeof($id) == 2) { - $_GET["gtp"] = $id[1]; + $ctrl->setParameterByClass("ilobjportfoliogui", "user_page", $id[1]); } - - include("ilias.php"); - exit; + $ctrl->redirectByClass(["ilsharedresourceGUI", "ilobjportfoliogui"], "preview"); } public function createPortfolioFromAssignment() @@ -1492,12 +1490,12 @@ public function printView($a_pdf_export = false) $page_content = '
' . $page_content . '
'; if (!$a_pdf_export) { - $page_content .= ''; } diff --git a/Modules/Portfolio/classes/class.ilPortfolioPageGUI.php b/Modules/Portfolio/classes/class.ilPortfolioPageGUI.php index a46ea16bf3cc..c42a7a653e66 100644 --- a/Modules/Portfolio/classes/class.ilPortfolioPageGUI.php +++ b/Modules/Portfolio/classes/class.ilPortfolioPageGUI.php @@ -34,6 +34,11 @@ class ilPortfolioPageGUI extends ilPageObjectGUI protected $export_material = array("js" => array(), "images" => array(), "files" => array()); protected static $initialized = 0; + + /** + * @var int + */ + protected $requested_ppage; /** * Constructor @@ -77,6 +82,8 @@ public function __construct($a_portfolio_id, $a_id = 0, $a_old_nr = 0, $a_enable ilObjStyleSheet::getPlaceHolderStylePath() ); $tpl->parseCurrentBlock(); + + $this->requested_ppage = (int) $_GET["ppage"]; } public function getParentType() @@ -1170,7 +1177,7 @@ public function getViewPageLink() $ctrl = $DIC->ctrl(); - $ctrl->setParameterByClass("ilobjportfoliogui", "user_page", $_GET["ppage"]); + $ctrl->setParameterByClass("ilobjportfoliogui", "user_page", $this->requested_ppage); return $ctrl->getLinkTargetByClass("ilobjportfoliogui", "preview"); } diff --git a/Modules/Portfolio/classes/class.ilPortfolioTemplatePageGUI.php b/Modules/Portfolio/classes/class.ilPortfolioTemplatePageGUI.php index c1265ea55a67..24e07bdfd39a 100644 --- a/Modules/Portfolio/classes/class.ilPortfolioTemplatePageGUI.php +++ b/Modules/Portfolio/classes/class.ilPortfolioTemplatePageGUI.php @@ -88,7 +88,7 @@ public function getViewPageLink() $ctrl = $DIC->ctrl(); - $ctrl->setParameterByClass("ilobjportfoliotemplategui", "user_page", $_GET["ppage"]); + $ctrl->setParameterByClass("ilobjportfoliotemplategui", "user_page", $this->requested_ppage); return $ctrl->getLinkTargetByClass("ilobjportfoliotemplategui", "preview"); } diff --git a/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php b/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php index 55a81f2d6a0c..e6e799d07bcb 100755 --- a/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php +++ b/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php @@ -182,9 +182,9 @@ public function executeCommand() if ($cmd == "infoScreen") { $this->checkPermission("visible"); } else { - try{ + try { $this->checkPermission("read"); - }catch (ilObjectException $exception){ + } catch (ilObjectException $exception) { $this->ctrl->redirectToURL("login.php?client_id=" . CLIENT_ID . "&cmd=force_login"); } } diff --git a/Modules/Scorm2004/classes/adlparser/SeqActivity.php b/Modules/Scorm2004/classes/adlparser/SeqActivity.php index 7030a9e96e24..aa689b8bfe6e 100644 --- a/Modules/Scorm2004/classes/adlparser/SeqActivity.php +++ b/Modules/Scorm2004/classes/adlparser/SeqActivity.php @@ -55,7 +55,7 @@ define("TIMING_ONCE", "once"); define("TIMING_EACHNEW", "onEachNewAttempt"); define("TER_EXITALL", "_EXITALL_"); - + define("TIMING_NEVER", "never"); class SeqActivity { diff --git a/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php b/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php index d0f9f3c73984..8222b78aab34 100755 --- a/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php +++ b/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php @@ -754,6 +754,23 @@ public static function _ISODurationToCentisec($str) } return $aV[0] * 3155760000 + $aV[1] * 262980000 + $aV[2] * 8640000 + $aV[3] * 360000 + $aV[4] * 6000 + round($aV[5] * 100); } + + public static function getQuantityOfSCOs(int $a_slm_id) + { + global $DIC; + $val_set = $DIC->database()->queryF( + ' + SELECT distinct(cp_node.cp_node_id) FROM cp_node,cp_resource,cp_item + WHERE cp_item.cp_node_id = cp_node.cp_node_id + AND cp_item.resourceid = cp_resource.id + AND scormtype = %s + AND nodename = %s + AND cp_node.slm_id = %s ', + array('text','text','integer'), + array('sco','item',$a_slm_id) + ); + return $DIC->database()->numRows($val_set); + } public function getCourseCompletionForUser($a_user) { @@ -774,7 +791,7 @@ public function getCourseCompletionForUser($a_user) array('text','text','integer'), array('sco','item',$this->getId()) ); - + while ($val_rec = $ilDB->fetchAssoc($val_set)) { array_push($scos, $val_rec['cp_node_id']); } diff --git a/Modules/Scorm2004/classes/class.ilSCORM2004StoreData.php b/Modules/Scorm2004/classes/class.ilSCORM2004StoreData.php index 4cb35707d1d2..6832f83d3967 100755 --- a/Modules/Scorm2004/classes/class.ilSCORM2004StoreData.php +++ b/Modules/Scorm2004/classes/class.ilSCORM2004StoreData.php @@ -35,8 +35,8 @@ public static function scormPlayerUnload($userId = null, $packageId, $time_from_ if ($data->total_time_sec != "") { $total_time_sec = $data->total_time_sec; $ilDB->manipulateF( - 'UPDATE sahs_user - SET total_time_sec = %s, last_visited = %s, hash_end =%s, last_access = %s + 'UPDATE sahs_user + SET total_time_sec = %s, last_visited = %s, hash_end =%s, last_access = %s WHERE obj_id = %s AND user_id = %s', array('integer', 'text', 'timestamp', 'timestamp', 'integer', 'integer'), array($total_time_sec,$last_visited, $endDate, date('Y-m-d H:i:s'), $packageId, $userId) @@ -53,8 +53,8 @@ public static function scormPlayerUnload($userId = null, $packageId, $time_from_ } } else { $ilDB->manipulateF( - 'UPDATE sahs_user - SET last_visited = %s, hash_end =%s, last_access = %s + 'UPDATE sahs_user + SET last_visited = %s, hash_end =%s, last_access = %s WHERE obj_id = %s AND user_id = %s', array('text', 'timestamp', 'timestamp', 'integer', 'integer'), array($last_visited, $endDate, date('Y-m-d H:i:s'), $packageId, $userId) @@ -102,7 +102,15 @@ public static function persistCMIData($userId = null, $packageId, $defaultLesson ilSCORM2004StoreData::setGlobalObjectives($userId, $packageId, $data); $new_global_status = $data->now_global_status; $return["new_global_status"] = $new_global_status; - + + // mantis #30293 + $score_scaled = $data->node[0][35]; + if ($score_scaled != null) { + if (ilObjSCORM2004LearningModule::getQuantityOfSCOs($packageId) == 1) { + ilLTIAppEventListener::handleOutcomeWithoutLP($packageId, $userId, $score_scaled * 100); + } + } + ilSCORM2004StoreData::syncGlobalStatus($userId, $packageId, $data, $new_global_status, $time_from_lms); $ilLog->write("SCORM: return of persistCMIData: " . json_encode($return)); @@ -249,8 +257,8 @@ public static function setCMIData($userId, $packageId, $data, $getComments, $get if ($i_set > 3) { $i_set -= 4; if ($getInteractions) { - $q = 'DELETE FROM cmi_correct_response - WHERE cmi_interaction_id IN ( + $q = 'DELETE FROM cmi_correct_response + WHERE cmi_interaction_id IN ( SELECT cmi_interaction.cmi_interaction_id FROM cmi_interaction WHERE cmi_interaction.cmi_node_id = %s)'; $ilDB->manipulateF($q, array('integer'), array($cmi_node_id)); } @@ -491,10 +499,10 @@ public static function writeGObjective($g_data, $user, $package) $pkg_id = $package; $res = $ilDB->queryF( - ' - SELECT user_id FROM cmi_gobjective - WHERE objective_id =%s - AND user_id = %s + ' + SELECT user_id FROM cmi_gobjective + WHERE objective_id =%s + AND user_id = %s AND scope_id = %s', array('text', 'integer', 'integer'), array($obj, $dbuser, $pkg_id) @@ -502,9 +510,9 @@ public static function writeGObjective($g_data, $user, $package) $ilLog->write("SCORM2004 Count is: " . $ilDB->numRows($res)); if (!$ilDB->numRows($res)) { $ilDB->manipulateF( - ' - INSERT INTO cmi_gobjective - (user_id, status, scope_id, measure, satisfied, objective_id) + ' + INSERT INTO cmi_gobjective + (user_id, status, scope_id, measure, satisfied, objective_id) VALUES (%s, %s, %s, %s, %s, %s)', array('integer', 'text', 'integer', 'text', 'text', 'text'), array($dbuser, $completed, $pkg_id, $measure, $satisfied, $obj) @@ -512,13 +520,13 @@ public static function writeGObjective($g_data, $user, $package) $ilLog->write("SCORM2004 cmi_gobjective Insert status=" . $completed . " scope_id=" . $pkg_id . " measure=" . $measure . " satisfied=" . $satisfied . " objective_id=" . $obj); } else { $ilDB->manipulateF( - ' - UPDATE cmi_gobjective - SET status = %s, - measure = %s, - satisfied = %s - WHERE objective_id = %s - AND user_id = %s + ' + UPDATE cmi_gobjective + SET status = %s, + measure = %s, + satisfied = %s + WHERE objective_id = %s + AND user_id = %s AND scope_id = %s', array('text', 'text', 'text', 'text', 'integer', 'integer'), array($completed, $measure, $satisfied, $obj, $dbuser, $pkg_id) @@ -537,8 +545,8 @@ public static function writeGObjective($g_data, $user, $package) //Get the scope for all the global objectives!!! $res = $ilDB->queryF( - "SELECT global_to_system - FROM cp_package + "SELECT global_to_system + FROM cp_package WHERE obj_id = %s", array('text'), array($package) @@ -558,10 +566,10 @@ public static function writeGObjective($g_data, $user, $package) if ($existing_key_template != "") { //Get the ones that need to be updated in a single query $res = $ilDB->queryF( - "SELECT objective_id - FROM cmi_gobjective - WHERE user_id = %s - AND scope_id = %s + "SELECT objective_id + FROM cmi_gobjective + WHERE user_id = %s + AND scope_id = %s AND objective_id IN ($existing_key_template)", array('integer', 'integer'), array($dbuser, $scope_id) @@ -575,16 +583,16 @@ public static function writeGObjective($g_data, $user, $package) foreach ($rows_to_insert as $obj_id => $vals) { if (in_array($obj_id, $existing_keys)) { $ilDB->manipulateF( - "UPDATE cmi_gobjective - SET satisfied=%s, - measure=%s, - score_raw=%s, - score_min=%s, - score_max=%s, - completion_status=%s, - progress_measure=%s - WHERE objective_id = %s - AND user_id = %s + "UPDATE cmi_gobjective + SET satisfied=%s, + measure=%s, + score_raw=%s, + score_min=%s, + score_max=%s, + completion_status=%s, + progress_measure=%s + WHERE objective_id = %s + AND user_id = %s AND scope_id = %s", array('text','text', 'text', 'text', 'text', 'text', 'text', 'text', 'integer', 'integer'), @@ -595,9 +603,9 @@ public static function writeGObjective($g_data, $user, $package) ); } else { $ilDB->manipulateF( - "INSERT INTO cmi_gobjective - (user_id, satisfied, measure, scope_id, status, objective_id, - score_raw, score_min, score_max, progress_measure, completion_status) + "INSERT INTO cmi_gobjective + (user_id, satisfied, measure, scope_id, status, objective_id, + score_raw, score_min, score_max, progress_measure, completion_status) VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", array('integer', 'text', 'text', 'integer', 'text', 'text', 'text', 'text', 'text', 'text', 'text'), diff --git a/Modules/Scorm2004/classes/class.ilSCORM2004TrackingItemsTableGUI.php b/Modules/Scorm2004/classes/class.ilSCORM2004TrackingItemsTableGUI.php old mode 100644 new mode 100755 index 4814e8b3c544..9c8150783de7 --- a/Modules/Scorm2004/classes/class.ilSCORM2004TrackingItemsTableGUI.php +++ b/Modules/Scorm2004/classes/class.ilSCORM2004TrackingItemsTableGUI.php @@ -163,7 +163,9 @@ public function getItems() { $lng = $this->lng; - $this->determineOffsetAndOrder(); + $this->determineOffsetAndOrder(true); + $this->determineLimit(); + $ilSCORM2004TrackingItems = new ilSCORM2004TrackingItems(); switch ($this->report) { case "exportSelectedCore": @@ -191,12 +193,12 @@ public function getItems() $tr_data = $ilSCORM2004TrackingItems->exportSelectedSuccess($this->userSelected, $this->allowExportPrivacy, $this->getObjId(), $this->lmTitle); break; } - $this->setMaxCount($tr_data["cnt"]); + // $this->setMaxCount($tr_data["cnt"]); if (ilUtil::stripSlashes($this->getOrderField()) != "") { include_once "Services/Utilities/classes/class.ilStr.php"; $tr_data = ilUtil::stableSortArray($tr_data, ilUtil::stripSlashes($this->getOrderField()), ilUtil::stripSlashes($this->getOrderDirection())); } - + $this->setData($tr_data); } protected function parseValue($id, $value, $type) diff --git a/Modules/Scorm2004/scripts/buildrte/rte-min.js b/Modules/Scorm2004/scripts/buildrte/rte-min.js index f64ddbe5784a..66521a0bb52f 100644 --- a/Modules/Scorm2004/scripts/buildrte/rte-min.js +++ b/Modules/Scorm2004/scripts/buildrte/rte-min.js @@ -1,4 +1,4 @@ -// Build: 2018118230830 +// Build: 2021417225813 function ADLAuxiliaryResource() {} @@ -2591,8 +2591,13 @@ if(async) {xhttp.onreadystatechange=onStateChange;xhttp.send(data?String(data):'');}else {xhttp.send(data?String(data):'');return onStateChange();}} function sendJSONRequest(url,data,callback,user,password,headers) -{if(typeof headers!=="object"){headers={};} -headers['Accept']='text/javascript';headers['Accept-Charset']='UTF-8';var r=sendAndLoad(url,toJSONString(data),callback,user,password,headers);if(r.content){if(r.content.indexOf("login.php")>-1||r.content.indexOf("formlogin")>-1){var thref=window.location.href;thref=thref.substring(0,thref.indexOf('ilias.php'))+"Modules/Scorm2004/templates/default/session_timeout.html";window.location.href=thref;}} +{function unloadChrome(){if(navigator.userAgent.indexOf("Chrom")>-1){if((typeof(document.getElementById("res"))!="undefined"&&typeof(document.getElementById("res").contentWindow)!="undefined"&&typeof(document.getElementById("res").contentWindow.event)!="undefined"&&(document.getElementById("res").contentWindow.event.type=="unload"||document.getElementById("res").contentWindow.event.type=="beforeunload"||document.getElementById("res").contentWindow.event.type=="pagehide"))||(typeof(window.event)!="undefined"&&(window.event.type=="unload"||window.event.type=="beforeunload"||window.event.type=="click"))||(typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1])!="undefined"&&typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow)!="undefined"&&typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow.event)!="undefined"&&(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow.event.type=="unload"||document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow.event.type=="beforeunload"))||(typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0])!="undefined"&&typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow)!="undefined"&&typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.event)!="undefined"&&(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.event.type=="unload"||document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.event.type=="beforeunload"))||(typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1])!="undefined"&&typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow)!="undefined"&&typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event)!="undefined"&&(document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="unload"||document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="beforeunload"))||(typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0])!="undefined"&&typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1])!="undefined"&&typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow)!="undefined"&&typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event)!="undefined"&&(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="unload"||document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="beforeunload"))){return true;}} +return false;} +if(typeof headers!=="object"){headers={};} +headers['Accept']='text/javascript';headers['Accept-Charset']='UTF-8';if(url==this.config.store_url&&unloadChrome()){var r=sendAndLoad(url,toJSONString(data),true,user,password,headers);console.log("async request for chrome");try{windowOpenerLoc.reload();}catch(e){} +return"1";} +if(url==this.config.scorm_player_unload_url&&navigator.userAgent.indexOf("Chrom")>-1){navigator.sendBeacon(url,toJSONString(data));return"1";} +var r=sendAndLoad(url,toJSONString(data),callback,user,password,headers);if(r.content){if(r.content.indexOf("login.php")>-1||r.content.indexOf("formlogin")>-1){var thref=window.location.href;thref=thref.substring(0,thref.indexOf('ilias.php'))+"Modules/Scorm2004/templates/default/session_timeout.html";window.location.href=thref;}} if((r.status===200&&(/^text\/javascript;?.*/i).test(r.type))||r.status===0) {return parseJSONString(r.content);} else @@ -2643,6 +2648,7 @@ var userInteraction=false;function launchTarget(target,isJump){if(userInteractio onItemUndeliver();mlaunch=msequencer.navigateStr(target,isJump);if(mlaunch.mSeqNonContent==null){onItemDeliver(activities[mlaunch.mActivityID]);}else{loadPage(gConfig.specialpage_url+"&page="+mlaunch.mSeqNonContent);}} function launchNavType(navType,isUserCurrentlyInteracting){if(!isUserCurrentlyInteracting&&userInteraction){userInteraction=false;return null;} if(navType=='SuspendAll'){err=currentAPI.SetValueIntern("cmi.exit","suspend");activities[msequencer.mSeqTree.mCurActivity.mActivityID].exit="suspend";} +if(navType==='ExitAll'||navType==='Exit'||navType==='SuspendAll'){onWindowUnload();} onItemUndeliver();mlaunch=new ADLLaunch();if(navType==='Start'){mlaunch=msequencer.navigate(NAV_START);} if(navType==='ResumeAll'){mlaunch=msequencer.navigate(NAV_RESUMEALL);} if(navType==='Exit'){mlaunch=msequencer.navigate(NAV_EXIT);} @@ -2960,7 +2966,7 @@ if(mObjStatus.mHasProgressMeasure) obj="cmi.objectives."+idx+".completion_status";err=currentAPI.SetValueIntern(obj,mObjStatus.mCompletionStatus);}}}} function syncCMIADLTree(){var mPRIMARY_OBJ_ID=null;var masteryStatus=null;var sessionTime=null;var entry=null;var normalScore=-1.0;var progressMeasure=null;var completionStatus=null;var SCOEntry=null;var suspended=false;SCOEntry=currentAPI.GetValueIntern("cmi.exit");completionStatus=currentAPI.GetValueIntern("cmi.completion_status");var completionSetBySCO=currentAPI.GetValueIntern("cmi.completion_status_SetBySco");if(completionStatus=="not attempted")completionStatus="incomplete";progressMeasure=currentAPI.GetValueIntern("cmi.progress_measure");if(progressMeasure==""||progressMeasure=="unknown") {progressMeasure=null;} -masteryStatus=currentAPI.GetValueIntern("cmi.success_status");var masterySetBySCO=currentAPI.GetValueIntern("cmi.success_status_SetBySco");SCOEntry=currentAPI.GetValueIntern("cmi.entry");score=currentAPI.GetValueIntern("cmi.score.scaled");sessionTime=currentAPI.GetValueIntern("cmi.session_time");var act=msequencer.mSeqTree.getActivity(mlaunch.mActivityID);if(act.getIsTracked()) +masteryStatus=currentAPI.GetValueIntern("cmi.success_status");var masterySetBySCO=currentAPI.GetValueIntern("cmi.success_status_SetBySco");SCOEntry=currentAPI.GetValueIntern("cmi.entry");score=currentAPI.GetValueIntern("cmi.score.scaled");sessionTime=currentAPI.GetValueIntern("cmi.session_time");var act=msequencer.mSeqTree.getActivity(mlaunch.mActivityID);if(act&&act.getIsTracked()) {var primaryObjID=null;var foundPrimaryObj=false;var setPrimaryObjSuccess=false;var setPrimaryObjScore=false;objs=act.getObjectives();if(objs!=null){for(var j=0;j -1) { + if ( + ( + typeof(document.getElementById("res")) != "undefined" + && typeof(document.getElementById("res").contentWindow) != "undefined" + && typeof(document.getElementById("res").contentWindow.event) != "undefined" + && (document.getElementById("res").contentWindow.event.type=="unload" || document.getElementById("res").contentWindow.event.type=="beforeunload" || document.getElementById("res").contentWindow.event.type=="pagehide") + ) + || ( + typeof(window.event) != "undefined" + && (window.event.type=="unload" || window.event.type=="beforeunload" || window.event.type=="click") + ) + || (//LM in frame 1 + typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1]) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow.event) != "undefined" + && ( + document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow.event.type=="unload" + || document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow.event.type=="beforeunload" + ) + ) + || (//LM in frame 0 + typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0]) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.event) != "undefined" + && ( + document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.event.type=="unload" + || document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.event.type=="beforeunload" + ) + ) + || ( //Articulate Rise + typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1]) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event) != "undefined" + && ( + document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="unload" + || document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="beforeunload" + ) + ) + || ( //Articulate Rise as SCORM 1.2 in 2004 Player + typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0]) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1]) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event) != "undefined" + && ( + document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="unload" + || document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="beforeunload" + ) + ) + ) { + return true; + } + } + return false; + } + if (typeof headers !== "object") {headers = {};} headers['Accept'] = 'text/javascript'; headers['Accept-Charset'] = 'UTF-8'; + if (url == this.config.store_url && unloadChrome()) { + var r = sendAndLoad(url, toJSONString(data), true, user, password, headers); + console.log("async request for chrome"); + // navigator.sendBeacon(url, toJSONString(data)); + // console.log('use sendBeacon'); + try{windowOpenerLoc.reload();} catch(e){} + return "1"; + } + if (url == this.config.scorm_player_unload_url && navigator.userAgent.indexOf("Chrom") > -1) { + navigator.sendBeacon(url, toJSONString(data)); + return "1"; + } + var r = sendAndLoad(url, toJSONString(data), callback, user, password, headers); if (r.content) { @@ -12362,7 +12432,10 @@ function launchNavType(navType, isUserCurrentlyInteracting) { //sync activities[msequencer.mSeqTree.mCurActivity.mActivityID].exit="suspend"; } - + if (navType==='ExitAll' || navType==='Exit' || navType==='SuspendAll') { + onWindowUnload(); + } + //throw away API from previous sco and sync CMI and ADLTree, no api...SCO has to care for termination onItemUndeliver(); @@ -14181,7 +14254,7 @@ function syncCMIADLTree(){ //get current activity var act = msequencer.mSeqTree.getActivity(mlaunch.mActivityID); - if (act.getIsTracked()) + if (act && act.getIsTracked()) { //alert("main.syncCMIADLTree:\nactivityid: " + mlaunch.mActivityID); var primaryObjID = null; @@ -14582,7 +14655,7 @@ function updateNav(ignore) { var disable=true; var disabled_str = ""; var test=null; - if (mlaunch.mNavState.mChoice!=null) { + if (mlaunch.mNavState && typeof(mlaunch.mNavState.mChoice)!="undefined" && mlaunch.mNavState.mChoice!=null) { test=mlaunch.mNavState.mChoice[i]; } if (test) { diff --git a/Modules/Scorm2004/scripts/rtemain/main.js b/Modules/Scorm2004/scripts/rtemain/main.js old mode 100644 new mode 100755 index 233adbce23f6..4c1853c4bb0c --- a/Modules/Scorm2004/scripts/rtemain/main.js +++ b/Modules/Scorm2004/scripts/rtemain/main.js @@ -1353,10 +1353,80 @@ function sendAndLoad(url, data, callback, user, password, headers) } function sendJSONRequest (url, data, callback, user, password, headers) -{ +{ + function unloadChrome() { + if (navigator.userAgent.indexOf("Chrom") > -1) { + if ( + ( + typeof(document.getElementById("res")) != "undefined" + && typeof(document.getElementById("res").contentWindow) != "undefined" + && typeof(document.getElementById("res").contentWindow.event) != "undefined" + && (document.getElementById("res").contentWindow.event.type=="unload" || document.getElementById("res").contentWindow.event.type=="beforeunload" || document.getElementById("res").contentWindow.event.type=="pagehide") + ) + || ( + typeof(window.event) != "undefined" + && (window.event.type=="unload" || window.event.type=="beforeunload" || window.event.type=="click") + ) + || (//LM in frame 1 + typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1]) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow.event) != "undefined" + && ( + document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow.event.type=="unload" + || document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[1].contentWindow.event.type=="beforeunload" + ) + ) + || (//LM in frame 0 + typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0]) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.event) != "undefined" + && ( + document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.event.type=="unload" + || document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.event.type=="beforeunload" + ) + ) + || ( //Articulate Rise + typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1]) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event) != "undefined" + && ( + document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="unload" + || document.getElementById("res").contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="beforeunload" + ) + ) + || ( //Articulate Rise as SCORM 1.2 in 2004 Player + typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0]) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1]) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow) != "undefined" + && typeof(document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event) != "undefined" + && ( + document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="unload" + || document.getElementById("res").contentWindow.document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type=="beforeunload" + ) + ) + ) { + return true; + } + } + return false; + } + if (typeof headers !== "object") {headers = {};} headers['Accept'] = 'text/javascript'; headers['Accept-Charset'] = 'UTF-8'; + if (url == this.config.store_url && unloadChrome()) { + var r = sendAndLoad(url, toJSONString(data), true, user, password, headers); + console.log("async request for chrome"); + // navigator.sendBeacon(url, toJSONString(data)); + // console.log('use sendBeacon'); + try{windowOpenerLoc.reload();} catch(e){} + return "1"; + } + if (url == this.config.scorm_player_unload_url && navigator.userAgent.indexOf("Chrom") > -1) { + navigator.sendBeacon(url, toJSONString(data)); + return "1"; + } + var r = sendAndLoad(url, toJSONString(data), callback, user, password, headers); if (r.content) { @@ -1577,7 +1647,10 @@ function launchNavType(navType, isUserCurrentlyInteracting) { //sync activities[msequencer.mSeqTree.mCurActivity.mActivityID].exit="suspend"; } - + if (navType==='ExitAll' || navType==='Exit' || navType==='SuspendAll') { + onWindowUnload(); + } + //throw away API from previous sco and sync CMI and ADLTree, no api...SCO has to care for termination onItemUndeliver(); @@ -3396,7 +3469,7 @@ function syncCMIADLTree(){ //get current activity var act = msequencer.mSeqTree.getActivity(mlaunch.mActivityID); - if (act.getIsTracked()) + if (act && act.getIsTracked()) { //alert("main.syncCMIADLTree:\nactivityid: " + mlaunch.mActivityID); var primaryObjID = null; @@ -3797,7 +3870,7 @@ function updateNav(ignore) { var disable=true; var disabled_str = ""; var test=null; - if (mlaunch.mNavState.mChoice!=null) { + if (mlaunch.mNavState && typeof(mlaunch.mNavState.mChoice)!="undefined" && mlaunch.mNavState.mChoice!=null) { test=mlaunch.mNavState.mChoice[i]; } if (test) { diff --git a/Modules/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php b/Modules/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php index 5a34e28481b9..d08293c4bab2 100755 --- a/Modules/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php +++ b/Modules/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php @@ -182,6 +182,8 @@ public static function storeJsApiCmi($user_id, $obj_id, $data) $ilDB = $DIC['ilDB']; $b_updateStatus = false; + $i_score_max = 0; + $i_score_raw = 0; $b_messageLog = false; if ($ilLog->current_log_level == 30) { @@ -256,6 +258,19 @@ public static function storeJsApiCmi($user_id, $obj_id, $data) $a_data["right"] . " for obj_id:" . $obj_id . ",sco_id:" . $a_data["sco_id"] . ",user_id:" . $user_id); } } + if ($a_data["left"] == 'cmi.core.score.max') { + $i_score_max = $a_data["right"]; + } + if ($a_data["left"] == 'cmi.core.score.raw') { + $i_score_raw = $a_data["right"]; + } + } + // mantis #30293 + if ($i_score_max > 0 && $i_score_raw > 0) { + if (count(ilSCORMObject::_lookupPresentableItems($obj_id)) == 1) { + ilLTIAppEventListener::handleOutcomeWithoutLP($obj_id, $user_id, + ($i_score_raw / $i_score_max) * 100); + } } } @@ -277,8 +292,6 @@ public static function syncGlobalStatus($userId, $packageId, $data, $new_global_ $saved_global_status = $data->saved_global_status; $ilLog->write("saved_global_status=" . $saved_global_status); - //last_visited! - // get attempts if (!$data->packageAttempts) { $val_set = $ilDB->queryF( @@ -299,9 +312,9 @@ public static function syncGlobalStatus($userId, $packageId, $data, $new_global_ $totalTime = (int) $data->totalTimeCentisec; $totalTime = round($totalTime / 100); $ilDB->queryF( - 'UPDATE sahs_user SET sco_total_time_sec=%s, status=%s, percentage_completed=%s, package_attempts=%s WHERE obj_id = %s AND user_id = %s', - array('integer', 'integer', 'integer', 'integer', 'integer', 'integer'), - array($totalTime, $new_global_status, $data->percentageCompleted, $attempts, $packageId, $userId) + 'UPDATE sahs_user SET last_visited=%s, last_access = %s, sco_total_time_sec=%s, status=%s, percentage_completed=%s, package_attempts=%s WHERE obj_id = %s AND user_id = %s', + array('text', 'timestamp', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer'), + array($data->last_visited, date('Y-m-d H:i:s'), $totalTime, $new_global_status, $data->percentageCompleted, $attempts, $packageId, $userId) ); // self::ensureObjectDataCacheExistence(); @@ -741,16 +754,13 @@ public static function scorm12PlayerUnload() global $DIC; $ilUser = $DIC['ilUser']; $ilDB = $DIC['ilDB']; - - //$user_id = $ilUser->getID(); $user_id = (int) $_GET["p"]; $ref_id = (int) $_GET["ref_id"]; - // $obj_id = ilObject::_lookupObjId($ref_id); $obj_id = (int) $_GET["package_id"]; if ($obj_id <= 1) { $GLOBALS['DIC']['ilLog']->write(__METHOD__ . ' no valid obj_id'); } else { - $last_visited = $_POST['last_visited']; + $last_visited = (string) $_GET['last_visited']; $endDate = date('Y-m-d H:i:s', mktime(date('H'), date('i') + 5, date('s'), date('m'), date('d'), date('Y'))); $ilDB->manipulateF( 'UPDATE sahs_user diff --git a/Modules/ScormAicc/classes/class.ilObjSCORMLearningModule.php b/Modules/ScormAicc/classes/class.ilObjSCORMLearningModule.php index f5c2cb842416..606dd7f32ad1 100755 --- a/Modules/ScormAicc/classes/class.ilObjSCORMLearningModule.php +++ b/Modules/ScormAicc/classes/class.ilObjSCORMLearningModule.php @@ -339,7 +339,7 @@ public function getAttemptsForUsers() /** - * get number of atttempts for a certain user and package + * get number of attempts for a certain user and package */ public function getAttemptsForUser($a_user_id) { @@ -348,7 +348,7 @@ public function getAttemptsForUser($a_user_id) $val_set = $ilDB->queryF( 'SELECT package_attempts FROM sahs_user WHERE obj_id = %s AND user_id = %s', array('integer','integer'), - array($this->getId(),$a_user_id,0) + array($this->getId(),$a_user_id) ); $val_rec = $ilDB->fetchAssoc($val_set); @@ -508,7 +508,7 @@ public function getTrackingDataAggSco($a_sco_id) AND sco_id = %s', array('integer','integer'), array($this->getId(),$a_sco_id) - ); + ); $data = array(); while ($user_rec = $ilDB->fetchAssoc($user_set)) { @@ -528,7 +528,7 @@ public function getTrackingDataAggSco($a_sco_id) "cmi.core.lesson_status", "cmi.core.total_time", "cmi.core.score.raw") - ); + ); $score = $time = $status = ""; @@ -786,7 +786,7 @@ public function importSuccessForSahsUser($user_id, $last_access, $status, $attem 'SELECT * FROM sahs_user WHERE obj_id = %s AND user_id = %s', array('integer','integer'), array($this->getID(),$user_id) - ); + ); if ($ilDB->numRows($statement) > 0) { $ilDB->update( 'sahs_user', diff --git a/Modules/ScormAicc/classes/class.ilSCORMTrackingItemsTableGUI.php b/Modules/ScormAicc/classes/class.ilSCORMTrackingItemsTableGUI.php old mode 100644 new mode 100755 index 96442e394866..f36bcaa3baa2 --- a/Modules/ScormAicc/classes/class.ilSCORMTrackingItemsTableGUI.php +++ b/Modules/ScormAicc/classes/class.ilSCORMTrackingItemsTableGUI.php @@ -149,7 +149,9 @@ public function getItems() global $DIC; $lng = $DIC['lng']; - $this->determineOffsetAndOrder(); + $this->determineOffsetAndOrder(true); + $this->determineLimit(); + $ilSCORMTrackingItems = new ilSCORMTrackingItems(); switch ($this->report) { case "exportSelectedCore": @@ -177,7 +179,7 @@ public function getItems() $tr_data = $ilSCORMTrackingItems->exportSelectedSuccess($this->userSelected, $this->allowExportPrivacy, $this->getObjId(), $this->lmTitle); break; } - $this->setMaxCount($tr_data["cnt"]); +// $this->setMaxCount($tr_data["cnt"]); if (ilUtil::stripSlashes($this->getOrderField()) != "") { include_once "Services/Utilities/classes/class.ilStr.php"; $tr_data = ilUtil::stableSortArray($tr_data, ilUtil::stripSlashes($this->getOrderField()), ilUtil::stripSlashes($this->getOrderDirection())); diff --git a/Modules/ScormAicc/classes/class.ilSCORMTrackingUsersTableGUI.php b/Modules/ScormAicc/classes/class.ilSCORMTrackingUsersTableGUI.php old mode 100644 new mode 100755 index 59091e0676ac..a04e455172a0 --- a/Modules/ScormAicc/classes/class.ilSCORMTrackingUsersTableGUI.php +++ b/Modules/ScormAicc/classes/class.ilSCORMTrackingUsersTableGUI.php @@ -41,24 +41,15 @@ public function parse() { $this->initTable(); - // @TODO add filter $users = $this->getParentObject()->object->getTrackedUsers($this->filter['lastname']); $attempts = $this->getParentObject()->object->getAttemptsForUsers(); $versions = $this->getParentObject()->object->getModuleVersionForUsers(); - include_once('./Services/PrivacySecurity/classes/class.ilPrivacySettings.php'); - $privacy = ilPrivacySettings::_getInstance(); - $allowExportPrivacy = $privacy->enabledExportSCORM(); - $data = array(); foreach ($users as $user) { $tmp = array(); $tmp['user'] = $user['user_id']; - if ($allowExportPrivacy == true) { - $tmp['name'] = $user['lastname'] . ', ' . $user['firstname']; - } else { - $tmp['name'] = $user['user_id']; - } + $tmp['name'] = $user['lastname'] . ', ' . $user['firstname']; $dt = new ilDateTime($user['last_access'], IL_CAL_DATETIME); $tmp['last_access'] = $dt->get(IL_CAL_UNIX); $tmp['attempts'] = (int) $attempts[$user['user_id']]; @@ -66,6 +57,18 @@ public function parse() $data[] = $tmp; } + $this->determineOffsetAndOrder(); + $orderField = $this->getOrderField(); + $orderDirection = $this->getOrderDirection(); + if ( in_array(ilUtil::stripSlashes($orderField), ['user', 'attempts', 'version']) ) { + $this->setExternalSorting(true); + $data = ilUtil::sortArray( + $data, + $orderField, + $orderDirection, + true + ); + } $this->setData($data); } diff --git a/Modules/ScormAicc/classes/class.ilScormLP.php b/Modules/ScormAicc/classes/class.ilScormLP.php old mode 100644 new mode 100755 index c4bc0d272ef9..826af06ebd8e --- a/Modules/ScormAicc/classes/class.ilScormLP.php +++ b/Modules/ScormAicc/classes/class.ilScormLP.php @@ -13,6 +13,11 @@ */ class ilScormLP extends ilObjectLP { + /** + * @var null | bool + */ + protected $precondition_cache = null; + public static function getDefaultModes($a_lp_active) { return array( @@ -61,12 +66,6 @@ public function getValidModes() } } - /** - * AK, 14Sep2018: This looks strange, the mode is auto-activated if this object is used - * as a precondition trigger? This is not implemented for any other object type. - * - * @return int - */ public function getCurrentMode() { if ($this->checkSCORMPreconditions()) { @@ -75,13 +74,21 @@ public function getCurrentMode() return parent::getCurrentMode(); } + /** + * @return bool + * @throws ilDatabaseException + */ protected function checkSCORMPreconditions() { - include_once('./Services/Conditions/classes/class.ilConditionHandler.php'); - if (count(ilConditionHandler::_getPersistedConditionsOfTrigger('sahs', $this->obj_id))) { - return true; + if (!is_null($this->precondition_cache)) { + return $this->precondition_cache; } - return false; + + $this->precondition_cache = + ilConditionHandler::getNumberOfConditionsOfTrigger('sahs', $this->obj_id) > 0 ? + true : + false; + return $this->precondition_cache; } protected static function isLPMember(array &$a_res, $a_usr_id, $a_obj_ids) diff --git a/Modules/ScormAicc/scripts/basisAPI.js b/Modules/ScormAicc/scripts/basisAPI.js index e3b0928bd0d1..79e6772ce27f 100755 --- a/Modules/ScormAicc/scripts/basisAPI.js +++ b/Modules/ScormAicc/scripts/basisAPI.js @@ -77,13 +77,20 @@ function sendRequest (url, data, callback, user, password, headers) { } function useSendBeacon() { - if (navigator.userAgent.indexOf("Chrome") > -1) { - if (typeof(window.sahs_content) != "undefined" && typeof(window.sahs_content.event) != "undefined" && (window.sahs_content.event.type=="unload" || window.sahs_content.event.type=="beforeunload")) { - var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); - var version = raw ? parseInt(raw[2], 10) : false; - if (version === false) return false; - if (version >= 80) return true; - } + if (navigator.userAgent.indexOf("Chrom") > -1) { + var winev = null; + if (window.sahs_content && typeof(window.sahs_content.event) != "undefined") winev = window.sahs_content.event.type; + else if (typeof(window.event) != "undefined") winev = window.event.type; + else if (window.parent && typeof(window.parent.event) != "undefined") winev = window.parent.event.type; + else if (window.parent.parent && typeof(window.parent.parent.event) != "undefined") winev = window.parent.parent.event.type; + //contentstart + try{winev = document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("frame")[1].contentWindow.event.type;} catch(e){} + //Articulate Rise + try{winev = document.getElementsByTagName("frame")[0].contentWindow.document.getElementsByTagName("iframe")[1].contentWindow.event.type;} catch(e){} + + if (winev == "unload" || winev == "beforeunload" || winev == "click") { + return true; + } } return false; } @@ -198,7 +205,9 @@ function message(s_send){ function warning(s_send){ s_send = 'lm_'+iv.objId+': '+s_send; - sendRequest ('./ilias.php?baseClass=ilSAHSPresentationGUI&ref_id='+iv.refId+'&cmd=logWarning', s_send); + if (navigator.userAgent.indexOf("Chrom") < 0) { + sendRequest ('./ilias.php?baseClass=ilSAHSPresentationGUI&ref_id='+iv.refId+'&cmd=logWarning', s_send); + } } // avoid sessionTimeOut @@ -316,6 +325,8 @@ function IliasCommit() { } var s_s="",a_tmp,s_v,a_cmiTmp,i_numCompleted=0,b_statusFailed=false; var LP_STATUS_IN_PROGRESS_NUM=1, LP_STATUS_COMPLETED_NUM=2,LP_STATUS_FAILED_NUM=3; + $last_visited = ""; + if (iv.b_autoLastVisited==true) $last_visited = iv.launchId; var o_data={ "cmi":[], "saved_global_status":iv.status.saved_global_status, @@ -324,7 +335,8 @@ function IliasCommit() { "lp_mode":iv.status.lp_mode, "hash":iv.status.hash, "p":iv.status.p, - "totalTimeCentisec":0 + "totalTimeCentisec":0, + "last_visited":$last_visited }; for (var i=0; isetDisabledObjectTypes(array("itgr", "sess")); + $gui->setDisabledObjectTypes( + array_merge( + [ + 'itgr', 'sess' + ], + $objDefinition->getSideBlockTypes() + ) + ); $gui->setAfterCreationCallback($this->ref_id); $gui->render(); diff --git a/Modules/Session/classes/class.ilSessionMailTemplateParticipantContext.php b/Modules/Session/classes/class.ilSessionMailTemplateParticipantContext.php index 523fd34c7da7..d666a0aa6525 100644 --- a/Modules/Session/classes/class.ilSessionMailTemplateParticipantContext.php +++ b/Modules/Session/classes/class.ilSessionMailTemplateParticipantContext.php @@ -94,18 +94,23 @@ public function getSpecificPlaceholders() */ public function resolveSpecificPlaceholder($placeholder_id, array $context_parameters, ilObjUser $recipient = null, $html_markup = false) { - /** - * @var $ilObjDataCache ilObjectDataCache - */ global $DIC; $ilObjDataCache = $DIC['ilObjDataCache']; - - if ('crs_title' == $placeholder_id) { - return $ilObjDataCache->lookupTitle($ilObjDataCache->lookupObjId($context_parameters['ref_id'])); - } elseif ('crs_link' == $placeholder_id) { - require_once './Services/Link/classes/class.ilLink.php'; - return ilLink::_getLink($context_parameters['ref_id'], 'crs'); + $obj_id = $ilObjDataCache->lookupObjId($context_parameters['ref_id']); + $sess_data = ilObjSession::lookupSession($obj_id); + $sess_app = ilSessionAppointment::_lookupAppointment($obj_id); + + + switch ($placeholder_id) { + case 'sess_title': + return $ilObjDataCache->lookupTitle($obj_id); + case 'sess_appointment': + return ilSessionAppointment::_appointmentToString($sess_app['start'], $sess_app['end'], $sess_app['fullday']); + case 'sess_location': + return $sess_data['location']; + case 'sess_details': + return $sess_data['details']; } return ''; diff --git a/Modules/Survey/classes/class.ilObjSurvey.php b/Modules/Survey/classes/class.ilObjSurvey.php index dc21a9872f39..c8b0678ff124 100755 --- a/Modules/Survey/classes/class.ilObjSurvey.php +++ b/Modules/Survey/classes/class.ilObjSurvey.php @@ -1749,14 +1749,21 @@ public function moveQuestions($move_questions, $target_index, $insert_mode) $part1 = array_slice($this->questions, 0, $array_pos + 1); $part2 = array_slice($this->questions, $array_pos + 1); } + $found = 0; foreach ($move_questions as $question_id) { if (!(array_search($question_id, $part1) === false)) { unset($part1[array_search($question_id, $part1)]); + $found++; } if (!(array_search($question_id, $part2) === false)) { unset($part2[array_search($question_id, $part2)]); + $found++; } } + // sanity check: do not move questions if they have not be found in the array + if ($found != count($move_questions)) { + return; + } $part1 = array_values($part1); $part2 = array_values($part2); $this->questions = array_values(array_merge($part1, $move_questions, $part2)); @@ -3860,10 +3867,12 @@ public function cloneObject($a_target_id, $a_copy_id = 0, $a_omit_tree = false) include_once "./Modules/SurveyQuestionPool/classes/class.SurveyQuestion.php"; foreach ($this->questions as $key => $question_id) { + /** @var $question SurveyQuestion */ $question = self::_instanciateQuestion($question_id); if ($question) { // #10824 $question->id = -1; $original_id = SurveyQuestion::_getOriginalId($question_id, false); + $question->setObjId($newObj->getId()); $question->saveToDb($original_id); $newObj->questions[$key] = $question->getId(); $question_pointer[$question_id] = $question->getId(); @@ -6263,7 +6272,7 @@ protected function sentReminderPlaceholders($a_message, $a_user_id, array $a_con $user = new \ilObjUser($a_user_id); $processor = new \ilMailTemplatePlaceholderResolver($context, $a_message); - $a_message = $processor->resolve($user, \ilMailFormCall::getContextParameters()); + $a_message = $processor->resolve($user, $a_context_params); } catch (\Exception $e) { ilLoggerFactory::getLogger('mail')->error(__METHOD__ . ' has been called with invalid context.'); } diff --git a/Modules/Survey/classes/class.ilSurveyEditorGUI.php b/Modules/Survey/classes/class.ilSurveyEditorGUI.php index d20390a501db..e061fef43a70 100644 --- a/Modules/Survey/classes/class.ilSurveyEditorGUI.php +++ b/Modules/Survey/classes/class.ilSurveyEditorGUI.php @@ -394,6 +394,7 @@ public function moveQuestionsObject() $this->ctrl->redirect($this, "questions"); } else { $_SESSION["move_questions"] = $move_questions; + $_SESSION["move_questions_survey_id"] = $this->object->getId(); ilUtil::sendInfo($this->lng->txt("select_target_position_for_move_question")); $this->questionsObject(); } @@ -443,6 +444,7 @@ protected function insertQuestions($insert_mode) ilUtil::sendSuccess($this->lng->txt('msg_obj_modified'), true); $this->object->moveQuestions($_SESSION["move_questions"], $insert_id, $insert_mode); unset($_SESSION["move_questions"]); + unset($_SESSION["move_questions_survey_id"]); } $this->ctrl->redirect($this, "questions"); diff --git a/Modules/Survey/classes/class.ilSurveyQuestionTableGUI.php b/Modules/Survey/classes/class.ilSurveyQuestionTableGUI.php index 1f9c711b4435..f6c47acc9a4a 100644 --- a/Modules/Survey/classes/class.ilSurveyQuestionTableGUI.php +++ b/Modules/Survey/classes/class.ilSurveyQuestionTableGUI.php @@ -43,7 +43,8 @@ public function __construct($a_parent_obj, $a_parent_cmd, ilObjSurvey $a_survey_ if (!$this->read_only) { // command dropdown - if (!array_key_exists("move_questions", $_SESSION)) { + if (!array_key_exists("move_questions", $_SESSION) || + $_SESSION["move_questions_survey_id"] != $this->object->getId()) { $this->addMultiCommand("createQuestionblock", $lng->txt("define_questionblock")); $this->addMultiCommand("unfoldQuestionblock", $lng->txt("unfold")); $this->addMultiCommand("removeQuestions", $lng->txt("remove_question")); diff --git a/Modules/Survey/classes/forms/FormMailCodesGUI.php b/Modules/Survey/classes/forms/FormMailCodesGUI.php index c25905d5282f..77bacd189030 100644 --- a/Modules/Survey/classes/forms/FormMailCodesGUI.php +++ b/Modules/Survey/classes/forms/FormMailCodesGUI.php @@ -124,14 +124,10 @@ public function __construct($guiclass) } } - if ($ilAccess->checkAccess("write", "", $_GET["ref_id"]) && $rbacsystem->checkAccess('smtp_mail', ilMailGlobalServices::getMailObjectRefId())) { - if ((int) $ilSetting->get('mail_allow_external')) { - $this->addCommandButton("sendCodesMail", $this->lng->txt("send")); - } else { - ilUtil::sendInfo($lng->txt("cant_send_email_smtp_disabled")); - } + if ((int) $ilSetting->get('mail_allow_external')) { + $this->addCommandButton("sendCodesMail", $this->lng->txt("send")); } else { - ilUtil::sendInfo($lng->txt("cannot_send_emails")); + ilUtil::sendInfo($lng->txt("cant_send_email_smtp_disabled")); } } diff --git a/Modules/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolListGUI.php b/Modules/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolListGUI.php index 4ed7551bc52b..a2994d4961cc 100644 --- a/Modules/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolListGUI.php +++ b/Modules/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolListGUI.php @@ -54,7 +54,7 @@ public function init() // general commands array include_once("./Modules/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolAccess.php"); - $this->commands = ilObjSurveyQUestionPoolAccess::_getCommands(); + $this->commands = ilObjSurveyQuestionPoolAccess::_getCommands(); } diff --git a/Modules/Test/classes/class.ilAssQuestionPageCommandForwarder.php b/Modules/Test/classes/class.ilAssQuestionPageCommandForwarder.php index 9a09e04c78d9..b88e21382bc7 100644 --- a/Modules/Test/classes/class.ilAssQuestionPageCommandForwarder.php +++ b/Modules/Test/classes/class.ilAssQuestionPageCommandForwarder.php @@ -71,7 +71,7 @@ public function forward() $DIC->ctrl()->saveParameter($this, "q_id"); $DIC->language()->loadLanguageModule("content"); $DIC->ctrl()->setReturnByClass("ilAssQuestionPageGUI", "view"); - $DIC->ctrl()->setReturn($this, "questions"); + $DIC->ctrl()->setReturnByClass("ilObjTestGUI", "questions"); $page_gui = new ilAssQuestionPageGUI($_GET["q_id"]); $page_gui->setEditPreview(true); if (strlen($DIC->ctrl()->getCmd()) == 0) { diff --git a/Modules/Test/classes/class.ilObjTest.php b/Modules/Test/classes/class.ilObjTest.php index ee2a5db1873c..07a7cbe5fcd3 100755 --- a/Modules/Test/classes/class.ilObjTest.php +++ b/Modules/Test/classes/class.ilObjTest.php @@ -7056,7 +7056,14 @@ public static function _getAvailableTests($use_object_id = false) $ilDB = $DIC['ilDB']; $result_array = array(); - $tests = ilUtil::_getObjectsByOperations("tst", "write", $ilUser->getId(), -1); + $tests = array_slice( + array_reverse( + ilUtil::_getObjectsByOperations("tst", "write", $ilUser->getId(), PHP_INT_MAX) + ), + 0, + 10000 + ); + if (count($tests)) { $titles = ilObject::_prepareCloneSelection($tests, "tst"); foreach ($tests as $ref_id) { @@ -10437,7 +10444,7 @@ public function getEvaluationAdditionalFields() { include_once "./Modules/Test/classes/class.ilObjTestGUI.php"; include_once "./Modules/Test/classes/tables/class.ilEvaluationAllTableGUI.php"; - $table_gui = new ilEvaluationAllTableGUI(new ilObjTestGUI(''), 'outEvaluation', $this->getAnonymity()); + $table_gui = new ilEvaluationAllTableGUI(new ilObjTestGUI($this->getRefId()), 'outEvaluation', $this->getAnonymity()); return $table_gui->getSelectedColumns(); } diff --git a/Modules/Test/classes/class.ilObjTestGUI.php b/Modules/Test/classes/class.ilObjTestGUI.php index 3ff8c05b1d77..99deed3fda1b 100755 --- a/Modules/Test/classes/class.ilObjTestGUI.php +++ b/Modules/Test/classes/class.ilObjTestGUI.php @@ -88,8 +88,9 @@ class ilObjTestGUI extends ilObjectGUI /** * Constructor * @access public + * @param mixed|null $refId */ - public function __construct() + public function __construct($refId = null) { global $DIC; $lng = $DIC['lng']; @@ -101,7 +102,10 @@ public function __construct() $this->type = "tst"; $this->ctrl = $ilCtrl; $this->ctrl->saveParameter($this, array("ref_id", "test_ref_id", "calling_test", "test_express_mode", "q_id")); - parent::__construct("", $_GET["ref_id"], true, false); + if (isset($_GET['ref_id']) && is_numeric($_GET['ref_id'])) { + $refId = (int) $_GET['ref_id']; + } + parent::__construct("", (int) $refId, true, false); if ($this->object instanceof ilObjTest) { require_once 'Modules/Test/classes/class.ilTestQuestionSetConfigFactory.php'; diff --git a/Modules/Test/classes/class.ilObjTestSettingsScoringResultsGUI.php b/Modules/Test/classes/class.ilObjTestSettingsScoringResultsGUI.php index 4e51abf03ebb..2b49230db1b0 100644 --- a/Modules/Test/classes/class.ilObjTestSettingsScoringResultsGUI.php +++ b/Modules/Test/classes/class.ilObjTestSettingsScoringResultsGUI.php @@ -169,11 +169,16 @@ private function saveFormCmd($isConfirmedSave = false) return $this->showConfirmation($form); } + // saving the form leads to isScoreRecalculationRequired($form) + // returning false, so remember whether recalculation is needed + + $recalcRequired = $this->isScoreRecalculationRequired($form); + // perform save $this->performSaveForm($form); - if ($this->isScoreRecalculationRequired($form)) { + if ($recalcRequired) { $this->testOBJ->recalculateScores(true); } diff --git a/Modules/Test/classes/class.ilObjTestXMLParser.php b/Modules/Test/classes/class.ilObjTestXMLParser.php index 32ca4e147976..3f92196a69cc 100644 --- a/Modules/Test/classes/class.ilObjTestXMLParser.php +++ b/Modules/Test/classes/class.ilObjTestXMLParser.php @@ -259,11 +259,28 @@ protected function getRandomQuestionSourcePoolDefinitionInstance() protected function importRandomQuestionSourcePoolDefinition(ilTestRandomQuestionSetSourcePoolDefinition $sourcePoolDefinition, $attr) { - $sourcePoolDefinition->setPoolId($this->getImportMapping()->getMapping( + $source_pool_id = (int) $attr['poolId']; + $effective_pool_id = (int) $this->getImportMapping()->getMapping( 'Modules/Test', 'pool', - (int) $attr['poolId'] - )); + $source_pool_id + ); + $sourcePoolDefinition->setPoolId($effective_pool_id); + + $derive_from_obj_id = true; + // The ref_id might not be given in old export files, so we have to check for existence + if (isset($attr['ref_id']) && is_numeric($attr['ref_id'])) { + if ($source_pool_id === $effective_pool_id) { + $derive_from_obj_id = false; + $sourcePoolDefinition->setPoolRefId((int) $attr['ref_id']); + } + } + + if ($derive_from_obj_id) { + $ref_ids = ilObject::_getAllReferences($effective_pool_id); + $ref_id = current($ref_ids); + $sourcePoolDefinition->setPoolRefId($ref_id ? (int) $ref_id : null); + } $sourcePoolDefinition->setPoolQuestionCount((int) $attr['poolQuestCount']); $sourcePoolDefinition->setQuestionAmount((int) $attr['questAmount']); diff --git a/Modules/Test/classes/class.ilParticipantsTestResultsGUI.php b/Modules/Test/classes/class.ilParticipantsTestResultsGUI.php index b9b64b5fdee5..b5a80e68dc36 100644 --- a/Modules/Test/classes/class.ilParticipantsTestResultsGUI.php +++ b/Modules/Test/classes/class.ilParticipantsTestResultsGUI.php @@ -486,10 +486,6 @@ public function createUserResults($show_pass_details, $show_answers, $show_reach $count = 0; foreach ($show_user_results as $key => $active_id) { - if ($this->getTestObj()->getFixedParticipants()) { - $active_id = $this->getTestObj()->getActiveIdOfUser($active_id); - } - if (!in_array($active_id, $participantData->getActiveIds())) { continue; } diff --git a/Modules/Test/classes/class.ilTestCorrectionsGUI.php b/Modules/Test/classes/class.ilTestCorrectionsGUI.php index 4c816ec18591..2db85ea80464 100644 --- a/Modules/Test/classes/class.ilTestCorrectionsGUI.php +++ b/Modules/Test/classes/class.ilTestCorrectionsGUI.php @@ -74,19 +74,32 @@ protected function showQuestionList() { $this->DIC->tabs()->activateTab(ilTestTabsManager::TAB_ID_CORRECTION); - $table_gui = new ilTestQuestionsTableGUI( - $this, - 'showQuestionList', - $this->testOBJ->getRefId() - ); - - $table_gui->setQuestionTitleLinksEnabled(true); - $table_gui->setQuestionRemoveRowButtonEnabled(true); - $table_gui->init(); - - $table_gui->setData($this->getQuestions()); - - $this->DIC->ui()->mainTemplate()->setContent($table_gui->getHTML()); + $ui = $this->DIC->ui(); + + if ($this->testOBJ->isFixedTest()) { + $table_gui = new ilTestQuestionsTableGUI( + $this, + 'showQuestionList', + $this->testOBJ->getRefId() + ); + + $table_gui->setQuestionTitleLinksEnabled(true); + $table_gui->setQuestionRemoveRowButtonEnabled(true); + $table_gui->init(); + + $table_gui->setData($this->getQuestions()); + + $rendered_gui_component = $table_gui->getHTML(); + } else { + $lng = $this->DIC->language(); + $txt = $lng->txt('tst_corrections_incompatible_question_set_type'); + + $infoBox = $ui->factory()->messageBox()->info($txt); + + $rendered_gui_component = $ui->renderer()->render($infoBox); + } + + $ui->mainTemplate()->setContent($rendered_gui_component); } protected function showQuestion(ilPropertyFormGUI $form = null) diff --git a/Modules/Test/classes/class.ilTestEvaluationData.php b/Modules/Test/classes/class.ilTestEvaluationData.php index 8a9cff6de9ae..542cde83f18d 100755 --- a/Modules/Test/classes/class.ilTestEvaluationData.php +++ b/Modules/Test/classes/class.ilTestEvaluationData.php @@ -205,6 +205,7 @@ public function generateOverview() $this->getParticipant($row["active_fi"])->getPass($row["pass"])->setNrOfAnsweredQuestions($row["answeredquestions"]); $this->getParticipant($row["active_fi"])->getPass($row["pass"])->setWorkingTime($row["workingtime"]); + $this->getParticipant($row["active_fi"])->getPass($row["pass"])->setExamId((string) $row["exam_id"]); $this->getParticipant($row['active_fi'])->getPass($row['pass'])->setRequestedHintsCount($row['hint_count']); $this->getParticipant($row['active_fi'])->getPass($row['pass'])->setDeductedHintPoints($row['hint_points']); diff --git a/Modules/Test/classes/class.ilTestEvaluationGUI.php b/Modules/Test/classes/class.ilTestEvaluationGUI.php index e8c1f359eaf7..ed0d56dd3879 100644 --- a/Modules/Test/classes/class.ilTestEvaluationGUI.php +++ b/Modules/Test/classes/class.ilTestEvaluationGUI.php @@ -274,6 +274,7 @@ public function outEvaluation() $evaluationrow['reached'] = $userdata->getReached(); $evaluationrow['max'] = $userdata->getMaxpoints(); $evaluationrow['hint_count'] = $userdata->getRequestedHintsCountFromScoredPass(); + $evaluationrow['exam_id'] = $userdata->getExamIdFromScoredPass(); $percentage = $userdata->getReachedPointsInPercent(); $mark = $this->object->getMarkSchema()->getMatchingMark($percentage); if (is_object($mark)) { @@ -1000,6 +1001,13 @@ public function outParticipantsPassDetails() $template->setVariable("LIST_OF_ANSWERS", $list_of_answers); $template->setVariable("PASS_DETAILS", $this->ctrl->getHTML($overviewTableGUI)); + $data = &$this->object->getCompleteEvaluationData(); + $result = $data->getParticipant($active_id)->getReached() . " " . strtolower($this->lng->txt("of")) . " " . $data->getParticipant($active_id)->getMaxpoints() . " (" . sprintf("%2.2f", $data->getParticipant($active_id)->getReachedPointsInPercent()) . " %" . ")"; + $template->setCurrentBlock('total_score'); + $template->setVariable("TOTAL_RESULT_TEXT",$this->lng->txt('tst_stat_result_resultspoints')); + $template->setVariable("TOTAL_RESULT",$result); + $template->parseCurrentBlock(); + if (!$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) { $template->setVariable("USER_DATA", $user_data); @@ -1011,6 +1019,7 @@ public function outParticipantsPassDetails() $template->setVariable("FORMACTION", $this->ctrl->getFormAction($this)); + $this->populateExamId($template, (int) $active_id, (int) $pass); $this->populatePassFinishDate($template, ilObjTest::lookupLastTestPassAccess($active_id, $pass)); $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print.css", "Modules/Test"), "print"); @@ -1317,6 +1326,13 @@ public function outUserPassDetails() $overviewTableGUI->setTitle($testResultHeaderLabelBuilder->getPassDetailsHeaderLabel($pass + 1)); $tpl->setVariable("PASS_DETAILS", $this->ctrl->getHTML($overviewTableGUI)); + $data = &$this->object->getCompleteEvaluationData(); + $result = $data->getParticipant($active_id)->getReached() . " " . strtolower($this->lng->txt("of")) . " " . $data->getParticipant($active_id)->getMaxpoints() . " (" . sprintf("%2.2f", $data->getParticipant($active_id)->getReachedPointsInPercent()) . " %" . ")"; + $tpl->setCurrentBlock('total_score'); + $tpl->setVariable("TOTAL_RESULT_TEXT",$this->lng->txt('tst_stat_result_resultspoints')); + $tpl->setVariable("TOTAL_RESULT",$result); + $tpl->parseCurrentBlock(); + if ($this->object->canShowSolutionPrintview()) { $list_of_answers = $this->getPassListOfAnswers( $result_array, @@ -1347,6 +1363,7 @@ public function outUserPassDetails() } } + $this->populateExamId($tpl, (int) $active_id, (int) $pass); $this->populatePassFinishDate($tpl, ilObjTest::lookupLastTestPassAccess($active_id, $pass)); $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print.css", "Modules/Test"), "print"); diff --git a/Modules/Test/classes/class.ilTestEvaluationPassData.php b/Modules/Test/classes/class.ilTestEvaluationPassData.php index ef0570722434..3570d656da1f 100755 --- a/Modules/Test/classes/class.ilTestEvaluationPassData.php +++ b/Modules/Test/classes/class.ilTestEvaluationPassData.php @@ -87,11 +87,14 @@ class ilTestEvaluationPassData * @var boolean */ private $obligationsAnswered = null; + + /** @var string */ + private $examId = ''; public function __sleep() { return array('answeredQuestions', 'pass', 'nrOfAnsweredQuestions', 'reachedpoints', - 'maxpoints', 'questioncount', 'workingtime'); + 'maxpoints', 'questioncount', 'workingtime', 'examId'); } /** @@ -181,7 +184,7 @@ public function addAnsweredQuestion($question_id, $max_points, $reached_points, ); } - public function &getAnsweredQuestion($index) + public function getAnsweredQuestion($index) { if (array_key_exists($index, $this->answeredQuestions)) { return $this->answeredQuestions[$index]; @@ -190,7 +193,7 @@ public function &getAnsweredQuestion($index) } } - public function &getAnsweredQuestionByQuestionId($question_id) + public function getAnsweredQuestionByQuestionId($question_id) { foreach ($this->answeredQuestions as $question) { if ($question["id"] == $question_id) { @@ -254,7 +257,23 @@ public function setObligationsAnswered($obligationsAnswered) { $this->obligationsAnswered = (bool) $obligationsAnswered; } - + + /** + * @return string + */ + public function getExamId() : string + { + return $this->examId; + } + + /** + * @param string $examId + */ + public function setExamId(string $examId) + { + $this->examId = $examId; + } + /** * getter for property obligationsAnswered. * if property wasn't set yet the method is trying diff --git a/Modules/Test/classes/class.ilTestEvaluationUserData.php b/Modules/Test/classes/class.ilTestEvaluationUserData.php index 571c09087e66..d7780f2c98fb 100755 --- a/Modules/Test/classes/class.ilTestEvaluationUserData.php +++ b/Modules/Test/classes/class.ilTestEvaluationUserData.php @@ -123,7 +123,7 @@ class ilTestEvaluationUserData /** * Test passes * - * @var array + * @var array */ public $passes; @@ -340,13 +340,21 @@ public function getPasses() { return $this->passes; } - + + /** + * @param int $pass_nr + * @param ilTestEvaluationPassData $pass + */ public function addPass($pass_nr, $pass) { $this->passes[$pass_nr] = $pass; } - - public function &getPass($pass_nr) + + /** + * @param $pass_nr + * @return ilTestEvaluationPassData|null + */ + public function getPass($pass_nr) { if (array_key_exists($pass_nr, $this->passes)) { return $this->passes[$pass_nr]; @@ -409,7 +417,7 @@ public function getQuestionTitles() return $this->questionTitles; } - public function &getQuestions($pass = 0) + public function getQuestions($pass = 0) { if (array_key_exists($pass, $this->questions)) { return $this->questions[$pass]; @@ -432,7 +440,7 @@ public function addQuestion($original_id, $question_id, $max_points, $sequence = ); } - public function &getQuestion($index, $pass = 0) + public function getQuestion($index, $pass = 0) { if (array_key_exists($index, $this->questions[$pass])) { return $this->questions[$pass][$index]; @@ -527,6 +535,21 @@ public function getRequestedHintsCountFromScoredPass() { return $this->getRequestedHintsCount($this->getScoredPass()); } + + /** + * @return string + */ + public function getExamIdFromScoredPass() : string + { + $examId = ''; + $scoredPass = $this->getScoredPass(); + + if (isset($this->passes[$scoredPass]) && $this->passes[$scoredPass] instanceof ilTestEvaluationPassData) { + $examId = $this->passes[$scoredPass]->getExamId(); + } + + return $examId; + } /** * returns the count of hints requested by participant for given testpass diff --git a/Modules/Test/classes/class.ilTestExport.php b/Modules/Test/classes/class.ilTestExport.php index 4c032aa41776..623a00e3062a 100755 --- a/Modules/Test/classes/class.ilTestExport.php +++ b/Modules/Test/classes/class.ilTestExport.php @@ -354,6 +354,10 @@ public function exportToExcel($deliver = true, $filterby = "", $filtertext = "", if (count($additionalFields)) { foreach ($additionalFields as $fieldname) { + if (strcmp($fieldname, "exam_id") == 0) { + $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord($col++) . $row, $this->lng->txt('exam_id_label')); + continue; + } $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord($col++) . $row, $this->lng->txt($fieldname)); } } @@ -411,6 +415,8 @@ public function exportToExcel($deliver = true, $filterby = "", $filtertext = "", foreach ($additionalFields as $fieldname) { if (strcmp($fieldname, 'gender') == 0) { $worksheet->setCell($row, $col++, $this->lng->txt('gender_' . $userfields[$fieldname])); + } elseif (strcmp($fieldname, "exam_id") == 0) { + $worksheet->setCell($row, $col++, $userdata->getExamIdFromScoredPass()); } else { $worksheet->setCell($row, $col++, $userfields[$fieldname]); } @@ -547,6 +553,9 @@ public function exportToExcel($deliver = true, $filterby = "", $filtertext = "", if (strcmp($fieldname, "matriculation") == 0) { $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord($col++) . $row, $this->lng->txt('matriculation')); } + if (strcmp($fieldname, "exam_id") == 0) { + $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord($col++) . $row, $this->lng->txt('exam_id_label')); + } } } $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord($col++) . $row, $this->lng->txt('test')); @@ -577,6 +586,13 @@ public function exportToExcel($deliver = true, $filterby = "", $filtertext = "", $col++; } } + if (strcmp($fieldname, "exam_id") == 0) { + if (strlen($userfields[$fieldname])) { + $worksheet->setCell($row, $col++, $userdata->getExamIdFromScoredPass()); + } else { + $col++; + } + } } } $worksheet->setCell($row, $col++, $this->test_obj->getTitle()); @@ -618,6 +634,9 @@ public function exportToExcel($deliver = true, $filterby = "", $filtertext = "", if (strcmp($fieldname, "matriculation") == 0) { $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord($col++) . $row, $this->lng->txt('matriculation')); } + if (strcmp($fieldname, "exam_id") == 0) { + $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord($col++) . $row, $this->lng->txt('exam_id_label')); + } } } $worksheet->setFormattedExcelTitle($worksheet->getColumnCoord($col++) . $row, $this->lng->txt('test')); @@ -648,6 +667,13 @@ public function exportToExcel($deliver = true, $filterby = "", $filtertext = "", $col++; } } + if (strcmp($fieldname, "exam_id") == 0) { + if (strlen($userfields[$fieldname])) { + $worksheet->setCell($row, $col++, $userdata->getExamIdFromScoredPass()); + } else { + $col++; + } + } } } $worksheet->setCell($row, $col++, $this->test_obj->getTitle()); @@ -764,6 +790,11 @@ public function exportToCSV($deliver = true, $filterby = "", $filtertext = "", $ $additionalFields = $this->test_obj->getEvaluationAdditionalFields(); if (count($additionalFields)) { foreach ($additionalFields as $fieldname) { + if (strcmp($fieldname, "exam_id") == 0) { + array_push($datarow, $this->lng->txt('exam_id_label')); + $col++; + continue; + } array_push($datarow, $this->lng->txt($fieldname)); $col++; } @@ -833,6 +864,8 @@ public function exportToCSV($deliver = true, $filterby = "", $filtertext = "", $ foreach ($additionalFields as $fieldname) { if (strcmp($fieldname, "gender") == 0) { array_push($datarow2, $this->lng->txt("gender_" . $userfields[$fieldname])); + } elseif (strcmp($fieldname, "exam_id") == 0) { + array_push($datarow2, $userdata->getExamIdFromScoredPass()); } else { array_push($datarow2, $userfields[$fieldname]); } diff --git a/Modules/Test/classes/class.ilTestExportRandomQuestionSet.php b/Modules/Test/classes/class.ilTestExportRandomQuestionSet.php index 68e84e56b491..488302c2fb0b 100644 --- a/Modules/Test/classes/class.ilTestExportRandomQuestionSet.php +++ b/Modules/Test/classes/class.ilTestExportRandomQuestionSet.php @@ -102,6 +102,7 @@ protected function populateSelectionDefinitions(ilXmlWriter $xmlWriter) foreach ($this->srcPoolDefList as $definition) { $attributes = array( 'id' => $definition->getId(), + 'ref_id' => $definition->getPoolRefId(), 'poolId' => $definition->getPoolId(), 'poolQuestCount' => $definition->getPoolQuestionCount(), 'questAmount' => $definition->getQuestionAmount(), diff --git a/Modules/Test/classes/class.ilTestLP.php b/Modules/Test/classes/class.ilTestLP.php index 4d1a3ed9d3e6..db1b0524744c 100644 --- a/Modules/Test/classes/class.ilTestLP.php +++ b/Modules/Test/classes/class.ilTestLP.php @@ -109,7 +109,7 @@ protected static function isLPMember(array &$a_res, $a_usr_id, $a_obj_ids) $set = $ilDB->query("SELECT tt.obj_fi" . " FROM tst_active ta" . " JOIN tst_tests tt ON (ta.test_fi = tt.test_id)" . - " WHERE " . $ilDB->in("tt.obj_fi", (array) $a_obj_ids, "", "integer") . + " WHERE " . $ilDB->in("tt.obj_fi", (array) $a_obj_ids, false, "integer") . " AND ta.user_fi = " . $ilDB->quote($a_usr_id, "integer")); while ($row = $ilDB->fetchAssoc($set)) { $a_res[$row["obj_fi"]] = true; diff --git a/Modules/Test/classes/class.ilTestOutputGUI.php b/Modules/Test/classes/class.ilTestOutputGUI.php index 2326da66c401..95e5682a02e7 100755 --- a/Modules/Test/classes/class.ilTestOutputGUI.php +++ b/Modules/Test/classes/class.ilTestOutputGUI.php @@ -650,7 +650,7 @@ public function saveQuestionSolution($authorized = true, $force = false) } } - if ($this->saveResult == false) { + if ($this->saveResult == false || (!$questionOBJ->validateSolutionSubmit() && $questionOBJ->savePartial()) ) { $this->ctrl->setParameter($this, "save_error", "1"); $_SESSION["previouspost"] = $_POST; } diff --git a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php index d8bce4e494e5..83c8999af1bc 100755 --- a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php +++ b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php @@ -544,6 +544,10 @@ public function handleUserSettings() } } + /** + * Redirect the user after an automatic save when the time limit is reached + * @throws ilTestException + */ public function redirectAfterAutosaveCmd() { $active_id = $this->testSession->getActiveId(); @@ -582,27 +586,26 @@ public function redirectAfterDashboardCmd() abstract protected function getCurrentQuestionId(); + /** + * Automatically save a user answer while working on the test + * (called repeatedly by asynchronous posts in configured autosave interval) + */ public function autosaveCmd() { - $canSaveResult = $this->canSaveResult(); - $authorizedSolution = !$canSaveResult; - $result = ""; if (is_array($_POST) && count($_POST) > 0) { - if ($this->isParticipantsAnswerFixed($this->getCurrentQuestionId())) { + if (!$this->canSaveResult() || $this->isParticipantsAnswerFixed($this->getCurrentQuestionId())) { $result = '-IGNORE-'; } else { - // fau: testNav - delete intermediate solution if answer is unchanged - // answer is changed, so save the change as intermediate solution + // answer is changed from authorized solution, so save the change as intermediate solution if ($this->getAnswerChangedParameter()) { - $res = $this->saveQuestionSolution($authorizedSolution, true); + $res = $this->saveQuestionSolution(false, true); } - // answer is not changed, so delete an intermediate solution + // answer is not changed from authorized solution, so delete an intermediate solution else { $db_res = $this->removeIntermediateSolution(); $res = is_int($db_res); } - // fau. if ($res) { $result = $this->lng->txt("autosave_success"); } else { @@ -610,18 +613,24 @@ public function autosaveCmd() } } } - // fau: testNav - simplify the redirection if time is reached - if (!$canSaveResult && !$this->ctrl->isAsynch()) { - // this was the last action in the test, saving is no longer allowed - // form was directly submitted in saveOnTimeReached() - // instead of ajax with autoSave() - $this->ctrl->redirect($this, ilTestPlayerCommands::REDIRECT_ON_TIME_LIMIT); - } - // fau. echo $result; exit; } - + + /** + * Automatically save a user answer when the limited duration of a test run is reached + * (called by synchronous form submit when the remaining time count down reaches zero) + */ + public function autosaveOnTimeLimitCmd() + { + if (!$this->isParticipantsAnswerFixed($this->getCurrentQuestionId())) { + // time limit saves the user solution as authorized + $this->saveQuestionSolution(true, true); + } + $this->ctrl->redirect($this, ilTestPlayerCommands::REDIRECT_ON_TIME_LIMIT); + } + + // fau: testNav - new function detectChangesCmd() /** * Detect changes sent in the background to the server @@ -1565,13 +1574,19 @@ abstract protected function isQuestionSummaryFinishTestButtonRequired(); /** * Output of a summary of all test questions for test participants */ - public function outQuestionSummaryCmd($fullpage = true, $contextFinishTest = false, $obligationsNotAnswered = false, $obligationsFilter = false) + public function outQuestionSummaryCmd($fullpage = true, $contextFinishTest = false, $obligationsInfo = false, $obligationsFilter = false) { if ($fullpage) { $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_question_summary.html", "Modules/Test"); } - - if ($obligationsNotAnswered) { + + $obligationsFulfilled = \ilObjTest::allObligationsAnswered( + $this->object->getId(), + $this->testSession->getActiveId(), + $this->testSession->getPass() + ); + + if ($obligationsInfo && $this->object->areObligationsEnabled() && !$obligationsFulfilled) { ilUtil::sendFailure($this->lng->txt('not_all_obligations_answered')); } @@ -1596,7 +1611,7 @@ public function outQuestionSummaryCmd($fullpage = true, $contextFinishTest = fal $table_gui->setShowPointsEnabled(!$this->object->getTitleOutput()); $table_gui->setShowMarkerEnabled($this->object->getShowMarker()); - $table_gui->setObligationsNotAnswered($obligationsNotAnswered); + $table_gui->setObligationsNotAnswered(!$obligationsFulfilled); $table_gui->setShowObligationsEnabled($this->object->areObligationsEnabled()); $table_gui->setObligationsFilterEnabled($obligationsFilter); $table_gui->setFinishTestButtonEnabled($this->isQuestionSummaryFinishTestButtonRequired()); @@ -1610,6 +1625,17 @@ public function outQuestionSummaryCmd($fullpage = true, $contextFinishTest = fal if ($this->object->getEnableProcessingTime()) { $this->outProcessingTime($active_id); } + + if ($this->object->isShowExamIdInTestPassEnabled()) { + $this->tpl->setCurrentBlock('exam_id_footer'); + $this->tpl->setVariable('EXAM_ID_VAL', ilObjTest::lookupExamId( + $this->testSession->getActiveId(), + $this->testSession->getPass(), + $this->object->getId() + )); + $this->tpl->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id')); + $this->tpl->parseCurrentBlock(); + } } } @@ -1968,7 +1994,9 @@ protected function cancelAnswerOptionalQuestionsCmd() protected function populateHelperGuiContent($helperGui) { if ($this->object->getKioskMode()) { - $this->tpl->addBlockfile($this->getContentBlockName(), 'content', "tpl.il_as_tst_kiosk_mode_content.html", "Modules/Test"); + $this->tpl->setBodyClass("kiosk"); + //$this->tpl->hideFooter(); + $this->tpl->addBlockfile('CONTENT', 'content', "tpl.il_as_tst_kiosk_mode_content.html", "Modules/Test"); $this->tpl->setContent($this->ctrl->getHTML($helperGui)); } else { $this->tpl->setVariable($this->getContentBlockName(), $this->ctrl->getHTML($helperGui)); @@ -2819,7 +2847,7 @@ protected function populateQuestionEditControl($questionGUI) // set url to which the for should be submitted when the working time is over // don't use asynch url because the form is submitted directly // but use simple '&' because url is copied by javascript into the form action - $config['saveOnTimeReachedUrl'] = str_replace('&', '&', $this->ctrl->getFormAction($this, ilTestPlayerCommands::AUTO_SAVE)); + $config['saveOnTimeReachedUrl'] = str_replace('&', '&', $this->ctrl->getFormAction($this, ilTestPlayerCommands::AUTO_SAVE_ON_TIME_LIMIT)); // enable the auto saving function // the autosave url is asynch because it will be used by an ajax request diff --git a/Modules/Test/classes/class.ilTestPlayerCommands.php b/Modules/Test/classes/class.ilTestPlayerCommands.php index 6760e3adbdc5..9843759756fb 100644 --- a/Modules/Test/classes/class.ilTestPlayerCommands.php +++ b/Modules/Test/classes/class.ilTestPlayerCommands.php @@ -52,6 +52,7 @@ class ilTestPlayerCommands const UNFREEZE_ANSWERS = 'unfreezeCheckedQuestionsAnswers'; const AUTO_SAVE = 'autosave'; + const AUTO_SAVE_ON_TIME_LIMIT = 'autosaveOnTimeLimit'; const REDIRECT_ON_TIME_LIMIT = 'redirectAfterAutosave'; const SUSPEND_TEST = 'suspendTest'; @@ -69,7 +70,7 @@ class ilTestPlayerCommands // fau: testNav - declare DETECT_CHANGES as non execution command self::DETECT_CHANGES, // fau. - self::AUTO_SAVE, self::REDIRECT_ON_TIME_LIMIT, + self::AUTO_SAVE, self::AUTO_SAVE_ON_TIME_LIMIT, self::REDIRECT_ON_TIME_LIMIT, self::AFTER_TEST_PASS_FINISHED, self::SHOW_FINAL_STATMENT ); diff --git a/Modules/Test/classes/class.ilTestQuestionSetConfig.php b/Modules/Test/classes/class.ilTestQuestionSetConfig.php index 4f9e9e9f5b29..0733e974995f 100644 --- a/Modules/Test/classes/class.ilTestQuestionSetConfig.php +++ b/Modules/Test/classes/class.ilTestQuestionSetConfig.php @@ -135,30 +135,19 @@ abstract public function cloneQuestionSetRelatedData(ilObjTest $cloneTestOBJ); */ public function getQuestionPoolPathString($poolId) { - $nodePath = $this->tree->getNodePath( - current(ilObject::_getAllReferences($poolId)) - ); + $ref_id = current(ilObject::_getAllReferences($poolId)); - $questionPoolPathString = ''; - - $i = 0; - $j = count($nodePath) - 2; - - foreach ($nodePath as $node) { - if ($i > 0) { - $questionPoolPathString .= ' > '; - } - - $questionPoolPathString .= $node['title']; - - if ($i == $j) { - break; - } - - $i++; - } - - return $questionPoolPathString; + $path = new ilPathGUI(); + $path->enableTextOnly(true); + return $path->getPath(ROOT_FOLDER_ID, $ref_id); + } + + public function getFirstQuestionPoolRefIdByObjId(int $pool_obj_id) : int + { + $refs_ids = ilObject::_getAllReferences($pool_obj_id); + $refs_id = current($refs_ids); + + return (int) $refs_id; } abstract public function isResultTaxonomyFilterSupported(); diff --git a/Modules/Test/classes/class.ilTestRandomQuestionSetConfigGUI.php b/Modules/Test/classes/class.ilTestRandomQuestionSetConfigGUI.php index 391aba23243d..81b50d5fb8d0 100644 --- a/Modules/Test/classes/class.ilTestRandomQuestionSetConfigGUI.php +++ b/Modules/Test/classes/class.ilTestRandomQuestionSetConfigGUI.php @@ -816,6 +816,7 @@ private function getSourcePoolDefinitionByAvailableQuestionPoolId($poolId) $originalPoolData = $availablePools[$poolId]; $originalPoolData['qpl_path'] = $this->questionSetConfig->getQuestionPoolPathString($poolId); + $originalPoolData['qpl_ref_id'] = $this->questionSetConfig->getFirstQuestionPoolRefIdByObjId($poolId); return $this->sourcePoolDefinitionFactory->getSourcePoolDefinitionByOriginalPoolData($originalPoolData); } @@ -891,6 +892,7 @@ private function deriveNewPoolsCmd() $srcPoolDefinition = $this->sourcePoolDefinitionList->getDefinitionBySourcePoolId($newPool->getId()); $srcPoolDefinition->setPoolTitle($newPool->getTitle()); $srcPoolDefinition->setPoolPath($this->questionSetConfig->getQuestionPoolPathString($newPool->getId())); + $srcPoolDefinition->setPoolRefId($this->questionSetConfig->getFirstQuestionPoolRefIdByObjId((int) $newPool->getId())); $srcPoolDefinition->saveToDb(); ilTestRandomQuestionSetStagingPoolQuestionList::updateSourceQuestionPoolId( diff --git a/Modules/Test/classes/class.ilTestRandomQuestionSetNonAvailablePool.php b/Modules/Test/classes/class.ilTestRandomQuestionSetNonAvailablePool.php index 69f52d69f57a..88f4b8819e3d 100644 --- a/Modules/Test/classes/class.ilTestRandomQuestionSetNonAvailablePool.php +++ b/Modules/Test/classes/class.ilTestRandomQuestionSetNonAvailablePool.php @@ -23,6 +23,9 @@ class ilTestRandomQuestionSetNonAvailablePool */ protected $id; + /** @var int|null */ + protected $ref_id = null; + /** * @var string */ @@ -97,6 +100,16 @@ public function setUnavailabilityStatus($unavailabilityStatus) $this->unavailabilityStatus = $unavailabilityStatus; } + public function getRefId() : ?int + { + return $this->ref_id; + } + + public function setRefId(?int $ref_id) : void + { + $this->ref_id = $ref_id; + } + /** * @param array $row */ @@ -105,6 +118,7 @@ public function assignDbRow($row) foreach ($row as $field => $value) { switch ($field) { case 'pool_fi': $this->setId($value); break; + case 'pool_ref_id': $this->setRefId($value ? (int) $value : null); break; case 'pool_title': $this->setTitle($value); break; case 'pool_path': $this->setPath($value); break; } diff --git a/Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinition.php b/Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinition.php index f63717c4ff32..014f280431aa 100644 --- a/Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinition.php +++ b/Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinition.php @@ -26,6 +26,9 @@ class ilTestRandomQuestionSetSourcePoolDefinition private $id = null; private $poolId = null; + + /** @var null|int */ + private $poolRefId = null; private $poolTitle = null; @@ -88,6 +91,16 @@ public function getPoolId() { return $this->poolId; } + + public function getPoolRefId() : ?int + { + return $this->poolRefId; + } + + public function setPoolRefId(?int $poolRefId) : void + { + $this->poolRefId = $poolRefId; + } public function setPoolTitle($poolTitle) { @@ -312,6 +325,7 @@ public function initFromArray($dataArray) switch ($field) { case 'def_id': $this->setId($value); break; case 'pool_fi': $this->setPoolId($value); break; + case 'pool_ref_id': $this->setPoolRefId($value ? (int) $value : null); break; case 'pool_title': $this->setPoolTitle($value); break; case 'pool_path': $this->setPoolPath($value); break; case 'pool_quest_count': $this->setPoolQuestionCount($value); break; @@ -384,6 +398,7 @@ private function updateDbRecord($testId) array( 'test_fi' => array('integer', $testId), 'pool_fi' => array('integer', $this->getPoolId()), + 'pool_ref_id' => array('integer', $this->getPoolRefId()), 'pool_title' => array('text', $this->getPoolTitle()), 'pool_path' => array('text', $this->getPoolPath()), 'pool_quest_count' => array('integer', $this->getPoolQuestionCount()), @@ -416,6 +431,7 @@ private function insertDbRecord($testId) 'def_id' => array('integer', $nextId), 'test_fi' => array('integer', $testId), 'pool_fi' => array('integer', $this->getPoolId()), + 'pool_ref_id' => array('integer', $this->getPoolRefId()), 'pool_title' => array('text', $this->getPoolTitle()), 'pool_path' => array('text', $this->getPoolPath()), 'pool_quest_count' => array('integer', $this->getPoolQuestionCount()), @@ -439,10 +455,17 @@ private function insertDbRecord($testId) public function getPoolInfoLabel(ilLanguage $lng) { + $pool_path = $this->getPoolPath(); + if (is_int($this->getPoolRefId()) && ilObject::_lookupObjId($this->getPoolRefId())) { + $path = new ilPathGUI(); + $path->enableTextOnly(true); + $pool_path = $path->getPath(ROOT_FOLDER_ID, $this->getPoolRefId()); + } + $poolInfoLabel = sprintf( $lng->txt('tst_dynamic_question_set_source_questionpool_summary_string'), $this->getPoolTitle(), - $this->getPoolPath(), + $pool_path, $this->getPoolQuestionCount() ); diff --git a/Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinitionFactory.php b/Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinitionFactory.php index bb8a4cd3c67c..5d003c777c06 100644 --- a/Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinitionFactory.php +++ b/Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinitionFactory.php @@ -39,6 +39,7 @@ public function getSourcePoolDefinitionByOriginalPoolData($originalPoolData) $sourcePoolDefinition = $this->buildDefinitionInstance(); $sourcePoolDefinition->setPoolId($originalPoolData['qpl_id']); + $sourcePoolDefinition->setPoolRefId($originalPoolData['qpl_ref_id']); $sourcePoolDefinition->setPoolTitle($originalPoolData['qpl_title']); $sourcePoolDefinition->setPoolPath($originalPoolData['qpl_path']); $sourcePoolDefinition->setPoolQuestionCount($originalPoolData['count']); diff --git a/Modules/Test/classes/class.ilTestScoringByQuestionsGUI.php b/Modules/Test/classes/class.ilTestScoringByQuestionsGUI.php index 3502cdbe86fb..20212a5f7806 100644 --- a/Modules/Test/classes/class.ilTestScoringByQuestionsGUI.php +++ b/Modules/Test/classes/class.ilTestScoringByQuestionsGUI.php @@ -47,7 +47,7 @@ protected function showManScoringByQuestionParticipantsTable($manPointsPost = ar $tpl = $DIC->ui()->mainTemplate(); $DIC->tabs()->activateTab(ilTestTabsManager::TAB_ID_MANUAL_SCORING); - + include_once 'Services/jQuery/classes/class.iljQueryUtil.php'; iljQueryUtil::initjQuery(); @@ -69,7 +69,7 @@ protected function showManScoringByQuestionParticipantsTable($manPointsPost = ar require_once 'Modules/Test/classes/tables/class.ilTestManScoringParticipantsBySelectedQuestionAndPassTableGUI.php'; $table = new ilTestManScoringParticipantsBySelectedQuestionAndPassTableGUI($this); - + $table->setManualScoringPointsPostData($manPointsPost); $qst_id = $table->getFilterItemByPostVar('question')->getValue(); @@ -92,18 +92,20 @@ protected function showManScoringByQuestionParticipantsTable($manPointsPost = ar if ($selected_questionData && is_numeric($passNr)) { $data = $this->object->getCompleteEvaluationData(false); $participants = $data->getParticipants(); - + require_once 'Modules/Test/classes/class.ilTestParticipantData.php'; $participantData = new ilTestParticipantData($DIC->database(), $DIC->language()); $participantData->setActiveIdsFilter(array_keys($data->getParticipants())); - + $participantData->setParticipantAccessFilter( ilTestParticipantAccessFilter::getScoreParticipantsUserFilter($this->ref_id) ); - + $participantData->load($this->object->getTestId()); foreach ($participantData->getActiveIds() as $active_id) { + + /** @var $participant ilTestEvaluationUserData */ $participant = $participants[$active_id]; $testResultData = $this->object->getTestResult($active_id, $passNr - 1); foreach ($testResultData as $questionData) { @@ -111,6 +113,7 @@ protected function showManScoringByQuestionParticipantsTable($manPointsPost = ar continue; } + $user = ilObjUser::_getUserData(array($participant->user_id)); $table_data[] = array( 'pass_id' => $passNr - 1, 'active_id' => $active_id, @@ -118,6 +121,9 @@ protected function showManScoringByQuestionParticipantsTable($manPointsPost = ar 'reached_points' => assQuestion::_getReachedPoints($active_id, $questionData['qid'], $passNr - 1), 'maximum_points' => assQuestion::_getMaximumPoints($questionData['qid']), 'participant' => $participant, + 'lastname' => $user[0]['lastname'], + 'firstname' => $user[0]['firstname'], + 'login' => $participant->getLogin(), ); } } @@ -141,30 +147,30 @@ protected function showManScoringByQuestionParticipantsTable($manPointsPost = ar $table->setData($table_data); $tpl->setContent($table->getHTML()); } - + protected function saveManScoringByQuestion() { global $DIC; /* @var ILIAS\DI\Container $DIC */ - + if (!isset($_POST['scoring']) || !is_array($_POST['scoring'])) { ilUtil::sendFailure($this->lng->txt('tst_save_manscoring_failed_unknown')); $this->showManScoringByQuestionParticipantsTable(); return; } - + $pass = key($_POST['scoring']); $activeData = current($_POST['scoring']); - + require_once 'Modules/Test/classes/class.ilTestParticipantData.php'; $participantData = new ilTestParticipantData($DIC->database(), $DIC->language()); $participantData->setActiveIdsFilter(array_keys($activeData)); - + $participantData->setParticipantAccessFilter( ilTestParticipantAccessFilter::getScoreParticipantsUserFilter($this->ref_id) ); - + $participantData->load($this->object->getTestId()); - + include_once 'Modules/TestQuestionPool/classes/class.assQuestion.php'; include_once 'Modules/Test/classes/class.ilObjTestAccess.php'; include_once 'Services/Tracking/classes/class.ilLPStatusWrapper.php'; @@ -181,7 +187,7 @@ protected function saveManScoringByQuestion() if (!isset($skipParticipant[$pass])) { $skipParticipant[$pass] = array(); } - + $skipParticipant[$pass][$active_id] = true; continue; @@ -197,11 +203,11 @@ protected function saveManScoringByQuestion() } $maxPointsByQuestionId[$qst_id] = assQuestion::_getMaximumPoints($qst_id); - + if ($reached_points > $maxPointsByQuestionId[$qst_id]) { $oneExceededMaxPoints = true; } - + $manPointsPost[$pass][$active_id][$qst_id] = $reached_points; } } @@ -260,12 +266,14 @@ protected function saveManScoringByQuestion() ); ilUtil::sendSuccess($msg, true); + /* disabled for Mantis 25850 require_once './Modules/Test/classes/class.ilTestScoring.php'; $scorer = new ilTestScoring($this->object); $scorer->setPreserveManualScores(true); $scorer->recalculateSolutions(); + */ } - + $this->showManScoringByQuestionParticipantsTable(); } @@ -305,16 +313,27 @@ protected function getAnswerDetail() $data = $this->object->getCompleteEvaluationData(false); $participant = $data->getParticipant($active_id); - + $question_gui = $this->object->createQuestionGUI('', $question_id); $tmp_tpl = new ilTemplate('tpl.il_as_tst_correct_solution_output.html', true, true, 'Modules/Test'); + if ($question_gui->supportsIntermediateSolutionOutput() && $question_gui->hasIntermediateSolution($active_id, $pass)) { + $question_gui->setUseIntermediateSolution(true); + $aresult_output = $question_gui->getSolutionOutput($active_id, $pass, false, false, true, false, false, true); + $question_gui->setUseIntermediateSolution(false); + $tmp_tpl->setVariable('TEXT_ASOLUTION_OUTPUT', $this->lng->txt('autosavecontent')); + $tmp_tpl->setVariable('ASOLUTION_OUTPUT', $aresult_output); + } + $result_output = $question_gui->getSolutionOutput($active_id, $pass, false, false, false, $this->object->getShowSolutionFeedback(), false, true); $tmp_tpl->setVariable('TEXT_YOUR_SOLUTION', $this->lng->txt('answers_of') . ' ' . $participant->getName()); + + + $maxpoints = $question_gui->object->getMaximumPoints(); $add_title = ' [' . $this->lng->txt('question_id_short') . ': ' . $question_id . ']'; - + if ($maxpoints == 1) { $tmp_tpl->setVariable('QUESTION_TITLE', $this->object->getQuestionTitle($question_gui->object->getTitle()) . ' (' . $maxpoints . ' ' . $this->lng->txt('point') . ')' . $add_title); } else { diff --git a/Modules/Test/classes/class.ilTestScoringGUI.php b/Modules/Test/classes/class.ilTestScoringGUI.php index 88ae49e0898f..74d9cb907803 100755 --- a/Modules/Test/classes/class.ilTestScoringGUI.php +++ b/Modules/Test/classes/class.ilTestScoringGUI.php @@ -417,7 +417,8 @@ private function buildManScoringParticipantForm($questionGuiList, $activeId, $pa $form->setTitle(sprintf($lng->txt('manscoring_results_pass'), $pass + 1)); $form->setTableWidth('100%'); - + + /** @var assQuestionGUI $questionGUI */ foreach ($questionGuiList as $questionId => $questionGUI) { $questionHeader = sprintf($lng->txt('tst_manscoring_question_section_header'), $questionGUI->object->getTitle()); $questionSolution = $questionGUI->getSolutionOutput($activeId, $pass, false, false, true, false, false, true); @@ -431,6 +432,15 @@ private function buildManScoringParticipantForm($questionGuiList, $activeId, $pa $cust->setHtml($questionSolution); $form->addItem($cust); + if ($questionGUI->supportsIntermediateSolutionOutput() && $questionGUI->hasIntermediateSolution($activeId, $pass)) { + $questionGUI->setUseIntermediateSolution(true); + $intermediateSolution = $questionGUI->getSolutionOutput($activeId, $pass, false, false, true, false, false, true); + $questionGUI->setUseIntermediateSolution(false); + $cust = new ilCustomInputGUI($lng->txt('autosavecontent')); + $cust->setHtml($intermediateSolution); + $form->addItem($cust); + } + $text = new ilTextInputGUI($lng->txt('tst_change_points_for_question'), "question__{$questionId}__points"); if ($initValues) { $text->setValue(assQuestion::_getReachedPoints($activeId, $questionId, $pass)); @@ -442,7 +452,7 @@ private function buildManScoringParticipantForm($questionGuiList, $activeId, $pa $nonedit->setValue(assQuestion::_getMaximumPoints($questionId)); } $form->addItem($nonedit); - + $area = new ilTextAreaInputGUI($lng->txt('set_manual_feedback'), "question__{$questionId}__feedback"); $area->setUseRTE(true); if ($initValues) { diff --git a/Modules/Test/classes/class.ilTestServiceGUI.php b/Modules/Test/classes/class.ilTestServiceGUI.php index 661bab4b054d..d4b7396798fc 100755 --- a/Modules/Test/classes/class.ilTestServiceGUI.php +++ b/Modules/Test/classes/class.ilTestServiceGUI.php @@ -398,7 +398,7 @@ public function getPassListOfAnswers(&$result_array, $active_id, $pass, $show_so $showFeedback = $this->isContextResultPresentation() && $this->object->getShowSolutionFeedback(); $show_solutions = $this->isContextResultPresentation() && $show_solutions; - + if ($show_solutions) { $compare_template = new ilTemplate('tpl.il_as_tst_answers_compare.html', true, true, 'Modules/Test'); $compare_template->setVariable("HEADER_PARTICIPANT", $this->lng->txt('tst_header_participant')); @@ -408,9 +408,23 @@ public function getPassListOfAnswers(&$result_array, $active_id, $pass, $show_so $compare_template->setVariable('PARTICIPANT', $result_output); $compare_template->setVariable('SOLUTION', $best_output); + if ($question_gui->supportsIntermediateSolutionOutput() && $question_gui->hasIntermediateSolution($active_id, $pass)) { + $question_gui->setUseIntermediateSolution(true); + $intermediate_output = $question_gui->getSolutionOutput($active_id, $pass, $show_solutions, false, true, $showFeedback); + $question_gui->setUseIntermediateSolution(false); + $compare_template->setVariable('TXT_INTERMEDIATE', $this->lng->txt('autosavecontent')); + $compare_template->setVariable('INTERMEDIATE', $intermediate_output); + } $template->setVariable('SOLUTION_OUTPUT', $compare_template->get()); } else { $result_output = $question_gui->getSolutionOutput($active_id, $pass, $show_solutions, false, $show_question_only, $showFeedback); + if ($question_gui->supportsIntermediateSolutionOutput() && $question_gui->hasIntermediateSolution($active_id, $pass)) { + $question_gui->setUseIntermediateSolution(true); + $intermediate_output = $question_gui->getSolutionOutput($active_id, $pass, $show_solutions, false, true, $showFeedback); + $question_gui->setUseIntermediateSolution(false); + $template->setVariable('TXT_INTERMEDIATE', $this->lng->txt('autosavecontent')); + $template->setVariable('INTERMEDIATE', $intermediate_output); + } $template->setVariable('SOLUTION_OUTPUT', $result_output); } @@ -854,6 +868,7 @@ public function getResultsOfUserOutput($testSession, $active_id, $pass, $targetG $template->setVariable("TEXT_HEADING", sprintf($this->lng->txt("tst_result_user_name"), $uname)); $template->setVariable("USER_DATA", $user_data); + $this->populateExamId($template, (int) $active_id, (int) $pass); $this->populatePassFinishDate($template, ilObjTest::lookupLastTestPassAccess($active_id, $pass)); return $template->get(); @@ -1197,6 +1212,22 @@ public function populatePassFinishDate($tpl, $passFinishDate) $tpl->setVariable("PASS_FINISH_DATE_LABEL", $this->lng->txt('tst_pass_finished_on')); $tpl->setVariable("PASS_FINISH_DATE_VALUE", $passFinishDate); } + + /** + * @param ilTemplate $tpl + * @param int $activeId + * @param int $pass + */ + public function populateExamId(ilTemplate $tpl, int $activeId, int $pass) + { + if ($this->object->isShowExamIdInTestResultsEnabled()) { + $tpl->setVariable("EXAM_ID_TXT", $this->lng->txt('exam_id')); + $tpl->setVariable('EXAM_ID', ilObjTest::lookupExamId( + $activeId, + $pass + )); + } + } } // internal sort function to sort the result array diff --git a/Modules/Test/classes/class.ilTestSubmissionReviewGUI.php b/Modules/Test/classes/class.ilTestSubmissionReviewGUI.php index 4bd2b0a2c42f..d0b6fd2cec47 100644 --- a/Modules/Test/classes/class.ilTestSubmissionReviewGUI.php +++ b/Modules/Test/classes/class.ilTestSubmissionReviewGUI.php @@ -184,8 +184,21 @@ protected function show() $html = $this->buildToolbar('review_nav_top')->getHTML(); $html .= $this->buildUserReviewOutput() . '
'; $html .= $this->buildToolbar('review_nav_bottom')->getHTML(); + + if ($this->object->isShowExamIdInTestPassEnabled() && !$this->object->getKioskMode()) { + $examIdTpl = new ilTemplate("tpl.exam_id_block.html", true, true, 'Modules/Test'); + $examIdTpl->setVariable('EXAM_ID_VAL', ilObjTest::lookupExamId( + $this->testSession->getActiveId(), + $this->testSession->getPass(), + $this->object->getId() + )); + $examIdTpl->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id')); + $html .= $examIdTpl->get(); + } - $this->tpl->setVariable($this->getContentBlockName(), $html); + $this->tpl->setVariable( + $this->getContentBlockName(), $html + ); } protected function pdfDownload() diff --git a/Modules/Test/classes/tables/class.ilEvaluationAllTableGUI.php b/Modules/Test/classes/tables/class.ilEvaluationAllTableGUI.php index c791dca90308..2f8b71565d92 100644 --- a/Modules/Test/classes/tables/class.ilEvaluationAllTableGUI.php +++ b/Modules/Test/classes/tables/class.ilEvaluationAllTableGUI.php @@ -51,6 +51,9 @@ public function __construct($a_parent_obj, $a_parent_cmd, $anonymity = false, $o if (strcmp($c, 'email') == 0) { $this->addColumn($this->lng->txt("email"), 'email', ''); } + if (strcmp($c, 'exam_id') == 0 && $this->parent_obj->object->isShowExamIdInTestResultsEnabled()) { + $this->addColumn($this->lng->txt("exam_id_label"), 'exam_id', ''); + } if (strcmp($c, 'institution') == 0) { $this->addColumn($this->lng->txt("institution"), 'institution', ''); } @@ -133,6 +136,7 @@ public function numericOrdering($a_field) break; case 'reached': case 'hint_count': + case 'exam_id': case 'answered': return true; break; @@ -191,6 +195,12 @@ public function getSelectableColumns() "txt" => $lng->txt("matriculation"), "default" => false ); + if ($this->parent_obj->object->isShowExamIdInTestResultsEnabled()) { + $cols["exam_id"] = array( + "txt" => $lng->txt("exam_id_label"), + "default" => false + ); + } } if ($this->parent_obj->object->getECTSOutput()) { $cols["ects_grade"] = array( @@ -308,6 +318,12 @@ protected function fillRow($data) $this->tpl->setVariable("MATRICULATION", strlen($data['matriculation']) ? $data['matriculation'] : ' '); $this->tpl->parseCurrentBlock(); } + if (strcmp($c, 'exam_id') == 0 && $this->parent_obj->object->isShowExamIdInTestResultsEnabled()) { + $this->tpl->setCurrentBlock('exam_id'); + $examId = is_string($data['exam_id']) && strlen($data['exam_id']) ? $data['exam_id'] : ' '; + $this->tpl->setVariable('EXAM_ID', $examId); + $this->tpl->parseCurrentBlock(); + } } if ($this->parent_obj->object->getECTSOutput()) { if (strcmp($c, 'ects_grade') == 0) { diff --git a/Modules/Test/classes/tables/class.ilParticipantsTestResultsTableGUI.php b/Modules/Test/classes/tables/class.ilParticipantsTestResultsTableGUI.php index 03ea12c5381e..0efb5f692883 100644 --- a/Modules/Test/classes/tables/class.ilParticipantsTestResultsTableGUI.php +++ b/Modules/Test/classes/tables/class.ilParticipantsTestResultsTableGUI.php @@ -100,7 +100,7 @@ public function setAnonymity($anonymity) public function numericOrdering($field) { return in_array($field, array( - 'scored_pass', 'answered_questions', 'points', 'percent_result' + 'scored_pass', 'answered_questions', 'reached_points', 'percent_result' )); } diff --git a/Modules/Test/classes/tables/class.ilTestManScoringParticipantsBySelectedQuestionAndPassTableGUI.php b/Modules/Test/classes/tables/class.ilTestManScoringParticipantsBySelectedQuestionAndPassTableGUI.php index 3eb14b058dda..0c4248e137ea 100644 --- a/Modules/Test/classes/tables/class.ilTestManScoringParticipantsBySelectedQuestionAndPassTableGUI.php +++ b/Modules/Test/classes/tables/class.ilTestManScoringParticipantsBySelectedQuestionAndPassTableGUI.php @@ -41,8 +41,6 @@ public function __construct($parentObj) parent::__construct($parentObj, self::PARENT_DEFAULT_CMD); - $this->disable('sort'); - $this->setFormAction($ilCtrl->getFormAction($parentObj, self::PARENT_DEFAULT_CMD)); $this->setRowTemplate("tpl.il_as_tst_man_scoring_by_question_tblrow.html", "Modules/Test"); @@ -51,17 +49,28 @@ public function __construct($parentObj) $this->addCommandButton(self::PARENT_SAVE_SCORING_CMD, $this->lng->txt('save')); + $this->initOrdering(); $this->initColumns(); $this->initFilter(); } private function initColumns() { - $this->addColumn($this->lng->txt('name'), 'lastname', '40%'); - $this->addColumn($this->lng->txt('tst_reached_points'), 'lastname', '40%'); + $this->addColumn($this->lng->txt('lastname'), 'lastname', '20%'); + $this->addColumn($this->lng->txt('firstname'), 'firstname', '20%'); + $this->addColumn($this->lng->txt('login'), 'login', '20%'); + $this->addColumn($this->lng->txt('tst_reached_points'), 'reached_points', '20%'); $this->addColumn('', '', '20%'); } + private function initOrdering() + { + $this->enable('sort'); + + $this->setDefaultOrderField("lastname"); + $this->setDefaultOrderDirection("asc"); + } + public function initFilter() { $this->setDisableFilterHiding(true); @@ -128,7 +137,10 @@ public function fillRow($row) $this->tpl->touchBlock('row_js'); } - $this->tpl->setVariable('VAL_NAME', $row['participant']->getName()); + $this->tpl->setVariable('PARTICIPANT_LASTNAME', $row['lastname']); + $this->tpl->setVariable('PARTICIPANT_FIRSTNAME', $row['firstname']); + $this->tpl->setVariable('PARTICIPANT_LOGIN', $row['login']); + $reached_points = new ilNumberInputGUI('', 'scoring[' . $row['pass_id'] . '][' . $row['active_id'] . '][' . $row['qst_id'] . ']'); $reached_points->allowDecimals(true); $reached_points->setSize(5); diff --git a/Modules/Test/classes/tables/class.ilTestQuestionBrowserTableGUI.php b/Modules/Test/classes/tables/class.ilTestQuestionBrowserTableGUI.php index 4d00e6ac9209..3948b6e478e0 100644 --- a/Modules/Test/classes/tables/class.ilTestQuestionBrowserTableGUI.php +++ b/Modules/Test/classes/tables/class.ilTestQuestionBrowserTableGUI.php @@ -415,6 +415,7 @@ public function initFilter() require_once 'Services/Form/classes/class.ilRepositorySelectorInputGUI.php'; $ri = new ilRepositorySelectorInputGUI($this->lng->txt('repository'), 'repository_root_node'); $ri->setHeaderMessage($this->lng->txt('question_browse_area_info')); + $ri->setClickableTypes(array('qpl')); $this->addFilterItem($ri); $ri->readFromSession(); $this->filter['repository_root_node'] = $ri->getValue(); diff --git a/Modules/Test/classes/tables/class.ilTestQuestionsTableGUI.php b/Modules/Test/classes/tables/class.ilTestQuestionsTableGUI.php index 1812a667e143..110b153fa415 100644 --- a/Modules/Test/classes/tables/class.ilTestQuestionsTableGUI.php +++ b/Modules/Test/classes/tables/class.ilTestQuestionsTableGUI.php @@ -46,7 +46,7 @@ class ilTestQuestionsTableGUI extends ilTable2GUI protected $obligatoryQuestionsHandlingEnabled = false; /** - * @var int + * @var float */ protected $totalPoints = 0; @@ -450,17 +450,17 @@ public function setObligatoryQuestionsHandlingEnabled(bool $obligatoryQuestionsH } /** - * @return int + * @return float */ - public function getTotalPoints() : int + public function getTotalPoints() : float { return $this->totalPoints; } /** - * @param int $totalPoints + * @param float $totalPoints */ - public function setTotalPoints(int $totalPoints) + public function setTotalPoints(float $totalPoints) { $this->totalPoints = $totalPoints; } diff --git a/Modules/Test/templates/default/tpl.exam_id_block.html b/Modules/Test/templates/default/tpl.exam_id_block.html new file mode 100644 index 000000000000..8dd2564e5c28 --- /dev/null +++ b/Modules/Test/templates/default/tpl.exam_id_block.html @@ -0,0 +1 @@ +

{EXAM_ID_TXT} {EXAM_ID_VAL}

\ No newline at end of file diff --git a/Modules/Test/templates/default/tpl.il_as_tst_answers_compare.html b/Modules/Test/templates/default/tpl.il_as_tst_answers_compare.html index a3f9b046b1ff..84ed35953e19 100644 --- a/Modules/Test/templates/default/tpl.il_as_tst_answers_compare.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_answers_compare.html @@ -2,6 +2,10 @@
{HEADER_PARTICIPANT}
{PARTICIPANT}
+ +
{TXT_INTERMEDIATE}
+
{INTERMEDIATE}
+
{HEADER_SOLUTION}
diff --git a/Modules/Test/templates/default/tpl.il_as_tst_correct_solution_output.html b/Modules/Test/templates/default/tpl.il_as_tst_correct_solution_output.html index 6f29fddebcab..ef8a01202588 100755 --- a/Modules/Test/templates/default/tpl.il_as_tst_correct_solution_output.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_correct_solution_output.html @@ -11,13 +11,25 @@

{TEXT_YOUR_SOLUTION}:

{SOLUTION_OUTPUT} - -

{OUTLINE_SPECIFIC_FEEDBACK}

- +
+ +

{TEXT_ASOLUTION_OUTPUT}:

+ {ASOLUTION_OUTPUT} +
+ -

{TEXT_BEST_SOLUTION}:

+

{TEXT_BEST_SOLUTION}:

{BEST_OUTPUT} +
+ +

{TEXT_SOLUTION_HINT}:

+ {SOLUTION_HINT} +
+ + +

{OUTLINE_SPECIFIC_FEEDBACK}

+

{RECEIVED_POINTS}

diff --git a/Modules/Test/templates/default/tpl.il_as_tst_man_scoring_by_question_tblrow.html b/Modules/Test/templates/default/tpl.il_as_tst_man_scoring_by_question_tblrow.html index ff5a79c0ad58..99ee0536c5fc 100644 --- a/Modules/Test/templates/default/tpl.il_as_tst_man_scoring_by_question_tblrow.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_man_scoring_by_question_tblrow.html @@ -1,5 +1,9 @@ - {VAL_NAME} + {PARTICIPANT_LASTNAME} + + {PARTICIPANT_FIRSTNAME} + {PARTICIPANT_LOGIN} + {VAL_REACHED_POINTS} diff --git a/Modules/Test/templates/default/tpl.il_as_tst_pass_details_overview_participants.html b/Modules/Test/templates/default/tpl.il_as_tst_pass_details_overview_participants.html index c79831414416..d195f3c6ac2a 100755 --- a/Modules/Test/templates/default/tpl.il_as_tst_pass_details_overview_participants.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_pass_details_overview_participants.html @@ -8,7 +8,12 @@

{TEXT_HEADING}

{GRADING_MESSAGE} {PASS_DETAILS} + +

+ {TOTAL_RESULT_TEXT}: {TOTAL_RESULT} +


+ {LIST_OF_ANSWERS}

diff --git a/Modules/Test/templates/default/tpl.il_as_tst_question_summary.html b/Modules/Test/templates/default/tpl.il_as_tst_question_summary.html index ffb94ac33203..e6e17b5c2cef 100644 --- a/Modules/Test/templates/default/tpl.il_as_tst_question_summary.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_question_summary.html @@ -11,4 +11,7 @@ {TEXT_CANCELTEST} {TEXT_ALTCANCELTEXT}

-{TABLE_LIST_OF_QUESTIONS} \ No newline at end of file +{TABLE_LIST_OF_QUESTIONS} + +

{EXAM_ID_TXT} {EXAM_ID_VAL}

+ \ No newline at end of file diff --git a/Modules/Test/templates/default/tpl.il_as_tst_results_participant.html b/Modules/Test/templates/default/tpl.il_as_tst_results_participant.html index adc366b43a28..8c50283439aa 100755 --- a/Modules/Test/templates/default/tpl.il_as_tst_results_participant.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_results_participant.html @@ -11,7 +11,12 @@ {PASS_DETAILS} + +

+ {TOTAL_RESULT_TEXT}: {TOTAL_RESULT} +


+ {LIST_OF_ANSWERS} diff --git a/Modules/Test/templates/default/tpl.table_evaluation_all.html b/Modules/Test/templates/default/tpl.table_evaluation_all.html index 61196522320b..d8eacc08bfc4 100644 --- a/Modules/Test/templates/default/tpl.table_evaluation_all.html +++ b/Modules/Test/templates/default/tpl.table_evaluation_all.html @@ -10,6 +10,7 @@ {COUNTRY} {DEPARTMENT} {MATRICULATION} + {EXAM_ID} {REACHED} {HINT_COUNT} {MARK} diff --git a/Modules/TestQuestionPool/classes/class.assClozeTest.php b/Modules/TestQuestionPool/classes/class.assClozeTest.php index 923c1de83e82..a0ef1162176b 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeTest.php +++ b/Modules/TestQuestionPool/classes/class.assClozeTest.php @@ -43,7 +43,7 @@ class assClozeTest extends assQuestion implements ilObjQuestionScoringAdjustable public $gap_combinations_exists; - + /** * The start tag beginning a cloze gap * @@ -61,7 +61,7 @@ class assClozeTest extends assQuestion implements ilObjQuestionScoringAdjustable * @var string */ public $end_tag; - + /** * The rating option for text gaps * @@ -93,12 +93,12 @@ class assClozeTest extends assQuestion implements ilObjQuestionScoringAdjustable public $fixedTextLength; public $cloze_text; - + /** * @var ilAssClozeTestFeedback */ public $feedbackOBJ; - + protected $feedbackMode = ilAssClozeTestFeedback::FB_MODE_GAP_QUESTION; /** @@ -158,12 +158,30 @@ public function isComplete() */ public function cleanQuestiontext($text) { - $text = preg_replace("/\[gap[^\]]*?\]/", "[gap]", $text); + // fau: fixGapReplace - mask dollars for replacement + $text = str_replace('$','GAPMASKEDDOLLAR', $text);$text = preg_replace("/\[gap[^\]]*?\]/", "[gap]", $text); $text = preg_replace("/\]*?)\>/", "[gap]", $text); - $text = str_replace("", "[/gap]", $text); + $text = str_replace("", "[/gap]", $text);$text = str_replace('GAPMASKEDDOLLAR', '$', $text); +// fau. return $text; } + // fau: fixGapReplace - add function replaceFirstGap() + /** + * Replace the first gap in a string without treating backreferences + * @param string $gaptext text with gap tags + * @param string $content content for the first gap + * @return string + */ + public function replaceFirstGap($gaptext, $content) + { + $content = str_replace('$','GAPMASKEDDOLLAR', $content); + $output = preg_replace("/\[gap\].*?\[\/gap\]/", $content, $gaptext, 1); + $output = str_replace('GAPMASKEDDOLLAR', '$', $output); + + return $output; + } +// fau. /** * Loads a assClozeTest object from a database * @@ -201,7 +219,7 @@ public function loadFromDb($question_id) $this->cloze_text = ilRTE::_replaceMediaObjectImageSrc($this->cloze_text, 1); $this->setTextgapRating($data["textgap_rating"]); $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2)); - + try { $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']); } catch (ilTestQuestionPoolException $e) { @@ -229,7 +247,7 @@ public function loadFromDb($question_id) $data["aorder"] ); $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']); - + $this->gaps[$data["gap_id"]]->addItem($answer); break; case CLOZE_SELECT: @@ -272,7 +290,7 @@ public function loadFromDb($question_id) } #region Save question to db - + /** * Saves a assClozeTest object to a database * @@ -298,7 +316,7 @@ public function saveAnswerSpecificDataToDb() { global $DIC; $ilDB = $DIC['ilDB']; - + $ilDB->manipulateF( "DELETE FROM qpl_a_cloze WHERE question_fi = %s", array( "integer" ), @@ -318,14 +336,14 @@ public function saveAnswerSpecificDataToDb() public function saveAdditionalQuestionDataToDb() { global $DIC; /* @var ILIAS\DI\Container $DIC */ - - + + $DIC->database()->manipulateF( "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s", array( "integer" ), array( $this->getId() ) ); - + $DIC->database()->insert($this->getAdditionalTableName(), array( 'question_fi' => array('integer', $this->getId()), 'textgap_rating' => array('text', $this->getTextgapRating()), @@ -449,7 +467,7 @@ protected function saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap) { global $DIC; $ilDB = $DIC['ilDB']; - + include_once "./Services/Math/classes/class.EvalMath.php"; $eval = new EvalMath(); $eval->suppress_errors = true; @@ -534,7 +552,7 @@ public function setClozeTextValue($cloze_text = "") { $this->cloze_text = $cloze_text; } - + /** * Returns the cloze text * @@ -570,7 +588,7 @@ public function setStartTag($start_tag = "[gap]") { $this->start_tag = $start_tag; } - + /** * Returns the end tag of a cloze gap * @@ -594,7 +612,7 @@ public function setEndTag($end_tag = "[/gap]") { $this->end_tag = $end_tag; } - + /** * @return string */ @@ -602,7 +620,7 @@ public function getFeedbackMode() { return $this->feedbackMode; } - + /** * @param string $feedbackMode */ @@ -637,7 +655,7 @@ public function createGapsFromQuestiontext() } } } - + /** * Set the type of a gap with a given index * @@ -715,7 +733,7 @@ public function addGapAnswer($gap_index, $order, $answer) $this->gaps[$gap_index]->addItem(new assAnswerCloze($answer, 0, $order)); } } - + /** * Returns the gap at a given index * @@ -739,7 +757,7 @@ public function setGapSize($gap_index, $order, $size) $this->gaps[$gap_index]->setGapSize($size); } } - + /** * Sets the points of a gap with a given index and an answer with a given order. The index of the first * gap is 0, the index of the second gap is 1 and so on. @@ -777,7 +795,7 @@ public function addGapText($gap_index) $this->gaps[$gap_index]->addItem($answer); } } - + /** * Adds a ClozeGap object at a given index * @@ -869,7 +887,7 @@ public function getMaximumPoints() } } } - + return $points; } @@ -887,16 +905,16 @@ public function duplicate($for_test = true, $title = "", $author = "", $owner = // duplicate the question in database $this_id = $this->getId(); $thisObjId = $this->getObjId(); - + $clone = $this; include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php"); $original_id = assQuestion::_getOriginalId($this->id); $clone->id = -1; - + if ((int) $testObjId > 0) { $clone->setObjId($testObjId); } - + if ($title) { $clone->setTitle($title); } @@ -928,7 +946,7 @@ public function duplicate($for_test = true, $title = "", $author = "", $owner = return $clone->getId(); } - + /** * Copies an assClozeTest object * @@ -940,10 +958,10 @@ public function copyObject($target_questionpool_id, $title = "") // The question has not been saved. It cannot be duplicated return; } - + $thisId = $this->getId(); $thisObjId = $this->getObjId(); - + $clone = $this; include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php"); $original_id = assQuestion::_getOriginalId($this->getId()); @@ -966,7 +984,7 @@ public function copyObject($target_questionpool_id, $title = "") $clone->copyXHTMLMediaObjectsOfQuestion($original_id); $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId()); - + return $clone->getId(); } @@ -993,7 +1011,7 @@ public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuest } $clone->saveToDb(); - + if ($this->gap_combinations_exists) { $this->copyGapCombination($sourceQuestionId, $clone->getId()); $clone->saveToDb(); @@ -1007,7 +1025,7 @@ public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuest return $clone->id; } - + public function copyGapCombination($orgID, $newID) { $assClozeGapCombinationObj = new assClozeGapCombination(); @@ -1028,12 +1046,14 @@ public function updateClozeTextFromGaps() foreach ($gap->getItemsRaw() as $item) { array_push($answers, str_replace(",", "\\,", $item->getAnswerText())); } - $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]", $output, 1); + // fau: fixGapReplace - use replace function + $output = $this->replaceFirstGap($output, "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]"); +// fau. } $output = str_replace("_gap]", "gap]", $output); $this->cloze_text = $output; } - + /** * Deletes the answer text of a gap with a given index and an answer with a given order. The index of the first * gap is 0, the index of the second gap is 1 and so on. @@ -1075,9 +1095,13 @@ public function deleteGap($gap_index) array_push($answers, str_replace(",", "\\,", $item->getAnswerText())); } if ($replace_gap_index == $gap_index) { - $output = preg_replace("/\[gap\].*?\[\/gap\]/", "", $output, 1); + // fau: fixGapReplace - use replace function + $output = $this->replaceFirstGap($output, ''); +// fau. } else { - $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . join(",", $answers) . "[/_gap]", $output, 1); + // fau: fixGapReplace - use replace function + $output = $this->replaceFirstGap($output, "[_gap]" . join(",", $answers) . "[/_gap]"); +// fau. } } $output = str_replace("_gap]", "gap]", $output); @@ -1140,7 +1164,7 @@ public function getTextgapPoints($a_original, $a_entered, $max_points) } return $result; } - + /** * Returns the points for a text gap and compares the given solution with * the entered solution using the text gap rating options. @@ -1208,7 +1232,7 @@ public function calculateReachedPoints($active_id, $pass = null, $authorized = t { global $DIC; $ilDB = $DIC['ilDB']; - + if (is_null($pass)) { $pass = $this->getSolutionMaxPass($active_id); } @@ -1223,7 +1247,7 @@ public function calculateReachedPoints($active_id, $pass = null, $authorized = t ); } } - + ksort($user_result); // this is required when identical scoring for same solutions is disabled if ($returndetails) { @@ -1231,45 +1255,72 @@ public function calculateReachedPoints($active_id, $pass = null, $authorized = t $this->calculateReachedPointsForSolution($user_result, $detailed); return $detailed; } - + return $this->calculateReachedPointsForSolution($user_result); } - + protected function isValidNumericSubmitValue($submittedValue) { if (is_numeric($submittedValue)) { return true; } - + if (preg_match('/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) { return true; } - + return false; } - + public function validateSolutionSubmit() { - foreach ($this->getSolutionSubmit() as $gapIndex => $value) { + foreach ($this->getSolutionSubmitValidation() as $gapIndex => $value) { $gap = $this->getGap($gapIndex); - + if ($gap->getType() != CLOZE_NUMERIC) { continue; } - + if (strlen($value) && !$this->isValidNumericSubmitValue($value)) { ilUtil::sendFailure($this->lng->txt("err_no_numeric_value"), true); return false; } } - + return true; } - + public function fetchSolutionSubmit($submit) { $solutionSubmit = array(); - + + foreach ($submit as $key => $value) { + if (preg_match("/^gap_(\d+)/", $key, $matches)) { + $value = ilUtil::stripSlashes($value, false); + if (strlen($value)) { + $gap = $this->getGap($matches[1]); + if (is_object($gap)) { + if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) { + if ($gap->getType() == CLOZE_NUMERIC && !is_numeric(str_replace(",", ".", $value))) { + $value = null; + } else if ($gap->getType() == CLOZE_NUMERIC) { + $value = str_replace(",", ".", $value); + } + $solutionSubmit[trim($matches[1])] = $value; + } + } + } + } + } + + return $solutionSubmit; + } + + public function getSolutionSubmitValidation() + { + $submit = $_POST; + $solutionSubmit = array(); + foreach ($submit as $key => $value) { if (preg_match("/^gap_(\d+)/", $key, $matches)) { $value = ilUtil::stripSlashes($value, false); @@ -1286,15 +1337,15 @@ public function fetchSolutionSubmit($submit) } } } - + return $solutionSubmit; } - + public function getSolutionSubmit() { return $this->fetchSolutionSubmit($_POST); } - + /** * Saves the learners input of the question to the database. * @@ -1343,7 +1394,7 @@ public function saveWorkingData($active_id, $pass = null, $authorized = true) assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId()); } } - + return true; } @@ -1440,7 +1491,7 @@ public function getAnswerTableName() { return array("qpl_a_cloze",'qpl_a_cloze_combi_res'); } - + /** * Sets a fixed text length for all text fields in the cloze question * @@ -1451,7 +1502,7 @@ public function setFixedTextLength($a_text_len) { $this->fixedTextLength = $a_text_len; } - + /** * Gets the fixed text length for all text fields in the cloze question * @@ -1553,7 +1604,7 @@ public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass) return $startrow + $i + 1; } - + /** * @param ilAssSelfAssessmentMigrator $migrator */ @@ -1564,7 +1615,7 @@ protected function lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigra $this->cloze_text = $migrator->migrateToLmContent($this->getClozeText()); // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^ } - + /** * Returns a JSON representation of the question */ @@ -1583,7 +1634,7 @@ public function toJSON() 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)), 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true)) ); - + $gaps = array(); foreach ($this->getGaps() as $key => $gap) { $items = array(); @@ -1608,7 +1659,7 @@ public function toJSON() $jgap['shuffle'] = $gap->getShuffle(); $jgap['type'] = $gap->getType(); $jgap['item'] = $items; - + array_push($gaps, $jgap); } $result['gaps'] = $gaps; @@ -1688,7 +1739,7 @@ public function getUserQuestionResult($active_id, $pass) array($active_id, $pass, $this->getId()) ); } - + while ($row = $ilDB->fetchAssoc($data)) { if ($row["cloze_type"] == 1) { $row["value2"]++; @@ -1724,9 +1775,9 @@ public function getAvailableAnswerOptions($index = null) public function calculateCombinationResult($user_result) { $points = 0; - + $assClozeGapCombinationObj = new assClozeGapCombination(); - + if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) { $combinations_for_question = $assClozeGapCombinationObj->getCleanCombinationArray($this->getId()); $gap_answers = array(); @@ -1814,7 +1865,7 @@ protected function calculateReachedPointsForSolution($user_result, &$detailed = if (is_string($value)) { $value = array("value" => $value); } - + if (array_key_exists($gap_id, $this->gaps) && !array_key_exists($gap_id, $combinations[1])) { switch ($this->gaps[$gap_id]->getType()) { case CLOZE_TEXT: @@ -1886,64 +1937,69 @@ protected function calculateReachedPointsForSolution($user_result, &$detailed = } } } - + return $points; } public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession) { $userSolution = array(); - + foreach ($previewSession->getParticipantsSolution() as $key => $val) { // fau: fixEmptyGapPreview - add the gap_id as array index $userSolution[$key] = array('gap_id' => $key, 'value' => $val); // fau. } - + $reachedPoints = $this->calculateReachedPointsForSolution($userSolution); $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints); - + return $this->ensureNonNegativePoints($reachedPoints); } - + public function fetchAnswerValueForGap($userSolution, $gapIndex) { $answerValue = ''; - + foreach ($userSolution as $value1 => $value2) { if ($value1 == $gapIndex) { $answerValue = $value2; break; } } - + return $answerValue; } - + public function isAddableAnswerOptionValue($qIndex, $answerOptionValue) { $gap = $this->getGap($qIndex); - + if ($gap->getType() != CLOZE_TEXT) { return false; } - + foreach ($gap->getItems(new ilArrayElementOrderKeeper()) as $item) { if ($item->getAnswertext() == $answerOptionValue) { return false; } } - + return true; } - + public function addAnswerOptionValue($qIndex, $answerOptionValue, $points) { $gap = $this->getGap($qIndex); /* @var assClozeGap $gap */ - + $item = new assAnswerCloze($answerOptionValue, $points); $item->setOrder($gap->getItemCount()); - + $gap->addItem($item); } + + public function savePartial() + { + return true; + } } diff --git a/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php b/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php index 0abee346ff51..36a209bfacc4 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php +++ b/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php @@ -23,12 +23,12 @@ class assClozeTestGUI extends assQuestionGUI implements ilGuiQuestionScoringAdjustable, ilGuiAnswerScoringAdjustable { const OLD_CLOZE_TEST_UI = false; - + /** * A temporary variable to store gap indexes of ilCtrl commands in the getCommand method */ private $gapIndex; - + /** * assClozeTestGUI constructor * @@ -89,17 +89,17 @@ public function writeAnswerSpecificPostData(ilPropertyFormGUI $form) if ($this->ctrl->getCmd() != 'createGaps') { $this->object->clearGapAnswers(); } - + foreach ($_POST['gap'] as $idx => $hidden) { $clozetype = $_POST['clozetype_' . $idx]; - + $this->object->setGapType($idx, $clozetype); - + switch ($clozetype) { case CLOZE_TEXT: $this->object->setGapShuffle($idx, 0); - + if ($this->ctrl->getCmd() != 'createGaps') { if (is_array($_POST['gap_' . $idx]['answer'])) { foreach ($_POST['gap_' . $idx]['answer'] as $order => $value) { @@ -109,7 +109,7 @@ public function writeAnswerSpecificPostData(ilPropertyFormGUI $form) $this->object->addGapAnswer($idx, 0, ''); } } - + if (is_array($_POST['gap_' . $idx]['points'])) { foreach ($_POST['gap_' . $idx]['points'] as $order => $value) { $this->object->setGapAnswerPoints($idx, $order, $value); @@ -119,9 +119,9 @@ public function writeAnswerSpecificPostData(ilPropertyFormGUI $form) if (array_key_exists('gap_' . $idx . '_gapsize', $_POST)) { $this->object->setGapSize($idx, $order, $_POST['gap_' . $idx . '_gapsize']); } - + break; - + case CLOZE_SELECT: $this->object->setGapShuffle($idx, (int) (isset($_POST["shuffle_$idx"]) && $_POST["shuffle_$idx"])); @@ -135,7 +135,7 @@ public function writeAnswerSpecificPostData(ilPropertyFormGUI $form) $this->object->addGapAnswer($idx, 0, ''); } } - + if (is_array($_POST['gap_' . $idx]['points'])) { foreach ($_POST['gap_' . $idx]['points'] as $order => $value) { $this->object->setGapAnswerPoints($idx, $order, $value); @@ -144,14 +144,14 @@ public function writeAnswerSpecificPostData(ilPropertyFormGUI $form) break; case CLOZE_NUMERIC: - + $this->object->setGapShuffle($idx, 0); $gap = $this->object->getGap($idx); if (!$gap) { break; } - + $this->object->getGap($idx)->clearItems(); if (array_key_exists('gap_' . $idx . '_numeric', $_POST)) { @@ -180,7 +180,7 @@ public function writeAnswerSpecificPostData(ilPropertyFormGUI $form) if ($this->ctrl->getCmd() != 'createGaps') { $this->object->addGapAnswer($idx, 0, ''); } - + $this->object->setGapAnswerLowerBound($idx, 0, ''); $this->object->setGapAnswerUpperBound($idx, 0, ''); @@ -327,7 +327,7 @@ public function addBasicQuestionFormProperties($form) // $button = new ilCustomInputGUI(' ',''); // $button->setHtml($tpl->get()); // $form->addItem($button); - + if (!$this->object->getSelfAssessmentEditingMode()) { // duration $duration = new ilDurationInputGUI($this->lng->txt("working_time"), "Estimated"); @@ -443,7 +443,7 @@ public function populateQuestionSpecificFormPart(ilPropertyFormGUI $form) // text field length $fixedTextLength = new ilNumberInputGUI($this->lng->txt("cloze_fixed_textlength"), "fixedTextLength"); $ftl = $this->object->getFixedTextLength(); - + $fixedTextLength->setValue($ftl > 0 ? $ftl : ''); $fixedTextLength->setMinValue(0); $fixedTextLength->setSize(3); @@ -516,7 +516,7 @@ protected function populateJSON() 'points' => $value[$j]->getPoints(), 'error' => false ); - + if ($content->getType() == 1) { $shuffle = $content->getShuffle(); } @@ -571,7 +571,7 @@ protected function populateGapFormPart($form, $gapCounter) if ($gap->getType() == CLOZE_TEXT) { $this->populateGapSizeFormPart($form, $gap, $gapCounter); - + if (count($gap->getItemsRaw()) == 0) { $gap->addItem(new assAnswerCloze("", 0, 0)); } @@ -583,7 +583,7 @@ protected function populateGapFormPart($form, $gapCounter) $this->populateSelectGapFormPart($form, $gap, $gapCounter); } elseif ($gap->getType() == CLOZE_NUMERIC) { $this->populateGapSizeFormPart($form, $gap, $gapCounter); - + if (count($gap->getItemsRaw()) == 0) { $gap->addItem(new assAnswerCloze("", 0, 0)); } @@ -602,7 +602,7 @@ protected function populateGapFormPart($form, $gapCounter) protected function populateGapSizeFormPart($form, $gap, $gapCounter) { $gapSizeFormItem = new ilNumberInputGUI($this->lng->txt('cloze_fixed_textlength'), "gap_" . $gapCounter . '_gapsize'); - + $gapSizeFormItem->allowDecimals(false); $gapSizeFormItem->setMinValue(0); $gapSizeFormItem->setSize(3); @@ -610,10 +610,10 @@ protected function populateGapSizeFormPart($form, $gap, $gapCounter) $gapSizeFormItem->setInfo($this->lng->txt('cloze_gap_size_info')); $gapSizeFormItem->setValue($gap->getGapSize()); $form->addItem($gapSizeFormItem); - + return $form; } - + /** * Populates the form-part for a select gap. * @@ -713,7 +713,7 @@ protected function populateNumericGapFormPart($form, $gap, $gapCounter) $upperbound = new ilNumberInputGUI($this->lng->txt('range_upper_limit'), "gap_" . $gapCounter . "_numeric_upper"); $upperbound->allowDecimals(true); } - + $value->setSize(10); $value->setValue(ilUtil::prepareFormOutput($gap->getAnswertext())); $value->setRequired(true); @@ -788,7 +788,7 @@ public function addgap() public function getPreview($show_question_only = false, $showInlineFeedback = false) { $user_solution = is_object($this->getPreviewSession()) ? (array) $this->getPreviewSession()->getParticipantsSolution() : array(); - + // generate the question output include_once "./Services/UICore/classes/class.ilTemplate.php"; $template = new ilTemplate("tpl.il_as_qpl_cloze_question_output.html", true, true, "Modules/TestQuestionPool"); @@ -810,7 +810,9 @@ public function getPreview($show_question_only = false, $showInlineFeedback = fa $gaptemplate->setVariable("VALUE_GAP", " value=\"" . ilUtil::prepareFormOutput($val2) . "\""); } } - $output = preg_replace("/\[gap\].*?\[\/gap\]/", $gaptemplate->get(), $output, 1); + // fau: fixGapReplace - use replace function + $output = $this->object->replaceFirstGap($output, $gaptemplate->get()); +// fau. break; case CLOZE_SELECT: $gaptemplate = new ilTemplate("tpl.il_as_qpl_cloze_question_gap_select.html", true, true, "Modules/TestQuestionPool"); @@ -828,8 +830,9 @@ public function getPreview($show_question_only = false, $showInlineFeedback = fa $gaptemplate->parseCurrentBlock(); } $gaptemplate->setVariable("PLEASE_SELECT", $this->lng->txt("please_select")); - $gaptemplate->setVariable("GAP_COUNTER", $gap_index); - $output = preg_replace("/\[gap\].*?\[\/gap\]/", $gaptemplate->get(), $output, 1); + $gaptemplate->setVariable("GAP_COUNTER", $gap_index);// fau: fixGapReplace - use replace function + $output = $this->object->replaceFirstGap($output, $gaptemplate->get()); +// fau. break; case CLOZE_NUMERIC: $gaptemplate = new ilTemplate("tpl.il_as_qpl_cloze_question_gap_numeric.html", true, true, "Modules/TestQuestionPool"); @@ -845,7 +848,9 @@ public function getPreview($show_question_only = false, $showInlineFeedback = fa $gaptemplate->setVariable("VALUE_GAP", " value=\"" . ilUtil::prepareFormOutput($val2) . "\""); } } - $output = preg_replace("/\[gap\].*?\[\/gap\]/", $gaptemplate->get(), $output, 1); + // fau: fixGapReplace - use replace function + $output = $this->object->replaceFirstGap($output, $gaptemplate->get()); +// fau. break; } } @@ -859,6 +864,16 @@ public function getPreview($show_question_only = false, $showInlineFeedback = fa return $questionoutput; } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * @@ -889,7 +904,7 @@ public function getSolutionOutput( $user_solution = array(); if (($active_id > 0) && (!$show_correct_solution)) { // get the solutions of a user - $user_solution = &$this->object->getSolutionValues($active_id, $pass); + $user_solution = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); if (!is_array($user_solution)) { $user_solution = array(); } @@ -915,7 +930,7 @@ public function getSolutionOutput( // output of ok/not ok icons for user entered solutions $details = $this->object->calculateReachedPoints($active_id, $pass, true, true); $check = $details[$gap_index]; - + if (count($check_for_gap_combinations) != 0) { $gaps_used_in_combination = $assClozeGapCombinationObject->getGapsWhichAreUsedInCombination($this->object->getId()); $custom_user_solution = array(); @@ -1007,7 +1022,9 @@ public function getSolutionOutput( $solutiontext = $this-> getBestSolutionText($gap, $gap_index, $check_for_gap_combinations); } $this->populateSolutiontextToGapTpl($gaptemplate, $gap, $solutiontext); - $output = preg_replace("/\[gap\].*?\[\/gap\]/", $gaptemplate->get(), $output, 1); + // fau: fixGapReplace - use replace function + $output = $this->object->replaceFirstGap($output, $gaptemplate->get()); +// fau. break; case CLOZE_SELECT: $solutiontext = ""; @@ -1030,7 +1047,9 @@ public function getSolutionOutput( $solutiontext = $this-> getBestSolutionText($gap, $gap_index, $check_for_gap_combinations); } $this->populateSolutiontextToGapTpl($gaptemplate, $gap, $solutiontext); - $output = preg_replace("/\[gap\].*?\[\/gap\]/", $gaptemplate->get(), $output, 1); + // fau: fixGapReplace - use replace function + $output = $this->object->replaceFirstGap($output, $gaptemplate->get()); +// fau. break; case CLOZE_NUMERIC: $solutiontext = ""; @@ -1046,11 +1065,13 @@ public function getSolutionOutput( $solutiontext = $this-> getBestSolutionText($gap, $gap_index, $check_for_gap_combinations); } $this->populateSolutiontextToGapTpl($gaptemplate, $gap, $solutiontext); - $output = preg_replace("/\[gap\].*?\[\/gap\]/", $gaptemplate->get(), $output, 1); + // fau: fixGapReplace - use replace function + $output = $this->object->replaceFirstGap($output, $gaptemplate->get()); +// fau. break; } } - + if ($show_question_text) { $template->setVariable( "QUESTIONTEXT", @@ -1069,7 +1090,7 @@ public function getSolutionOutput( $fb = $this->getGenericFeedbackOutput($active_id, $pass); $feedback .= strlen($fb) ? $fb : ''; } - + $fb = $this->getSpecificFeedbackOutput( $this->object->fetchIndexedValuesFromValuePairs($user_solution) ); @@ -1080,11 +1101,11 @@ public function getSolutionOutput( $this->hasCorrectSolution($active_id, $pass) ? ilAssQuestionFeedback::CSS_CLASS_FEEDBACK_CORRECT : ilAssQuestionFeedback::CSS_CLASS_FEEDBACK_WRONG ); - + $solutiontemplate->setVariable("ILC_FB_CSS_CLASS", $cssClass); $solutiontemplate->setVariable("FEEDBACK", $this->object->prepareTextareaOutput($feedback, true)); } - + $solutiontemplate->setVariable("SOLUTION_OUTPUT", $questionoutput); $solutionoutput = $solutiontemplate->get(); @@ -1093,7 +1114,7 @@ public function getSolutionOutput( // get page object output $solutionoutput = $this->getILIASPage($solutionoutput); } - + return $solutionoutput; } @@ -1140,7 +1161,7 @@ public function getAnswerFeedbackOutput($active_id, $pass) $test = new ilObjTest($this->object->active_id); return $this->object->prepareTextareaOutput($output, true); } - + public function getTestOutput( $active_id, // hey: prevPassSolutions - will be always available from now on @@ -1168,7 +1189,7 @@ public function getTestOutput( $user_solution = array(); } } - + // generate the question output include_once "./Services/UICore/classes/class.ilTemplate.php"; $template = new ilTemplate("tpl.il_as_qpl_cloze_question_output.html", true, true, "Modules/TestQuestionPool"); @@ -1184,14 +1205,16 @@ public function getTestOutput( $gaptemplate->setVariable("TEXT_GAP_SIZE", $gap_size); $gaptemplate->parseCurrentBlock(); } - + $gaptemplate->setVariable("GAP_COUNTER", $gap_index); foreach ($user_solution as $solution) { if (strcmp($solution["value1"], $gap_index) == 0) { $gaptemplate->setVariable("VALUE_GAP", " value=\"" . ilUtil::prepareFormOutput($solution["value2"]) . "\""); } } - $output = preg_replace("/\[gap\].*?\[\/gap\]/", $gaptemplate->get(), $output, 1); + // fau: fixGapReplace - use replace function + $output = $this->object->replaceFirstGap($output, $gaptemplate->get()); +// fau. break; case CLOZE_SELECT: $gaptemplate = new ilTemplate("tpl.il_as_qpl_cloze_question_gap_select.html", true, true, "Modules/TestQuestionPool"); @@ -1209,8 +1232,9 @@ public function getTestOutput( $gaptemplate->parseCurrentBlock(); } $gaptemplate->setVariable("PLEASE_SELECT", $this->lng->txt("please_select")); - $gaptemplate->setVariable("GAP_COUNTER", $gap_index); - $output = preg_replace("/\[gap\].*?\[\/gap\]/", $gaptemplate->get(), $output, 1); + $gaptemplate->setVariable("GAP_COUNTER", $gap_index);// fau: fixGapReplace - use replace function + $output = $this->object->replaceFirstGap($output, $gaptemplate->get()); +// fau. break; case CLOZE_NUMERIC: $gaptemplate = new ilTemplate("tpl.il_as_qpl_cloze_question_gap_numeric.html", true, true, "Modules/TestQuestionPool"); @@ -1220,18 +1244,20 @@ public function getTestOutput( $gaptemplate->setVariable("TEXT_GAP_SIZE", $gap_size); $gaptemplate->parseCurrentBlock(); } - + $gaptemplate->setVariable("GAP_COUNTER", $gap_index); foreach ($user_solution as $solution) { if (strcmp($solution["value1"], $gap_index) == 0) { $gaptemplate->setVariable("VALUE_GAP", " value=\"" . ilUtil::prepareFormOutput($solution["value2"]) . "\""); } } - $output = preg_replace("/\[gap\].*?\[\/gap\]/", $gaptemplate->get(), $output, 1); + // fau: fixGapReplace - use replace function + $output = $this->object->replaceFirstGap($output, $gaptemplate->get()); +// fau. break; } } - + $template->setVariable("QUESTIONTEXT", $this->object->prepareTextareaOutput($this->object->getQuestion(), true)); $template->setVariable("CLOZETEXT", $this->object->prepareTextareaOutput($output, true)); $questionoutput = $template->get(); @@ -1253,7 +1279,7 @@ public function setQuestionTabs() $ilTabs = $DIC['ilTabs']; $ilTabs->clearTargets(); - + $this->ctrl->setParameterByClass("ilAssQuestionPageGUI", "q_id", $_GET["q_id"]); include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php"; $q_type = $this->object->getQuestionType(); @@ -1277,7 +1303,7 @@ public function setQuestionTabs() $force_active ); } - + $this->addTab_QuestionPreview($ilTabs); } @@ -1310,7 +1336,7 @@ public function setQuestionTabs() // add tab for question feedback within common class assQuestionGUI $this->addTab_QuestionFeedback($ilTabs); - + // add tab for question hint within common class assQuestionGUI $this->addTab_QuestionHints($ilTabs); @@ -1330,13 +1356,13 @@ public function setQuestionTabs() $this->addBackTab($ilTabs); } - + public function getSpecificFeedbackOutput($userSolution) { if (!$this->object->feedbackOBJ->specificAnswerFeedbackExists()) { return ''; } - + global $DIC; $lng = $DIC['lng']; @@ -1346,7 +1372,7 @@ public function getSpecificFeedbackOutput($userSolution) $answerValue = $this->object->fetchAnswerValueForGap($userSolution, $gapIndex); $answerIndex = $this->object->feedbackOBJ->determineAnswerIndexForAnswerValue($gap, $answerValue); $fb = $this->object->feedbackOBJ->determineTestOutputGapFeedback($gapIndex, $answerIndex); - + $caption = $lng->txt('gap') . ' ' . ($gapIndex + 1) . ': '; $feedback .= ''; $feedback .= $caption . ''; @@ -1506,21 +1532,21 @@ private function populateSolutiontextToGapTpl($gaptemplate, $gap, $solutiontext) $gaptemplate->setVariable('SELECT_SOLUTION', $solutiontext); } else { $gap_size = $gap->getGapSize() > 0 ? $gap->getGapSize() : $this->object->getFixedTextLength(); - + if ($gap_size > 0) { $gaptemplate->setCurrentBlock('gap_size'); $gaptemplate->setVariable("GAP_SIZE", $gap_size); $gaptemplate->parseCurrentBlock(); } - + $gaptemplate->setCurrentBlock('gap_input'); $gaptemplate->setVariable('INPUT_SOLUTION', $solutiontext); } - - + + $gaptemplate->parseCurrentBlock(); } - + protected function hasAddAnswerAction($relevantAnswers, $questionIndex) { foreach ($this->getAnswersFrequency($relevantAnswers, $questionIndex) as $answer) { @@ -1528,145 +1554,145 @@ protected function hasAddAnswerAction($relevantAnswers, $questionIndex) return true; } } - + return false; } - + public function getAnswerFrequencyTableGUI($parentGui, $parentCmd, $relevantAnswers, $questionIndex) { global $DIC; /* @var ILIAS\DI\Container $DIC */ - + $table = parent::getAnswerFrequencyTableGUI( $parentGui, $parentCmd, $relevantAnswers, $questionIndex ); - + $table->setTitle(sprintf( $DIC->language()->txt('tst_corrections_answers_tbl_subindex'), $DIC->language()->txt('gap') . ' ' . ($questionIndex + 1) )); - + if ($this->hasAddAnswerAction($relevantAnswers, $questionIndex)) { $table->addColumn('', '', '200'); } - + return $table; } - + public function getSubQuestionsIndex() { return array_keys($this->object->getGaps()); } - + protected function getAnswerTextLabel($gapIndex, $answer) { $gap = $this->object->getGap($gapIndex); - + switch ($gap->type) { case CLOZE_NUMERIC: case CLOZE_TEXT: return $answer; - + case CLOZE_SELECT: - + $items = $gap->getItems(new ilArrayElementOrderKeeper()); return $items[$answer]->getAnswertext(); } } - + protected function completeAddAnswerAction($answers, $questionIndex) { $gap = $this->object->getGap($questionIndex); - + if ($gap->type != CLOZE_TEXT) { return $answers; } - + foreach ($answers as $key => $ans) { $found = false; - + foreach ($gap->getItems(new ilArrayElementOrderKeeper()) as $item) { - if ($ans['answer'] != $item->getAnswerText()) { + if ($ans['answer'] !== $item->getAnswerText()) { continue; } - + $found = true; break; } - + if (!$found) { $answers[$key]['addable'] = true; } } - + return $answers; } - + public function getAnswersFrequency($relevant_answers, $questionIndex) { $answers = array(); - + foreach ($relevant_answers as $row) { if ($row['value1'] != $questionIndex) { continue; } - + if (!isset($answers[$row['value2']])) { $label = $this->getAnswerTextLabel($row['value1'], $row['value2']); - + $answers[$row['value2']] = array( 'answer' => $label, 'frequency' => 0 ); } - + $answers[$row['value2']]['frequency']++; } - + $answers = $this->completeAddAnswerAction($answers, $questionIndex); - + return $answers; } - + protected function isUsedInCombinations($gapIndex) { foreach ($this->object->getGapCombinations() as $combination) { if ($combination['gap_fi'] != $gapIndex) { continue; } - + return true; } - + return false; } - + protected function getGapCombinations() { $combinations = array(); - + foreach ($this->object->getGapCombinations() as $c) { if (!isset($combinations[$c['cid']])) { $combinations[$c['cid']] = array(); } - + if (!isset($combinations[$c['cid']][$c['row_id']])) { $combinations[$c['cid']][$c['row_id']] = array( 'gaps' => array(), 'points' => $c['points'], ); } - + if (!isset($combinations[$c['cid']][$c['row_id']]['gaps'][$c['gap_fi']])) { $combinations[$c['cid']][$c['row_id']]['gaps'][$c['gap_fi']] = array(); } - + $combinations[$c['cid']][$c['row_id']]['gaps'][$c['gap_fi']] = $c['answer']; } - + return $combinations; } - + public function populateCorrectionsFormProperties(ilPropertyFormGUI $form) { foreach ($this->object->getGaps() as $gapIndex => $gap) { @@ -1677,26 +1703,26 @@ public function populateCorrectionsFormProperties(ilPropertyFormGUI $form) $this->isUsedInCombinations($gapIndex) ); } - + if ($this->object->getGapCombinationsExists()) { foreach ($this->getGapCombinations() as $combiIndex => $gapCombination) { $this->populateGapCombinationCorrectionFormProperty($form, $gapCombination, $combiIndex); } } } - + protected function populateGapCombinationCorrectionFormProperty(ilPropertyFormGUI $form, $gapCombi, $combiIndex) { $header = new ilFormSectionHeaderGUI(); $header->setTitle("Gap Combination " . ($combiIndex + 1)); $form->addItem($header); - + require_once 'Modules/TestQuestionPool/classes/forms/class.ilAssClozeTestCombinationVariantsInputGUI.php'; $inp = new ilAssClozeTestCombinationVariantsInputGUI('Answers', 'combination_' . $combiIndex); $inp->setValues($gapCombi); $form->addItem($inp); } - + /** * @param ilPropertyFormGUI $form * @param assClozeGap $gap @@ -1707,7 +1733,7 @@ protected function populateGapCorrectionFormProperties($form, $gap, $gapIndex, $ $header = new ilFormSectionHeaderGUI(); $header->setTitle($this->lng->txt("gap") . " " . ($gapIndex + 1)); $form->addItem($header); - + if ($gap->getType() == CLOZE_TEXT || $gap->getType() == CLOZE_SELECT) { $this->populateTextOrSelectGapCorrectionFormProperty($form, $gap, $gapIndex, $hidePoints); } elseif ($gap->getType() == CLOZE_NUMERIC) { @@ -1716,7 +1742,7 @@ protected function populateGapCorrectionFormProperties($form, $gap, $gapIndex, $ } } } - + protected function populateTextOrSelectGapCorrectionFormProperty($form, $gap, $gapIndex, $hidePoints) { require_once "Modules/TestQuestionPool/classes/forms/class.ilAssAnswerCorrectionsInputGUI.php"; @@ -1727,7 +1753,7 @@ protected function populateTextOrSelectGapCorrectionFormProperty($form, $gap, $g $values->setValues($gap->getItemsRaw()); $form->addItem($values); } - + protected function populateNumericGapCorrectionFormProperty($form, $item, $gapIndex, $hidePoints) { $value = new ilNumberInputGUI($this->lng->txt('value'), "gap_" . $gapIndex . "_numeric"); @@ -1736,21 +1762,21 @@ protected function populateNumericGapCorrectionFormProperty($form, $item, $gapIn $value->setValue(ilUtil::prepareFormOutput($item->getAnswertext())); $value->setRequired(true); $form->addItem($value); - + $lowerbound = new ilNumberInputGUI($this->lng->txt('range_lower_limit'), "gap_" . $gapIndex . "_numeric_lower"); $lowerbound->allowDecimals(true); $lowerbound->setSize(10); $lowerbound->setRequired(true); $lowerbound->setValue(ilUtil::prepareFormOutput($item->getLowerBound())); $form->addItem($lowerbound); - + $upperbound = new ilNumberInputGUI($this->lng->txt('range_upper_limit'), "gap_" . $gapIndex . "_numeric_upper"); $upperbound->allowDecimals(true); $upperbound->setSize(10); $upperbound->setRequired(true); $upperbound->setValue(ilUtil::prepareFormOutput($item->getUpperBound())); $form->addItem($upperbound); - + if (!$hidePoints) { $points = new ilNumberInputGUI($this->lng->txt('points'), "gap_" . $gapIndex . "_numeric_points"); $points->allowDecimals(true); @@ -1760,7 +1786,7 @@ protected function populateNumericGapCorrectionFormProperty($form, $item, $gapIn $form->addItem($points); } } - + /** * @param ilPropertyFormGUI $form */ @@ -1770,15 +1796,15 @@ public function saveCorrectionsFormProperties(ilPropertyFormGUI $form) if ($this->isUsedInCombinations($gapIndex)) { continue; } - + $this->saveGapCorrectionFormProperty($form, $gap, $gapIndex); } - + if ($this->object->getGapCombinationsExists()) { $this->saveGapCombinationCorrectionFormProperties($form); } } - + protected function saveGapCorrectionFormProperty(ilPropertyFormGUI $form, assClozeGap $gap, $gapIndex) { if ($gap->getType() == CLOZE_TEXT || $gap->getType() == CLOZE_SELECT) { @@ -1789,16 +1815,16 @@ protected function saveGapCorrectionFormProperty(ilPropertyFormGUI $form, assClo } } } - + protected function saveTextOrSelectGapCorrectionFormProperty(ilPropertyFormGUI $form, assClozeGap $gap, $gapIndex) { $answers = $form->getItemByPostVar('gap_' . $gapIndex)->getValues(); - + foreach ($gap->getItemsRaw() as $index => $item) { $item->setPoints((float) $answers[$index]->getPoints()); } } - + protected function saveNumericGapCorrectionFormProperty(ilPropertyFormGUI $form, assAnswerCloze $item, $gapIndex) { $item->setAnswertext($form->getInput('gap_' . $gapIndex . '_numeric')); @@ -1806,36 +1832,36 @@ protected function saveNumericGapCorrectionFormProperty(ilPropertyFormGUI $form, $item->setUpperBound($form->getInput('gap_' . $gapIndex . '_numeric_upper')); $item->setPoints($form->getInput('gap_' . $gapIndex . '_numeric_points')); } - + protected function saveGapCombinationCorrectionFormProperties(ilPropertyFormGUI $form) { // please dont ask (!) -.- - + $combinationPoints = array('points' => array(), 'select' => array()); $combinationValues = array(); - + foreach ($this->getGapCombinations() as $combiId => $combi) { $values = $form->getItemByPostVar('combination_' . $combiId)->getValues(); - + if (!isset($combinationPoints['points'][$combiId])) { $combinationPoints['points'][$combiId] = array(); $combinationPoints['select'][$combiId] = array(); $combinationValues[$combiId] = array(); } - + foreach ($combi as $varId => $variant) { $combinationPoints['points'][$combiId][$varId] = (float) $values[$varId]['points']; $combinationPoints['select'][$combiId] = array_keys($values[$varId]['gaps']); $combinationValues[$combiId][$varId] = array_values($values[$varId]['gaps']); } } - + $combinationPoints = ilUtil::stripSlashesRecursive($combinationPoints); $combinationValues = ilUtil::stripSlashesRecursive($combinationValues); - + $assClozeGapCombinationObject = new assClozeGapCombination(); $assClozeGapCombinationObject->clearGapCombinationsFromDb($this->object->getId()); - + $assClozeGapCombinationObject->saveGapCombinationToDb( $this->object->getId(), $combinationPoints, diff --git a/Modules/TestQuestionPool/classes/class.assErrorText.php b/Modules/TestQuestionPool/classes/class.assErrorText.php index 40dce7da3acc..f84721231fe1 100644 --- a/Modules/TestQuestionPool/classes/class.assErrorText.php +++ b/Modules/TestQuestionPool/classes/class.assErrorText.php @@ -789,7 +789,7 @@ public function createErrorTextExport($selections = null) } } else { $appendComma = ""; - if ($item{$posClosingBrackets + 2} == ',') { + if ($item[$posClosingBrackets + 2] == ',') { $appendComma = ","; } diff --git a/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php b/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php index bf8fd940b706..260a4c1347b2 100644 --- a/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php +++ b/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php @@ -209,6 +209,16 @@ public function analyze() $this->editQuestion(); } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * @@ -246,7 +256,7 @@ public function getSolutionOutput( /* Retrieve tst_solutions entries. */ $reached_points = $this->object->getReachedPoints($active_id, $pass); - $solutions = &$this->object->getSolutionValues($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); if (is_array($solutions)) { foreach ($solutions as $solution) { array_push($selections, (int) $solution['value1']); diff --git a/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php b/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php index ee32a0764feb..33ffea20247c 100755 --- a/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php @@ -1262,7 +1262,11 @@ public function getBestSolution($solutions) $unit_factor = assFormulaQuestionUnit::lookupUnitFactor($user_solution[$result_name]['unit']); } - $user_solution[$result->getResult()]["value"] = ilMath::_div($resVal, $unit_factor, 55); + try { + $user_solution[$result->getResult()]["value"] = ilMath::_div($resVal, $unit_factor, 55); + } catch (ilMathDivisionByZeroException $ex) { + $user_solution[$result->getResult()]["value"] = 0; + } } if ($result->getResultType() == assFormulaQuestionResult::RESULT_CO_FRAC || $result->getResultType() == assFormulaQuestionResult::RESULT_FRAC) { @@ -1275,11 +1279,14 @@ public function getBestSolution($solutions) $user_solution[$result->getResult()]["frac_helper"] = null; } } else { + $user_solution[$result->getResult()]["value"] = round($user_solution[$result->getResult()]["value"], $result->getPrecision()); + /* $user_solution[$result->getResult()]["value"] = ilMath::_div( $user_solution[$result->getResult()]["value"], 1, $result->getPrecision() ); + */ } } return $user_solution; diff --git a/Modules/TestQuestionPool/classes/class.assFormulaQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assFormulaQuestionGUI.php index d610034d1e26..3cfb4452c4b6 100755 --- a/Modules/TestQuestionPool/classes/class.assFormulaQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assFormulaQuestionGUI.php @@ -93,7 +93,7 @@ public function setQuestionTabs() } // edit question properties $ilTabs->addTarget( - "edit_properties", + "edit_question", $url, array( "editQuestion", "save", "cancel", "addSuggestedSolution", @@ -467,7 +467,7 @@ public function editQuestion($checkonly = false) $tolerance->setSize(3); $tolerance->setMinValue(0); $tolerance->setMaxValue(100); - $tolerance->allowDecimals(false); + $tolerance->allowDecimals(true); $tolerance->setInfo($this->lng->txt('tolerance_info')); $tolerance->setValue($result->getTolerance()); @@ -891,6 +891,17 @@ public function checkInput() return true; } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + + /** * Get the question solution output * @param integer $active_id The active user id @@ -926,7 +937,7 @@ public function getSolutionOutput( } $user_solution["active_id"] = $active_id; $user_solution["pass"] = $pass; - $solutions = $this->object->getSolutionValues($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); foreach ($solutions as $idx => $solution_value) { if (preg_match("/^(\\\$v\\d+)$/", $solution_value["value1"], $matches)) { $user_solution[$matches[1]] = $solution_value["value2"]; diff --git a/Modules/TestQuestionPool/classes/class.assFormulaQuestionResult.php b/Modules/TestQuestionPool/classes/class.assFormulaQuestionResult.php index 170801f2a3ac..dde468bf4bda 100755 --- a/Modules/TestQuestionPool/classes/class.assFormulaQuestionResult.php +++ b/Modules/TestQuestionPool/classes/class.assFormulaQuestionResult.php @@ -132,7 +132,7 @@ public function calculateFormula($variables, $results, $question_id = 0, $use_pr $res = $result * 1; if (is_numeric($this->getPrecision())) { if ($this->getResultType() == self::RESULT_DEC || $this->getResultType() == self::RESULT_NO_SELECTION) { - $result = ilMath::_div($res, 1, $this->getPrecision()); + $result = ilMath::_round($res, $this->getPrecision()); } } } @@ -261,19 +261,25 @@ public function isCorrect($variables, $results, $value, $unit = null) $math->suppress_errors = true; $result = $math->evaluate($formula); // baseunit-result!! - $resultWithRespectedUnit = ilMath::_div($result, 1, $this->getPrecision()); + $resultWithRespectedUnit = ilMath::_round($result, $this->getPrecision()); if (is_object($this->getUnit())) { //there is a "fix" result_unit defined! // if expected resultunit != baseunit convert to "fix" result_unit if ($this->getUnit()->getBaseUnit() != -1) { $resultWithRespectedUnit = ilMath::_div($result, $this->getUnit()->getFactor(), $this->getPrecision()); + } else { + //if resultunit == baseunit calculate to get correct precision + $resultWithRespectedUnit = ilMath::_mul($result, 1, $this->getPrecision()); } } elseif ($this->getUnit() == null && $unit != null) { // there is no "fix" result_unit defined, but the user has selected a unit ... // so .... there are "available resultunits" in multi-selectbox selected // -> check if selected user-unit is baseunit - if (!($unit->getFactor() == 1 && strlen(trim($unit->getFactor())) == 1)) { + if ($unit->getFactor() == 1 && strlen(trim($unit->getFactor())) == 1) { + // result is already calculated to baseunit.... -> get correct precision.. + $resultWithRespectedUnit = ilMath::_mul($result, 1, $this->getPrecision()); + } else { $resultWithRespectedUnit = ilMath::_div($result, $unit->getFactor(), $this->getPrecision()); } } @@ -298,7 +304,7 @@ public function isCorrect($variables, $results, $value, $unit = null) $frac_value = $value; } - $frac_value = ilMath::_div($frac_value, 1, $this->getPrecision()); + $frac_value = ilMath::_round($frac_value, $this->getPrecision()); if (substr_count($value, '/') >= 1) { $check_fraction = false; @@ -318,7 +324,15 @@ public function isCorrect($variables, $results, $value, $unit = null) $check_fraction = false; } } else { - $frac_value = ilMath::_div($exp_val[0], $exp_val[1], $this->getPrecision()); + try { + $frac_value = ilMath::_div($exp_val[0], $exp_val[1], $this->getPrecision()); + } catch (ilMathDivisionByZeroException $ex) { + if ($result) { + return false; + } else { + return true; + } + } $frac_value = str_replace(',', '.', $frac_value); if (ilMath::_equals($frac_value, $resultWithRespectedUnit, $this->getPrecision())) { @@ -343,11 +357,19 @@ public function isCorrect($variables, $results, $value, $unit = null) $frac_value = str_replace(',', '.', $value); } elseif (substr_count($value, '/') == 1) { $exp_val = explode('/', $value); - $frac_value = ilMath::_div($exp_val[0], $exp_val[1], $this->getPrecision()); + try { + $frac_value = ilMath::_div($exp_val[0], $exp_val[1], $this->getPrecision()); + } catch (ilMathDivisionByZeroException $ex) { + if ($result) { + return false; + } else { + return true; + } + } } else { $frac_value = $value; } - $frac_value = ilMath::_div($frac_value, 1, $this->getPrecision()); + $frac_value = ilMath::_round($frac_value, $this->getPrecision()); $check_fraction = true; break; } diff --git a/Modules/TestQuestionPool/classes/class.assImagemapQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assImagemapQuestionGUI.php index f1589f350fd8..53588b3a4d33 100755 --- a/Modules/TestQuestionPool/classes/class.assImagemapQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assImagemapQuestionGUI.php @@ -325,11 +325,11 @@ public function areaEditor($shape = '') ilUtil::sendInfo($this->lng->txt("polygon_click_starting_point")); } elseif (count($coords) == 1) { ilUtil::sendInfo($this->lng->txt("polygon_click_next_point")); - $preview->addPoint($preview->getAreaCount(), join(",", $coords), true, "blue"); + $preview->addPoint($preview->getAreaCount(), implode(",", $coords), true, "blue"); } elseif (count($coords) > 1) { ilUtil::sendInfo($this->lng->txt("polygon_click_next_or_save")); $disabled_save = ""; - $c = join(",". $coords); + $c = implode(",", $coords); } break; } @@ -408,6 +408,16 @@ protected function completeTestOutputFormAction($formAction, $active_id, $pass) // hey. // hey. + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * @@ -441,7 +451,7 @@ public function getSolutionOutput( $pass = ilObjTest::_getPass($active_id); } } - $solutions = &$this->object->getSolutionValues($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); } else { if (!$this->object->getIsMultipleChoice()) { $found_index = -1; diff --git a/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php b/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php index 623c135cfa93..8bdb5d95ea7c 100644 --- a/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php +++ b/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php @@ -535,6 +535,17 @@ public function getPreview($show_question_only = false, $showInlineFeedback = fa return $questionoutput; } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + + /** * @param $active_id * @param null $pass @@ -563,7 +574,7 @@ public function getSolutionOutput( // get the solution of the user for the active pass or from the last pass if allowed $user_solution = array(); if (($active_id > 0) && (!$show_correct_solution)) { - $solutions = &$this->object->getSolutionValues($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); foreach ($solutions as $idx => $solution_value) { $user_solution[$solution_value['value1']] = $solution_value['value2']; } diff --git a/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php b/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php index 80004ff06043..1d7f5c84321c 100644 --- a/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php +++ b/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php @@ -51,7 +51,8 @@ protected function getUserSolution($active_id, $pass) #{ # if (is_null($pass)) $pass = ilObjTest::_getPass($active_id); #} - $solutions = $this->object->getTestOutputSolutions($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); + //$solutions = $this->object->getTestOutputSolutions($active_id, $pass); // hey. foreach ($solutions as $idx => $solution_value) { $user_solution[$solution_value["value1"]] = $solution_value["value2"]; @@ -251,6 +252,16 @@ public function populateAnswerSpecificFormPart(ilPropertyFormGUI $form) return $form; } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * diff --git a/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php index c8a48a6574e8..19689b61f726 100755 --- a/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php @@ -405,6 +405,16 @@ public function populateQuestionSpecificFormPart(\ilPropertyFormGUI $form) $form->addItem($mode); } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * @@ -437,7 +447,7 @@ public function getSolutionOutput( $solutions = array(); if (($active_id > 0) && (!$show_correct_solution)) { include_once "./Modules/Test/classes/class.ilObjTest.php"; - $solutions = &$this->object->getSolutionValues($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); $solution_script .= ""; } else { foreach ($this->object->getMaximumScoringMatchingPairs() as $pair) { diff --git a/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php b/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php index df61f88b6a78..c94b42c7e789 100755 --- a/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php +++ b/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php @@ -183,6 +183,16 @@ public function removechoice() $this->editQuestion(); } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * @@ -215,7 +225,7 @@ public function getSolutionOutput( // get the solution of the user for the active pass or from the last pass if allowed $user_solution = array(); if (($active_id > 0) && (!$show_correct_solution)) { - $solutions = &$this->object->getSolutionValues($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); foreach ($solutions as $idx => $solution_value) { array_push($user_solution, $solution_value["value1"]); } diff --git a/Modules/TestQuestionPool/classes/class.assNumericGUI.php b/Modules/TestQuestionPool/classes/class.assNumericGUI.php index e7195eb2a125..b259a29ca85f 100755 --- a/Modules/TestQuestionPool/classes/class.assNumericGUI.php +++ b/Modules/TestQuestionPool/classes/class.assNumericGUI.php @@ -147,6 +147,16 @@ public function checkRange($lower, $upper) return false; } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * @@ -176,7 +186,7 @@ public function getSolutionOutput( // get the solution of the user for the active pass or from the last pass if allowed $solutions = array(); if (($active_id > 0) && (!$show_correct_solution)) { - $solutions = &$this->object->getSolutionValues($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); } else { array_push($solutions, array("value1" => sprintf($this->lng->txt("value_between_x_and_y"), $this->object->getLowerLimit(), $this->object->getUpperLimit()))); } diff --git a/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php b/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php index 3fc7a25ca8b2..28447f940d24 100644 --- a/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php @@ -106,6 +106,16 @@ public function editQuestion($checkonly = false) return $errors; } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * @@ -136,7 +146,7 @@ public function getSolutionOutput( //$solutionvalue = ""; if (($active_id > 0) && (!$show_correct_solution)) { $elements = []; - $solutions = &$this->object->getSolutionValues($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); if (strlen($solutions[0]["value1"])) { $elements = explode("{::}", $solutions[0]["value1"]); } diff --git a/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php b/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php index 88b75ed8dff2..7e574381ff45 100755 --- a/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php @@ -413,13 +413,13 @@ public function hasOrderingTypeUploadSupport() * @param $passIndex * @return ilAssOrderingElementList */ - public function getOrderingElementListForSolutionOutput($forceCorrectSolution, $activeId, $passIndex) + public function getOrderingElementListForSolutionOutput($forceCorrectSolution, $activeId, $passIndex, $getUseIntermediateSolution = false) { if ($forceCorrectSolution || !$activeId || $passIndex === null) { return $this->getOrderingElementList(); } - $solutionValues = $this->getSolutionValues($activeId, $passIndex); + $solutionValues = $this->getSolutionValues($activeId, $passIndex, !$getUseIntermediateSolution); if (!count($solutionValues)) { return $this->getShuffledOrderingElementList(); diff --git a/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php index a6ae27998faa..900c4e2181df 100755 --- a/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php @@ -410,6 +410,16 @@ protected function buildEditForm() return $form; } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * @@ -439,7 +449,8 @@ public function getSolutionOutput( $solutionOrderingList = $this->object->getOrderingElementListForSolutionOutput( $forceCorrectSolution, $active_id, - $pass + $pass, + $this->getUseIntermediateSolution() ); $answers_gui = $this->object->buildNestedOrderingElementInputGui(); diff --git a/Modules/TestQuestionPool/classes/class.assQuestion.php b/Modules/TestQuestionPool/classes/class.assQuestion.php index 460198e55f48..93108c3e68ae 100755 --- a/Modules/TestQuestionPool/classes/class.assQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assQuestion.php @@ -1297,7 +1297,7 @@ final public function calculateResultsFromSolution($active_id, $pass = null, $ob */ final public function persistWorkingState($active_id, $pass = null, $obligationsEnabled = false, $authorized = true) { - if (!$this->validateSolutionSubmit()) { + if (!$this->validateSolutionSubmit() && !$this->savePartial()) { return false; } @@ -5458,4 +5458,9 @@ protected function buildTestPresentationConfig() } // hey. // fau. + + public function savePartial() + { + return false; + } } diff --git a/Modules/TestQuestionPool/classes/class.assQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assQuestionGUI.php index 32cca1ea3333..f837d488b6ac 100755 --- a/Modules/TestQuestionPool/classes/class.assQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assQuestionGUI.php @@ -105,6 +105,12 @@ abstract class assQuestionGUI */ protected $editForm; + /** + * Prefer the intermediate solution for solution output + * @var bool + */ + protected $use_intermediate_solution = false; + /** * assQuestionGUI constructor */ @@ -2111,7 +2117,47 @@ abstract public function getSolutionOutput( $show_manual_scoring = false, $show_question_text = true ); - + + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return false; + } + + /** + * Check if the question has an intermediate solution + * @param int $activeId + * @param int $passIndex + * @return bool + */ + public function hasIntermediateSolution($activeId, $passIndex) + { + $result = $this->object->lookupForExistingSolutions($activeId, $passIndex); + return ($result['intermediate']); + } + + /** + * Set to use the intermediate solution for solution output + * @var bool $use + */ + public function setUseIntermediateSolution($use) + { + $this->use_intermediate_solution = (bool) $use; + } + + /** + * Get if intermediate solution should be used for solution output + * @return bool + */ + public function getUseIntermediateSolution() + { + return (bool) $this->use_intermediate_solution; + } + protected function hasCorrectSolution($activeId, $passIndex) { $reachedPoints = $this->object->getAdjustedReachedPoints($activeId, $passIndex, true); diff --git a/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php b/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php index 142429fe10a7..2be2bac2c142 100755 --- a/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php +++ b/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php @@ -182,6 +182,16 @@ public function removechoice() $this->editQuestion(); } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * @@ -212,7 +222,7 @@ public function getSolutionOutput( // get the solution of the user for the active pass or from the last pass if allowed $user_solution = ""; if (($active_id > 0) && (!$show_correct_solution)) { - $solutions = &$this->object->getSolutionValues($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); foreach ($solutions as $idx => $solution_value) { $user_solution = $solution_value["value1"]; } diff --git a/Modules/TestQuestionPool/classes/class.assTextQuestion.php b/Modules/TestQuestionPool/classes/class.assTextQuestion.php index af7037ab0f17..11bfb7560413 100755 --- a/Modules/TestQuestionPool/classes/class.assTextQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assTextQuestion.php @@ -146,7 +146,7 @@ public function loadFromDb($question_id) $this->matchcondition = (strlen($data['matchcondition'])) ? $data['matchcondition'] : 0; $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2)); $this->setKeywordRelation(($data['keyword_relation'])); - + try { $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']); } catch (ilTestQuestionPoolException $e) { @@ -1038,4 +1038,31 @@ public function countLetters($text) return ilStr::strLen($text); } + + public function getLatestAutosaveContent($active_id) + { + $question_fi = $this->getId(); + + // Do we have an unauthorized result? + $cntresult = $this->db->query(' + SELECT count(solution_id) cnt + FROM tst_solutions + WHERE active_fi = ' . $this->db->quote($active_id, 'int') . ' + AND question_fi = ' . $this->db->quote($this->getId(), 'int') . ' + AND authorized = ' . $this->db->quote(0, 'int') + ); + $row = $this->db->fetchAssoc($cntresult); + if($row['cnt'] > 0 ) { + $tresult = $this->db->query(' + SELECT value1 + FROM tst_solutions + WHERE active_fi = ' . $this->db->quote($active_id, 'int') . ' + AND question_fi = ' . $this->db->quote($this->getId(), 'int') . ' + AND authorized = ' . $this->db->quote(0, 'int') + ); + $trow = $this->db->fetchAssoc($tresult); + return $trow['value1']; + } + return ''; + } } diff --git a/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php index 223f784d7042..c67cba0329c5 100755 --- a/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php @@ -134,6 +134,16 @@ public function magicAfterTestOutput() // TODO - END: what exactly is done here? cant we use the parent method? } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * @@ -256,6 +266,7 @@ public function getSolutionOutput( return $solutionoutput; } + private function getBestAnswer($asHtml) { $answers = $this->object->getAnswers(); @@ -305,7 +316,7 @@ private function getBestAnswer($asHtml) private function getUserAnswer($active_id, $pass) { $user_solution = ""; - $solutions = $this->object->getSolutionValues($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); foreach ($solutions as $idx => $solution_value) { $user_solution = $solution_value["value1"]; } @@ -389,6 +400,7 @@ public function getTestOutput($active_id, $pass = null, $is_postponed = false, $ $template->setVariable("CHARACTERS", $this->lng->txt("characters")); $template->parseCurrentBlock(); } + $template->setVariable("QID", $this->object->getId()); $template->setVariable("ESSAY", ilUtil::prepareFormOutput($user_solution)); $questiontext = $this->object->getQuestion(); diff --git a/Modules/TestQuestionPool/classes/class.assTextSubsetGUI.php b/Modules/TestQuestionPool/classes/class.assTextSubsetGUI.php index 18c3231a60e7..7e3022485fee 100755 --- a/Modules/TestQuestionPool/classes/class.assTextSubsetGUI.php +++ b/Modules/TestQuestionPool/classes/class.assTextSubsetGUI.php @@ -121,6 +121,16 @@ public function removeanswers() $this->editQuestion(); } + /** + * Question type specific support of intermediate solution output + * The function getSolutionOutput respects getUseIntermediateSolution() + * @return bool + */ + public function supportsIntermediateSolutionOutput() + { + return true; + } + /** * Get the question solution output * @@ -148,7 +158,7 @@ public function getSolutionOutput( // get the solution of the user for the active pass or from the last pass if allowed $solutions = array(); if (($active_id > 0) && (!$show_correct_solution)) { - $solutions = &$this->object->getSolutionValues($active_id, $pass); + $solutions = $this->object->getSolutionValues($active_id, $pass, !$this->getUseIntermediateSolution()); } else { $rank = array(); foreach ($this->object->answers as $answer) { diff --git a/Modules/TestQuestionPool/classes/class.ilUnitConfigurationRepository.php b/Modules/TestQuestionPool/classes/class.ilUnitConfigurationRepository.php index 59a60b473e68..16aa50ad6a3f 100644 --- a/Modules/TestQuestionPool/classes/class.ilUnitConfigurationRepository.php +++ b/Modules/TestQuestionPool/classes/class.ilUnitConfigurationRepository.php @@ -115,7 +115,7 @@ public function copyCategory($a_category_id, $a_question_fi, $a_category_name = 'question_fi' => array('integer', (int) $a_question_fi) ) ); - + self::$result_buffer = null; return $next_id; } @@ -201,6 +201,7 @@ public function copyUnitsByCategories($a_from_category_id, $a_to_category_id, $a ) ); } + self::$result_buffer = null; } public function getCategoryUnitCount($id) @@ -294,30 +295,38 @@ public function deleteUnit($id) if ($affectedRows > 0) { $this->clearUnits(); } + self::$result_buffer = null; return null; } + public static $result_buffer; + protected function loadUnits() { global $DIC; $ilDB = $DIC['ilDB']; - $result = $ilDB->query( - " + if (self::$result_buffer == null) { + $result = $ilDB->query( + " SELECT units.*, il_qpl_qst_fq_ucat.category, baseunits.unit baseunit_title FROM il_qpl_qst_fq_unit units INNER JOIN il_qpl_qst_fq_ucat ON il_qpl_qst_fq_ucat.category_id = units.category_fi LEFT JOIN il_qpl_qst_fq_unit baseunits ON baseunits.unit_id = units.baseunit_fi ORDER BY il_qpl_qst_fq_ucat.category, units.sequence" - ); + ); - if ($result->numRows()) { - while ($row = $ilDB->fetchAssoc($result)) { - $unit = new assFormulaQuestionUnit(); - $unit->initFormArray($row); - $this->addUnit($unit); + + if ($result->numRows()) { + while ($row = $ilDB->fetchAssoc($result)) { + $unit = new assFormulaQuestionUnit(); + $unit->initFormArray($row); + $this->addUnit($unit); + } } + self::$result_buffer = $this->units; } + $this->units = self::$result_buffer; } public function getCategorizedUnits() @@ -364,6 +373,7 @@ public function getCategorizedUnits() protected function clearUnits() { $this->units = array(); + self::$result_buffer = null; } protected function addUnit($unit) @@ -508,6 +518,7 @@ public function saveUnitOrder($unit_id, $sequence) array('integer', 'integer', 'integer'), array((int) $sequence, $unit_id, $this->getConsumerId()) ); + self::$result_buffer = null; } public function checkDeleteUnit($id, $category_id = null) @@ -601,6 +612,7 @@ public function saveCategory(assFormulaQuestionUnitCategory $category) array('text', 'integer', 'integer'), array($category->getCategory(), $this->getConsumerId(), $category->getId()) ); + self::$result_buffer = null; } /** @@ -635,6 +647,7 @@ public function saveNewUnitCategory(assFormulaQuestionUnitCategory $category) ) ); $category->setId($next_id); + self::$result_buffer = null; } /** @@ -697,6 +710,7 @@ public function deleteCategory($id) if ($ar > 0) { $this->clearUnits(); } + self::$result_buffer = null; return null; } @@ -731,6 +745,7 @@ public function createNewUnit(assFormulaQuestionUnit $unit) $unit->setSequence(0); $this->clearUnits(); + self::$result_buffer = null; } /** @@ -764,6 +779,7 @@ public function saveUnit(assFormulaQuestionUnit $unit) $this->clearUnits(); } } + self::$result_buffer = null; } /** @@ -858,5 +874,6 @@ public function cloneUnits($a_from_consumer_id, $a_to_consumer_id) ); } } + self::$result_buffer = null; } } diff --git a/Modules/TestQuestionPool/classes/import/qti12/class.assLongMenuImport.php b/Modules/TestQuestionPool/classes/import/qti12/class.assLongMenuImport.php index 740090165c8b..35807d092b5a 100644 --- a/Modules/TestQuestionPool/classes/import/qti12/class.assLongMenuImport.php +++ b/Modules/TestQuestionPool/classes/import/qti12/class.assLongMenuImport.php @@ -39,6 +39,38 @@ public function fromXML(&$item, $questionpool_id, &$tst_id, &$tst_object, &$ques break; } } + + // fixLongMenuImageImport - process images in question and long menu text when question is imported + $questiontext = $this->object->getQuestion(); + $longmenutext = $this->object->getLongMenuTextValue(); + if (is_array($_SESSION["import_mob_xhtml"])) + { + foreach ($_SESSION["import_mob_xhtml"] as $mob) + { + if ($tst_id > 0) + { + $importfile = $this->getTstImportArchivDirectory() . '/' . $mob["uri"]; + } + else + { + $importfile = $this->getQplImportArchivDirectory() . '/' . $mob["uri"]; + } + + global $DIC; /* @var ILIAS\DI\Container $DIC */ + $DIC['ilLog']->write(__METHOD__.': import mob from dir: '. $importfile); + + $media_object = ilObjMediaObject::_saveTempFileAsMediaObject(basename($importfile), $importfile, FALSE); + ilObjMediaObject::_saveUsage($media_object->getId(), "qpl:html", $this->object->getId()); + + $questiontext = str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $questiontext); + $longmenutext = str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $longmenutext); + + } + } + $this->object->setQuestion(ilRTE::_replaceMediaObjectImageSrc($questiontext, 1)); + $this->object->setLongMenuTextValue(ilRTE::_replaceMediaObjectImageSrc($longmenutext, 1)); + // fau. + foreach ($item->resprocessing as $resprocessing) { foreach ($resprocessing->respcondition as $respcondition) { $correctness = 1; diff --git a/Modules/TestQuestionPool/classes/tables/class.ilKprimChoiceAnswerFreqStatTableGUI.php b/Modules/TestQuestionPool/classes/tables/class.ilKprimChoiceAnswerFreqStatTableGUI.php index 0958622ef41f..5bcd07c821a0 100644 --- a/Modules/TestQuestionPool/classes/tables/class.ilKprimChoiceAnswerFreqStatTableGUI.php +++ b/Modules/TestQuestionPool/classes/tables/class.ilKprimChoiceAnswerFreqStatTableGUI.php @@ -39,35 +39,36 @@ protected function getFalseOptionLabel() ); } + public function initColumns() { - $this->addColumn('Answer', ''); - - $this->addColumn( - 'Frequency: ' . $this->getTrueOptionLabel(), - '', - '25%' - ); + $lng = $this->DIC->language(); + $this->addColumn($lng->txt('tst_corr_answ_stat_tbl_header_answer'), ''); + $this->addColumn($lng->txt('tst_corr_answ_stat_tbl_header_frequency') . ': ' . $this->getTrueOptionLabel(), ''); + $this->addColumn($lng->txt('tst_corr_answ_stat_tbl_header_frequency') . ': ' . $this->getFalseOptionLabel(), ''); - $this->addColumn( - 'Frequency: ' . $this->getFalseOptionLabel(), - '', - '25%' - ); + foreach ($this->getData() as $row) { + if (isset($row['addable'])) { + $this->setActionsColumnEnabled(true); + $this->addColumn('', '', '1%'); + break; + } + } } public function fillRow($data) { $this->tpl->setCurrentBlock('answer'); - $this->tpl->setVariable('ANSWER', $data['answer']); + $this->tpl->setVariable('ANSWER', \ilUtil::prepareFormOutput($data['answer'])); $this->tpl->parseCurrentBlock(); $this->tpl->setCurrentBlock('frequency'); $this->tpl->setVariable('FREQUENCY', $data['frequency_true']); $this->tpl->parseCurrentBlock(); - + $this->tpl->setCurrentBlock('frequency'); $this->tpl->setVariable('FREQUENCY', $data['frequency_false']); $this->tpl->parseCurrentBlock(); + } } diff --git a/Modules/TestQuestionPool/templates/default/cloze_gap_builder.js b/Modules/TestQuestionPool/templates/default/cloze_gap_builder.js index fb1d6ecb4ed9..33734bc5c7c7 100644 --- a/Modules/TestQuestionPool/templates/default/cloze_gap_builder.js +++ b/Modules/TestQuestionPool/templates/default/cloze_gap_builder.js @@ -66,13 +66,14 @@ var ClozeGapBuilder = (function () { pro.addModalFakeFooter = function () { if (ClozeGlobals.jour_fixe_incompatible === false) { if ($('.modal-fake-footer').length === 0) { - $('').appendTo('.modal-content'); - $('#modal_ok_button').on('click', function () { + var footer = $(''); + $(footer).find('.modal_ok_button').on('click', function () { pro.closeModalWithOkButton(); }); - $('#modal_cancel_button').on('click', function () { + $(footer).find('.modal_cancel_button').on('click', function () { pro.closeModalWithCancelButton(); }); + footer.appendTo('.modal-content'); } } }; @@ -1132,6 +1133,34 @@ var ClozeGapBuilder = (function () { } }; + pro.removeSelectOption = function() { + let getPosition, pos, value; + if ($(this).attr('class') !== 'clone_fields_remove combination btn btn-link') { + value = $(this).parent().parent().find('.text_field').val(); + $('[data-answer="' + value + '"]').show(); + getPosition = $(this).attr('name'); + pos = getPosition.split('_'); + pos = getPosition.split('_'); + ClozeSettings.gaps_php[0][pos[2]].values.splice(pos[3], 1); + pro.editTextarea(pos[2]); + if (ClozeSettings.gaps_php[0][pos[2]].values.length === 0) { + ClozeSettings.gaps_php[0].splice(pos[2], 1); + pro.removeFromTextarea(pos[2]); + } + } else { + getPosition = $(this).parent().attr('name'); + pos = getPosition.split('_'); + ClozeSettings.gaps_combination[pos[2]][0].splice(parseInt(pos[3], 10), 1); + ClozeSettings.gaps_combination[pos[2]][1].forEach(function (answers) { + answers.splice(parseInt(pos[3], 10), 1); + }); + if (ClozeSettings.gaps_combination[pos[2]][0].length < 2) { + ClozeSettings.gaps_combination.splice(parseInt(pos[2], 10), 1); + } + } + pub.paintGaps(); + return false; + }, pro.appendEventListenerToBeRefactored = function(){ $('.clone_fields_add').off('click'); @@ -1198,38 +1227,8 @@ var ClozeGapBuilder = (function () { return false; }); - $('.clone_fields_remove').on('click', function () - { - var getPosition, pos, value; - if($(this).attr('class') != 'clone_fields_remove combination btn btn-link') - { - value = $(this).parent().parent().find('.text_field').val(); - $('[data-answer="'+value+'"]').show(); - getPosition = $(this).attr('name'); - pos = getPosition.split('_'); - ClozeSettings.gaps_php[0][pos[2]].values.splice(pos[3], 1); - pro.editTextarea(pos[2]); - if (ClozeSettings.gaps_php[0][pos[2]].values.length === 0) { - ClozeSettings.gaps_php[0].splice(pos[2], 1); - pro.removeFromTextarea(pos[2]); - } - } - else - { - getPosition = $(this).parent().attr('name'); - pos = getPosition.split('_'); - ClozeSettings.gaps_combination[pos[2]][0].splice(parseInt(pos[3], 10), 1); - ClozeSettings.gaps_combination[pos[2]][1].forEach(function (answers) { - answers.splice(parseInt(pos[3], 10), 1); - }); - if(ClozeSettings.gaps_combination[pos[2]][0].length < 2) - { - ClozeSettings.gaps_combination.splice(parseInt(pos[2], 10),1); - } - } - pub.paintGaps(); - return false; - }); + $('.clone_fields_remove').off('click', pro.removeSelectOption); + $('.clone_fields_remove').on('click', pro.removeSelectOption); $('.remove_gap_button').off('click'); $('.remove_gap_button').on('click', function () { diff --git a/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_cloze_question_gap_numeric.html b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_cloze_question_gap_numeric.html index c1fb5a97168e..bb71cd14a1f8 100755 --- a/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_cloze_question_gap_numeric.html +++ b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_cloze_question_gap_numeric.html @@ -1 +1 @@ - size="{TEXT_GAP_SIZE}" maxlength="{TEXT_GAP_SIZE}" /> \ No newline at end of file + size="{TEXT_GAP_SIZE}" maxlength="{TEXT_GAP_SIZE}" /> diff --git a/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_question_printview.html b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_question_printview.html index 0c21244477a2..c1853855d9fd 100644 --- a/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_question_printview.html +++ b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_question_printview.html @@ -10,7 +10,11 @@
{OBJECTIVES}

{SOLUTION_OUTPUT}

- + +

{TXT_INTERMEDIATE}

+

{INTERMEDIATE}

+ +

{RESULT_POINTS}

diff --git a/Modules/TestQuestionPool/test/Jasmine/cloze_gap_builderTest.js b/Modules/TestQuestionPool/test/Jasmine/cloze_gap_builderTest.js index fbcd6c0b51cf..d5339e98ed56 100644 --- a/Modules/TestQuestionPool/test/Jasmine/cloze_gap_builderTest.js +++ b/Modules/TestQuestionPool/test/Jasmine/cloze_gap_builderTest.js @@ -47,7 +47,7 @@ describe("cloze_gap_builder", function() { }); it("after the call div should be empty fake footer should be in place", function () { ClozeGapBuilder.protect.addModalFakeFooter(); - expect($('.modal-content').html()).toEqual(''); + expect($('.modal-content').html()).toEqual(''); }); }); diff --git a/Modules/TestQuestionPool/test/assFormulaQuestionTest.php b/Modules/TestQuestionPool/test/assFormulaQuestionTest.php index b7286d1d2519..b01b63fb45d8 100644 --- a/Modules/TestQuestionPool/test/assFormulaQuestionTest.php +++ b/Modules/TestQuestionPool/test/assFormulaQuestionTest.php @@ -376,7 +376,7 @@ public function simpleRatedCalculationsData() // RESULT_NO_SELECTION [$r11, $variables, $results, '1/3', null, true], // Test for #22381 - [$r12, $variables, $results, '3.0', null, true], + [$r12, $variables, $results, '3.1', null, true], ]; } } diff --git a/Modules/Wiki/classes/class.ilWikiDataSet.php b/Modules/Wiki/classes/class.ilWikiDataSet.php index a400a667dff5..e0ece223fc9f 100644 --- a/Modules/Wiki/classes/class.ilWikiDataSet.php +++ b/Modules/Wiki/classes/class.ilWikiDataSet.php @@ -45,7 +45,7 @@ public function __construct() */ public function getSupportedVersions() { - return array("4.1.0", "4.3.0", "4.4.0", "5.1.0"); + return array("4.1.0", "4.3.0", "4.4.0", "5.1.0", "5.4.0"); } /** @@ -129,6 +129,26 @@ protected function getTypes($a_entity, $a_version) "RatingExt" => "integer", "RatingOverall" => "integer", "LinkMdValues" => "integer" + ); + + case "5.4.0": + return array( + "Id" => "integer", + "Title" => "text", + "Description" => "text", + "StartPage" => "text", + "Short" => "text", + "Introduction" => "text", + "Rating" => "integer", + "PublicNotes" => "integer", + // "ImpPages" => "integer", + "PageToc" => "integer", + "RatingSide" => "integer", + "RatingNew" => "integer", + "RatingExt" => "integer", + "RatingOverall" => "integer", + "LinkMdValues" => "integer", + "EmptyPageTempl" => "integer" ); } } @@ -150,12 +170,24 @@ protected function getTypes($a_entity, $a_version) "WikiId" => "integer", "Blocked" => "integer", "Rating" => "integer"); + + case "5.4.0": + return array( + "Id" => "integer", + "Title" => "text", + "WikiId" => "integer", + "Blocked" => "integer", + "Rating" => "integer", + "TemplateNewPages" => "integer", + "TemplateAddToPage" => "integer" + ); } } if ($a_entity == "wiki_imp_page") { switch ($a_version) { case "5.1.0": + case "5.4.0": return array( "WikiId" => "integer", "PageId" => "integer", @@ -212,6 +244,14 @@ public function readData($a_entity, $a_version, $a_ids, $a_field = "") " FROM il_wiki_data JOIN object_data ON (il_wiki_data.id = object_data.obj_id)" . " WHERE " . $ilDB->in("id", $a_ids, false, "integer")); break; + + case "5.4.0": + $this->getDirectDataFromQuery("SELECT id, title, description," . + " startpage start_page, short, rating, rating_overall, introduction," . // imp_pages, + " public_notes, page_toc, rating_side, rating_new, rating_ext, link_md_values, empty_page_templ" . + " FROM il_wiki_data JOIN object_data ON (il_wiki_data.id = object_data.obj_id)" . + " WHERE " . $ilDB->in("id", $a_ids, false, "integer")); + break; } } @@ -231,12 +271,33 @@ public function readData($a_entity, $a_version, $a_ids, $a_field = "") " FROM il_wiki_page" . " WHERE " . $ilDB->in("wiki_id", $a_ids, false, "integer")); break; + + case "5.4.0": + $this->getDirectDataFromQuery("SELECT id, title, wiki_id," . + " blocked, rating" . + " FROM il_wiki_page" . + " WHERE " . $ilDB->in("wiki_id", $a_ids, false, "integer")); + foreach ($this->data as $k => $v) { + $set = $ilDB->queryF( + "SELECT * FROM wiki_page_template " . + " WHERE wiki_id = %s " . + " AND wpage_id = %s ", + ["integer", "integer"], + [$v["WikiId"], $v["Id"]] + ); + if ($rec = $ilDB->fetchAssoc($set)) { + $this->data[$k]["TemplateNewPages"] = $rec["new_pages"]; + $this->data[$k]["TemplateAddToPage"] = $rec["add_to_page"]; + } + } + break; } } if ($a_entity == "wiki_imp_page") { switch ($a_version) { case "5.1.0": + case "5.4.0": $this->getDirectDataFromQuery("SELECT wiki_id, page_id, ord, indent " . " FROM il_wiki_imp_pages " . " WHERE " . $ilDB->in("wiki_id", $a_ids, false, "integer")); @@ -303,7 +364,8 @@ public function importRecord($a_entity, $a_types, $a_rec, $a_mapping, $a_schema_ $newObj->setRatingCategories($a_rec["RatingExt"]); } $newObj->setLinkMetadataValues($a_rec["LinkMdValues"]); - + $newObj->setEmptyPageTemplate((int) $a_rec["EmptyPageTempl"]); + $newObj->update(true); $this->current_obj = $newObj; $a_mapping->addMapping("Modules/Wiki", "wiki", $a_rec["Id"], $newObj->getId()); @@ -326,7 +388,12 @@ public function importRecord($a_entity, $a_types, $a_rec, $a_mapping, $a_schema_ } $wpage->create(true); - + + if (isset($a_rec["TemplateNewPages"]) || isset($a_rec["TemplateAddToPage"])) { + $wtpl = new ilWikiPageTemplate($wiki_id); + $wtpl->save($wpage->getId(), (int) $a_rec["TemplateNewPages"], (int) $a_rec["TemplateAddToPage"]); + } + $a_mapping->addMapping("Modules/Wiki", "wpg", $a_rec["Id"], $wpage->getId()); $a_mapping->addMapping("Services/COPage", "pg", "wpg:" . $a_rec["Id"], "wpg:" . $wpage->getId()); $a_mapping->addMapping("Services/AdvancedMetaData", "advmd_sub_item", "advmd:wpg:" . $a_rec["Id"], $wpage->getId()); diff --git a/Modules/Wiki/classes/class.ilWikiExporter.php b/Modules/Wiki/classes/class.ilWikiExporter.php index 950f49106ec4..92e94ec615ac 100644 --- a/Modules/Wiki/classes/class.ilWikiExporter.php +++ b/Modules/Wiki/classes/class.ilWikiExporter.php @@ -149,6 +149,12 @@ public function getXmlRepresentation($a_entity, $a_schema_version, $a_id) public function getValidSchemaVersions($a_entity) { return array( + "5.4.0" => array( + "namespace" => "http://www.ilias.de/Modules/Wiki/wiki/5_4", + "xsd_file" => "ilias_wiki_5_4.xsd", + "uses_dataset" => true, + "min" => "5.4.0", + "max" => ""), "4.1.0" => array( "namespace" => "http://www.ilias.de/Modules/Wiki/wiki/4_1", "xsd_file" => "ilias_wiki_4_1.xsd", @@ -172,7 +178,7 @@ public function getValidSchemaVersions($a_entity) "xsd_file" => "ilias_wiki_5_1.xsd", "uses_dataset" => true, "min" => "5.1.0", - "max" => "") + "max" => "5.3.99") ); } } diff --git a/Modules/Wiki/classes/class.ilWikiStat.php b/Modules/Wiki/classes/class.ilWikiStat.php index e1f81c5257ce..63d09c23560d 100644 --- a/Modules/Wiki/classes/class.ilWikiStat.php +++ b/Modules/Wiki/classes/class.ilWikiStat.php @@ -204,7 +204,7 @@ protected static function writeData($a_table, array $a_primary, array $a_values) $ilAtomQuery->addTableLock($a_table); $ilAtomQuery->addQueryCallable( - function (ilDBInterface $ilDB) use ($a_table, $a_primary, $a_values, $tstamp, &$is_update) { + function (ilDBInterface $ilDB) use ($a_table, $a_primary, $a_values, $tstamp, &$is_update) { $primary = array(); foreach ($a_primary as $column => $value) { $primary[] = $column . " = " . $ilDB->quote($value[1], $value[0]); diff --git a/Modules/Wiki/mediawiki/Title.php b/Modules/Wiki/mediawiki/Title.php index 22f8b84742ef..e42319b87a64 100644 --- a/Modules/Wiki/mediawiki/Title.php +++ b/Modules/Wiki/mediawiki/Title.php @@ -1784,7 +1784,7 @@ private function secureAndSplit() # Initial colon indicates main namespace rather than specified default # but should not create invalid {ns,title} pairs such as {0,Project:Foo} - if (':' == $dbkey{0}) { + if (':' == $dbkey[0]) { $this->mNamespace = NS_MAIN; $dbkey = substr($dbkey, 1); # remove the colon but continue processing $dbkey = trim($dbkey, '_'); # remove any subsequent whitespace @@ -1913,7 +1913,7 @@ private function secureAndSplit() } // Any remaining initial :s are illegal. - if ($dbkey !== '' && ':' == $dbkey{0}) { + if ($dbkey !== '' && ':' == $dbkey[0]) { return false; } diff --git a/README.md b/README.md index 0eaa6738a72b..6f7864159af8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Build Status](https://api.travis-ci.com/ILIAS-eLearning/ILIAS.svg?branch=release_5-4)](https://travis-ci.com/ILIAS-eLearning/ILIAS) -[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg)](https://php.net/) +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg)](https://php.net/) # ILIAS @@ -31,3 +31,6 @@ We highly appreciate Pull-Request from external developers. Due to some regulati - Features/Refactorings need an entry in Feature-Wiki and has to get through the existing procedure for Feature-Requests: http://feature.ilias.de . Pull-Request target to trunk. Pull-Request will be assigned to the responsible maintainer(s). See further information on how contributions are handled in [/docs/documentation/contributing.md](/docs/documentation/contributing.md) + +### ILIAS Root Directory +Requests to add new files and directories in the root directory of ILIAS MUST be presented as development issues at and approved by the Jour Fixe. \ No newline at end of file diff --git a/Services/AccessControl/classes/class.ilObjRoleFolderGUI.php b/Services/AccessControl/classes/class.ilObjRoleFolderGUI.php index 7d74d5d2e46b..e8af1adeddea 100755 --- a/Services/AccessControl/classes/class.ilObjRoleFolderGUI.php +++ b/Services/AccessControl/classes/class.ilObjRoleFolderGUI.php @@ -658,7 +658,8 @@ protected function confirmDeleteObject() $ilCtrl = $DIC['ilCtrl']; - if (!count($_POST['roles'])) { + $roles = (array) ($_POST['roles'] ?? []); + if (!count($roles)) { ilUtil::sendFailure($this->lng->txt('select_one'), true); $ilCtrl->redirect($this, 'view'); } @@ -675,7 +676,7 @@ protected function confirmDeleteObject() include_once './Services/AccessControl/classes/class.ilObjRole.php'; - foreach ($_POST['roles'] as $role_id) { + foreach ($roles as $role_id) { $confirm->addItem( 'roles[]', $role_id, diff --git a/Services/Accordion/js/accordion.js b/Services/Accordion/js/accordion.js index 74597f97416b..227daa30b251 100644 --- a/Services/Accordion/js/accordion.js +++ b/Services/Accordion/js/accordion.js @@ -97,6 +97,11 @@ il.Accordion = { }); t.on("click", { id: id, el: t}, il.Accordion.clickHandler); + t.on('keypress', function (e) { + if (e.which === 13 || e.which === 32) { + $(this).find("div[role='button']").trigger('click'); + } + }); }); } @@ -171,8 +176,9 @@ il.Accordion = { var a = il.Accordion.data[id]; if (a.active_head_class && a.active_head_class != "" && acc_el) { - $(acc_el.parentNode).children("div:first").children("div:first"). - addClass(a.active_head_class); + const b = $(acc_el.parentNode).children(":first").children(":first"); + b.addClass(a.active_head_class); + b.attr("aria-expanded", true); } }, @@ -180,8 +186,9 @@ il.Accordion = { var a = il.Accordion.data[id]; if (a.active_head_class && a.active_head_class != "" && acc_el) { - $(acc_el.parentNode).children("div:first").children("div:first"). - removeClass(a.active_head_class); + const b = $(acc_el.parentNode).children(":first").children(":first"); + b.removeClass(a.active_head_class); + b.attr("aria-expanded", false); } }, @@ -199,10 +206,7 @@ il.Accordion = { t = $(this); if (t.hasClass("ilAccHideContent")) { - if (a.active_head_class) { - $(this.parentNode).children("div:first").children("div:first"). - addClass(a.active_head_class); - } + il.Accordion.addActiveHeadClass(id, this); // fade in the accordion (currentAccordion) options = il.Accordion.prepareShow(a, t); @@ -234,10 +238,7 @@ il.Accordion = { t = $(this); if (t.hasClass("ilAccHideContent")) { - if (a.active_head_class) { - $(this.parentNode).children("div:first").children("div:first"). - addClass(a.active_head_class); - } + il.Accordion.addActiveHeadClass(id, this); // fade in the accordion (currentAccordion) options = il.Accordion.prepareShow(a, t); @@ -401,11 +402,9 @@ il.Accordion = { // add active class to opened accordion if (a.active_head_class && a.active_head_class != '') { if (a.last_opened_acc && !a.multi) { - $(a.last_opened_acc.parentNode).children("div:first").children("div:first"). - removeClass(a.active_head_class); + il.Accordion.removeActiveHeadClass(id, a.last_opened_acc); } - $(a.clicked_acc.parentNode).children("div:first").children("div:first"). - addClass(a.active_head_class); + il.Accordion.addActiveHeadClass(id, a.clicked_acc); } // fade in the new accordion (currentAccordion) diff --git a/Services/Accordion/templates/default/tpl.accordion.html b/Services/Accordion/templates/default/tpl.accordion.html index 4d8ae4827fc6..1a35a71e7172 100644 --- a/Services/Accordion/templates/default/tpl.accordion.html +++ b/Services/Accordion/templates/default/tpl.accordion.html @@ -1,7 +1,7 @@
-
{HEADER}
+
{CONTENT}
diff --git a/Services/Administration/classes/class.ilAdministrationCommandGUI.php b/Services/Administration/classes/class.ilAdministrationCommandGUI.php index 0457b091a82b..c52c6ce275e2 100644 --- a/Services/Administration/classes/class.ilAdministrationCommandGUI.php +++ b/Services/Administration/classes/class.ilAdministrationCommandGUI.php @@ -180,7 +180,7 @@ public function cut() * Show target selection * @return */ - public function showMoveIntoObjectTree() + public function showPasteTree() { $objDefinition = $this->obj_definition; @@ -195,7 +195,7 @@ public function showMoveIntoObjectTree() // create instance include_once($location . "/class." . $class_name . ".php"); $container = new $class_name(array(), (int) $_GET['ref_id'], true, false); - $container->showMoveIntoObjectTreeObject(); + $container->showPasteTreeObject(); return true; } diff --git a/Services/Administration/interfaces/interface.ilAdministrationCommandHandling.php b/Services/Administration/interfaces/interface.ilAdministrationCommandHandling.php index a335e50ea30f..bf346c3060f9 100644 --- a/Services/Administration/interfaces/interface.ilAdministrationCommandHandling.php +++ b/Services/Administration/interfaces/interface.ilAdministrationCommandHandling.php @@ -63,7 +63,7 @@ public function showLinkIntoMultipleObjectsTree(); * Target selection cut * @return */ - public function showMoveIntoObjectTree(); + public function showPasteTree(); /** * Perform paste into multiple objects diff --git a/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php b/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php index e721041c7e50..724b213b492f 100644 --- a/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php +++ b/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php @@ -229,7 +229,6 @@ public function prepareCustomDefinitionFormConfirmation(ilPropertyFormGUI $a_for $objDefinition = $DIC['objDefinition']; $a_form->getItemByPostVar("opts")->setDisabled(true); - if (is_array($this->confirm_objects) && count($this->confirm_objects) > 0) { $new_options = $a_form->getInput("opts"); @@ -275,10 +274,8 @@ public function prepareCustomDefinitionFormConfirmation(ilPropertyFormGUI $a_for ilUtil::sendFailure($lng->txt("form_input_not_valid")); } } - $single = new ilRadioOption($lng->txt("md_adv_confirm_definition_select_option_single"), "sgl"); $details->addOption($single); - foreach ($items as $item) { $obj_id = $item[0]; $sub_type = $item[1]; @@ -294,7 +291,8 @@ public function prepareCustomDefinitionFormConfirmation(ilPropertyFormGUI $a_for $class = "ilObj" . $objDefinition->getClassName($type); $class_path = $objDefinition->getLocation($type); include_once $class_path . "/class." . $class . ".php"; - if (class_implements($class, ilAdvancedMetaDataSubItem)) { + $ints = class_implements($class); + if (isset($ints["ilAdvancedMetaDataSubItems"])) { $sub_title = $class::getAdvMDSubItemTitle($obj_id, $sub_type, $sub_id); if ($sub_title) { $title .= ' (' . $sub_title . ')'; diff --git a/Services/AuthApache/classes/class.ilWhiteListUrlValidator.php b/Services/AuthApache/classes/class.ilWhiteListUrlValidator.php index f4084e98bf83..673e2a646c8e 100644 --- a/Services/AuthApache/classes/class.ilWhiteListUrlValidator.php +++ b/Services/AuthApache/classes/class.ilWhiteListUrlValidator.php @@ -38,7 +38,7 @@ private function isValidDomain(string $domain) : bool return true; } - $firstChar = $validDomain{0}; + $firstChar = $validDomain[0]; if ('.' !== $firstChar) { $validDomain = '.' . $validDomain; } diff --git a/Services/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsSoap.php b/Services/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsSoap.php new file mode 100644 index 000000000000..f160e56fce6d --- /dev/null +++ b/Services/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsSoap.php @@ -0,0 +1,96 @@ +httpRequest = $httpRequest; + $this->ctrl = $ctrl; + $this->settings = $settings; + parent::__construct(); + } + + /** + * Check if an authentication attempt should be done when login page has been called. + */ + public function tryAuthenticationOnLoginPage() + { + $cmd = ''; + if (isset($this->httpRequest->getQueryParams()['cmd']) && is_string($this->httpRequest->getQueryParams()['cmd'])) { + $cmd = $this->httpRequest->getQueryParams()['cmd']; + } + if ('' === $cmd) { + if (isset($this->httpRequest->getParsedBody()['cmd']) && is_string($this->httpRequest->getParsedBody()['cmd'])) { + $cmd = $this->httpRequest->getParsedBody()['cmd']; + } + } + + $passedSso = ''; + if (isset($this->httpRequest->getQueryParams()['passed_sso']) && is_string($this->httpRequest->getQueryParams()['passed_sso'])) { + $passedSso = $this->httpRequest->getParsedBody()['passed_sso']; + } + + if ('force_login' === $cmd || !empty($passedSso)) { + return false; + } + + if (!$this->settings->get('soap_auth_active', false)) { + return false; + } + + if (empty($this->getUsername()) || empty($this->getPassword())) { + return false; + } + + $this->getLogger()->debug('Using SOAP authentication.'); + + $status = ilAuthStatus::getInstance(); + + require_once 'Services/SOAPAuth/classes/class.ilAuthProviderSoap.php'; + $provider = new ilAuthProviderSoap($this); + + $frontend_factory = new ilAuthFrontendFactory(); + $frontend_factory->setContext(ilAuthFrontendFactory::CONTEXT_STANDARD_FORM); + $frontend = $frontend_factory->getFrontend( + $GLOBALS['DIC']['ilAuthSession'], + $status, + $this, + [$provider] + ); + + $frontend->authenticate(); + + switch ($status->getStatus()) { + case ilAuthStatus::STATUS_AUTHENTICATED: + ilLoggerFactory::getLogger('auth')->debug( + 'Redirecting to default starting page.' + ); + ilInitialisation::redirectToStartingPage(); + break; + + case ilAuthStatus::STATUS_AUTHENTICATION_FAILED: + ilUtil::sendFailure($status->getTranslatedReason(), true); + $this->ctrl->redirectToURL(ilUtil::appendUrlParameterString( + $this->ctrl->getLinkTargetByClass('ilStartupGUI', 'showLoginPage', '', false, false), + 'passed_sso=1' + )); + break; + } + } +} \ No newline at end of file diff --git a/Services/Authentication/classes/Provider/class.ilAuthProviderFactory.php b/Services/Authentication/classes/Provider/class.ilAuthProviderFactory.php index f3b7ee658b62..328708c3f21b 100644 --- a/Services/Authentication/classes/Provider/class.ilAuthProviderFactory.php +++ b/Services/Authentication/classes/Provider/class.ilAuthProviderFactory.php @@ -76,6 +76,11 @@ public function getProviderByAuthMode(ilAuthCredentials $credentials, $a_authmod include_once './Services/Authentication/classes/Provider/class.ilAuthProviderDatabase.php'; return new ilAuthProviderDatabase($credentials); + case AUTH_SOAP: + $this->getLogger()->debug('Using SOAP authentication.'); + include_once './Services/SOAPAuth/classes/class.ilAuthProviderSoap.php'; + return new ilAuthProviderSoap($credentials); + case AUTH_APACHE: $this->getLogger()->debug('Using apache authentication.'); include_once './Services/AuthApache/classes/class.ilAuthProviderApache.php'; diff --git a/Services/Authentication/classes/class.ilObjAuthSettings.php b/Services/Authentication/classes/class.ilObjAuthSettings.php index c98787556585..c94e818c87b7 100755 --- a/Services/Authentication/classes/class.ilObjAuthSettings.php +++ b/Services/Authentication/classes/class.ilObjAuthSettings.php @@ -62,8 +62,8 @@ public function checkAuthLDAP() public function checkAuthSHIB() { $settings = $this->ilias->getAllSettings(); - - if (!$settings["hos_type"] or !$settings["shib_user_default_role"] or !$settings["shib_login"] + + if (!$settings["shib_hos_type"] or !$settings["shib_user_default_role"] or !$settings["shib_login"] or !$settings["shib_firstname"] or !$settings["shib_lastname"]) { return false; } diff --git a/Services/Authentication/classes/class.ilObjAuthSettingsGUI.php b/Services/Authentication/classes/class.ilObjAuthSettingsGUI.php index f1636ff4579a..e739e571343f 100755 --- a/Services/Authentication/classes/class.ilObjAuthSettingsGUI.php +++ b/Services/Authentication/classes/class.ilObjAuthSettingsGUI.php @@ -290,7 +290,18 @@ public function setAuthModeObject() case AUTH_SHIBBOLETH: if ($this->object->checkAuthSHIB() !== true) { ilUtil::sendFailure($this->lng->txt("auth_shib_not_configured"), true); - ilUtil::redirect($this->getReturnLocation("authSettings", $this->ctrl->getLinkTarget($this, "editSHIB", "", false, false))); + ilUtil::redirect( + $this->getReturnLocation( + 'authSettings', + $this->ctrl->getLinkTargetByClass( + ilAuthShibbolethSettingsGUI::class, + 'settings', + '', + false, + false + ) + ) + ); } break; @@ -1257,6 +1268,12 @@ private function validateApacheAuthAllowedDomains($text) return join("\n", preg_split("/[\r\n]+/", $text)); } + public function registrationSettingsObject() + { + $registration_gui = new ilRegistrationSettingsGUI(); + $this->ctrl->redirect($registration_gui); + } + /** * @param string $a_form_id * @return array @@ -1266,11 +1283,16 @@ public function addToExternalSettingsForm($a_form_id) switch ($a_form_id) { case ilAdministrationSettingsFormHandler::FORM_ACCESSIBILITY: require_once 'Services/Captcha/classes/class.ilCaptchaUtil.php'; - $fields = array( + $fields_login = array( 'adm_captcha_anonymous_short' => array(ilCaptchaUtil::isActiveForLogin(), ilAdministrationSettingsFormHandler::VALUE_BOOL), ); - return array('authentication_settings' => array('authSettings', $fields)); + $fields_registration = array( + 'adm_captcha_anonymous_short' => array(ilCaptchaUtil::isActiveForRegistration(), ilAdministrationSettingsFormHandler::VALUE_BOOL) + ); + + + return array('adm_auth_login' => array('authSettings', $fields_login), 'adm_auth_reg' => array('registrationSettings', $fields_registration)); } } } // END class.ilObjAuthSettingsGUI diff --git a/Services/Authentication/classes/class.ilSessionDBHandler.php b/Services/Authentication/classes/class.ilSessionDBHandler.php index e4c699089537..259ed50365f5 100644 --- a/Services/Authentication/classes/class.ilSessionDBHandler.php +++ b/Services/Authentication/classes/class.ilSessionDBHandler.php @@ -20,6 +20,10 @@ class ilSessionDBHandler public function setSaveHandler() { // register save handler functions + if (session_status() === PHP_SESSION_ACTIVE) { + return true; + } + if (ini_get("session.save_handler") == "user" || version_compare(PHP_VERSION, '7.2.0', '>=')) { session_set_save_handler( array($this, "open"), diff --git a/Services/Authentication/classes/class.ilSessionReminderCheck.php b/Services/Authentication/classes/class.ilSessionReminderCheck.php index 58b14b6b946f..435f3d3b67e0 100644 --- a/Services/Authentication/classes/class.ilSessionReminderCheck.php +++ b/Services/Authentication/classes/class.ilSessionReminderCheck.php @@ -9,10 +9,10 @@ class ilSessionReminderCheck { /** - * @param int $sessionId + * @param string $sessionIdHash * @return string */ - public function getJsonResponse($sessionId) + public function getJsonResponse($sessionIdHash) { /** * @var $ilDB ilDB @@ -25,24 +25,21 @@ public function getJsonResponse($sessionId) $lng = $DIC['lng']; $ilClientIniFile = $DIC['ilClientIniFile']; - include_once 'Services/JSON/classes/class.ilJsonUtil.php'; - - - $GLOBALS['DIC']->logger()->auth()->debug('Session reminder call for: ' . $sessionId); - - // disable session writing and extension of expire time - include_once './Services/Authentication/classes/class.ilSession.php'; + $GLOBALS['DIC']->logger()->auth()->debug('Session reminder call for session id hash: ' . $sessionIdHash); + + // disable session writing and extension of expiration time ilSession::enableWebAccessWithoutSession(true); $response = array('remind' => false); $res = $ilDB->queryF( ' - SELECT expires, user_id, data - FROM usr_session - WHERE session_id = %s', - array('text'), - array($sessionId) + SELECT expires, user_id, data + FROM usr_session + WHERE MD5(session_id) = %s + ', + ['text'], + [$sessionIdHash] ); $num = $ilDB->numRows($res); diff --git a/Services/Authentication/classes/class.ilSessionReminderGUI.php b/Services/Authentication/classes/class.ilSessionReminderGUI.php index 80addf88a9f5..cbfafeb10ce1 100644 --- a/Services/Authentication/classes/class.ilSessionReminderGUI.php +++ b/Services/Authentication/classes/class.ilSessionReminderGUI.php @@ -49,9 +49,7 @@ public function getHtml() $reminder_tpl = new ilTemplate('tpl.session_reminder.html', true, true, 'Services/Authentication'); $reminder_tpl->setVariable('DEBUG', defined('DEVMODE') && DEVMODE ? 1 : 0); $reminder_tpl->setVariable('CLIENT_ID', CLIENT_ID); - $reminder_tpl->setVariable('SESSION_NAME', session_name()); $reminder_tpl->setVariable('FREQUENCY', 60); - $reminder_tpl->setVariable('SESSION_ID', session_id()); $reminder_tpl->setVariable('SESSION_ID_HASH', md5(session_id())); $reminder_tpl->setVariable( 'URL', diff --git a/Services/Authentication/js/session_reminder.js b/Services/Authentication/js/session_reminder.js index a6ba10b3d892..8d206c927b93 100644 --- a/Services/Authentication/js/session_reminder.js +++ b/Services/Authentication/js/session_reminder.js @@ -71,7 +71,7 @@ dataType:'json', type: 'POST', data: { - session_id: properties.session_id + hash: properties.hash }, success: function (response) { if (response.message && typeof response.message == "string") { @@ -130,13 +130,11 @@ properties: $.extend( true, {}, { - url :"", - client_id :"", - session_name:"", - session_id :"", - session_id_hash :"", - frequency :60, - debug :0 + url: "", + client_id: "", + hash: "", + frequency: 60, + debug: 0 }, params ) @@ -156,10 +154,10 @@ }); internals.log("Session reminder started"); - if (YAHOO.util.Cookie.get(cookie_prefix + "session_id_hash") !== properties.session_id_hash) { + if (YAHOO.util.Cookie.get(cookie_prefix + "session_id_hash") !== properties.hash) { YAHOO.util.Cookie.set(cookie_prefix + "activation", "enabled"); YAHOO.util.Cookie.set(cookie_prefix + "status", "unlocked"); - YAHOO.util.Cookie.set(cookie_prefix + "session_id_hash", properties.session_id_hash); + YAHOO.util.Cookie.set(cookie_prefix + "session_id_hash", properties.hash); internals.log("Session cookie changed after new login or session reminder initially started for current session: Release lock and enabled reminder"); } diff --git a/Services/Authentication/templates/default/tpl.session_reminder.html b/Services/Authentication/templates/default/tpl.session_reminder.html index a36139fcd6ad..49911326fc0a 100644 --- a/Services/Authentication/templates/default/tpl.session_reminder.html +++ b/Services/Authentication/templates/default/tpl.session_reminder.html @@ -4,9 +4,7 @@ $("body").ilSessionReminder({ url: "{URL}", client_id: "{CLIENT_ID}", - session_name: "{SESSION_NAME}", - session_id: "{SESSION_ID}", - session_id_hash: "{SESSION_ID_HASH}", + hash: "{SESSION_ID_HASH}", frequency: {FREQUENCY}, debug: {DEBUG} }); diff --git a/Services/BackgroundTasks/classes/class.ilBTControllerGUI.php b/Services/BackgroundTasks/classes/class.ilBTControllerGUI.php index 34b15b59de22..69e58d3ad47d 100644 --- a/Services/BackgroundTasks/classes/class.ilBTControllerGUI.php +++ b/Services/BackgroundTasks/classes/class.ilBTControllerGUI.php @@ -20,6 +20,8 @@ class ilBTControllerGUI const CMD_REMOVE = 'abortBucket'; const CMD_GET_POPOVER_CONTENT = 'getPopoverContent'; const CMD_USER_INTERACTION = 'userInteraction'; + const IS_ASYNC = 'bt_task_is_async'; + const CMD_GET_REPLACEMENT_ITEM = "getAsyncReplacementItem"; public function executeCommand() @@ -35,11 +37,18 @@ protected function performCommand() { $cmd = $this->ctrl()->getCmd(); switch ($cmd) { - case self::CMD_USER_INTERACTION: case self::CMD_GET_POPOVER_CONTENT: + $this->getPopoverContent(); + break; + case self::CMD_USER_INTERACTION: + $this->userInteraction(); + break; case self::CMD_ABORT: case self::CMD_REMOVE: - $this->$cmd(); + $this->abortBucket(); + break; + default: + break; } } @@ -49,10 +58,16 @@ protected function userInteraction() $observer_id = (int) $this->http()->request()->getQueryParams()[self::OBSERVER_ID]; $selected_option = $this->http()->request()->getQueryParams()[self::SELECTED_OPTION]; $from_url = $this->getFromURL(); - - $observer = $this->dic()->backgroundTasks()->persistence()->loadBucket($observer_id); + try { + $observer = $this->dic()->backgroundTasks()->persistence()->loadBucket($observer_id); + } catch (Throwable $t) { + $this->ctrl()->redirectToURL($from_url); + } $option = new UserInteractionOption("", $selected_option); $this->dic()->backgroundTasks()->taskManager()->continueTask($observer, $option); + if ($this->http()->request()->getQueryParams()[self::IS_ASYNC] === "true") { + exit; + } $this->ctrl()->redirectToURL($from_url); } @@ -65,7 +80,9 @@ protected function abortBucket() $bucket = $this->dic()->backgroundTasks()->persistence()->loadBucket($observer_id); $this->dic()->backgroundTasks()->taskManager()->quitBucket($bucket); - + if ($this->http()->request()->getQueryParams()[self::IS_ASYNC] === "true") { + exit; + } $this->ctrl()->redirectToURL($from_url); } @@ -84,6 +101,7 @@ protected function getPopoverContent() echo $this->ui()->renderer()->renderAsync($gui->getPopOverContent($this->user() ->getId(), $this->getFromURL(), $replace_url)); + exit; } diff --git a/Services/BackgroundTasks/classes/class.ilCopyDefinition.php b/Services/BackgroundTasks/classes/class.ilCopyDefinition.php new file mode 100644 index 000000000000..58965733e3b5 --- /dev/null +++ b/Services/BackgroundTasks/classes/class.ilCopyDefinition.php @@ -0,0 +1,262 @@ + + * + */ +class ilCopyDefinition extends AbstractValue +{ + const COPY_SOURCE_DIR = 'source'; + const COPY_TARGET_DIR = 'target'; + /** + * Copy Jobs: source file => relative target file in zip directory. + * + * @param string[] + */ + private $copy_definitions = []; + /** + * Temporary directory using the normalized title of the bucket. + * + * @var string + */ + private $temp_dir; + /** + * Ref_ids of all selected objects (files as well as folders) + * + * @var string[] + */ + private $object_ref_ids = []; + /** + * Number of files to be downloaded. Required to determine whether there is anything to download or not. + * + * @var int + */ + private $num_files = 0; + /** + * Sum of the size of all files. Required to determine whether the global limit has been violated or not. + * + * @var int + */ + private $sum_file_sizes = 0; + /** + * States if the sum of all file sizes adheres to the global limit. + * + * @var bool + */ + private $adheres_to_limit = false; + + + /** + * Get copy definitions + * + * @return string[] + */ + public function getCopyDefinitions() + { + return $this->copy_definitions; + } + + + /** + * Set copy definitions + * + * @param string[] $a_definitions + */ + public function setCopyDefinitions($a_definitions) + { + $this->copy_definitions = $a_definitions; + } + + + /** + * Get directory name located in /temp/ directory. + * + * @return string + */ + public function getTempDir() + { + return $this->temp_dir; + } + + + /** + * Set directory name located in /temp/ directory. + * + * @param $temp_dir + */ + public function setTempDir($temp_dir) + { + $this->temp_dir = $temp_dir; + } + + + /** + * @return string[] + */ + public function getObjectRefIds() + { + return $this->object_ref_ids; + } + + + /** + * @param $object_ref_ids + * @param $append + */ + public function setObjectRefIds($object_ref_ids, $append = false) + { + if ($append) { + array_merge($this->object_ref_ids, $object_ref_ids); + } else { + $this->object_ref_ids = $object_ref_ids; + } + } + + + /** + * @return int + */ + public function getNumFiles() + { + return $this->num_files; + } + + + /** + * @param $num_files + */ + public function setNumFiles($num_files) + { + $this->num_files = $num_files; + } + + + /** + * @return int + */ + public function getSumFileSizes() + { + return $this->sum_file_sizes; + } + + + /** + * @param int $sum_file_sizes + */ + public function setSumFileSizes($sum_file_sizes) + { + $this->sum_file_sizes = $sum_file_sizes; + } + + + /** + * @return bool + */ + public function getAdheresToLimit() + { + return $this->adheres_to_limit; + } + + + /** + * @param bool $adheres_to_limit + */ + public function setAdheresToLimit($adheres_to_limit) + { + $this->adheres_to_limit = $adheres_to_limit; + } + + + /** + * Add copy definition + * + * @param string $a_source + * @param string $a_target + */ + public function addCopyDefinition($a_source, $a_target) + { + $this->copy_definitions[] + = [ + self::COPY_SOURCE_DIR => $a_source, + self::COPY_TARGET_DIR => $a_target, + ]; + } + + + /** + * Check equality + * + * @param Value $other + * + * @return bool + */ + public function equals(Value $other) + { + return strcmp($this->getHash(), $other->getHash()); + } + + + /** + * Get hash + * + * @return string + */ + public function getHash() + { + return md5($this->serialize()); + } + + + /** + * Serialize content + */ + public function serialize() + { + return serialize( + [ + "copy_definition" => $this->getCopyDefinitions(), + "temp_dir" => $this->getTempDir(), + "object_ref_ids" => implode(",", $this->getObjectRefIds()), + "num_files" => $this->getNumFiles(), + "sum_file_sizes" => $this->getSumFileSizes(), + "adheres_to_limit" => $this->getAdheresToLimit(), + ] + ); + } + + + /** + * Set value + * + * @param string[] $value + */ + public function setValue($value) + { + $this->copy_definitions = $value; + } + + + /** + * Unserialize definitions + * + * @param string $serialized + */ + public function unserialize($serialized) + { + $elements = unserialize($serialized); + + $this->setCopyDefinitions($elements["copy_definition"]); + $this->setTempDir($elements['temp_dir']); + $this->setObjectRefIds(explode(",", $elements["object_ref_ids"])); + $this->setNumFiles($elements["num_files"]); + $this->setSumFileSizes($elements["sum_file_sizes"]); + $this->setAdheresToLimit($elements["adheres_to_limit"]); + } +} diff --git a/Services/BackgroundTasks/classes/class.ilSoapBackgroundTasksAdministration.php b/Services/BackgroundTasks/classes/class.ilSoapBackgroundTasksAdministration.php index d7b39132593b..4b706a0b7c8d 100644 --- a/Services/BackgroundTasks/classes/class.ilSoapBackgroundTasksAdministration.php +++ b/Services/BackgroundTasks/classes/class.ilSoapBackgroundTasksAdministration.php @@ -21,7 +21,6 @@ +-----------------------------------------------------------------------------+ */ - include_once './webservice/soap/classes/class.ilSoapAdministration.php'; class ilSoapBackgroundTasksAdministration extends ilSoapAdministration diff --git a/Services/BackgroundTasks/js/background_task_refresh.js b/Services/BackgroundTasks/js/background_task_refresh.js index d435f913a986..f7a41959b735 100644 --- a/Services/BackgroundTasks/js/background_task_refresh.js +++ b/Services/BackgroundTasks/js/background_task_refresh.js @@ -1,30 +1,26 @@ -$(document).ready(function () { - var glyph = $(".mm_background_tasks > a"); - var refresh_uri = $(".mm_background_tasks").data('refreshUri'); +il = il || {}; +il.BGTask = il.BGTask || {}; +(function ($, il) { + il.BGTask = (function ($) { + var refreshments = 0; - var refresh = function () { - var popover_content = $(".bt-popover-content"); - var popover_container = popover_content.parent(); + var refreshItem = function (notification_item, url) { + setTimeout(function () { + console.log("Item has been refreshed: " + refreshments++); + //@TODO, when do we need to replace content? + if (true) { + notification_item.replaceByAsyncItem(url, {refreshes: refreshments}); + } + // do some stuff + }, 2000); + }; - if (popover_content.length > 0 && popover_container.length > 0 && popover_content.is(":visible") && popover_container.is(":visible")) { - $.ajax({ - url: refresh_uri, - type: 'GET', + return { + refreshItem: refreshItem + }; + })($); +})($, il); - success: function (data) { - var btt = $(data).attr('background-tasks-total'); - var btui = $(data).attr('background-tasks-user-interaction'); - glyph.find(".il-counter-novelty").html(btui); - glyph.find(".il-counter-status").html(btt - btui); - popover_content.replaceWith(data); - } - }); - } - // do some stuff - setTimeout(arguments.callee, 2000); - }; - setTimeout(refresh, 2000); -}); diff --git a/Services/BackgroundTasks/maintenance.json b/Services/BackgroundTasks/maintenance.json index 63e74c8d27c8..ffa956be97e7 100644 --- a/Services/BackgroundTasks/maintenance.json +++ b/Services/BackgroundTasks/maintenance.json @@ -2,9 +2,7 @@ "maintenance_model": "Classic", "first_maintainer": "fschmid(21087)", "second_maintainer": "", - "implicit_maintainers": [ - "otruffer(29130)" - ], + "implicit_maintainers": [], "coordinator": [ "" ], @@ -12,5 +10,7 @@ "testcase_writer": "", "path": "Services/BackgroundTasks", "belong_to_component": "BackgroundTasks", - "used_in_components": [] + "used_in_components": [ + "File" + ] } \ No newline at end of file diff --git a/Services/Block/classes/class.ilExternalFeedBlockGUIGen.php b/Services/Block/classes/class.ilExternalFeedBlockGUIGen.php index 5e6d116d9d3f..b2bb777f330a 100755 --- a/Services/Block/classes/class.ilExternalFeedBlockGUIGen.php +++ b/Services/Block/classes/class.ilExternalFeedBlockGUIGen.php @@ -177,16 +177,16 @@ public function saveFeedBlock() public function updateFeedBlock() { $this->initFormFeedBlock(IL_FORM_EDIT); - $this->external_feed_block->isFeedUrlLocal($_POST["block_feed_url"]); + if ($this->form_gui->checkInput() && - !!$this->external_feed_block->isFeedUrlLocal($this->form_gui->getInput("block_feed_url"))) { + !$this->external_feed_block->isFeedUrlLocal($this->form_gui->getInput("block_feed_url"))) { $this->external_feed_block->setTitle($this->form_gui->getInput("block_title")); $this->external_feed_block->setFeedUrl($this->form_gui->getInput("block_feed_url")); $this->external_feed_block->update(); $this->exitUpdateFeedBlock(); } else { if ($this->external_feed_block->isFeedUrlLocal($this->form_gui->getInput("block_feed_url"))) { - ilUtil::sendFailure($this->lng->txt("feed_no_local_url"), true); + ilUtil::sendFailure($this->lng->txt("feed_no_local_url")); } $this->form_gui->setValuesByPost(); return $this->form_gui->getHtml(); diff --git a/Services/COPage/classes/class.ilPCSection.php b/Services/COPage/classes/class.ilPCSection.php index 03c46df5d021..ea37f4161ee8 100755 --- a/Services/COPage/classes/class.ilPCSection.php +++ b/Services/COPage/classes/class.ilPCSection.php @@ -360,15 +360,19 @@ public function handleAccess($a_html, $a_mode) $end = strpos($a_html, "}}}}}", $start); $access_attr = explode(";", substr($a_html, $start, $end - $start)); $id = explode("_", $access_attr[3]); + $section_nr = $access_attr[6]; $access = true; + if (in_array($id[1], array("", 0, IL_INST_ID)) && $id[3] > 0) { $access = $ilAccess->checkAccess($access_attr[5], "", $id[3]); } + $end_limiter = "{{{{{Section;AccessEnd;".$section_nr."}}}}}"; if ($access) { $a_html = substr($a_html, 0, $start) . substr($a_html, $end + 5); + $a_html = str_replace($end_limiter, "", $a_html); } else { - $end = strpos($a_html, "{{{{{Section;Access}}}}}", $start); - $a_html = substr($a_html, 0, $start) . substr($a_html, $end + 24); + $end = strpos($a_html, $end_limiter, $start); + $a_html = substr($a_html, 0, $start) . substr($a_html, $end + strlen($end_limiter)); } } @@ -447,7 +451,7 @@ public static function getCacheTriggerString($a_page) " WHERE page_id = " . $ilDB->quote($a_page->getId(), "integer") . " AND parent_type = " . $ilDB->quote($a_page->getParentType(), "text") ); - $str = ""; + $str = "1"; // changed to 1 to force cache miss for #24277 $current_ts = new ilDateTime(time(), IL_CAL_UNIX); $current_ts = $current_ts->get(IL_CAL_UNIX); while ($rec = $ilDB->fetchAssoc($set)) { diff --git a/Services/COPage/classes/class.ilPageObjectGUI.php b/Services/COPage/classes/class.ilPageObjectGUI.php index ae058844d730..dead2148b74b 100755 --- a/Services/COPage/classes/class.ilPageObjectGUI.php +++ b/Services/COPage/classes/class.ilPageObjectGUI.php @@ -1829,6 +1829,7 @@ public function showPage() // check cache (same parameters, non-edit mode and rendered time // > last change + $is_error = false; if (($this->getOutputMode() == "preview" || $this->getOutputMode() == "presentation") && !$this->getCompareMode() && !$this->getAbstractOnly() && @@ -1842,10 +1843,17 @@ public function showPage() $xsl = file_get_contents("./Services/COPage/xsl/page.xsl"); $this->log->debug("Calling XSLT, content: " . substr($content, 0, 100)); - $args = array( '/_xml' => $content, '/_xsl' => $xsl ); - $xh = xslt_create(); - $output = xslt_process($xh, "arg:/_xml", "arg:/_xsl", null, $args, $params); - + try { + $args = array( '/_xml' => $content, '/_xsl' => $xsl ); + $xh = xslt_create(); + $output = xslt_process($xh, "arg:/_xml", "arg:/_xsl", null, $args, $params); + } catch (Exception $e) { + $output = ""; + if ($this->getOutputMode() == "edit") { + $output = "
".$e->getMessage()."
".htmlentities($content)."
"; + $is_error = true; + } + } if (($this->getOutputMode() == "presentation" || $this->getOutputMode() == "preview") && !$this->getAbstractOnly() && $this->obj->old_nr == 0) { @@ -1854,78 +1862,81 @@ public function showPage() xslt_free($xh); } - // unmask user html - if (($this->getOutputMode() != "edit" || - $this->user->getPref("ilPageEditor_HTMLMode") != "disable") - && !$this->getPageConfig()->getPreventHTMLUnmasking()) { - $output = str_replace("<", "<", $output); - $output = str_replace(">", ">", $output); - } - $output = str_replace("&", "&", $output); - - include_once './Services/MathJax/classes/class.ilMathJax.php'; - $output = ilMathJax::getInstance()->insertLatexImages($output); + if (!$is_error) { + // unmask user html + if (($this->getOutputMode() != "edit" || + $this->user->getPref("ilPageEditor_HTMLMode") != "disable") + && !$this->getPageConfig()->getPreventHTMLUnmasking()) { + $output = str_replace("<", "<", $output); + $output = str_replace(">", ">", $output); + } - // insert page snippets - //$output = $this->insertContentIncludes($output); + $output = str_replace("&", "&", $output); - // insert resource blocks - $output = $this->insertResources($output); + include_once './Services/MathJax/classes/class.ilMathJax.php'; + $output = ilMathJax::getInstance()->insertLatexImages($output); - // insert page toc - if ($this->getPageConfig()->getEnablePageToc()) { - $output = $this->insertPageToc($output); - } + // insert page snippets + //$output = $this->insertContentIncludes($output); + + // insert resource blocks + $output = $this->insertResources($output); - // insert advanced output trigger - $output = $this->insertAdvTrigger($output); + // insert page toc + if ($this->getPageConfig()->getEnablePageToc()) { + $output = $this->insertPageToc($output); + } - // workaround for preventing template engine - // from hiding paragraph text that is enclosed - // in curly brackets (e.g. "{a}", see ilLMEditorGUI::executeCommand()) - $output = $this->replaceCurlyBrackets($output); + // insert advanced output trigger + $output = $this->insertAdvTrigger($output); - // remove all newlines (important for code / pre output) - $output = str_replace("\n", "", $output); + // workaround for preventing template engine + // from hiding paragraph text that is enclosed + // in curly brackets (e.g. "{a}", see ilLMEditorGUI::executeCommand()) + $output = $this->replaceCurlyBrackets($output); - //echo htmlentities($output); - $output = $this->postOutputProcessing($output); - //echo htmlentities($output); - if ($this->getOutputMode() == "edit" && - !$this->getPageObject()->getActive($this->getPageConfig()->getEnableScheduledActivation())) { - $output = '
' . $this->getDisabledText() . '
' . $output . '
'; - } - - // for all page components... - include_once("./Services/COPage/classes/class.ilCOPagePCDef.php"); - $defs = ilCOPagePCDef::getPCDefinitions(); - foreach ($defs as $def) { - ilCOPagePCDef::requirePCClassByName($def["name"]); - $pc_class = $def["pc_class"]; - $pc_obj = new $pc_class($this->getPageObject()); - $pc_obj->setSourcecodeDownloadScript($this->determineSourcecodeDownloadScript()); - $pc_obj->setFileDownloadLink($this->determineFileDownloadLink()); - $pc_obj->setFullscreenLink($this->determineFullscreenLink()); + // remove all newlines (important for code / pre output) + $output = str_replace("\n", "", $output); - // post xsl page content modification by pc elements - $output = $pc_obj->modifyPageContentPostXsl($output, $this->getOutputMode()); - - // javascript files - $js_files = $pc_obj->getJavascriptFiles($this->getOutputMode()); - foreach ($js_files as $js) { - $GLOBALS["tpl"]->addJavascript($js); + //echo htmlentities($output); + $output = $this->postOutputProcessing($output); + //echo htmlentities($output); + if ($this->getOutputMode() == "edit" && + !$this->getPageObject()->getActive($this->getPageConfig()->getEnableScheduledActivation())) { + $output = '
' . $this->getDisabledText() . '
' . $output . '
'; } - // css files - $css_files = $pc_obj->getCssFiles($this->getOutputMode()); - foreach ($css_files as $css) { - $GLOBALS["tpl"]->addCss($css); - } + // for all page components... + include_once("./Services/COPage/classes/class.ilCOPagePCDef.php"); + $defs = ilCOPagePCDef::getPCDefinitions(); + foreach ($defs as $def) { + ilCOPagePCDef::requirePCClassByName($def["name"]); + $pc_class = $def["pc_class"]; + $pc_obj = new $pc_class($this->getPageObject()); + $pc_obj->setSourcecodeDownloadScript($this->determineSourcecodeDownloadScript()); + $pc_obj->setFileDownloadLink($this->determineFileDownloadLink()); + $pc_obj->setFullscreenLink($this->determineFullscreenLink()); + + // post xsl page content modification by pc elements + $output = $pc_obj->modifyPageContentPostXsl($output, $this->getOutputMode()); + + // javascript files + $js_files = $pc_obj->getJavascriptFiles($this->getOutputMode()); + foreach ($js_files as $js) { + $GLOBALS["tpl"]->addJavascript($js); + } + + // css files + $css_files = $pc_obj->getCssFiles($this->getOutputMode()); + foreach ($css_files as $css) { + $GLOBALS["tpl"]->addCss($css); + } - // onload code - $onload_code = $pc_obj->getOnloadCode($this->getOutputMode()); - foreach ($onload_code as $code) { - $GLOBALS["tpl"]->addOnloadCode($code); + // onload code + $onload_code = $pc_obj->getOnloadCode($this->getOutputMode()); + foreach ($onload_code as $code) { + $GLOBALS["tpl"]->addOnloadCode($code); + } } } diff --git a/Services/COPage/xsl/page.xsl b/Services/COPage/xsl/page.xsl index 885e400f7432..12e1f4a7eaa8 100755 --- a/Services/COPage/xsl/page.xsl +++ b/Services/COPage/xsl/page.xsl @@ -3343,7 +3343,7 @@ () - {{{{{Section;Access;PermissionRefId;;Permission;}}}}} + {{{{{Section;Access;PermissionRefId;;Permission;;}}}}} @@ -3370,7 +3370,7 @@
- {{{{{Section;Access}}}}} + {{{{{Section;AccessEnd;}}}}} @@ -3779,7 +3779,7 @@ -
+